Skip to content

Commit ea91de5

Browse files
Kirill Turanskiyclaude
andcommitted
fix: address Gemini and CodeRabbit review comments
- Add type hints to ConfigManager constructor (config.py) - Add error handling for config save operations (config.py, global_config.py) - Fix type hint: `any` -> `Any` in global_config.py - Refactor CLI detection to reduce duplication (doctor.py) - Update qwen_cli.py to use shutil.which for cross-platform detection - Fix test imports in test_qwen_cli.py Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a43e2cb commit ea91de5

5 files changed

Lines changed: 46 additions & 39 deletions

File tree

src/mcpm/clients/managers/qwen_cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"""
44

55
import logging
6-
import os
76
import shutil
7+
from pathlib import Path
88
from typing import Any, Dict
99

1010
from mcpm.clients.base import JSONClientManager
@@ -27,7 +27,7 @@ def __init__(self, config_path_override: str | None = None):
2727
config_path_override: Optional path to override the default config file location
2828
"""
2929
# Qwen CLI stores its settings in ~/.qwen/settings.json
30-
self.config_path = os.path.expanduser("~/.qwen/settings.json")
30+
self.config_path = str(Path.home() / ".qwen" / "settings.json")
3131
super().__init__(config_path_override=config_path_override)
3232

3333
def _get_empty_config(self) -> Dict[str, Any]:

src/mcpm/commands/doctor.py

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,32 +49,27 @@ def doctor():
4949
console.print(f" ✅ Python version: {sys.version.split()[0]}")
5050
console.print(f" ✅ Python executable: {sys.executable}")
5151

52+
# Helper function to check CLI tools
53+
def _check_cli_tool(tool_name: str, display_name: str, not_found_msg: str) -> int:
54+
"""Checks for a CLI tool, prints status, and returns 1 if an issue is found."""
55+
tool_path = shutil.which(tool_name)
56+
if tool_path:
57+
try:
58+
version = subprocess.check_output([tool_path, "--version"], stderr=subprocess.DEVNULL).decode().strip()
59+
console.print(f" ✅ {display_name} version: {version}")
60+
return 0
61+
except (subprocess.CalledProcessError, OSError):
62+
console.print(f" ⚠️ {display_name} found but failed to get version")
63+
return 1
64+
else:
65+
console.print(f" ⚠️ {not_found_msg}")
66+
return 1
67+
5268
# 3. Check Node.js (for npx servers)
5369
# Use shutil.which() to find executables - handles Windows .cmd/.bat files via PATHEXT
5470
console.print("[bold cyan]📊 Node.js Environment[/]")
55-
node_path = shutil.which("node")
56-
if node_path:
57-
try:
58-
node_version = subprocess.check_output([node_path, "--version"], stderr=subprocess.DEVNULL).decode().strip()
59-
console.print(f" ✅ Node.js version: {node_version}")
60-
except (subprocess.CalledProcessError, OSError):
61-
console.print(" ⚠️ Node.js found but failed to get version")
62-
issues_found += 1
63-
else:
64-
console.print(" ⚠️ Node.js not found - npx servers will not work")
65-
issues_found += 1
66-
67-
npm_path = shutil.which("npm")
68-
if npm_path:
69-
try:
70-
npm_version = subprocess.check_output([npm_path, "--version"], stderr=subprocess.DEVNULL).decode().strip()
71-
console.print(f" ✅ npm version: {npm_version}")
72-
except (subprocess.CalledProcessError, OSError):
73-
console.print(" ⚠️ npm found but failed to get version")
74-
issues_found += 1
75-
else:
76-
console.print(" ⚠️ npm not found - package installation may fail")
77-
issues_found += 1
71+
issues_found += _check_cli_tool("node", "Node.js", "Node.js not found - npx servers will not work")
72+
issues_found += _check_cli_tool("npm", "npm", "npm not found - package installation may fail")
7873

7974
# 4. Check MCPM configuration
8075
console.print("[bold cyan]⚙️ MCPM Configuration[/]")

src/mcpm/global_config.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import json
99
import logging
1010
from pathlib import Path
11-
from typing import Dict, List, Optional
11+
from typing import Any, Dict, List, Optional
1212

1313
from pydantic import TypeAdapter
1414

@@ -69,8 +69,11 @@ def _save_servers(self) -> None:
6969
self._ensure_dirs()
7070
servers_data = {name: config.model_dump() for name, config in self._servers.items()}
7171

72-
with open(self.config_path, "w", encoding="utf-8") as f:
73-
json.dump(servers_data, f, indent=2)
72+
try:
73+
with open(self.config_path, "w", encoding="utf-8") as f:
74+
json.dump(servers_data, f, indent=2)
75+
except OSError as e:
76+
logger.error(f"Error saving servers to {self.config_path}: {e}")
7477

7578
def _load_profile_metadata(self) -> Dict[str, ProfileMetadata]:
7679
"""Load profile metadata from the metadata configuration file."""
@@ -99,8 +102,11 @@ def _save_profile_metadata(self) -> None:
99102
self._ensure_dirs()
100103
metadata_data = {name: meta.model_dump() for name, meta in self._profile_metadata.items()}
101104

102-
with open(self.metadata_path, "w", encoding="utf-8") as f:
103-
json.dump(metadata_data, f, indent=2)
105+
try:
106+
with open(self.metadata_path, "w", encoding="utf-8") as f:
107+
json.dump(metadata_data, f, indent=2)
108+
except OSError as e:
109+
logger.error(f"Error saving profile metadata to {self.metadata_path}: {e}")
104110

105111
def add_server(self, server_config: ServerConfig, force: bool = False) -> bool:
106112
"""Add a server to the global configuration.
@@ -359,7 +365,7 @@ def list_profile_metadata(self) -> Dict[str, ProfileMetadata]:
359365
"""
360366
return self._profile_metadata.copy()
361367

362-
def get_complete_profile(self, name: str) -> Optional[Dict[str, any]]:
368+
def get_complete_profile(self, name: str) -> Optional[Dict[str, Any]]:
363369
"""Get complete profile information including metadata and servers.
364370
365371
Args:

src/mcpm/utils/config.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class ConfigManager:
3030
Client-specific configurations are managed by ClientConfigManager.
3131
"""
3232

33-
def __init__(self, config_path=DEFAULT_CONFIG_FILE, auth_path=DEFAULT_AUTH_FILE):
33+
def __init__(self, config_path: Path | str = DEFAULT_CONFIG_FILE, auth_path: Path | str = DEFAULT_AUTH_FILE):
3434
# Normalize paths to Path objects for consistent handling
3535
self.config_path = Path(config_path)
3636
self.auth_path = Path(auth_path)
@@ -78,13 +78,19 @@ def _default_config(self) -> Dict[str, Any]:
7878

7979
def _save_config(self) -> None:
8080
"""Save current configuration to file"""
81-
with open(self.config_path, "w", encoding="utf-8") as f:
82-
json.dump(self._config, f, indent=2)
81+
try:
82+
with open(self.config_path, "w", encoding="utf-8") as f:
83+
json.dump(self._config, f, indent=2)
84+
except OSError as e:
85+
logger.error(f"Error saving config file: {self.config_path} - {e}")
8386

8487
def _save_auth_config(self) -> None:
8588
"""Save current auth configuration to file"""
86-
with open(self.auth_path, "w", encoding="utf-8") as f:
87-
json.dump(self._auth_config, f, indent=2)
89+
try:
90+
with open(self.auth_path, "w", encoding="utf-8") as f:
91+
json.dump(self._auth_config, f, indent=2)
92+
except OSError as e:
93+
logger.error(f"Error saving auth file: {self.auth_path} - {e}")
8894

8995
def get_config(self) -> Dict[str, Any]:
9096
"""Get the complete configuration"""

tests/test_clients/test_qwen_cli.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
Test for Qwen CLI manager
33
"""
44

5-
import os
65
import tempfile
6+
from pathlib import Path
77
from unittest.mock import patch
88

99
from mcpm.clients.managers.qwen_cli import QwenCliManager
@@ -16,7 +16,7 @@ def test_qwen_cli_manager_initialization():
1616
assert manager.client_key == "qwen-cli"
1717
assert manager.display_name == "Qwen CLI"
1818
assert manager.download_url == "https://github.com/QwenLM/qwen-code"
19-
assert manager.config_path == os.path.expanduser("~/.qwen/settings.json")
19+
assert manager.config_path == str(Path.home() / ".qwen" / "settings.json")
2020

2121
# Test with custom config path
2222
custom_path = "/tmp/custom_settings.json"
@@ -91,5 +91,5 @@ def test_qwen_cli_manager_get_client_info():
9191
info = manager.get_client_info()
9292
assert info["name"] == "Qwen CLI"
9393
assert info["download_url"] == "https://github.com/QwenLM/qwen-code"
94-
assert info["config_file"] == os.path.expanduser("~/.qwen/settings.json")
94+
assert info["config_file"] == str(Path.home() / ".qwen" / "settings.json")
9595
assert info["description"] == "Alibaba's Qwen CLI tool"

0 commit comments

Comments
 (0)