aixtools 0.1.6__tar.gz → 0.1.7__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 aixtools might be problematic. Click here for more details.
- {aixtools-0.1.6 → aixtools-0.1.7}/PKG-INFO +1 -1
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/_version.py +3 -3
- aixtools-0.1.7/aixtools/vault/vault.py +100 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools.egg-info/PKG-INFO +1 -1
- aixtools-0.1.7/tests/unit/vault/test_vault.py +171 -0
- aixtools-0.1.6/aixtools/vault/vault.py +0 -73
- aixtools-0.1.6/tests/unit/vault/test_vault.py +0 -114
- {aixtools-0.1.6 → aixtools-0.1.7}/.env_template +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/.github/workflows/build_and_publish_docker.yml +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/.github/workflows/lint-and-test.yml +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/.github/workflows/release.yml +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/.gitignore +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/.python-version +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/.roo/rules/rules-mcp.md +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/.roo/rules/rules.md +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/.vscode/settings.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/README.md +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/README.ori.md +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/config.toml +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/translations/bn.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/translations/en-US.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/translations/gu.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/translations/he-IL.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/translations/hi.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/translations/ja.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/translations/kn.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/translations/ml.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/translations/mr.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/translations/nl.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/translations/ta.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/translations/te.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/.chainlit/translations/zh-CN.json +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/a2a/app.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/a2a/google_sdk/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/a2a/google_sdk/card.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/a2a/google_sdk/pydantic_ai_adapter/agent_executor.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/a2a/google_sdk/pydantic_ai_adapter/storage.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/a2a/google_sdk/remote_agent_connection.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/a2a/google_sdk/utils.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/a2a/utils.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/agents/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/agents/agent.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/agents/agent_batch.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/agents/prompt.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/app.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/chainlit.md +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/context.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/db/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/db/database.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/db/vector_db.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/google/client.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/log_view/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/log_view/app.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/log_view/display.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/log_view/export.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/log_view/filters.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/log_view/log_utils.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/log_view/node_summary.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/logfilters/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/logfilters/context_filter.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/logging/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/logging/log_objects.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/logging/logging_config.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/logging/mcp_log_models.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/logging/mcp_logger.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/logging/model_patch_logging.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/logging/open_telemetry.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/mcp/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/mcp/client.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/mcp/example_client.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/mcp/example_server.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/mcp/fast_mcp_log.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/mcp/faulty_mcp.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/model_patch/model_patch.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/server/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/server/app_mounter.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/server/path.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/server/utils.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/testing/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/testing/aix_test_model.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/testing/mock_tool.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/testing/model_patch_cache.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/tools/doctor/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/tools/doctor/tool_doctor.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/tools/doctor/tool_recommendation.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/utils/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/utils/chainlit/cl_agent_show.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/utils/chainlit/cl_utils.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/utils/config.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/utils/config_util.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/utils/enum_with_description.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/utils/files.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/utils/persisted_dict.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/utils/utils.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools/vault/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools.egg-info/SOURCES.txt +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools.egg-info/dependency_links.txt +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools.egg-info/entry_points.txt +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools.egg-info/requires.txt +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/aixtools.egg-info/top_level.txt +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/docker/mcp-base/Dockerfile +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/notebooks/example_faulty_mcp_server.ipynb +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/notebooks/example_mcp_server_stdio.ipynb +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/notebooks/example_raw_mcp_client.ipynb +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/notebooks/example_tool_doctor.ipynb +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/pyproject.toml +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/scripts/config.sh +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/scripts/lint.sh +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/scripts/log_view.sh +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/scripts/run_example_mcp_server.sh +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/scripts/run_faulty_mcp_server.sh +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/scripts/run_server.sh +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/scripts/test.sh +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/setup.cfg +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/a2a/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/a2a/google_sdk/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/a2a/google_sdk/pydantic_ai_adapter/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_agent_executor.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_storage.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/a2a/google_sdk/test_card.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/a2a/google_sdk/test_remote_agent_connection.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/a2a/google_sdk/test_utils.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/agents/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/agents/test_prompt.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/google/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/google/test_client.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/mcp/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/mcp/test_client.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/server/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/server/test_path.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/server/test_utils.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/utils/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/utils/test_files.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/vault/__init__.py +0 -0
- {aixtools-0.1.6 → aixtools-0.1.7}/uv.lock +0 -0
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.1.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
31
|
+
__version__ = version = '0.1.7'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 7)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g78a5dc717'
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# ruff: noqa: PLR0913
|
|
2
|
+
"""
|
|
3
|
+
Provides a Vault client for storing and retrieving user service api keys.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
|
|
9
|
+
import hvac
|
|
10
|
+
from hvac.exceptions import InvalidPath
|
|
11
|
+
|
|
12
|
+
from aixtools.utils import config
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VaultAuthError(Exception):
|
|
18
|
+
"""Exception raised for vault authentication errors."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class VaultClient:
|
|
22
|
+
"""Vault client for storing and retrieving user service api keys."""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.client = hvac.Client(url=config.VAULT_ADDRESS, token=config.VAULT_TOKEN)
|
|
26
|
+
|
|
27
|
+
if not self.client.is_authenticated():
|
|
28
|
+
raise VaultAuthError("Vault client authentication failed. Check vault_token.")
|
|
29
|
+
|
|
30
|
+
def _get_secret_path(self, user_id: str, service_name: str) -> str:
|
|
31
|
+
"""Generate the vault secret path for a user and service."""
|
|
32
|
+
return f"{config.VAULT_PATH_PREFIX}/{config.VAULT_ENV}/{user_id}/{service_name}"
|
|
33
|
+
|
|
34
|
+
def store_user_service_api_key(self, *, user_id: str, service_name: str, user_api_key: str):
|
|
35
|
+
"""
|
|
36
|
+
Store user's service api key in the Vault at the specified vault mount
|
|
37
|
+
point, where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
38
|
+
|
|
39
|
+
This is a convenience method for storing a single API key.
|
|
40
|
+
For storing multiple secrets, use store_user_service_secret().
|
|
41
|
+
"""
|
|
42
|
+
secret_dict = {"user-api-key": user_api_key}
|
|
43
|
+
self.store_user_service_secret(user_id=user_id, service_name=service_name, secret_data=secret_dict)
|
|
44
|
+
|
|
45
|
+
def read_user_service_api_key(self, *, user_id: str, service_name: str) -> Optional[str]:
|
|
46
|
+
"""
|
|
47
|
+
Read user's service api key in from vault at the specified mount point,
|
|
48
|
+
where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
49
|
+
|
|
50
|
+
This is a convenience method for reading a single API key.
|
|
51
|
+
For reading multiple secrets, use read_user_service_secret().
|
|
52
|
+
"""
|
|
53
|
+
secret_data = self.read_user_service_secret(user_id=user_id, service_name=service_name)
|
|
54
|
+
if secret_data is None:
|
|
55
|
+
return None
|
|
56
|
+
return secret_data.get("user-api-key")
|
|
57
|
+
|
|
58
|
+
def store_user_service_secret(self, *, user_id: str, service_name: str, secret_data: Dict[str, str]):
|
|
59
|
+
"""
|
|
60
|
+
Store complete user service secret with multiple key-value pairs in the Vault
|
|
61
|
+
at the specified vault mount point, where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
62
|
+
"""
|
|
63
|
+
secret_path = None
|
|
64
|
+
try:
|
|
65
|
+
secret_path = self._get_secret_path(user_id, service_name)
|
|
66
|
+
logger.info("Writing complete secret to path %s", secret_path)
|
|
67
|
+
self.client.secrets.kv.v2.create_or_update_secret(
|
|
68
|
+
secret_path, secret=secret_data, mount_point=config.VAULT_MOUNT_POINT
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
logger.info("Complete secret written to path %s", secret_path)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error("Failed to write complete secret to path %s: %s", secret_path, str(e))
|
|
74
|
+
raise VaultAuthError(e) from e
|
|
75
|
+
|
|
76
|
+
def read_user_service_secret(self, *, user_id: str, service_name: str) -> Optional[Dict[str, str]]:
|
|
77
|
+
"""
|
|
78
|
+
Read complete user service secret from vault at the specified mount point,
|
|
79
|
+
where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
80
|
+
Returns all key-value pairs in the secret or None if the secret doesn't exist.
|
|
81
|
+
"""
|
|
82
|
+
secret_path = None
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
secret_path = self._get_secret_path(user_id, service_name)
|
|
86
|
+
logger.info("Reading complete secret from path %s", secret_path)
|
|
87
|
+
response = self.client.secrets.kv.v2.read_secret_version(
|
|
88
|
+
secret_path, mount_point=config.VAULT_MOUNT_POINT, raise_on_deleted_version=True
|
|
89
|
+
)
|
|
90
|
+
secret_data = response["data"]["data"]
|
|
91
|
+
logger.info("Complete secret read from path %s", secret_path)
|
|
92
|
+
return secret_data
|
|
93
|
+
except InvalidPath:
|
|
94
|
+
# Secret path does not exist
|
|
95
|
+
logger.warning("Secret path does not exist %s", secret_path)
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
logger.error("Failed to read complete secret from path %s: %s", secret_path, str(e))
|
|
100
|
+
raise VaultAuthError(e) from e
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from unittest.mock import MagicMock, patch
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from hvac.exceptions import InvalidPath
|
|
5
|
+
|
|
6
|
+
from aixtools.vault.vault import VaultAuthError, VaultClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def patched_vault_client():
|
|
11
|
+
with patch("aixtools.vault.vault.hvac.Client") as mock_hvac_client_cls:
|
|
12
|
+
fake_hvac_client = MagicMock()
|
|
13
|
+
fake_hvac_client.is_authenticated.return_value = True
|
|
14
|
+
mock_hvac_client_cls.return_value = fake_hvac_client
|
|
15
|
+
|
|
16
|
+
client = VaultClient()
|
|
17
|
+
return client
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def valid_params():
|
|
22
|
+
return {
|
|
23
|
+
"vault_mount_point": "secret",
|
|
24
|
+
"path_prefix": "path",
|
|
25
|
+
"env": "dev",
|
|
26
|
+
"user_id": "test-user",
|
|
27
|
+
"service_name": "test-service",
|
|
28
|
+
"user_api_key": "test-api-key",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_store_user_service_api_key_success(patched_vault_client, valid_params):
|
|
33
|
+
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.return_value = {}
|
|
34
|
+
|
|
35
|
+
patched_vault_client.store_user_service_api_key(
|
|
36
|
+
user_id=valid_params["user_id"],
|
|
37
|
+
service_name=valid_params["service_name"],
|
|
38
|
+
user_api_key=valid_params["user_api_key"],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
secret_path = (
|
|
42
|
+
f"{valid_params['path_prefix']}/{valid_params['env']}/{valid_params['user_id']}/{valid_params['service_name']}"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.assert_called_once_with(
|
|
46
|
+
secret_path,
|
|
47
|
+
secret={"user-api-key": valid_params["user_api_key"]},
|
|
48
|
+
mount_point=valid_params["vault_mount_point"],
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_store_user_service_api_key_invalid_path(patched_vault_client, valid_params):
|
|
53
|
+
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.side_effect = Exception("Invalid Path")
|
|
54
|
+
|
|
55
|
+
with pytest.raises(VaultAuthError, match="Invalid Path"):
|
|
56
|
+
patched_vault_client.store_user_service_api_key(
|
|
57
|
+
user_id=valid_params["user_id"],
|
|
58
|
+
service_name=valid_params["service_name"],
|
|
59
|
+
user_api_key=valid_params["user_api_key"],
|
|
60
|
+
)
|
|
61
|
+
patched_vault_client.client.assert_not_called()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_read_user_service_api_key_success(patched_vault_client):
|
|
65
|
+
"""Test successful read of user service API key."""
|
|
66
|
+
mock_response = {"data": {"data": {"user-api-key": "test-api-key"}}}
|
|
67
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.return_value = mock_response
|
|
68
|
+
|
|
69
|
+
result = patched_vault_client.read_user_service_api_key(user_id="test-user", service_name="test-service")
|
|
70
|
+
|
|
71
|
+
assert result == "test-api-key"
|
|
72
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
73
|
+
"path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_read_user_service_api_key_secret_not_found(patched_vault_client):
|
|
78
|
+
"""Test read_user_service_api_key when the secret path does not exist."""
|
|
79
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.side_effect = InvalidPath
|
|
80
|
+
|
|
81
|
+
result = patched_vault_client.read_user_service_api_key(user_id="test-user", service_name="test-service")
|
|
82
|
+
|
|
83
|
+
assert result is None
|
|
84
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
85
|
+
"path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_read_user_service_api_key_unexpected_error(patched_vault_client):
|
|
90
|
+
"""Test read_user_service_api_key when an unexpected exception occurs."""
|
|
91
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.side_effect = Exception("Unexpected error")
|
|
92
|
+
|
|
93
|
+
with pytest.raises(VaultAuthError, match="Unexpected error"):
|
|
94
|
+
patched_vault_client.read_user_service_api_key(user_id="test-user", service_name="test-service")
|
|
95
|
+
|
|
96
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
97
|
+
"path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_store_user_service_secret_success(patched_vault_client, valid_params):
|
|
102
|
+
"""Test successful storage of complete user service secret."""
|
|
103
|
+
secret_data = {"api_key": "test-api-key", "token": "test-token", "endpoint": "https://api.example.com"}
|
|
104
|
+
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.return_value = {}
|
|
105
|
+
|
|
106
|
+
patched_vault_client.store_user_service_secret(
|
|
107
|
+
user_id=valid_params["user_id"],
|
|
108
|
+
service_name=valid_params["service_name"],
|
|
109
|
+
secret_data=secret_data,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
secret_path = (
|
|
113
|
+
f"{valid_params['path_prefix']}/{valid_params['env']}/{valid_params['user_id']}/{valid_params['service_name']}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.assert_called_once_with(
|
|
117
|
+
secret_path,
|
|
118
|
+
secret=secret_data,
|
|
119
|
+
mount_point=valid_params["vault_mount_point"],
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_store_user_service_secret_error(patched_vault_client, valid_params):
|
|
124
|
+
"""Test store_user_service_secret when an error occurs."""
|
|
125
|
+
secret_data = {"api_key": "test-api-key", "token": "test-token"}
|
|
126
|
+
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.side_effect = Exception("Storage error")
|
|
127
|
+
|
|
128
|
+
with pytest.raises(VaultAuthError, match="Storage error"):
|
|
129
|
+
patched_vault_client.store_user_service_secret(
|
|
130
|
+
user_id=valid_params["user_id"],
|
|
131
|
+
service_name=valid_params["service_name"],
|
|
132
|
+
secret_data=secret_data,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_read_user_service_secret_success(patched_vault_client):
|
|
137
|
+
"""Test successful read of complete user service secret."""
|
|
138
|
+
secret_data = {"api_key": "test-api-key", "token": "test-token", "endpoint": "https://api.example.com"}
|
|
139
|
+
mock_response = {"data": {"data": secret_data}}
|
|
140
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.return_value = mock_response
|
|
141
|
+
|
|
142
|
+
result = patched_vault_client.read_user_service_secret(user_id="test-user", service_name="test-service")
|
|
143
|
+
|
|
144
|
+
assert result == secret_data
|
|
145
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
146
|
+
"path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_read_user_service_secret_not_found(patched_vault_client):
|
|
151
|
+
"""Test read_user_service_secret when the secret path does not exist."""
|
|
152
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.side_effect = InvalidPath
|
|
153
|
+
|
|
154
|
+
result = patched_vault_client.read_user_service_secret(user_id="test-user", service_name="test-service")
|
|
155
|
+
|
|
156
|
+
assert result is None
|
|
157
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
158
|
+
"path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_read_user_service_secret_error(patched_vault_client):
|
|
163
|
+
"""Test read_user_service_secret when an unexpected exception occurs."""
|
|
164
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.side_effect = Exception("Read error")
|
|
165
|
+
|
|
166
|
+
with pytest.raises(VaultAuthError, match="Read error"):
|
|
167
|
+
patched_vault_client.read_user_service_secret(user_id="test-user", service_name="test-service")
|
|
168
|
+
|
|
169
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
170
|
+
"path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
|
|
171
|
+
)
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# ruff: noqa: PLR0913
|
|
2
|
-
"""
|
|
3
|
-
Provides a Vault client for storing and retrieving user service api keys.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import logging
|
|
7
|
-
from typing import Optional
|
|
8
|
-
|
|
9
|
-
import hvac
|
|
10
|
-
from hvac.exceptions import InvalidPath
|
|
11
|
-
|
|
12
|
-
from aixtools.utils import config
|
|
13
|
-
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class VaultAuthError(Exception):
|
|
18
|
-
"""Exception raised for vault authentication errors."""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class VaultClient:
|
|
22
|
-
"""Vault client for storing and retrieving user service api keys."""
|
|
23
|
-
|
|
24
|
-
def __init__(self):
|
|
25
|
-
self.client = hvac.Client(url=config.VAULT_ADDRESS, token=config.VAULT_TOKEN)
|
|
26
|
-
|
|
27
|
-
if not self.client.is_authenticated():
|
|
28
|
-
raise VaultAuthError("Vault client authentication failed. Check vault_token.")
|
|
29
|
-
|
|
30
|
-
def store_user_service_api_key(self, *, user_id: str, service_name: str, user_api_key: str):
|
|
31
|
-
"""
|
|
32
|
-
Store user's service api key in the Vault at the specified vault mount
|
|
33
|
-
point, where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
34
|
-
"""
|
|
35
|
-
secret_path = None
|
|
36
|
-
try:
|
|
37
|
-
secret_path = f"{config.VAULT_PATH_PREFIX}/{config.VAULT_ENV}/{user_id}/{service_name}"
|
|
38
|
-
print("secret_path", secret_path)
|
|
39
|
-
secret_dict = {"user-api-key": user_api_key}
|
|
40
|
-
self.client.secrets.kv.v2.create_or_update_secret(
|
|
41
|
-
secret_path, secret=secret_dict, mount_point=config.VAULT_MOUNT_POINT
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
logger.info("Secret written to path %s", secret_path)
|
|
45
|
-
except Exception as e:
|
|
46
|
-
logger.error("Failed to write secret to path %s: %s", secret_path, str(e))
|
|
47
|
-
raise VaultAuthError(e) from e
|
|
48
|
-
|
|
49
|
-
def read_user_service_api_key(self, *, user_id: str, service_name) -> Optional[str]:
|
|
50
|
-
"""
|
|
51
|
-
Read user's service api key in from vault at the specified mount point,
|
|
52
|
-
where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
53
|
-
"""
|
|
54
|
-
secret_path = None
|
|
55
|
-
|
|
56
|
-
try:
|
|
57
|
-
secret_path = f"{config.VAULT_PATH_PREFIX}/{config.VAULT_ENV}/{user_id}/{service_name}"
|
|
58
|
-
logger.info("Reading secret from path %s", secret_path)
|
|
59
|
-
response = self.client.secrets.kv.v2.read_secret_version(
|
|
60
|
-
secret_path, mount_point=config.VAULT_MOUNT_POINT, raise_on_deleted_version=True
|
|
61
|
-
)
|
|
62
|
-
secret_data = response["data"]["data"]
|
|
63
|
-
user_api_key = secret_data["user-api-key"]
|
|
64
|
-
logger.info("Secret read from path %s ", secret_path)
|
|
65
|
-
return user_api_key
|
|
66
|
-
except InvalidPath:
|
|
67
|
-
# Secret path does not exist
|
|
68
|
-
logger.warning("Secret path does not exist %s ", secret_path)
|
|
69
|
-
return None
|
|
70
|
-
|
|
71
|
-
except Exception as e:
|
|
72
|
-
logger.error("Failed to read secret from path %s: %s", secret_path, str(e))
|
|
73
|
-
raise VaultAuthError(e) from e
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
from unittest.mock import MagicMock, patch
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from hvac.exceptions import InvalidPath
|
|
5
|
-
|
|
6
|
-
from aixtools.vault.vault import VaultClient, VaultAuthError
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@pytest.fixture
|
|
10
|
-
def patched_vault_client():
|
|
11
|
-
with patch("aixtools.vault.vault.hvac.Client") as mock_hvac_client_cls:
|
|
12
|
-
fake_hvac_client = MagicMock()
|
|
13
|
-
fake_hvac_client.is_authenticated.return_value = True
|
|
14
|
-
mock_hvac_client_cls.return_value = fake_hvac_client
|
|
15
|
-
|
|
16
|
-
client = VaultClient()
|
|
17
|
-
return client
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@pytest.fixture
|
|
21
|
-
def valid_params():
|
|
22
|
-
return {
|
|
23
|
-
"vault_mount_point": "secret",
|
|
24
|
-
"path_prefix": "path",
|
|
25
|
-
"env": "dev",
|
|
26
|
-
"user_id": "test-user",
|
|
27
|
-
"service_name": "test-service",
|
|
28
|
-
"user_api_key": "test-api-key"
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def test_store_user_service_api_key_success(patched_vault_client, valid_params):
|
|
33
|
-
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.return_value = {}
|
|
34
|
-
|
|
35
|
-
patched_vault_client.store_user_service_api_key(user_id=valid_params["user_id"],
|
|
36
|
-
service_name=valid_params['service_name'],
|
|
37
|
-
user_api_key=valid_params['user_api_key'])
|
|
38
|
-
|
|
39
|
-
secret_path = f"{valid_params['path_prefix']}/{valid_params['env']}/{valid_params['user_id']}/{valid_params['service_name']}"
|
|
40
|
-
|
|
41
|
-
print("expected secret path", secret_path)
|
|
42
|
-
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.assert_called_once_with(
|
|
43
|
-
secret_path,
|
|
44
|
-
secret={"user-api-key": valid_params["user_api_key"]},
|
|
45
|
-
mount_point=valid_params["vault_mount_point"]
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def test_store_user_service_api_key_invalid_path(patched_vault_client, valid_params):
|
|
50
|
-
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.side_effect = Exception("Invalid Path")
|
|
51
|
-
|
|
52
|
-
with pytest.raises(VaultAuthError, match="Invalid Path"):
|
|
53
|
-
patched_vault_client.store_user_service_api_key(user_id=valid_params["user_id"],
|
|
54
|
-
service_name=valid_params['service_name'],
|
|
55
|
-
user_api_key=valid_params['user_api_key'])
|
|
56
|
-
patched_vault_client.client.assert_not_called()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def test_read_user_service_api_key_success(patched_vault_client):
|
|
60
|
-
"""Test successful read of user service API key."""
|
|
61
|
-
mock_response = {
|
|
62
|
-
"data": {
|
|
63
|
-
"data": {
|
|
64
|
-
"user-api-key": "test-api-key"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
patched_vault_client.client.secrets.kv.v2.read_secret_version.return_value = mock_response
|
|
69
|
-
|
|
70
|
-
result = patched_vault_client.read_user_service_api_key(
|
|
71
|
-
user_id="test-user",
|
|
72
|
-
service_name="test-service"
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
assert result == "test-api-key"
|
|
76
|
-
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
77
|
-
"path/dev/test-user/test-service",
|
|
78
|
-
mount_point='secret',
|
|
79
|
-
raise_on_deleted_version=True
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def test_read_user_service_api_key_secret_not_found(patched_vault_client):
|
|
84
|
-
"""Test read_user_service_api_key when the secret path does not exist."""
|
|
85
|
-
patched_vault_client.client.secrets.kv.v2.read_secret_version.side_effect = InvalidPath
|
|
86
|
-
|
|
87
|
-
result = patched_vault_client.read_user_service_api_key(
|
|
88
|
-
user_id="test-user",
|
|
89
|
-
service_name="test-service"
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
assert result is None
|
|
93
|
-
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
94
|
-
"path/dev/test-user/test-service",
|
|
95
|
-
mount_point="secret",
|
|
96
|
-
raise_on_deleted_version=True
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def test_read_user_service_api_key_unexpected_error(patched_vault_client):
|
|
101
|
-
"""Test read_user_service_api_key when an unexpected exception occurs."""
|
|
102
|
-
patched_vault_client.client.secrets.kv.v2.read_secret_version.side_effect = Exception("Unexpected error")
|
|
103
|
-
|
|
104
|
-
with pytest.raises(VaultAuthError, match="Unexpected error"):
|
|
105
|
-
patched_vault_client.read_user_service_api_key(
|
|
106
|
-
user_id="test-user",
|
|
107
|
-
service_name="test-service"
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
111
|
-
"path/dev/test-user/test-service",
|
|
112
|
-
mount_point="secret",
|
|
113
|
-
raise_on_deleted_version=True
|
|
114
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aixtools-0.1.6 → aixtools-0.1.7}/aixtools/a2a/google_sdk/pydantic_ai_adapter/agent_executor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aixtools-0.1.6 → aixtools-0.1.7}/tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_storage.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|