fix: resolve CI test failures (config access, mocks, imports)
This commit is contained in:
@@ -1,133 +1,142 @@
|
|||||||
"""Configuration management for ShellGenius."""
|
"""Configuration loader for ShellGenius."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
CONFIG_DIR = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "shellgenius"
|
|
||||||
CONFIG_FILE = CONFIG_DIR / "config.yaml"
|
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
"""Configuration class for ShellGenius."""
|
"""Configuration management for ShellGenius."""
|
||||||
|
|
||||||
def __init__(self, config_path: str | None = None):
|
def __init__(self, config_path: Optional[str] = None):
|
||||||
"""Initialize configuration.
|
"""Initialize configuration.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config_path: Path to config file
|
config_path: Path to config.yaml file
|
||||||
"""
|
"""
|
||||||
if config_path is None:
|
self.config_path = config_path or os.environ.get(
|
||||||
config_path = str(CONFIG_FILE)
|
"SHELLGENIUS_CONFIG", "config.yaml"
|
||||||
|
)
|
||||||
|
self.config: Dict[str, Any] = self._load_config()
|
||||||
|
|
||||||
self._config_path = config_path
|
def _load_config(self) -> Dict[str, Any]:
|
||||||
self._config = self._load_config()
|
"""Load configuration from YAML file.
|
||||||
|
|
||||||
def _load_config(self) -> dict[str, Any]:
|
|
||||||
"""Load configuration from file.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Configuration dictionary
|
Dictionary containing configuration
|
||||||
"""
|
"""
|
||||||
defaults = {
|
default_config = {
|
||||||
"ollama": {
|
"ollama": {
|
||||||
"host": "localhost:11434",
|
"host": "localhost:11434",
|
||||||
"model": "llama3",
|
"model": "codellama",
|
||||||
|
"timeout": 120,
|
||||||
},
|
},
|
||||||
"safety": {
|
"safety": {
|
||||||
"level": "moderate",
|
"level": "moderate",
|
||||||
|
"warn_on_destructive": True,
|
||||||
|
"allow_dangerous": False,
|
||||||
|
"blocked_patterns": [
|
||||||
|
"rm -rf /",
|
||||||
|
":(){:|:&};:",
|
||||||
|
"chmod 777",
|
||||||
|
"sudo su",
|
||||||
|
"dd if=/dev/zero",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"ui": {
|
||||||
|
"theme": "default",
|
||||||
|
"show_syntax_highlighting": True,
|
||||||
|
"auto_suggest": True,
|
||||||
|
"max_history": 100,
|
||||||
|
},
|
||||||
|
"shell": {
|
||||||
|
"default": "bash",
|
||||||
|
"prefer_shebang": True,
|
||||||
|
},
|
||||||
|
"history": {
|
||||||
|
"enabled": True,
|
||||||
|
"storage_path": "~/.config/shellgenius/history.yaml",
|
||||||
|
"similarity_threshold": 0.7,
|
||||||
},
|
},
|
||||||
"default_shell": "bash",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
if self.config_path and Path(self.config_path).exists():
|
||||||
with open(self._config_path) as f:
|
try:
|
||||||
config = yaml.safe_load(f) or {}
|
with open(self.config_path, "r") as f:
|
||||||
except FileNotFoundError:
|
user_config = yaml.safe_load(f) or {}
|
||||||
config = {}
|
return self._merge_configs(default_config, user_config)
|
||||||
|
except Exception:
|
||||||
|
return default_config
|
||||||
|
return default_config
|
||||||
|
|
||||||
if isinstance(config, dict):
|
def _merge_configs(
|
||||||
defaults.update(config)
|
self, default: Dict[str, Any], user: Dict[str, Any]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""Merge user config with defaults.
|
||||||
|
|
||||||
return defaults
|
Args:
|
||||||
|
default: Default configuration
|
||||||
|
user: User-provided configuration
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Merged configuration
|
||||||
|
"""
|
||||||
|
result = default.copy()
|
||||||
|
for key, value in user.items():
|
||||||
|
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
||||||
|
result[key] = self._merge_configs(result[key], value)
|
||||||
|
else:
|
||||||
|
result[key] = value
|
||||||
|
return result
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
def get(self, key: str, default: Any = None) -> Any:
|
||||||
"""Get configuration value by key.
|
"""Get configuration value by key.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: Dot-separated key (e.g., "ollama.host")
|
key: Dot-separated key path (e.g., "ollama.host")
|
||||||
default: Default value if key not found
|
default: Default value if key not found
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Configuration value
|
Configuration value
|
||||||
"""
|
"""
|
||||||
keys = key.split(".")
|
keys = key.split(".")
|
||||||
value: Any = self._config
|
value = self.config
|
||||||
|
|
||||||
for k in keys:
|
for k in keys:
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict) and k in value:
|
||||||
value = value.get(k) if k in value else None
|
value = value[k]
|
||||||
else:
|
else:
|
||||||
return default
|
return default
|
||||||
|
return value
|
||||||
return value if value is not None else default
|
|
||||||
|
|
||||||
def save(self) -> None:
|
|
||||||
"""Save configuration to file."""
|
|
||||||
Path(self._config_path).parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with open(self._config_path, "w") as f:
|
|
||||||
yaml.dump(self._config, f)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ollama_host(self) -> str:
|
def ollama_host(self) -> str:
|
||||||
"""Get Ollama host URL."""
|
"""Get Ollama host."""
|
||||||
return self.get("ollama.host", "localhost:11434")
|
return os.environ.get("OLLAMA_HOST", self.get("ollama.host", "localhost:11434"))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ollama_model(self) -> str:
|
def ollama_model(self) -> str:
|
||||||
"""Get Ollama model name."""
|
"""Get Ollama model."""
|
||||||
return self.get("ollama.model", "llama3")
|
return os.environ.get("OLLAMA_MODEL", self.get("ollama.model", "codellama"))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def safety_level(self) -> str:
|
def safety_level(self) -> str:
|
||||||
"""Get safety level."""
|
"""Get safety level."""
|
||||||
return self.get("safety.level", "moderate")
|
return os.environ.get("SHELLGENIUS_SAFETY", self.get("safety.level", "moderate"))
|
||||||
|
|
||||||
@property
|
def reload(self) -> None:
|
||||||
def default_shell(self) -> str:
|
"""Reload configuration from file."""
|
||||||
"""Get default shell type."""
|
self.config = self._load_config()
|
||||||
return self.get("default_shell", "bash")
|
|
||||||
|
|
||||||
|
|
||||||
def get_config(config_path: str | None = None) -> dict[str, Any]:
|
def get_config(config_path: Optional[str] = None) -> Config:
|
||||||
"""Load configuration from file or return defaults."""
|
"""Get configuration instance.
|
||||||
if config_path is None:
|
|
||||||
config_path = str(CONFIG_FILE)
|
|
||||||
|
|
||||||
defaults = {
|
Args:
|
||||||
"ollama_host": "http://localhost:11434",
|
config_path: Optional path to config file
|
||||||
"ollama_model": "llama3",
|
|
||||||
"default_shell": "bash",
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
Returns:
|
||||||
with open(config_path) as f:
|
Config instance
|
||||||
config = yaml.safe_load(f) or {}
|
"""
|
||||||
except FileNotFoundError:
|
return Config(config_path)
|
||||||
config = {}
|
|
||||||
|
|
||||||
merged = defaults.copy()
|
|
||||||
merged.update(config)
|
|
||||||
return merged
|
|
||||||
|
|
||||||
|
|
||||||
def save_config(config: dict[str, Any], config_path: str | None = None) -> None:
|
|
||||||
"""Save configuration to file."""
|
|
||||||
if config_path is None:
|
|
||||||
config_path = str(CONFIG_FILE)
|
|
||||||
|
|
||||||
Path(config_path).parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with open(config_path, "w") as f:
|
|
||||||
yaml.dump(config, f)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user