awslabs.redshift-mcp-server 0.0.4__tar.gz → 0.0.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.
Files changed (23) hide show
  1. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/PKG-INFO +39 -5
  2. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/README.md +38 -4
  3. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/awslabs/redshift_mcp_server/__init__.py +1 -1
  4. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/awslabs/redshift_mcp_server/consts.py +16 -8
  5. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/awslabs/redshift_mcp_server/redshift.py +42 -50
  6. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/awslabs/redshift_mcp_server/server.py +1 -1
  7. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/pyproject.toml +1 -1
  8. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/tests/test_redshift.py +53 -73
  9. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/tests/test_server.py +1 -0
  10. awslabs_redshift_mcp_server-0.0.7/uv-requirements.txt +24 -0
  11. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/uv.lock +668 -668
  12. awslabs_redshift_mcp_server-0.0.4/uv-requirements.txt +0 -26
  13. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/.gitignore +0 -0
  14. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/.python-version +0 -0
  15. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/CHANGELOG.md +0 -0
  16. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/Dockerfile +0 -0
  17. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/LICENSE +0 -0
  18. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/NOTICE +0 -0
  19. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/awslabs/__init__.py +0 -0
  20. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/awslabs/redshift_mcp_server/models.py +0 -0
  21. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/docker-healthcheck.sh +0 -0
  22. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/tests/test_init.py +0 -0
  23. {awslabs_redshift_mcp_server-0.0.4 → awslabs_redshift_mcp_server-0.0.7}/tests/test_main.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: awslabs.redshift-mcp-server
3
- Version: 0.0.4
3
+ Version: 0.0.7
4
4
  Summary: An AWS Labs Model Context Protocol (MCP) server for Redshift
5
5
  Project-URL: homepage, https://awslabs.github.io/mcp/
6
6
  Project-URL: docs, https://awslabs.github.io/mcp/servers/redshift-mcp-server/
@@ -52,7 +52,11 @@ This MCP server provides tools to discover, explore, and query Amazon Redshift c
52
52
  ### AWS Client Requirements
53
53
 
54
54
  1. **Credentials**: Configure AWS credentials via AWS CLI, or environment variables
55
- 2. **Permissions**: Ensure your AWS credentials have the required permissions (see [Permissions](#permissions) section)
55
+ 2. **Region**: Configure AWS region using one of the following (in order of precedence):
56
+ - `AWS_REGION` environment variable (highest priority)
57
+ - `AWS_DEFAULT_REGION` environment variable
58
+ - Region specified in your AWS profile configuration
59
+ 3. **Permissions**: Ensure your AWS credentials have the required permissions (see [Permissions](#permissions) section)
56
60
 
57
61
  ## Installation
58
62
 
@@ -70,7 +74,7 @@ Configure the MCP server in your MCP client configuration (e.g., for Amazon Q De
70
74
  "args": ["awslabs.redshift-mcp-server@latest"],
71
75
  "env": {
72
76
  "AWS_PROFILE": "default",
73
- "AWS_REGION": "us-east-1",
77
+ "AWS_DEFAULT_REGION": "us-east-1",
74
78
  "FASTMCP_LOG_LEVEL": "INFO"
75
79
  },
76
80
  "disabled": false,
@@ -80,6 +84,35 @@ Configure the MCP server in your MCP client configuration (e.g., for Amazon Q De
80
84
  }
81
85
  ```
82
86
 
87
+ ### Windows Installation
88
+
89
+ For Windows users, the MCP server configuration format is slightly different:
90
+
91
+ ```json
92
+ {
93
+ "mcpServers": {
94
+ "awslabs.redshift-mcp-server": {
95
+ "disabled": false,
96
+ "timeout": 60,
97
+ "type": "stdio",
98
+ "command": "uv",
99
+ "args": [
100
+ "tool",
101
+ "run",
102
+ "--from",
103
+ "awslabs.redshift-mcp-server@latest",
104
+ "awslabs.redshift-mcp-server.exe"
105
+ ],
106
+ "env": {
107
+ "AWS_PROFILE": "your-aws-profile",
108
+ "AWS_DEFAULT_REGION": "us-east-1",
109
+ "FASTMCP_LOG_LEVEL": "ERROR"
110
+ }
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
83
116
  or docker after a successful `docker build -t awslabs/redshift-mcp-server:latest .`:
84
117
 
85
118
  ```json
@@ -93,7 +126,7 @@ or docker after a successful `docker build -t awslabs/redshift-mcp-server:latest
93
126
  "--interactive",
94
127
  "--env", "AWS_ACCESS_KEY_ID=[your data]",
95
128
  "--env", "AWS_SECRET_ACCESS_KEY=[your data]",
96
- "--env", "AWS_REGION=[your data]",
129
+ "--env", "AWS_DEFAULT_REGION=[your data]",
97
130
  "awslabs/redshift-mcp-server:latest"
98
131
  ]
99
132
  }
@@ -103,7 +136,8 @@ or docker after a successful `docker build -t awslabs/redshift-mcp-server:latest
103
136
 
104
137
  ### Environment Variables
105
138
 
106
- - `AWS_REGION`: AWS region to use (default: `us-east-1`)
139
+ - `AWS_REGION`: AWS region to use (overrides all other region settings)
140
+ - `AWS_DEFAULT_REGION`: Default AWS region (used if AWS_REGION not set and no region in profile)
107
141
  - `AWS_PROFILE`: AWS profile to use (optional, uses default if not specified)
108
142
  - `FASTMCP_LOG_LEVEL`: Logging level (`DEBUG`, `INFO`, `WARNING`, `ERROR`)
109
143
  - `LOG_FILE`: Path to log file (optional, logs to stdout if not specified)
@@ -21,7 +21,11 @@ This MCP server provides tools to discover, explore, and query Amazon Redshift c
21
21
  ### AWS Client Requirements
22
22
 
23
23
  1. **Credentials**: Configure AWS credentials via AWS CLI, or environment variables
24
- 2. **Permissions**: Ensure your AWS credentials have the required permissions (see [Permissions](#permissions) section)
24
+ 2. **Region**: Configure AWS region using one of the following (in order of precedence):
25
+ - `AWS_REGION` environment variable (highest priority)
26
+ - `AWS_DEFAULT_REGION` environment variable
27
+ - Region specified in your AWS profile configuration
28
+ 3. **Permissions**: Ensure your AWS credentials have the required permissions (see [Permissions](#permissions) section)
25
29
 
26
30
  ## Installation
27
31
 
@@ -39,7 +43,7 @@ Configure the MCP server in your MCP client configuration (e.g., for Amazon Q De
39
43
  "args": ["awslabs.redshift-mcp-server@latest"],
40
44
  "env": {
41
45
  "AWS_PROFILE": "default",
42
- "AWS_REGION": "us-east-1",
46
+ "AWS_DEFAULT_REGION": "us-east-1",
43
47
  "FASTMCP_LOG_LEVEL": "INFO"
44
48
  },
45
49
  "disabled": false,
@@ -49,6 +53,35 @@ Configure the MCP server in your MCP client configuration (e.g., for Amazon Q De
49
53
  }
50
54
  ```
51
55
 
56
+ ### Windows Installation
57
+
58
+ For Windows users, the MCP server configuration format is slightly different:
59
+
60
+ ```json
61
+ {
62
+ "mcpServers": {
63
+ "awslabs.redshift-mcp-server": {
64
+ "disabled": false,
65
+ "timeout": 60,
66
+ "type": "stdio",
67
+ "command": "uv",
68
+ "args": [
69
+ "tool",
70
+ "run",
71
+ "--from",
72
+ "awslabs.redshift-mcp-server@latest",
73
+ "awslabs.redshift-mcp-server.exe"
74
+ ],
75
+ "env": {
76
+ "AWS_PROFILE": "your-aws-profile",
77
+ "AWS_DEFAULT_REGION": "us-east-1",
78
+ "FASTMCP_LOG_LEVEL": "ERROR"
79
+ }
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
52
85
  or docker after a successful `docker build -t awslabs/redshift-mcp-server:latest .`:
53
86
 
54
87
  ```json
@@ -62,7 +95,7 @@ or docker after a successful `docker build -t awslabs/redshift-mcp-server:latest
62
95
  "--interactive",
63
96
  "--env", "AWS_ACCESS_KEY_ID=[your data]",
64
97
  "--env", "AWS_SECRET_ACCESS_KEY=[your data]",
65
- "--env", "AWS_REGION=[your data]",
98
+ "--env", "AWS_DEFAULT_REGION=[your data]",
66
99
  "awslabs/redshift-mcp-server:latest"
67
100
  ]
68
101
  }
@@ -72,7 +105,8 @@ or docker after a successful `docker build -t awslabs/redshift-mcp-server:latest
72
105
 
73
106
  ### Environment Variables
74
107
 
75
- - `AWS_REGION`: AWS region to use (default: `us-east-1`)
108
+ - `AWS_REGION`: AWS region to use (overrides all other region settings)
109
+ - `AWS_DEFAULT_REGION`: Default AWS region (used if AWS_REGION not set and no region in profile)
76
110
  - `AWS_PROFILE`: AWS profile to use (optional, uses default if not specified)
77
111
  - `FASTMCP_LOG_LEVEL`: Logging level (`DEBUG`, `INFO`, `WARNING`, `ERROR`)
78
112
  - `LOG_FILE`: Path to log file (optional, logs to stdout if not specified)
@@ -14,4 +14,4 @@
14
14
 
15
15
  """awslabs.redshift-mcp-server"""
16
16
 
17
- __version__ = '0.0.4'
17
+ __version__ = '0.0.7'
@@ -14,13 +14,12 @@
14
14
 
15
15
  """Redshift MCP Server constants."""
16
16
 
17
- # Defaults
18
- DEFAULT_AWS_REGION = 'us-east-1'
17
+ # System
18
+ CLIENT_CONNECT_TIMEOUT = 60
19
+ CLIENT_READ_TIMEOUT = 600
20
+ CLIENT_RETRIES = {'max_attempts': 5, 'mode': 'adaptive'}
21
+ CLIENT_USER_AGENT_NAME = 'awslabs/mcp/redshift-mcp-server'
19
22
  DEFAULT_LOG_LEVEL = 'WARNING'
20
-
21
- # Timeouts (seconds), etc
22
- CLIENT_TIMEOUT = 60
23
- DATA_CLIENT_TIMEOUT = 60
24
23
  QUERY_TIMEOUT = 3600
25
24
  QUERY_POLL_INTERVAL = 2
26
25
 
@@ -29,11 +28,20 @@ QUERY_POLL_INTERVAL = 2
29
28
  CLIENT_BEST_PRACTICES = """
30
29
  ## AWS Client Best Practices
31
30
 
32
- ### Authentication
31
+ ### Authentication and Configuration
33
32
 
34
33
  - Default AWS credentials chain (IAM roles, ~/.aws/credentials, etc.).
35
34
  - AWS_PROFILE environment variable (if set).
36
- - AWS_REGION environment variable (if set).
35
+ - Region configuration (in order of precedence):
36
+ - AWS_REGION environment variable (highest priority)
37
+ - AWS_DEFAULT_REGION environment variable
38
+ - Region specified in AWS profile configuration
39
+
40
+ ### Error Handling
41
+
42
+ - Always print out AWS client errors in full to help diagnose configuration issues.
43
+ - For region-related errors, suggest checking AWS_REGION, AWS_DEFAULT_REGION, or AWS profile configuration.
44
+ - For credential errors, suggest verifying AWS credentials setup and permissions.
37
45
  """
38
46
 
39
47
  REDSHIFT_BEST_PRACTICES = """
@@ -20,8 +20,10 @@ import os
20
20
  import regex
21
21
  from awslabs.redshift_mcp_server import __version__
22
22
  from awslabs.redshift_mcp_server.consts import (
23
- CLIENT_TIMEOUT,
24
- DEFAULT_AWS_REGION,
23
+ CLIENT_CONNECT_TIMEOUT,
24
+ CLIENT_READ_TIMEOUT,
25
+ CLIENT_RETRIES,
26
+ CLIENT_USER_AGENT_NAME,
25
27
  QUERY_POLL_INTERVAL,
26
28
  QUERY_TIMEOUT,
27
29
  SUSPICIOUS_QUERY_REGEXP,
@@ -37,7 +39,9 @@ from loguru import logger
37
39
  class RedshiftClientManager:
38
40
  """Manages AWS clients for Redshift operations."""
39
41
 
40
- def __init__(self, config: Config, aws_region: str, aws_profile: str | None = None):
42
+ def __init__(
43
+ self, config: Config, aws_region: str | None = None, aws_profile: str | None = None
44
+ ):
41
45
  """Initialize the client manager."""
42
46
  self.aws_region = aws_region
43
47
  self.aws_profile = aws_profile
@@ -50,15 +54,12 @@ class RedshiftClientManager:
50
54
  """Get or create the Redshift client for provisioned clusters."""
51
55
  if self._redshift_client is None:
52
56
  try:
53
- if self.aws_profile:
54
- session = boto3.Session(profile_name=self.aws_profile)
55
- self._redshift_client = session.client('redshift', config=self._config)
56
- logger.info(f'Created Redshift client with profile: {self.aws_profile}')
57
- else:
58
- self._redshift_client = boto3.client(
59
- 'redshift', config=self._config, region_name=self.aws_region
60
- )
61
- logger.info('Created Redshift client with default credentials')
57
+ # Session works with None values - uses default credentials/region chain
58
+ session = boto3.Session(profile_name=self.aws_profile, region_name=self.aws_region)
59
+ self._redshift_client = session.client('redshift', config=self._config)
60
+ logger.info(
61
+ f'Created Redshift client with profile: {self.aws_profile or "default"}, region: {self.aws_region or "default"}'
62
+ )
62
63
  except Exception as e:
63
64
  logger.error(f'Error creating Redshift client: {str(e)}')
64
65
  raise
@@ -69,19 +70,14 @@ class RedshiftClientManager:
69
70
  """Get or create the Redshift Serverless client."""
70
71
  if self._redshift_serverless_client is None:
71
72
  try:
72
- if self.aws_profile:
73
- session = boto3.Session(profile_name=self.aws_profile)
74
- self._redshift_serverless_client = session.client(
75
- 'redshift-serverless', config=self._config
76
- )
77
- logger.info(
78
- f'Created Redshift Serverless client with profile: {self.aws_profile}'
79
- )
80
- else:
81
- self._redshift_serverless_client = boto3.client(
82
- 'redshift-serverless', config=self._config, region_name=self.aws_region
83
- )
84
- logger.info('Created Redshift Serverless client with default credentials')
73
+ # Session works with None values - uses default credentials/region chain
74
+ session = boto3.Session(profile_name=self.aws_profile, region_name=self.aws_region)
75
+ self._redshift_serverless_client = session.client(
76
+ 'redshift-serverless', config=self._config
77
+ )
78
+ logger.info(
79
+ f'Created Redshift Serverless client with profile: {self.aws_profile or "default"}, region: {self.aws_region or "default"}'
80
+ )
85
81
  except Exception as e:
86
82
  logger.error(f'Error creating Redshift Serverless client: {str(e)}')
87
83
  raise
@@ -92,19 +88,12 @@ class RedshiftClientManager:
92
88
  """Get or create the Redshift Data API client."""
93
89
  if self._redshift_data_client is None:
94
90
  try:
95
- if self.aws_profile:
96
- session = boto3.Session(profile_name=self.aws_profile)
97
- self._redshift_data_client = session.client(
98
- 'redshift-data', config=self._config
99
- )
100
- logger.info(
101
- f'Created Redshift Data API client with profile: {self.aws_profile}'
102
- )
103
- else:
104
- self._redshift_data_client = boto3.client(
105
- 'redshift-data', config=self._config, region_name=self.aws_region
106
- )
107
- logger.info('Created Redshift Data API client with default credentials')
91
+ # Session works with None values - uses default credentials/region chain
92
+ session = boto3.Session(profile_name=self.aws_profile, region_name=self.aws_region)
93
+ self._redshift_data_client = session.client('redshift-data', config=self._config)
94
+ logger.info(
95
+ f'Created Redshift Data API client with profile: {self.aws_profile or "default"}, region: {self.aws_region or "default"}'
96
+ )
108
97
  except Exception as e:
109
98
  logger.error(f'Error creating Redshift Data API client: {str(e)}')
110
99
  raise
@@ -193,19 +182,22 @@ async def execute_statement(
193
182
  )
194
183
 
195
184
  # Guard from executing read-write statements if not allowed
196
- protected_sqls = protect_sql(sql, allow_read_write)
197
- logger.debug(f'Protected SQL: {" ".join(protected_sqls)}')
185
+ sqls = protect_sql(sql, allow_read_write)
186
+ # Add application name and version
187
+ sqls = [f"SET application_name TO '{CLIENT_USER_AGENT_NAME}/{__version__}';"] + sqls
188
+
189
+ logger.debug(f'Protected and versioned SQL: {" ".join(sqls)}')
198
190
 
199
191
  # Execute the query using Data API
200
192
  if cluster_info['type'] == 'provisioned':
201
193
  logger.debug(f'Using ClusterIdentifier for provisioned cluster: {cluster_identifier}')
202
194
  response = data_client.batch_execute_statement(
203
- ClusterIdentifier=cluster_identifier, Database=database_name, Sqls=protected_sqls
195
+ ClusterIdentifier=cluster_identifier, Database=database_name, Sqls=sqls
204
196
  )
205
197
  elif cluster_info['type'] == 'serverless':
206
198
  logger.debug(f'Using WorkgroupName for serverless workgroup: {cluster_identifier}')
207
199
  response = data_client.batch_execute_statement(
208
- WorkgroupName=cluster_identifier, Database=database_name, Sqls=protected_sqls
200
+ WorkgroupName=cluster_identifier, Database=database_name, Sqls=sqls
209
201
  )
210
202
  else:
211
203
  raise Exception(f'Unknown cluster type: {cluster_info["type"]}')
@@ -237,9 +229,9 @@ async def execute_statement(
237
229
  raise Exception(f'Query timed out after {QUERY_TIMEOUT} seconds')
238
230
 
239
231
  # Get user query results
240
- subquery1_id = status_response['SubStatements'][1]['Id']
241
- results_response = data_client.get_statement_result(Id=subquery1_id)
242
- return results_response, subquery1_id
232
+ subquery2_id = status_response['SubStatements'][2]['Id']
233
+ results_response = data_client.get_statement_result(Id=subquery2_id)
234
+ return results_response, subquery2_id
243
235
 
244
236
 
245
237
  async def discover_clusters() -> list[dict]:
@@ -620,11 +612,11 @@ async def execute_query(cluster_identifier: str, database_name: str, sql: str) -
620
612
  # Global client manager instance
621
613
  client_manager = RedshiftClientManager(
622
614
  config=Config(
623
- connect_timeout=CLIENT_TIMEOUT,
624
- read_timeout=CLIENT_TIMEOUT,
625
- retries={'max_attempts': 3, 'mode': 'adaptive'},
626
- user_agent_extra=f'awslabs/mcp/redshift-mcp-server/{__version__}',
615
+ connect_timeout=CLIENT_CONNECT_TIMEOUT,
616
+ read_timeout=CLIENT_READ_TIMEOUT,
617
+ retries=CLIENT_RETRIES,
618
+ user_agent_extra=f'{CLIENT_USER_AGENT_NAME}/{__version__}',
627
619
  ),
628
- aws_region=os.environ.get('AWS_REGION', DEFAULT_AWS_REGION),
620
+ aws_region=os.environ.get('AWS_REGION'),
629
621
  aws_profile=os.environ.get('AWS_PROFILE'),
630
622
  )
@@ -85,7 +85,7 @@ This tool uses the Redshift Data API to run queries and return results.
85
85
 
86
86
  ## Getting Started
87
87
 
88
- 1. Ensure your AWS credentials are configured (via AWS_PROFILE or default credentials).
88
+ 1. Ensure your AWS configuration and credentials are configured (environment variables or profile configuration file).
89
89
  2. Use the list_clusters tool to discover available Redshift instances.
90
90
  3. Note the cluster identifiers for use with other tools (coming in future milestones).
91
91
 
@@ -2,7 +2,7 @@
2
2
  name = "awslabs.redshift-mcp-server"
3
3
 
4
4
  # NOTE: "Patch"=9223372036854775807 bumps next release to zero.
5
- version = "0.0.4"
5
+ version = "0.0.7"
6
6
 
7
7
  description = "An AWS Labs Model Context Protocol (MCP) server for Redshift"
8
8
  readme = "README.md"
@@ -29,21 +29,21 @@ class TestRedshiftClientManagerRedshiftClient:
29
29
  def test_redshift_client_creation_default_credentials(self, mocker):
30
30
  """Test Redshift client creation with default credentials."""
31
31
  mock_client = mocker.Mock()
32
- mock_boto3_client = mocker.patch('boto3.client', return_value=mock_client)
32
+ mock_boto3_session = mocker.patch('boto3.Session')
33
+ mock_boto3_session.return_value.client.return_value = mock_client
33
34
 
34
35
  config = Config()
35
- manager = RedshiftClientManager(config, 'us-east-1')
36
+ manager = RedshiftClientManager(config)
36
37
  client = manager.redshift_client()
37
38
 
38
39
  assert client == mock_client
39
40
 
40
- # Verify boto3.client was called with correct parameters
41
- mock_boto3_client.assert_called_once_with(
42
- 'redshift', config=config, region_name='us-east-1'
43
- )
41
+ # Verify boto3.Session was called with correct parameters
42
+ mock_boto3_session.assert_called_once_with(profile_name=None, region_name=None)
43
+ mock_boto3_session.return_value.client.assert_called_once_with('redshift', config=config)
44
44
 
45
- def test_redshift_client_creation_with_profile(self, mocker):
46
- """Test Redshift client creation with AWS profile."""
45
+ def test_redshift_client_creation_with_profile_and_region(self, mocker):
46
+ """Test Redshift client creation with AWS profile and region."""
47
47
  mock_session = mocker.Mock()
48
48
  mock_client = mocker.Mock()
49
49
  mock_session.client.return_value = mock_client
@@ -55,28 +55,20 @@ class TestRedshiftClientManagerRedshiftClient:
55
55
 
56
56
  assert client == mock_client
57
57
 
58
- # Verify session was created with profile and client was created
59
- mock_session_class.assert_called_once_with(profile_name='test-profile')
58
+ # Verify session was created with profile and region
59
+ mock_session_class.assert_called_once_with(
60
+ profile_name='test-profile', region_name='us-west-2'
61
+ )
60
62
  mock_session.client.assert_called_once_with('redshift', config=config)
61
63
 
62
- def test_redshift_client_creation_error_default_credentials(self, mocker):
63
- """Test error handling when client creation fails with default credentials."""
64
- mocker.patch('boto3.client', side_effect=Exception('Credentials error'))
65
-
66
- config = Config()
67
- manager = RedshiftClientManager(config, 'us-east-1')
68
-
69
- with pytest.raises(Exception, match='Credentials error'):
70
- manager.redshift_client()
71
-
72
- def test_redshift_client_creation_error_with_profile(self, mocker):
73
- """Test error handling when session creation fails with profile."""
74
- mocker.patch('boto3.Session', side_effect=Exception('Profile not found'))
64
+ def test_redshift_client_creation_error(self, mocker):
65
+ """Test error handling when session creation fails."""
66
+ mocker.patch('boto3.Session', side_effect=Exception('Session error'))
75
67
 
76
68
  config = Config()
77
- manager = RedshiftClientManager(config, 'us-east-1', 'non-existent-profile')
69
+ manager = RedshiftClientManager(config)
78
70
 
79
- with pytest.raises(Exception, match='Profile not found'):
71
+ with pytest.raises(Exception, match='Session error'):
80
72
  manager.redshift_client()
81
73
 
82
74
 
@@ -86,21 +78,23 @@ class TestRedshiftClientManagerServerlessClient:
86
78
  def test_redshift_serverless_client_creation_default_credentials(self, mocker):
87
79
  """Test Redshift Serverless client creation with default credentials."""
88
80
  mock_client = mocker.Mock()
89
- mock_boto3_client = mocker.patch('boto3.client', return_value=mock_client)
81
+ mock_boto3_session = mocker.patch('boto3.Session')
82
+ mock_boto3_session.return_value.client.return_value = mock_client
90
83
 
91
84
  config = Config()
92
- manager = RedshiftClientManager(config, 'us-east-1')
85
+ manager = RedshiftClientManager(config)
93
86
  client = manager.redshift_serverless_client()
94
87
 
95
88
  assert client == mock_client
96
89
 
97
- # Verify boto3.client was called with correct parameters
98
- mock_boto3_client.assert_called_once_with(
99
- 'redshift-serverless', config=config, region_name='us-east-1'
90
+ # Verify boto3.Session was called with correct parameters
91
+ mock_boto3_session.assert_called_once_with(profile_name=None, region_name=None)
92
+ mock_boto3_session.return_value.client.assert_called_once_with(
93
+ 'redshift-serverless', config=config
100
94
  )
101
95
 
102
- def test_redshift_serverless_client_creation_with_profile(self, mocker):
103
- """Test Redshift Serverless client creation with AWS profile."""
96
+ def test_redshift_serverless_client_creation_with_profile_and_region(self, mocker):
97
+ """Test Redshift Serverless client creation with AWS profile and region."""
104
98
  mock_session = mocker.Mock()
105
99
  mock_client = mocker.Mock()
106
100
  mock_session.client.return_value = mock_client
@@ -112,28 +106,20 @@ class TestRedshiftClientManagerServerlessClient:
112
106
 
113
107
  assert client == mock_client
114
108
 
115
- # Verify session was created with profile and client was created
116
- mock_session_class.assert_called_once_with(profile_name='test-profile')
109
+ # Verify session was created with profile and region
110
+ mock_session_class.assert_called_once_with(
111
+ profile_name='test-profile', region_name='us-west-2'
112
+ )
117
113
  mock_session.client.assert_called_once_with('redshift-serverless', config=config)
118
114
 
119
- def test_redshift_serverless_client_creation_error_default_credentials(self, mocker):
120
- """Test error handling when serverless client creation fails with default credentials."""
121
- mocker.patch('boto3.client', side_effect=Exception('Credentials error'))
115
+ def test_redshift_serverless_client_creation_error(self, mocker):
116
+ """Test error handling when session creation fails."""
117
+ mocker.patch('boto3.Session', side_effect=Exception('Session error'))
122
118
 
123
119
  config = Config()
124
- manager = RedshiftClientManager(config, 'us-east-1')
120
+ manager = RedshiftClientManager(config)
125
121
 
126
- with pytest.raises(Exception, match='Credentials error'):
127
- manager.redshift_serverless_client()
128
-
129
- def test_redshift_serverless_client_creation_error_with_profile(self, mocker):
130
- """Test error handling when session creation fails with profile."""
131
- mocker.patch('boto3.Session', side_effect=Exception('Profile not found'))
132
-
133
- config = Config()
134
- manager = RedshiftClientManager(config, 'us-east-1', 'non-existent-profile')
135
-
136
- with pytest.raises(Exception, match='Profile not found'):
122
+ with pytest.raises(Exception, match='Session error'):
137
123
  manager.redshift_serverless_client()
138
124
 
139
125
 
@@ -143,21 +129,23 @@ class TestRedshiftClientManagerDataClient:
143
129
  def test_redshift_data_client_creation_default_credentials(self, mocker):
144
130
  """Test Redshift Data API client creation with default credentials."""
145
131
  mock_client = mocker.Mock()
146
- mock_boto3_client = mocker.patch('boto3.client', return_value=mock_client)
132
+ mock_boto3_session = mocker.patch('boto3.Session')
133
+ mock_boto3_session.return_value.client.return_value = mock_client
147
134
 
148
135
  config = Config()
149
- manager = RedshiftClientManager(config, 'us-east-1')
136
+ manager = RedshiftClientManager(config)
150
137
  client = manager.redshift_data_client()
151
138
 
152
139
  assert client == mock_client
153
140
 
154
- # Verify boto3.client was called with correct parameters
155
- mock_boto3_client.assert_called_once_with(
156
- 'redshift-data', config=config, region_name='us-east-1'
141
+ # Verify boto3.Session was called with correct parameters
142
+ mock_boto3_session.assert_called_once_with(profile_name=None, region_name=None)
143
+ mock_boto3_session.return_value.client.assert_called_once_with(
144
+ 'redshift-data', config=config
157
145
  )
158
146
 
159
- def test_redshift_data_client_creation_with_profile(self, mocker):
160
- """Test Redshift Data API client creation with AWS profile."""
147
+ def test_redshift_data_client_creation_with_profile_and_region(self, mocker):
148
+ """Test Redshift Data API client creation with AWS profile and region."""
161
149
  mock_session = mocker.Mock()
162
150
  mock_client = mocker.Mock()
163
151
  mock_session.client.return_value = mock_client
@@ -169,28 +157,20 @@ class TestRedshiftClientManagerDataClient:
169
157
 
170
158
  assert client == mock_client
171
159
 
172
- # Verify session was created with profile and client was created
173
- mock_session_class.assert_called_once_with(profile_name='test-profile')
160
+ # Verify session was created with profile and region
161
+ mock_session_class.assert_called_once_with(
162
+ profile_name='test-profile', region_name='us-west-2'
163
+ )
174
164
  mock_session.client.assert_called_once_with('redshift-data', config=config)
175
165
 
176
- def test_redshift_data_client_creation_error_default_credentials(self, mocker):
177
- """Test error handling when data client creation fails with default credentials."""
178
- mocker.patch('boto3.client', side_effect=Exception('Credentials error'))
179
-
180
- config = Config()
181
- manager = RedshiftClientManager(config, 'us-east-1')
182
-
183
- with pytest.raises(Exception, match='Credentials error'):
184
- manager.redshift_data_client()
185
-
186
- def test_redshift_data_client_creation_error_with_profile(self, mocker):
187
- """Test error handling when session creation fails with profile."""
188
- mocker.patch('boto3.Session', side_effect=Exception('Profile not found'))
166
+ def test_redshift_data_client_creation_error(self, mocker):
167
+ """Test error handling when session creation fails."""
168
+ mocker.patch('boto3.Session', side_effect=Exception('Session error'))
189
169
 
190
170
  config = Config()
191
- manager = RedshiftClientManager(config, 'us-east-1', 'non-existent-profile')
171
+ manager = RedshiftClientManager(config)
192
172
 
193
- with pytest.raises(Exception, match='Profile not found'):
173
+ with pytest.raises(Exception, match='Session error'):
194
174
  manager.redshift_data_client()
195
175
 
196
176
 
@@ -815,6 +815,7 @@ class TestExecuteQueryTool:
815
815
  mock_data_client.describe_statement.return_value = {
816
816
  'Status': 'FINISHED',
817
817
  'SubStatements': [
818
+ {'Id': 'set-app-name'}, # SET application_name
818
819
  {'Id': 'sub-query-0'}, # BEGIN READ ONLY
819
820
  {'Id': 'query-123'}, # Our actual SQL query
820
821
  {'Id': 'sub-query-2'}, # END
@@ -0,0 +1,24 @@
1
+ # This file was autogenerated by uv via the following command:
2
+ # echo "uv==0.8.10" > uv-requirements.in
3
+ # uv pip compile --generate-hashes --output-file=uv-requirements.txt --strip-extras --python=3.10 uv-requirements.in
4
+ uv==0.8.10 \
5
+ --hash=sha256:31e4fc37ee94b94c032384a0957ad32ba7dce4ce6c04b4880fd3e31e25e51a82 \
6
+ --hash=sha256:36a5ce708d52388c37043e7335f9eb3fea5a19a56166a2cc6adb365179a1cd77 \
7
+ --hash=sha256:38286d230daad82388469c8dc7a1d2f5dc279c11178319c886d1a88d7938e513 \
8
+ --hash=sha256:3e190cee3bb2b4f574a419eef87ae8e33f713e9cd6f856b83277ece70ad9ca9b \
9
+ --hash=sha256:3fdf89fc40af9902141c39ed943bcfca15664623363335eb032a44f22001e2b4 \
10
+ --hash=sha256:4cc190d403a89e46d13cec83b6f8e8d7d07aaf1e5a996eac9a3f0c2a8cd92537 \
11
+ --hash=sha256:57b71dc79eff25a5419d3fe4a563d3b9397f55d789f685ef27f43f033b31f482 \
12
+ --hash=sha256:86fe044c2be43977566a0d184a487edd7aace2febb757fd95927684b629ef50b \
13
+ --hash=sha256:88df34c32555064fae459cce665757619fd1af7deb2dc393352b15d909d2d131 \
14
+ --hash=sha256:9ad21eeaa4156a1bf5ed85903f80db06e2c02badd3a587ba98d3171517960555 \
15
+ --hash=sha256:a5495b5a6e3111c03cf5e4dbdd598bc8fd1da887e3920d58cd5a2d4c8bc9a473 \
16
+ --hash=sha256:ab072cd3bf2f9dc264659a1ff48ad91a910ac4830bcfe965e2d3f89c86646f46 \
17
+ --hash=sha256:af8a5526b0e331775a264fa0dbccfd53c183cb974f269a208af136d7561f9eb2 \
18
+ --hash=sha256:b00637c63d5dfc9f879281c5c91db2bb909ab1f9ab275dab015e7fb6cac6be5b \
19
+ --hash=sha256:b3ff3c451fcd23ea78356d8c18e802d0e423cbe655273601e3ec039a51b33286 \
20
+ --hash=sha256:c4a493cd4b15b3aef11523531aff96a77a586666a63e842fa437966b7b7ee62d \
21
+ --hash=sha256:defc50bb319be2d58be74a680710cd4b7697e88d5f79974eacd354df95f0b6b0 \
22
+ --hash=sha256:e0a02bcec766eb0862b7082ab746b204add7d9fcaa62322502d159b5a7ccc54a \
23
+ --hash=sha256:eb79a46d8099f563ef58237bf4e9009f876a40145e757ea883a92b24b724d01e
24
+ # via -r uv-requirements.in