aixtools 0.1.6__py3-none-any.whl → 0.1.7__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 aixtools might be problematic. Click here for more details.

aixtools/_version.py CHANGED
@@ -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.6'
32
- __version_tuple__ = version_tuple = (0, 1, 6)
31
+ __version__ = version = '0.1.7'
32
+ __version_tuple__ = version_tuple = (0, 1, 7)
33
33
 
34
34
  __commit_id__ = commit_id = None
aixtools/vault/vault.py CHANGED
@@ -4,7 +4,7 @@ Provides a Vault client for storing and retrieving user service api keys.
4
4
  """
5
5
 
6
6
  import logging
7
- from typing import Optional
7
+ from typing import Dict, Optional
8
8
 
9
9
  import hvac
10
10
  from hvac.exceptions import InvalidPath
@@ -27,47 +27,74 @@ class VaultClient:
27
27
  if not self.client.is_authenticated():
28
28
  raise VaultAuthError("Vault client authentication failed. Check vault_token.")
29
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
+
30
34
  def store_user_service_api_key(self, *, user_id: str, service_name: str, user_api_key: str):
31
35
  """
32
36
  Store user's service api key in the Vault at the specified vault mount
33
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>.
34
62
  """
35
63
  secret_path = None
36
64
  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}
65
+ secret_path = self._get_secret_path(user_id, service_name)
66
+ logger.info("Writing complete secret to path %s", secret_path)
40
67
  self.client.secrets.kv.v2.create_or_update_secret(
41
- secret_path, secret=secret_dict, mount_point=config.VAULT_MOUNT_POINT
68
+ secret_path, secret=secret_data, mount_point=config.VAULT_MOUNT_POINT
42
69
  )
43
70
 
44
- logger.info("Secret written to path %s", secret_path)
71
+ logger.info("Complete secret written to path %s", secret_path)
45
72
  except Exception as e:
46
- logger.error("Failed to write secret to path %s: %s", secret_path, str(e))
73
+ logger.error("Failed to write complete secret to path %s: %s", secret_path, str(e))
47
74
  raise VaultAuthError(e) from e
48
75
 
49
- def read_user_service_api_key(self, *, user_id: str, service_name) -> Optional[str]:
76
+ def read_user_service_secret(self, *, user_id: str, service_name: str) -> Optional[Dict[str, str]]:
50
77
  """
51
- Read user's service api key in from vault at the specified mount point,
78
+ Read complete user service secret from vault at the specified mount point,
52
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.
53
81
  """
54
82
  secret_path = None
55
83
 
56
84
  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)
85
+ secret_path = self._get_secret_path(user_id, service_name)
86
+ logger.info("Reading complete secret from path %s", secret_path)
59
87
  response = self.client.secrets.kv.v2.read_secret_version(
60
88
  secret_path, mount_point=config.VAULT_MOUNT_POINT, raise_on_deleted_version=True
61
89
  )
62
90
  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
91
+ logger.info("Complete secret read from path %s", secret_path)
92
+ return secret_data
66
93
  except InvalidPath:
67
94
  # Secret path does not exist
68
- logger.warning("Secret path does not exist %s ", secret_path)
95
+ logger.warning("Secret path does not exist %s", secret_path)
69
96
  return None
70
97
 
71
98
  except Exception as e:
72
- logger.error("Failed to read secret from path %s: %s", secret_path, str(e))
99
+ logger.error("Failed to read complete secret from path %s: %s", secret_path, str(e))
73
100
  raise VaultAuthError(e) from e
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aixtools
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Tools for AI exploration and debugging
5
5
  Requires-Python: >=3.11.2
6
6
  Description-Content-Type: text/markdown
@@ -1,5 +1,5 @@
1
1
  aixtools/__init__.py,sha256=9NGHm7LjsQmsvjTZvw6QFJexSvAU4bCoN_KBk9SCa00,260
2
- aixtools/_version.py,sha256=riGXiVTWXmtdoju9hVCWvTxpszEMAAIK0sZZWoLKlnU,704
2
+ aixtools/_version.py,sha256=szvPIs2C82UunpzuvVg3MbF4QhzbBYTsVJ8DmPfq6_E,704
3
3
  aixtools/app.py,sha256=JzQ0nrv_bjDQokllIlGHOV0HEb-V8N6k_nGQH-TEsVU,5227
4
4
  aixtools/chainlit.md,sha256=yC37Ly57vjKyiIvK4oUvf4DYxZCwH7iocTlx7bLeGLU,761
5
5
  aixtools/context.py,sha256=I_MD40ZnvRm5WPKAKqBUAdXIf8YaurkYUUHSVVy-QvU,598
@@ -77,7 +77,7 @@ aixtools/utils/utils.py,sha256=5911Ej1ES2NU_FKIWA3CWKhKnwgjvi1aDR2aiD6Xv3E,4880
77
77
  aixtools/utils/chainlit/cl_agent_show.py,sha256=vaRuowp4BRvhxEr5hw0zHEJ7iaSF_5bo_9BH7pGPPpw,4398
78
78
  aixtools/utils/chainlit/cl_utils.py,sha256=fxaxdkcZg6uHdM8uztxdPowg3a2f7VR7B26VPY4t-3c,5738
79
79
  aixtools/vault/__init__.py,sha256=fsr_NuX3GZ9WZ7dGfe0gp_5-z3URxAfwVRXw7Xyc0dU,141
80
- aixtools/vault/vault.py,sha256=JeAnRnsXm_etkTUntE9ABIiNlI8M81E2b85IQHfGehI,2817
80
+ aixtools/vault/vault.py,sha256=WkzBTEYM-Vqjyoa5x5imbEDi0ePBklMjD_aAdvIK-34,4293
81
81
  docker/mcp-base/Dockerfile,sha256=sSpbt0sasSBHHeGwPIpJpiEQMU5HGeXzerK8biVSt7Q,1547
82
82
  notebooks/example_faulty_mcp_server.ipynb,sha256=b2Cy3GXfj-gOBZ7SoUzj25F1rxp5u-32EWPHWQ-sxn8,1729
83
83
  notebooks/example_mcp_server_stdio.ipynb,sha256=ya4dRKNFU2vQxob-uIhKHGAzINXGQ6MehgKVmSCpHLk,1634
@@ -112,9 +112,9 @@ tests/unit/server/test_utils.py,sha256=kvhzdgNfsJl5tqcRBWg2yTR5GPpyrFCOmEIOuHb39
112
112
  tests/unit/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
113
113
  tests/unit/utils/test_files.py,sha256=AKFmXQqXstyKd2PreE4EmQyhQYeqOmu1Sp80MwHrf_Q,5782
114
114
  tests/unit/vault/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
- tests/unit/vault/test_vault.py,sha256=YzmJ-JZo8OLGNcmc845knCh3zp8jhkANVgKT09cuhSU,4250
116
- aixtools-0.1.6.dist-info/METADATA,sha256=6JFv550ISVltjdNxLIcsM0TNd-O7KS-g2ZsvCuanuJ0,18569
117
- aixtools-0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
118
- aixtools-0.1.6.dist-info/entry_points.txt,sha256=dHoutULEZx7xXSqJrZdViSVjfInJibfLibi2nRXL3SE,56
119
- aixtools-0.1.6.dist-info/top_level.txt,sha256=ee4eF-0pqu45zCUVml0mWIhnXQgqMQper2-49BBVHLY,40
120
- aixtools-0.1.6.dist-info/RECORD,,
115
+ tests/unit/vault/test_vault.py,sha256=R_RTDsralvE0JhuIrHQTb85gU9ipDJD7aoYIMdcg0o4,7264
116
+ aixtools-0.1.7.dist-info/METADATA,sha256=PRE6AkiaiOoNeUWBs9PhV2O0jhSRRkPvqOvfodz57Lo,18569
117
+ aixtools-0.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
118
+ aixtools-0.1.7.dist-info/entry_points.txt,sha256=dHoutULEZx7xXSqJrZdViSVjfInJibfLibi2nRXL3SE,56
119
+ aixtools-0.1.7.dist-info/top_level.txt,sha256=ee4eF-0pqu45zCUVml0mWIhnXQgqMQper2-49BBVHLY,40
120
+ aixtools-0.1.7.dist-info/RECORD,,
@@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch
3
3
  import pytest
4
4
  from hvac.exceptions import InvalidPath
5
5
 
6
- from aixtools.vault.vault import VaultClient, VaultAuthError
6
+ from aixtools.vault.vault import VaultAuthError, VaultClient
7
7
 
8
8
 
9
9
  @pytest.fixture
@@ -25,24 +25,27 @@ def valid_params():
25
25
  "env": "dev",
26
26
  "user_id": "test-user",
27
27
  "service_name": "test-service",
28
- "user_api_key": "test-api-key"
28
+ "user_api_key": "test-api-key",
29
29
  }
30
30
 
31
31
 
32
32
  def test_store_user_service_api_key_success(patched_vault_client, valid_params):
33
33
  patched_vault_client.client.secrets.kv.v2.create_or_update_secret.return_value = {}
34
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'])
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
+ )
38
40
 
39
- secret_path = f"{valid_params['path_prefix']}/{valid_params['env']}/{valid_params['user_id']}/{valid_params['service_name']}"
41
+ secret_path = (
42
+ f"{valid_params['path_prefix']}/{valid_params['env']}/{valid_params['user_id']}/{valid_params['service_name']}"
43
+ )
40
44
 
41
- print("expected secret path", secret_path)
42
45
  patched_vault_client.client.secrets.kv.v2.create_or_update_secret.assert_called_once_with(
43
46
  secret_path,
44
47
  secret={"user-api-key": valid_params["user_api_key"]},
45
- mount_point=valid_params["vault_mount_point"]
48
+ mount_point=valid_params["vault_mount_point"],
46
49
  )
47
50
 
48
51
 
@@ -50,33 +53,24 @@ def test_store_user_service_api_key_invalid_path(patched_vault_client, valid_par
50
53
  patched_vault_client.client.secrets.kv.v2.create_or_update_secret.side_effect = Exception("Invalid Path")
51
54
 
52
55
  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.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
+ )
56
61
  patched_vault_client.client.assert_not_called()
57
62
 
58
63
 
59
64
  def test_read_user_service_api_key_success(patched_vault_client):
60
65
  """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
- }
66
+ mock_response = {"data": {"data": {"user-api-key": "test-api-key"}}}
68
67
  patched_vault_client.client.secrets.kv.v2.read_secret_version.return_value = mock_response
69
68
 
70
- result = patched_vault_client.read_user_service_api_key(
71
- user_id="test-user",
72
- service_name="test-service"
73
- )
69
+ result = patched_vault_client.read_user_service_api_key(user_id="test-user", service_name="test-service")
74
70
 
75
71
  assert result == "test-api-key"
76
72
  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
73
+ "path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
80
74
  )
81
75
 
82
76
 
@@ -84,16 +78,11 @@ def test_read_user_service_api_key_secret_not_found(patched_vault_client):
84
78
  """Test read_user_service_api_key when the secret path does not exist."""
85
79
  patched_vault_client.client.secrets.kv.v2.read_secret_version.side_effect = InvalidPath
86
80
 
87
- result = patched_vault_client.read_user_service_api_key(
88
- user_id="test-user",
89
- service_name="test-service"
90
- )
81
+ result = patched_vault_client.read_user_service_api_key(user_id="test-user", service_name="test-service")
91
82
 
92
83
  assert result is None
93
84
  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
85
+ "path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
97
86
  )
98
87
 
99
88
 
@@ -102,13 +91,81 @@ def test_read_user_service_api_key_unexpected_error(patched_vault_client):
102
91
  patched_vault_client.client.secrets.kv.v2.read_secret_version.side_effect = Exception("Unexpected error")
103
92
 
104
93
  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"
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,
108
133
  )
109
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
+
110
169
  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
170
+ "path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
114
171
  )