aixtools 0.1.10__py3-none-any.whl → 0.1.11__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.

Files changed (46) hide show
  1. aixtools/_version.py +2 -2
  2. aixtools/mcp/client.py +102 -1
  3. aixtools/testing/aix_test_model.py +2 -0
  4. {aixtools-0.1.10.dist-info → aixtools-0.1.11.dist-info}/METADATA +2 -1
  5. {aixtools-0.1.10.dist-info → aixtools-0.1.11.dist-info}/RECORD +8 -45
  6. aixtools-0.1.11.dist-info/top_level.txt +1 -0
  7. aixtools-0.1.10.dist-info/top_level.txt +0 -5
  8. docker/mcp-base/Dockerfile +0 -33
  9. docker/mcp-base/zscaler.crt +0 -28
  10. notebooks/example_faulty_mcp_server.ipynb +0 -74
  11. notebooks/example_mcp_server_stdio.ipynb +0 -76
  12. notebooks/example_raw_mcp_client.ipynb +0 -84
  13. notebooks/example_tool_doctor.ipynb +0 -65
  14. scripts/config.sh +0 -28
  15. scripts/lint.sh +0 -32
  16. scripts/log_view.sh +0 -18
  17. scripts/run_example_mcp_server.sh +0 -14
  18. scripts/run_faulty_mcp_server.sh +0 -13
  19. scripts/run_server.sh +0 -29
  20. scripts/test.sh +0 -30
  21. tests/__init__.py +0 -0
  22. tests/unit/__init__.py +0 -0
  23. tests/unit/a2a/__init__.py +0 -0
  24. tests/unit/a2a/google_sdk/__init__.py +0 -0
  25. tests/unit/a2a/google_sdk/pydantic_ai_adapter/__init__.py +0 -0
  26. tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_agent_executor.py +0 -188
  27. tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_storage.py +0 -156
  28. tests/unit/a2a/google_sdk/test_card.py +0 -114
  29. tests/unit/a2a/google_sdk/test_remote_agent_connection.py +0 -413
  30. tests/unit/a2a/google_sdk/test_utils.py +0 -208
  31. tests/unit/agents/__init__.py +0 -0
  32. tests/unit/agents/test_prompt.py +0 -363
  33. tests/unit/compliance/test_private_data.py +0 -329
  34. tests/unit/google/__init__.py +0 -1
  35. tests/unit/google/test_client.py +0 -233
  36. tests/unit/mcp/__init__.py +0 -0
  37. tests/unit/mcp/test_client.py +0 -242
  38. tests/unit/server/__init__.py +0 -0
  39. tests/unit/server/test_path.py +0 -225
  40. tests/unit/server/test_utils.py +0 -362
  41. tests/unit/utils/__init__.py +0 -0
  42. tests/unit/utils/test_files.py +0 -146
  43. tests/unit/vault/__init__.py +0 -0
  44. tests/unit/vault/test_vault.py +0 -246
  45. {aixtools-0.1.10.dist-info → aixtools-0.1.11.dist-info}/WHEEL +0 -0
  46. {aixtools-0.1.10.dist-info → aixtools-0.1.11.dist-info}/entry_points.txt +0 -0
@@ -1 +0,0 @@
1
- """Unit tests for aixtools.google module."""
@@ -1,233 +0,0 @@
1
- """Unit tests for aixtools.google.client module."""
2
-
3
- import os
4
- import unittest
5
- from pathlib import Path
6
- from unittest.mock import Mock, patch, MagicMock
7
-
8
- from aixtools.google.client import get_genai_client
9
-
10
-
11
- class TestGetGenaiClient(unittest.TestCase):
12
- """Test cases for get_genai_client function."""
13
-
14
- def setUp(self):
15
- """Set up test fixtures."""
16
- # Store original environment variables to restore later
17
- self.original_env = os.environ.copy()
18
-
19
- def tearDown(self):
20
- """Clean up after tests."""
21
- # Restore original environment variables
22
- os.environ.clear()
23
- os.environ.update(self.original_env)
24
-
25
- @patch('aixtools.google.client.genai.Client')
26
- @patch('aixtools.google.client.GOOGLE_CLOUD_PROJECT', 'test-project')
27
- @patch('aixtools.google.client.GOOGLE_CLOUD_LOCATION', 'us-central1')
28
- @patch('aixtools.google.client.GOOGLE_GENAI_USE_VERTEXAI', True)
29
- def test_get_genai_client_basic_success(self, mock_client):
30
- """Test get_genai_client with basic valid configuration."""
31
- mock_client_instance = Mock()
32
- mock_client.return_value = mock_client_instance
33
-
34
- result = get_genai_client()
35
-
36
- mock_client.assert_called_once_with(
37
- vertexai=True,
38
- project='test-project',
39
- location='us-central1'
40
- )
41
- self.assertEqual(result, mock_client_instance)
42
-
43
- @patch('aixtools.google.client.genai.Client')
44
- @patch('aixtools.google.client.GOOGLE_CLOUD_PROJECT', 'test-project')
45
- @patch('aixtools.google.client.GOOGLE_CLOUD_LOCATION', 'us-west1')
46
- @patch('aixtools.google.client.GOOGLE_GENAI_USE_VERTEXAI', False)
47
- def test_get_genai_client_without_vertexai(self, mock_client):
48
- """Test get_genai_client with GOOGLE_GENAI_USE_VERTEXAI set to False."""
49
- mock_client_instance = Mock()
50
- mock_client.return_value = mock_client_instance
51
-
52
- result = get_genai_client()
53
-
54
- mock_client.assert_called_once_with(
55
- vertexai=False,
56
- project='test-project',
57
- location='us-west1'
58
- )
59
- self.assertEqual(result, mock_client_instance)
60
-
61
- @patch('aixtools.google.client.GOOGLE_CLOUD_PROJECT', None)
62
- @patch('aixtools.google.client.GOOGLE_CLOUD_LOCATION', 'us-central1')
63
- def test_get_genai_client_missing_project(self):
64
- """Test get_genai_client raises AssertionError when GOOGLE_CLOUD_PROJECT is not set."""
65
- with self.assertRaises(AssertionError) as context:
66
- get_genai_client()
67
-
68
- self.assertIn("GOOGLE_CLOUD_PROJECT is not set", str(context.exception))
69
-
70
- @patch('aixtools.google.client.GOOGLE_CLOUD_PROJECT', 'test-project')
71
- @patch('aixtools.google.client.GOOGLE_CLOUD_LOCATION', None)
72
- def test_get_genai_client_missing_location(self):
73
- """Test get_genai_client raises AssertionError when GOOGLE_CLOUD_LOCATION is not set."""
74
- with self.assertRaises(AssertionError) as context:
75
- get_genai_client()
76
-
77
- self.assertIn("GOOGLE_CLOUD_LOCATION is not set", str(context.exception))
78
-
79
- @patch('aixtools.google.client.GOOGLE_CLOUD_PROJECT', '')
80
- @patch('aixtools.google.client.GOOGLE_CLOUD_LOCATION', 'us-central1')
81
- def test_get_genai_client_empty_project(self):
82
- """Test get_genai_client raises AssertionError when GOOGLE_CLOUD_PROJECT is empty."""
83
- with self.assertRaises(AssertionError) as context:
84
- get_genai_client()
85
-
86
- self.assertIn("GOOGLE_CLOUD_PROJECT is not set", str(context.exception))
87
-
88
- @patch('aixtools.google.client.GOOGLE_CLOUD_PROJECT', 'test-project')
89
- @patch('aixtools.google.client.GOOGLE_CLOUD_LOCATION', '')
90
- def test_get_genai_client_empty_location(self):
91
- """Test get_genai_client raises AssertionError when GOOGLE_CLOUD_LOCATION is empty."""
92
- with self.assertRaises(AssertionError) as context:
93
- get_genai_client()
94
-
95
- self.assertIn("GOOGLE_CLOUD_LOCATION is not set", str(context.exception))
96
-
97
- @patch('aixtools.google.client.genai.Client')
98
- @patch('aixtools.google.client.GOOGLE_CLOUD_PROJECT', 'test-project')
99
- @patch('aixtools.google.client.GOOGLE_CLOUD_LOCATION', 'us-central1')
100
- @patch('aixtools.google.client.GOOGLE_GENAI_USE_VERTEXAI', True)
101
- @patch('aixtools.google.client.logger')
102
- def test_get_genai_client_with_valid_service_account_key(self, mock_logger, mock_client):
103
- """Test get_genai_client with valid service account key file."""
104
- mock_client_instance = Mock()
105
- mock_client.return_value = mock_client_instance
106
-
107
- # Create a temporary file path that exists
108
- with patch('pathlib.Path.exists', return_value=True):
109
- service_account_path = Path('/tmp/test-service-account.json')
110
-
111
- result = get_genai_client(service_account_path)
112
-
113
- # Verify the environment variable was set
114
- self.assertEqual(os.environ.get('GOOGLE_APPLICATION_CREDENTIALS'), str(service_account_path))
115
-
116
- # Verify logging was called
117
- mock_logger.info.assert_called_once_with(f"✅ GCP Service Account Key File: {service_account_path}")
118
-
119
- # Verify client was created correctly
120
- mock_client.assert_called_once_with(
121
- vertexai=True,
122
- project='test-project',
123
- location='us-central1'
124
- )
125
- self.assertEqual(result, mock_client_instance)
126
-
127
- @patch('aixtools.google.client.GOOGLE_CLOUD_PROJECT', 'test-project')
128
- @patch('aixtools.google.client.GOOGLE_CLOUD_LOCATION', 'us-central1')
129
- def test_get_genai_client_with_nonexistent_service_account_key(self):
130
- """Test get_genai_client raises FileNotFoundError for nonexistent service account key."""
131
- with patch('pathlib.Path.exists', return_value=False):
132
- service_account_path = Path('/tmp/nonexistent-service-account.json')
133
-
134
- with self.assertRaises(FileNotFoundError) as context:
135
- get_genai_client(service_account_path)
136
-
137
- self.assertIn(f"Service account key file not found: {service_account_path}", str(context.exception))
138
-
139
- @patch('aixtools.google.client.genai.Client')
140
- @patch('aixtools.google.client.GOOGLE_CLOUD_PROJECT', 'test-project')
141
- @patch('aixtools.google.client.GOOGLE_CLOUD_LOCATION', 'us-central1')
142
- @patch('aixtools.google.client.GOOGLE_GENAI_USE_VERTEXAI', True)
143
- def test_get_genai_client_preserves_existing_credentials_env(self, mock_client):
144
- """Test get_genai_client preserves existing GOOGLE_APPLICATION_CREDENTIALS when no service account key provided."""
145
- mock_client_instance = Mock()
146
- mock_client.return_value = mock_client_instance
147
-
148
- # Set existing credentials
149
- existing_credentials = '/existing/path/to/credentials.json'
150
- os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = existing_credentials
151
-
152
- result = get_genai_client()
153
-
154
- # Verify existing credentials are preserved
155
- self.assertEqual(os.environ.get('GOOGLE_APPLICATION_CREDENTIALS'), existing_credentials)
156
-
157
- mock_client.assert_called_once_with(
158
- vertexai=True,
159
- project='test-project',
160
- location='us-central1'
161
- )
162
- self.assertEqual(result, mock_client_instance)
163
-
164
- @patch('aixtools.google.client.genai.Client')
165
- @patch('aixtools.google.client.GOOGLE_CLOUD_PROJECT', 'test-project')
166
- @patch('aixtools.google.client.GOOGLE_CLOUD_LOCATION', 'us-central1')
167
- @patch('aixtools.google.client.GOOGLE_GENAI_USE_VERTEXAI', True)
168
- @patch('aixtools.google.client.logger')
169
- def test_get_genai_client_overwrites_existing_credentials_env(self, mock_logger, mock_client):
170
- """Test get_genai_client overwrites existing GOOGLE_APPLICATION_CREDENTIALS when service account key provided."""
171
- mock_client_instance = Mock()
172
- mock_client.return_value = mock_client_instance
173
-
174
- # Set existing credentials
175
- existing_credentials = '/existing/path/to/credentials.json'
176
- os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = existing_credentials
177
-
178
- with patch('pathlib.Path.exists', return_value=True):
179
- new_service_account_path = Path('/tmp/new-service-account.json')
180
-
181
- result = get_genai_client(new_service_account_path)
182
-
183
- # Verify new credentials overwrite existing ones
184
- self.assertEqual(os.environ.get('GOOGLE_APPLICATION_CREDENTIALS'), str(new_service_account_path))
185
-
186
- # Verify logging was called
187
- mock_logger.info.assert_called_once_with(f"✅ GCP Service Account Key File: {new_service_account_path}")
188
-
189
- mock_client.assert_called_once_with(
190
- vertexai=True,
191
- project='test-project',
192
- location='us-central1'
193
- )
194
- self.assertEqual(result, mock_client_instance)
195
-
196
- @patch('aixtools.google.client.genai.Client')
197
- @patch('aixtools.google.client.GOOGLE_CLOUD_PROJECT', 'test-project')
198
- @patch('aixtools.google.client.GOOGLE_CLOUD_LOCATION', 'us-central1')
199
- @patch('aixtools.google.client.GOOGLE_GENAI_USE_VERTEXAI', True)
200
- def test_get_genai_client_genai_client_exception(self, mock_client):
201
- """Test get_genai_client propagates exceptions from genai.Client."""
202
- mock_client.side_effect = Exception("GenAI client initialization failed")
203
-
204
- with self.assertRaises(Exception) as context:
205
- get_genai_client()
206
-
207
- self.assertIn("GenAI client initialization failed", str(context.exception))
208
-
209
- @patch('aixtools.google.client.genai.Client')
210
- @patch('aixtools.google.client.GOOGLE_CLOUD_PROJECT', 'test-project')
211
- @patch('aixtools.google.client.GOOGLE_CLOUD_LOCATION', 'us-central1')
212
- @patch('aixtools.google.client.GOOGLE_GENAI_USE_VERTEXAI', True)
213
- @patch('aixtools.google.client.logger')
214
- def test_get_genai_client_service_account_path_as_string(self, mock_logger, mock_client):
215
- """Test get_genai_client handles service account path as string correctly."""
216
- mock_client_instance = Mock()
217
- mock_client.return_value = mock_client_instance
218
-
219
- with patch('pathlib.Path.exists', return_value=True):
220
- service_account_path = Path('/tmp/test-service-account.json')
221
-
222
- result = get_genai_client(service_account_path)
223
-
224
- # Verify the environment variable was set as string
225
- self.assertEqual(os.environ.get('GOOGLE_APPLICATION_CREDENTIALS'), str(service_account_path))
226
- self.assertIsInstance(os.environ.get('GOOGLE_APPLICATION_CREDENTIALS'), str)
227
-
228
- mock_client.assert_called_once()
229
- self.assertEqual(result, mock_client_instance)
230
-
231
-
232
- if __name__ == '__main__':
233
- unittest.main()
File without changes
@@ -1,242 +0,0 @@
1
- """Unit tests for aixtools.mcp.client module."""
2
-
3
- import asyncio
4
- import unittest
5
- from unittest.mock import Mock, patch
6
-
7
- from mcp import types as mcp_types
8
- from pydantic_ai import exceptions
9
-
10
- from aixtools.mcp.client import (
11
- CACHE_KEY,
12
- DEFAULT_MCP_CONNECTION_TIMEOUT,
13
- CachedMCPServerStreamableHTTP,
14
- get_configured_mcp_servers,
15
- get_mcp_headers,
16
- )
17
-
18
-
19
- class TestGetMcpHeaders(unittest.TestCase):
20
- """Test cases for get_mcp_headers function."""
21
-
22
- def test_get_mcp_headers_with_both_ids(self):
23
- """Test get_mcp_headers with both user_id and session_id."""
24
- session_id_tuple = ("user123", "session456")
25
- result = get_mcp_headers(session_id_tuple)
26
-
27
- expected = {
28
- "user-id": "user123",
29
- "session-id": "session456"
30
- }
31
- self.assertEqual(result, expected)
32
-
33
- def test_get_mcp_headers_with_user_id_only(self):
34
- """Test get_mcp_headers with only user_id."""
35
- session_id_tuple = ("user123", None) # type: ignore
36
- result = get_mcp_headers(session_id_tuple)
37
-
38
- expected = {
39
- "user-id": "user123"
40
- }
41
- self.assertEqual(result, expected)
42
-
43
- def test_get_mcp_headers_with_session_id_only(self):
44
- """Test get_mcp_headers with only session_id."""
45
- session_id_tuple = (None, "session456") # type: ignore
46
- result = get_mcp_headers(session_id_tuple)
47
-
48
- expected = {
49
- "session-id": "session456"
50
- }
51
- self.assertEqual(result, expected)
52
-
53
- def test_get_mcp_headers_with_empty_strings(self):
54
- """Test get_mcp_headers with empty strings."""
55
- session_id_tuple = ("", "")
56
- result = get_mcp_headers(session_id_tuple)
57
-
58
- self.assertIsNone(result)
59
-
60
- def test_get_mcp_headers_with_none_values(self):
61
- """Test get_mcp_headers with None values."""
62
- session_id_tuple = (None, None) # type: ignore
63
- result = get_mcp_headers(session_id_tuple)
64
-
65
- self.assertIsNone(result)
66
-
67
- def test_get_mcp_headers_with_mixed_empty_and_none(self):
68
- """Test get_mcp_headers with mixed empty and None values."""
69
- session_id_tuple = ("", None) # type: ignore
70
- result = get_mcp_headers(session_id_tuple)
71
-
72
- self.assertIsNone(result)
73
-
74
-
75
- class TestGetConfiguredMcpServers(unittest.TestCase):
76
- """Test cases for get_configured_mcp_servers function."""
77
-
78
- @patch('aixtools.mcp.client.CachedMCPServerStreamableHTTP')
79
- def test_get_configured_mcp_servers_basic(self, mock_cached_server):
80
- """Test get_configured_mcp_servers with basic parameters."""
81
- session_id_tuple = ("user123", "session456")
82
- mcp_urls = ["http://server1.com", "http://server2.com"]
83
-
84
- mock_server_instances = [Mock(), Mock()]
85
- mock_cached_server.side_effect = mock_server_instances
86
-
87
- result = get_configured_mcp_servers(session_id_tuple, mcp_urls)
88
-
89
- self.assertEqual(len(result), 2)
90
- self.assertEqual(result, mock_server_instances)
91
-
92
- # Verify that CachedMCPServerStreamableHTTP was called correctly
93
- expected_headers = {"user-id": "user123", "session-id": "session456"}
94
- mock_cached_server.assert_any_call(
95
- url="http://server1.com",
96
- headers=expected_headers,
97
- timeout=DEFAULT_MCP_CONNECTION_TIMEOUT
98
- )
99
- mock_cached_server.assert_any_call(
100
- url="http://server2.com",
101
- headers=expected_headers,
102
- timeout=DEFAULT_MCP_CONNECTION_TIMEOUT
103
- )
104
-
105
- @patch('aixtools.mcp.client.CachedMCPServerStreamableHTTP')
106
- def test_get_configured_mcp_servers_custom_timeout(self, mock_cached_server):
107
- """Test get_configured_mcp_servers with custom timeout."""
108
- session_id_tuple = ("user123", "session456")
109
- mcp_urls = ["http://server1.com"]
110
- custom_timeout = 60
111
-
112
- mock_server_instance = Mock()
113
- mock_cached_server.return_value = mock_server_instance
114
-
115
- result = get_configured_mcp_servers(session_id_tuple, mcp_urls, custom_timeout)
116
-
117
- self.assertEqual(len(result), 1)
118
- mock_cached_server.assert_called_once_with(
119
- url="http://server1.com",
120
- headers={"user-id": "user123", "session-id": "session456"},
121
- timeout=custom_timeout
122
- )
123
-
124
- @patch('aixtools.mcp.client.CachedMCPServerStreamableHTTP')
125
- def test_get_configured_mcp_servers_no_headers(self, mock_cached_server):
126
- """Test get_configured_mcp_servers when no headers are generated."""
127
- session_id_tuple = (None, None) # type: ignore
128
- mcp_urls = ["http://server1.com"]
129
-
130
- mock_server_instance = Mock()
131
- mock_cached_server.return_value = mock_server_instance
132
-
133
- result = get_configured_mcp_servers(session_id_tuple, mcp_urls)
134
-
135
- self.assertEqual(len(result), 1)
136
- mock_cached_server.assert_called_once_with(
137
- url="http://server1.com",
138
- headers=None,
139
- timeout=DEFAULT_MCP_CONNECTION_TIMEOUT
140
- )
141
-
142
- @patch('aixtools.mcp.client.CachedMCPServerStreamableHTTP')
143
- def test_get_configured_mcp_servers_empty_urls(self, mock_cached_server):
144
- """Test get_configured_mcp_servers with empty URL list."""
145
- session_id_tuple = ("user123", "session456")
146
- mcp_urls = []
147
-
148
- result = get_configured_mcp_servers(session_id_tuple, mcp_urls)
149
-
150
- self.assertEqual(len(result), 0)
151
- mock_cached_server.assert_not_called()
152
-
153
-
154
- class TestCachedMCPServerStreamableHTTP(unittest.IsolatedAsyncioTestCase):
155
- """Test cases for CachedMCPServerStreamableHTTP class."""
156
-
157
- def setUp(self):
158
- """Set up test fixtures."""
159
- self.server = CachedMCPServerStreamableHTTP(url="http://test.com")
160
- self.server._client = Mock()
161
-
162
- def test_init(self):
163
- """Test CachedMCPServerStreamableHTTP initialization."""
164
- server = CachedMCPServerStreamableHTTP(url="http://test.com")
165
-
166
- self.assertIsNotNone(server._tools_cache)
167
- self.assertIsNone(server._tools_list)
168
- self.assertIsNotNone(server._isolation_lock)
169
-
170
- async def test_run_direct_or_isolated_success(self):
171
- """Test _run_direct_or_isolated with successful execution."""
172
- async def test_func():
173
- return "success"
174
-
175
- def fallback(exc):
176
- return "fallback"
177
-
178
- result = await self.server._run_direct_or_isolated(test_func, fallback, None)
179
- self.assertEqual(result, "success")
180
-
181
- async def test_run_direct_or_isolated_timeout(self):
182
- """Test _run_direct_or_isolated with timeout."""
183
- async def test_func():
184
- await asyncio.sleep(2)
185
- return "success"
186
-
187
- def fallback(exc):
188
- return "fallback"
189
-
190
- result = await self.server._run_direct_or_isolated(test_func, fallback, 0.1)
191
- self.assertEqual(result, "fallback")
192
-
193
- async def test_run_direct_or_isolated_exception(self):
194
- """Test _run_direct_or_isolated with exception."""
195
- async def test_func():
196
- raise ValueError("test error")
197
-
198
- def fallback(exc):
199
- return f"fallback: {exc}"
200
-
201
- result = await self.server._run_direct_or_isolated(test_func, fallback, None)
202
- self.assertEqual(result, "fallback: test error")
203
-
204
- async def test_run_direct_or_isolated_model_retry(self):
205
- """Test _run_direct_or_isolated with ModelRetry exception."""
206
- async def test_func():
207
- raise exceptions.ModelRetry("retry error")
208
-
209
- def fallback(exc):
210
- return "fallback"
211
-
212
- with self.assertRaises(exceptions.ModelRetry):
213
- await self.server._run_direct_or_isolated(test_func, fallback, None)
214
-
215
- async def test_list_tools_uninitialized_client(self):
216
- """Test list_tools with uninitialized client."""
217
- self.server._client = None # type: ignore
218
-
219
- result = await self.server.list_tools()
220
-
221
- self.assertEqual(result, [])
222
-
223
- async def test_list_tools_cached(self):
224
- """Test list_tools with cached result."""
225
- cached_tools = [Mock(spec=mcp_types.Tool)]
226
- self.server._tools_cache[CACHE_KEY] = cached_tools
227
-
228
- result = await self.server.list_tools()
229
-
230
- self.assertEqual(result, cached_tools)
231
-
232
- async def test_call_tool_uninitialized_client(self):
233
- """Test call_tool with uninitialized client."""
234
- self.server._client = None # type: ignore
235
-
236
- result = await self.server.call_tool("test_tool", {}, Mock(), Mock())
237
-
238
- self.assertIn("MCP connection is uninitialized", str(result))
239
-
240
-
241
- if __name__ == '__main__':
242
- unittest.main()
File without changes