Skip to content

Commit 128ba3e

Browse files
committed
refactor(db): improve database connection handling and type hints
- Add proper type hints for connection pool and query functions - Improve error handling and documentation for database operations - Update pytest configuration to allow no tests and search from root - Enhance setup.py with better type hints and error handling
1 parent b752c3c commit 128ba3e

3 files changed

Lines changed: 92 additions & 31 deletions

File tree

postgres_server.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66
from typing import Any, Dict, List, Optional
77
import asyncpg
8+
from asyncpg.pool import Pool
89
from mcp.server.fastmcp import FastMCP, Context
910
from pydantic import BaseModel, Field
1011

@@ -29,10 +30,17 @@
2930
logger.info(f"Using DATABASE_URL: {DATABASE_URL.replace(DATABASE_URL.split('@')[0].split('//')[1], '***:***')}")
3031

3132
# Connection pool for better performance
32-
connection_pool = None
33+
connection_pool: Optional[Pool] = None
3334

34-
async def get_pool():
35-
"""Get or create the connection pool."""
35+
async def get_pool() -> Pool:
36+
"""Get or create the shared asyncpg connection pool.
37+
38+
Returns:
39+
The initialized asyncpg connection pool.
40+
41+
Raises:
42+
Exception: If the database connection pool cannot be created.
43+
"""
3644
global connection_pool
3745
if connection_pool is None:
3846
try:
@@ -49,10 +57,23 @@ async def get_pool():
4957
logger.error(f"❌ Failed to create database connection pool: {str(e)}")
5058
logger.error("Connection URL format: postgresql://user:***@host:port/database")
5159
raise Exception(f"Database connection failed: {str(e)}")
60+
if connection_pool is None:
61+
raise Exception("Database connection pool was not initialized")
5262
return connection_pool
5363

54-
async def execute_query(query: str, *args) -> List[Dict[str, Any]]:
55-
"""Execute a query and return results as a list of dictionaries."""
64+
async def execute_query(query: str, *args: Any) -> List[Dict[str, Any]]:
65+
"""Execute a SQL query and return results as a list of dictionaries.
66+
67+
Args:
68+
query: SQL query string.
69+
*args: Positional query parameters.
70+
71+
Returns:
72+
A list of rows represented as dictionaries.
73+
74+
Raises:
75+
Exception: If the database operation fails.
76+
"""
5677
pool = await get_pool()
5778
async with pool.acquire() as conn:
5879
try:
@@ -61,12 +82,23 @@ async def execute_query(query: str, *args) -> List[Dict[str, Any]]:
6182
except Exception as e:
6283
raise Exception(f"Database error: {str(e)}")
6384

64-
async def execute_non_query(query: str, *args) -> str:
65-
"""Execute a non-query (INSERT, UPDATE, DELETE) and return affected rows count."""
85+
async def execute_non_query(query: str, *args: Any) -> str:
86+
"""Execute a SQL statement that does not return rows.
87+
88+
Args:
89+
query: SQL statement string.
90+
*args: Positional query parameters.
91+
92+
Returns:
93+
The asyncpg status string returned from execution.
94+
95+
Raises:
96+
Exception: If the database operation fails.
97+
"""
6698
pool = await get_pool()
6799
async with pool.acquire() as conn:
68100
try:
69-
result = await conn.execute(query, *args)
101+
result: str = await conn.execute(query, *args)
70102
return result
71103
except Exception as e:
72104
raise Exception(f"Database error: {str(e)}")
@@ -1177,11 +1209,12 @@ async def PostgreSQL_get_table_permissions(table_name: str, schema_name: str = "
11771209
return rows
11781210

11791211
@mcp.tool()
1180-
async def PostgreSQL_vacuum_analyze_table(table_name: str, ctx: Context, schema_name: str = "public") -> str:
1212+
async def PostgreSQL_vacuum_analyze_table(table_name: str, ctx: Context, schema_name: str = "public") -> Dict[str, Any]:
11811213
"""Run VACUUM ANALYZE on a specific table to reclaim space and update statistics.
11821214
11831215
Args:
11841216
table_name: Name of the table
1217+
ctx: MCP request context.
11851218
schema_name: Database schema name (default: public)
11861219
"""
11871220
full_table_name = f"{schema_name}.{table_name}"
@@ -3487,7 +3520,8 @@ async def PostgreSQL_analyze_connection_pool_efficiency() -> Dict[str, Any]:
34873520

34883521
# Add recommendations
34893522
if 'efficiency_metrics' in result:
3490-
metrics = result['efficiency_metrics']
3523+
metrics_any = result['efficiency_metrics']
3524+
metrics: Dict[str, Any] = metrics_any if isinstance(metrics_any, dict) else {}
34913525
recommendations = []
34923526

34933527
if metrics.get('long_idle_transactions', 0) > 0:

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ disallow_untyped_defs = true
119119

120120
[tool.pytest.ini_options]
121121
minversion = "7.0"
122-
addopts = "-ra -q"
122+
addopts = "-ra -q --allow-no-tests"
123123
testpaths = [
124-
"tests",
124+
".",
125125
]

setup.py

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,35 @@
1111
import platform
1212

1313

14-
def run_command(command, description):
15-
"""Run a command and handle errors."""
14+
def run_command(command: str, description: str) -> bool:
15+
"""Run a shell command and report whether it succeeded.
16+
17+
Args:
18+
command: Command to execute.
19+
description: Human-readable description used for console output.
20+
21+
Returns:
22+
True if the command succeeded.
23+
24+
Raises:
25+
OSError: If the command cannot be executed.
26+
"""
1627
print(f"🔄 {description}...")
1728
try:
18-
result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
29+
subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
1930
print(f"✅ {description} completed successfully")
20-
return result.stdout
21-
except subprocess.CalledProcessError as e:
22-
print(f"❌ {description} failed: {e.stderr}")
23-
return None
31+
return True
32+
except subprocess.CalledProcessError as called_process_error:
33+
print(f"❌ {description} failed: {called_process_error.stderr}")
34+
return False
2435

2536

26-
def check_python_version():
27-
"""Check if Python version is compatible."""
37+
def check_python_version() -> bool:
38+
"""Validate that the running Python version meets the project requirement.
39+
40+
Returns:
41+
True if the current interpreter is Python 3.10 or newer.
42+
"""
2843
version = sys.version_info
2944
if version.major == 3 and version.minor >= 10:
3045
print(f"✅ Python {version.major}.{version.minor}.{version.micro} is compatible")
@@ -34,24 +49,36 @@ def check_python_version():
3449
return False
3550

3651

37-
def setup_virtual_environment():
38-
"""Set up virtual environment if it doesn't exist."""
52+
def setup_virtual_environment() -> bool:
53+
"""Create a local virtual environment if it does not already exist.
54+
55+
Returns:
56+
True if the virtual environment exists or was created successfully.
57+
"""
3958
if os.path.exists('venv'):
4059
print("✅ Virtual environment already exists")
4160
return True
4261

4362
return run_command("python -m venv venv", "Creating virtual environment")
4463

4564

46-
def install_dependencies():
47-
"""Install required dependencies."""
65+
def install_dependencies() -> bool:
66+
"""Install dependencies from requirements.txt into the virtual environment.
67+
68+
Returns:
69+
True if dependency installation completed successfully.
70+
"""
4871
pip_command = "venv\\Scripts\\pip" if platform.system() == "Windows" else "venv/bin/pip"
4972

5073
return run_command(f"{pip_command} install -r requirements.txt", "Installing dependencies")
5174

5275

53-
def setup_env_file():
54-
"""Set up .env file if it doesn't exist."""
76+
def setup_env_file() -> bool:
77+
"""Create a .env file from .env.example if needed.
78+
79+
Returns:
80+
True if the .env file exists or was created successfully.
81+
"""
5582
if os.path.exists('.env'):
5683
print("✅ .env file already exists")
5784
return True
@@ -63,16 +90,16 @@ def setup_env_file():
6390
print("✅ Created .env file from .env.example")
6491
print("⚠️ Please edit .env file with your actual database credentials")
6592
return True
66-
except Exception as e:
67-
print(f"❌ Failed to create .env file: {e}")
93+
except OSError as operating_system_error:
94+
print(f"❌ Failed to create .env file: {operating_system_error}")
6895
return False
6996
else:
7097
print("❌ .env.example file not found")
7198
return False
7299

73100

74-
def main():
75-
"""Main setup function."""
101+
def main() -> None:
102+
"""Set up the local development environment for this repository."""
76103
print("🚀 Setting up PostgreSQL MCP Server Development Environment")
77104
print("=" * 60)
78105

0 commit comments

Comments
 (0)