sapiopycommons 2025.10.10a779__py3-none-any.whl → 2025.10.15a782__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/agent_service_base.py +1 -1
- sapiopycommons/ai/external_credentials.py +112 -21
- sapiopycommons/ai/test_client.py +43 -10
- {sapiopycommons-2025.10.10a779.dist-info → sapiopycommons-2025.10.15a782.dist-info}/METADATA +1 -1
- {sapiopycommons-2025.10.10a779.dist-info → sapiopycommons-2025.10.15a782.dist-info}/RECORD +7 -7
- {sapiopycommons-2025.10.10a779.dist-info → sapiopycommons-2025.10.15a782.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.10.10a779.dist-info → sapiopycommons-2025.10.15a782.dist-info}/licenses/LICENSE +0 -0
|
@@ -908,7 +908,7 @@ class AgentBase(ABC):
|
|
|
908
908
|
if len(matching_creds) > 1:
|
|
909
909
|
raise ValueError(f"Multiple credentials found for category '{category}' and host '{host}'.")
|
|
910
910
|
|
|
911
|
-
return ExternalCredentials(matching_creds[0])
|
|
911
|
+
return ExternalCredentials.from_pbo(matching_creds[0])
|
|
912
912
|
|
|
913
913
|
def call_subprocess(self,
|
|
914
914
|
args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]],
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from sapiopycommons.ai.protoapi.externalcredentials.external_credentials_pb2 import ExternalCredentialsPbo
|
|
2
4
|
|
|
3
5
|
|
|
@@ -5,26 +7,97 @@ class ExternalCredentials:
|
|
|
5
7
|
"""
|
|
6
8
|
A class representing external credentials.
|
|
7
9
|
"""
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def __init__(self,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
10
|
+
_identifier: str
|
|
11
|
+
_display_name: str
|
|
12
|
+
_description: str
|
|
13
|
+
_category: str
|
|
14
|
+
_url: str
|
|
15
|
+
_username: str
|
|
16
|
+
_password: str
|
|
17
|
+
_token: str
|
|
18
|
+
_custom_fields: dict[str, str]
|
|
19
|
+
|
|
20
|
+
def __init__(self, url: str, category: str, identifier: str = "", display_name: str = "", description: str = "",
|
|
21
|
+
username: str = "", password: str = "", token: str = "", custom_fields: dict[str, str] | None = None):
|
|
22
|
+
"""
|
|
23
|
+
:param url: The URL that the credentials are for.
|
|
24
|
+
:param category: The category of the credentials. This can be used to search for the credentials using the
|
|
25
|
+
AgentBase.get_credentials function.
|
|
26
|
+
:param identifier: The unique identifier for the credentials.
|
|
27
|
+
:param display_name: The display name for the credentials.
|
|
28
|
+
:param description: A description of the credentials.
|
|
29
|
+
:param username: The username for the credentials.
|
|
30
|
+
:param password: The password for the credentials.
|
|
31
|
+
:param token: The token for the credentials.
|
|
32
|
+
:param custom_fields: A dictionary of custom fields associated with the credentials.
|
|
33
|
+
"""
|
|
34
|
+
self._identifier = identifier
|
|
35
|
+
self._display_name = display_name
|
|
36
|
+
self._description = description
|
|
37
|
+
self._category = category
|
|
38
|
+
self._url = url
|
|
39
|
+
self._username = username
|
|
40
|
+
self._password = password
|
|
41
|
+
self._token = token
|
|
42
|
+
self._custom_fields = custom_fields or {}
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def from_pbo(pbo: ExternalCredentialsPbo) -> ExternalCredentials:
|
|
46
|
+
"""
|
|
47
|
+
Create an ExternalCredentials instance from a protobuf object.
|
|
48
|
+
|
|
49
|
+
:param pbo: An ExternalCredentialsPbo object.
|
|
50
|
+
:return: An ExternalCredentials instance.
|
|
51
|
+
"""
|
|
52
|
+
creds = ExternalCredentials(pbo.url, pbo.category)
|
|
53
|
+
creds._identifier = pbo.id
|
|
54
|
+
creds._display_name = pbo.display_name
|
|
55
|
+
creds._description = pbo.description
|
|
56
|
+
creds._username = pbo.username
|
|
57
|
+
creds._password = pbo.password
|
|
58
|
+
creds._token = pbo.token
|
|
59
|
+
creds._custom_fields = dict(pbo.custom_field)
|
|
60
|
+
return creds
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def identifier(self) -> str:
|
|
64
|
+
"""The unique identifier for the credentials."""
|
|
65
|
+
return self._identifier
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def display_name(self) -> str:
|
|
69
|
+
"""The display name for the credentials."""
|
|
70
|
+
return self._display_name
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def description(self) -> str:
|
|
74
|
+
"""A description of the credentials."""
|
|
75
|
+
return self._description
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def category(self) -> str:
|
|
79
|
+
"""The category of the credentials."""
|
|
80
|
+
return self._category
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def url(self) -> str:
|
|
84
|
+
"""The URL that the credentials are for."""
|
|
85
|
+
return self._url
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def username(self) -> str:
|
|
89
|
+
"""The username for the credentials."""
|
|
90
|
+
return self._username
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def password(self) -> str:
|
|
94
|
+
"""The password for the credentials."""
|
|
95
|
+
return self._password
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def token(self) -> str:
|
|
99
|
+
"""The token for the credentials."""
|
|
100
|
+
return self._token
|
|
28
101
|
|
|
29
102
|
def get_custom_field(self, key: str, default: str = None) -> str | None:
|
|
30
103
|
"""
|
|
@@ -34,4 +107,22 @@ class ExternalCredentials:
|
|
|
34
107
|
:param default: The value to return if the key does not exist.
|
|
35
108
|
:return: The value of the custom field, or None if the key does not exist.
|
|
36
109
|
"""
|
|
37
|
-
return self.
|
|
110
|
+
return self._custom_fields.get(key, default)
|
|
111
|
+
|
|
112
|
+
def to_pbo(self) -> ExternalCredentialsPbo:
|
|
113
|
+
"""
|
|
114
|
+
Convert the ExternalCredentials instance to a protobuf object.
|
|
115
|
+
|
|
116
|
+
:return: An ExternalCredentialsPbo object.
|
|
117
|
+
"""
|
|
118
|
+
return ExternalCredentialsPbo(
|
|
119
|
+
id=self._identifier,
|
|
120
|
+
display_name=self._display_name,
|
|
121
|
+
description=self._description,
|
|
122
|
+
category=self._category,
|
|
123
|
+
url=self._url,
|
|
124
|
+
username=self._username,
|
|
125
|
+
password=self._password,
|
|
126
|
+
token=self._token,
|
|
127
|
+
custom_field=self._custom_fields
|
|
128
|
+
)
|
sapiopycommons/ai/test_client.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import json
|
|
3
|
+
import os
|
|
3
4
|
from enum import Enum
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
6
7
|
import grpc
|
|
7
8
|
from sapiopylib.rest.User import SapioUser
|
|
8
9
|
|
|
10
|
+
from sapiopycommons.ai.external_credentials import ExternalCredentials
|
|
11
|
+
from sapiopycommons.ai.protoapi.externalcredentials.external_credentials_pb2 import ExternalCredentialsPbo
|
|
9
12
|
from sapiopycommons.ai.protoapi.fielddefinitions.fields_pb2 import FieldValuePbo
|
|
10
13
|
from sapiopycommons.ai.protoapi.plan.converter.converter_pb2 import ConverterDetailsRequestPbo, \
|
|
11
14
|
ConverterDetailsResponsePbo, ConvertResponsePbo, ConvertRequestPbo
|
|
@@ -20,6 +23,7 @@ from sapiopycommons.ai.protoapi.plan.tool.tool_pb2_grpc import ToolServiceStub
|
|
|
20
23
|
from sapiopycommons.ai.protoapi.session.sapio_conn_info_pb2 import SapioConnectionInfoPbo, SapioUserSecretTypePbo
|
|
21
24
|
from sapiopycommons.ai.protobuf_utils import ProtobufUtils
|
|
22
25
|
from sapiopycommons.general.aliases import FieldValue
|
|
26
|
+
from sapiopycommons.general.time_util import TimeUtil
|
|
23
27
|
|
|
24
28
|
|
|
25
29
|
class ContainerType(Enum):
|
|
@@ -63,39 +67,43 @@ class AgentOutput:
|
|
|
63
67
|
self.new_records = []
|
|
64
68
|
self.logs = []
|
|
65
69
|
|
|
66
|
-
def save_outputs(self, path: str = "test_outputs",
|
|
70
|
+
def save_outputs(self, path: str = "test_outputs", subfolder: str | None = None,
|
|
71
|
+
file_extensions: list[str] | None = None) -> None:
|
|
67
72
|
"""
|
|
68
|
-
Save all outputs to files in the specified directory.
|
|
69
|
-
and the output type.
|
|
73
|
+
Save all outputs to files in the specified output directory.
|
|
70
74
|
|
|
71
75
|
:param path: The directory to save the output files to.
|
|
76
|
+
:param subfolder: An optional subfolder within the path to save the output files to. Useful for when you are
|
|
77
|
+
calling the same agent multiple times for separate test cases.
|
|
72
78
|
:param file_extensions: A list of file extensions to use for binary output files. The length of this list
|
|
73
79
|
should match the number of binary outputs.
|
|
74
80
|
"""
|
|
75
81
|
if not self:
|
|
76
82
|
return
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
output_path: str = os.path.join(path, self.agent_name)
|
|
84
|
+
if subfolder:
|
|
85
|
+
output_path = os.path.join(output_path, subfolder)
|
|
86
|
+
os.makedirs(output_path, exist_ok=True)
|
|
87
|
+
if self.binary_output and (file_extensions is None or len(file_extensions) != len(self.binary_output)):
|
|
80
88
|
raise ValueError("File extensions must be provided for each binary output.")
|
|
81
89
|
for i, output in enumerate(self.binary_output):
|
|
82
90
|
ext: str = "." + file_extensions[i].lstrip(".")
|
|
83
91
|
for j, entry in enumerate(output):
|
|
84
|
-
with open(os.path.join(
|
|
92
|
+
with open(os.path.join(output_path, f"binary_output_{i}_{j}{ext}"), "wb") as f:
|
|
85
93
|
f.write(entry)
|
|
86
94
|
for i, output in enumerate(self.csv_output):
|
|
87
|
-
with open(os.path.join(
|
|
95
|
+
with open(os.path.join(output_path, f"csv_output_{i}.csv"), "w", encoding="utf-8") as f:
|
|
88
96
|
headers = output[0].keys()
|
|
89
97
|
f.write(",".join(headers) + "\n")
|
|
90
98
|
for row in output:
|
|
91
99
|
f.write(",".join(f'"{str(row[h])}"' for h in headers) + "\n")
|
|
92
100
|
for i, output in enumerate(self.json_output):
|
|
93
101
|
for j, entry in enumerate(output):
|
|
94
|
-
with open(os.path.join(
|
|
102
|
+
with open(os.path.join(output_path, f"json_output_{i}_{j}.json"), "w", encoding="utf-8") as f:
|
|
95
103
|
json.dump(entry, f, indent=2)
|
|
96
104
|
for i, output in enumerate(self.text_output):
|
|
97
105
|
for j, entry in enumerate(output):
|
|
98
|
-
with open(os.path.join(
|
|
106
|
+
with open(os.path.join(output_path, f"text_output_{i}_{j}.txt"), "w", encoding="utf-8") as f:
|
|
99
107
|
f.write(entry)
|
|
100
108
|
|
|
101
109
|
def __bool__(self):
|
|
@@ -163,6 +171,7 @@ class TestClient:
|
|
|
163
171
|
connection: SapioConnectionInfoPbo
|
|
164
172
|
_request_inputs: list[StepItemContainerPbo]
|
|
165
173
|
_config_fields: dict[str, FieldValuePbo]
|
|
174
|
+
_credentials: list[ExternalCredentialsPbo]
|
|
166
175
|
|
|
167
176
|
def __init__(self, grpc_server_url: str, message_mb_size: int = 1024, user: SapioUser | None = None,
|
|
168
177
|
options: list[tuple[str, Any]] | None = None):
|
|
@@ -183,6 +192,7 @@ class TestClient:
|
|
|
183
192
|
self._create_connection(user)
|
|
184
193
|
self._request_inputs = []
|
|
185
194
|
self._config_fields = {}
|
|
195
|
+
self._credentials = []
|
|
186
196
|
|
|
187
197
|
def _create_connection(self, user: SapioUser | None = None):
|
|
188
198
|
"""
|
|
@@ -267,10 +277,28 @@ class TestClient:
|
|
|
267
277
|
"""
|
|
268
278
|
self._config_fields.clear()
|
|
269
279
|
|
|
280
|
+
def add_credentials(self, credentials: list[ExternalCredentials]) -> None:
|
|
281
|
+
"""
|
|
282
|
+
Add external credentials to the connection info for the next request.
|
|
283
|
+
|
|
284
|
+
:param credentials: A list of ExternalCredentialsPbo objects to add to the connection info.
|
|
285
|
+
"""
|
|
286
|
+
for cred in credentials:
|
|
287
|
+
self._credentials.append(cred.to_pbo())
|
|
288
|
+
|
|
289
|
+
def clear_credentials(self) -> None:
|
|
290
|
+
"""
|
|
291
|
+
Clear all external credentials that have been added to the next request.
|
|
292
|
+
This is useful if you want to start a new request without the previous credentials.
|
|
293
|
+
"""
|
|
294
|
+
self._credentials.clear()
|
|
295
|
+
|
|
270
296
|
def clear_request(self) -> None:
|
|
271
297
|
"""
|
|
272
298
|
Clear all inputs and configuration fields that have been added to the next request.
|
|
273
299
|
This is useful if you want to start a new request without the previous inputs and configurations.
|
|
300
|
+
|
|
301
|
+
Credentials are not cleared, as they may be reused across multiple requests.
|
|
274
302
|
"""
|
|
275
303
|
self.clear_inputs()
|
|
276
304
|
self.clear_configs()
|
|
@@ -314,9 +342,11 @@ class TestClient:
|
|
|
314
342
|
:param is_dry_run: If True, the agent will not be executed, but the request will be validated.
|
|
315
343
|
:return: An AgentOutput object containing the results of the agent service call.
|
|
316
344
|
"""
|
|
345
|
+
print(f"Calling agent \"{agent_name}\"...")
|
|
317
346
|
with grpc.insecure_channel(self.grpc_server_url, options=self.options) as channel:
|
|
318
347
|
stub = ToolServiceStub(channel)
|
|
319
348
|
|
|
349
|
+
start = TimeUtil.now_in_millis()
|
|
320
350
|
response: ProcessStepResponsePbo = stub.ProcessData(
|
|
321
351
|
ProcessStepRequestPbo(
|
|
322
352
|
sapio_user=self.connection,
|
|
@@ -324,12 +354,15 @@ class TestClient:
|
|
|
324
354
|
config_field_values=self._config_fields,
|
|
325
355
|
dry_run=is_dry_run,
|
|
326
356
|
verbose_logging=is_verbose,
|
|
357
|
+
external_credential=self._credentials,
|
|
327
358
|
input=[
|
|
328
359
|
StepInputBatchPbo(is_partial=False, item_container=item)
|
|
329
360
|
for item in self._request_inputs
|
|
330
361
|
]
|
|
331
362
|
)
|
|
332
363
|
)
|
|
364
|
+
end = TimeUtil.now_in_millis()
|
|
365
|
+
print(f"Agent call completed in {(end - start) / 1000.:.3f} seconds")
|
|
333
366
|
|
|
334
367
|
results = AgentOutput(agent_name)
|
|
335
368
|
|
{sapiopycommons-2025.10.10a779.dist-info → sapiopycommons-2025.10.15a782.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2025.10.
|
|
3
|
+
Version: 2025.10.15a782
|
|
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,12 +1,12 @@
|
|
|
1
1
|
sapiopycommons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
sapiopycommons/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
sapiopycommons/ai/agent_service_base.py,sha256=
|
|
3
|
+
sapiopycommons/ai/agent_service_base.py,sha256=LVZg_4Kw5WTcIB7ru5hEqJoMWQr2eTDYjMrX8pzpn9I,56301
|
|
4
4
|
sapiopycommons/ai/converter_service_base.py,sha256=HiUXmwqv1STgyQeF9_eTFXzjIFXp5-NJ7sEhMpV3aAU,6351
|
|
5
|
-
sapiopycommons/ai/external_credentials.py,sha256=
|
|
5
|
+
sapiopycommons/ai/external_credentials.py,sha256=ki_xIH4J843b_sSwEa8YHr8vW9erVv-jowZJXSgPQs8,4347
|
|
6
6
|
sapiopycommons/ai/protobuf_utils.py,sha256=cBjbxoFAwU02kNUxEce95WnMU2CMuDD-qFaeWgvQJMQ,24599
|
|
7
7
|
sapiopycommons/ai/request_validation.py,sha256=TD2EUi_G5cy1OOVK1AmY2SQc3PEoAKGWs2pT8Qnp2Oo,25092
|
|
8
8
|
sapiopycommons/ai/server.py,sha256=gutSskn_Fenq1uz0DDMvjx4QVFiKt2WVEP3-01a69eU,6384
|
|
9
|
-
sapiopycommons/ai/test_client.py,sha256=
|
|
9
|
+
sapiopycommons/ai/test_client.py,sha256=IRZ-8prhg7XMDmN9aC1MQr5mSkMgfT37aLsOy-VB-MU,20495
|
|
10
10
|
sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2.py,sha256=mEonoj6Iq-AyvO4m3YsPYu85aZfD1E0a0cL8B9yPfEo,2481
|
|
11
11
|
sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2.pyi,sha256=sfExq8fFwIwFxCpV50ytdxW5ePNBjJBr_80_trq_JQw,1658
|
|
12
12
|
sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2_grpc.py,sha256=TNS1CB_QGBSa1YU9sYR_hF-pmBwv2GpxjaNQoM_r9iU,948
|
|
@@ -106,7 +106,7 @@ sapiopycommons/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
|
106
106
|
sapiopycommons/webhook/webhook_context.py,sha256=D793uLsb1691SalaPnBUk3rOSxn_hYLhdvkaIxjNXss,1909
|
|
107
107
|
sapiopycommons/webhook/webhook_handlers.py,sha256=7o_wXOruhT9auNh8OfhJAh4WhhiPKij67FMBSpGPICc,39939
|
|
108
108
|
sapiopycommons/webhook/webservice_handlers.py,sha256=cvW6Mk_110BzYqkbk63Kg7jWrltBCDALOlkJRu8h4VQ,14300
|
|
109
|
-
sapiopycommons-2025.10.
|
|
110
|
-
sapiopycommons-2025.10.
|
|
111
|
-
sapiopycommons-2025.10.
|
|
112
|
-
sapiopycommons-2025.10.
|
|
109
|
+
sapiopycommons-2025.10.15a782.dist-info/METADATA,sha256=DIeUTx52EZpA1ZIecBFRA4_LsTMKBwOZLzB6i2iBgxs,3143
|
|
110
|
+
sapiopycommons-2025.10.15a782.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
111
|
+
sapiopycommons-2025.10.15a782.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
112
|
+
sapiopycommons-2025.10.15a782.dist-info/RECORD,,
|
|
File without changes
|
{sapiopycommons-2025.10.10a779.dist-info → sapiopycommons-2025.10.15a782.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|