Skip to content
This repository was archived by the owner on Apr 23, 2025. It is now read-only.

Commit 63a5760

Browse files
committed
Remove migration code from config.py and document dual configuration approach
1 parent 7bbc21a commit 63a5760

1 file changed

Lines changed: 86 additions & 82 deletions

File tree

src/cli_code/config.py

Lines changed: 86 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
"""
2-
Configuration management for CLI Code.
2+
Configuration management for the CLI Code application.
3+
4+
This module provides a Config class that manages configuration for the CLI Code
5+
application, including loading environment variables, ensuring the configuration
6+
file exists, and loading and saving configuration.
7+
8+
Configuration in CLI Code supports two approaches:
9+
1. File-based configuration (.yaml): Primary approach for end users who install from pip
10+
2. Environment variables: Used mainly during development for quick experimentation
11+
12+
Both approaches are supported simultaneously - there is no migration needed as both
13+
configuration methods can coexist. Environment variables take precedence over file-based
14+
configuration when both are present.
315
"""
416

517
import logging
@@ -12,25 +24,40 @@
1224

1325

1426
class Config:
15-
"""Manages configuration for the cli-code application."""
27+
"""
28+
Configuration management for the CLI Code application.
29+
30+
This class manages loading configuration from a YAML file, creating a
31+
default configuration file if one doesn't exist, and loading environment
32+
variables.
33+
34+
The configuration is loaded in the following order of precedence:
35+
1. Environment variables (highest precedence)
36+
2. Configuration file
37+
3. Default values (lowest precedence)
38+
"""
1639

1740
def __init__(self):
18-
self.config_dir = Path.home() / ".config" / "cli-code-agent"
41+
"""
42+
Initialize the configuration.
43+
44+
This will load environment variables, ensure the configuration file
45+
exists, and load the configuration from the file.
46+
"""
47+
self.config_dir = Path(os.path.expanduser("~/.config/cli-code"))
1948
self.config_file = self.config_dir / "config.yaml"
20-
self.config = {}
21-
22-
# First load environment variables from .env file if it exists
49+
50+
# Load environment variables
2351
self._load_dotenv()
24-
25-
try:
26-
self._ensure_config_exists()
27-
self.config = self._load_config()
28-
self._migrate_old_keys()
29-
30-
# Override config with environment variables if they exist
31-
self._apply_env_vars()
32-
except Exception as e:
33-
log.error(f"Error initializing configuration from {self.config_file}: {e}", exc_info=True)
52+
53+
# Ensure config file exists
54+
self._ensure_config_exists()
55+
56+
# Load config from file
57+
self.config = self._load_config()
58+
59+
# Apply environment variable overrides
60+
self._apply_env_vars()
3461

3562
def _load_dotenv(self):
3663
"""Load environment variables from .env file if it exists."""
@@ -77,32 +104,50 @@ def _load_dotenv(self):
77104
log.debug("No .env or .env.example file found in current directory")
78105

79106
def _apply_env_vars(self):
80-
"""Apply environment variables to override config settings."""
81-
# Map of environment variable names to config keys
82-
env_var_mapping = {
83-
"CLI_CODE_GOOGLE_API_KEY": "google_api_key",
84-
"CLI_CODE_OLLAMA_API_URL": "ollama_api_url",
85-
"CLI_CODE_DEFAULT_PROVIDER": "default_provider",
86-
"CLI_CODE_DEFAULT_MODEL": "default_model",
87-
"CLI_CODE_OLLAMA_DEFAULT_MODEL": "ollama_default_model",
107+
"""
108+
Apply environment variable overrides to the configuration.
109+
110+
Environment variables take precedence over configuration file values.
111+
Environment variables are formatted as:
112+
CLI_CODE_SETTING_NAME
113+
114+
For example:
115+
CLI_CODE_GOOGLE_API_KEY=my-api-key
116+
CLI_CODE_DEFAULT_PROVIDER=gemini
117+
CLI_CODE_SETTINGS_MAX_TOKENS=4096
118+
"""
119+
120+
# Direct mappings from env to config keys
121+
env_mappings = {
122+
'CLI_CODE_GOOGLE_API_KEY': 'google_api_key',
123+
'CLI_CODE_DEFAULT_PROVIDER': 'default_provider',
124+
'CLI_CODE_DEFAULT_MODEL': 'default_model',
125+
'CLI_CODE_OLLAMA_API_URL': 'ollama_api_url',
126+
'CLI_CODE_OLLAMA_DEFAULT_MODEL': 'ollama_default_model',
88127
}
89-
90-
for env_var, config_key in env_var_mapping.items():
91-
if env_var in os.environ:
92-
value = os.environ[env_var]
93-
# Mask sensitive values in logs
94-
log_value = "****" if "KEY" in env_var or "TOKEN" in env_var else value
95-
log.info(f"Using environment variable {env_var}={log_value} to override config key '{config_key}'")
96-
self.config[config_key] = value
97-
98-
# Apply and save if environment variables were found
99-
if any(env_var in os.environ for env_var in env_var_mapping):
100-
self._save_config()
101-
# Log all the current config values for debugging
102-
safe_config = self.config.copy()
103-
if "google_api_key" in safe_config and safe_config["google_api_key"]:
104-
safe_config["google_api_key"] = "****"
105-
log.debug(f"Final config after applying environment variables: {safe_config}")
128+
129+
# Apply direct mappings
130+
for env_key, config_key in env_mappings.items():
131+
if env_key in os.environ:
132+
self.config[config_key] = os.environ[env_key]
133+
134+
# Settings with CLI_CODE_SETTINGS_ prefix go into settings dict
135+
if 'settings' not in self.config:
136+
self.config['settings'] = {}
137+
138+
for env_key, env_value in os.environ.items():
139+
if env_key.startswith('CLI_CODE_SETTINGS_'):
140+
setting_name = env_key[len('CLI_CODE_SETTINGS_'):].lower()
141+
142+
# Try to convert to appropriate type (int, float, bool)
143+
if env_value.isdigit():
144+
self.config['settings'][setting_name] = int(env_value)
145+
elif env_value.replace('.', '', 1).isdigit() and env_value.count('.') <= 1:
146+
self.config['settings'][setting_name] = float(env_value)
147+
elif env_value.lower() in ('true', 'false'):
148+
self.config['settings'][setting_name] = env_value.lower() == 'true'
149+
else:
150+
self.config['settings'][setting_name] = env_value
106151

107152
def _ensure_config_exists(self):
108153
"""Create config directory and file with defaults if they don't exist."""
@@ -154,47 +199,6 @@ def _save_config(self):
154199
except Exception as e:
155200
log.error(f"Error saving config file {self.config_file}: {e}", exc_info=True)
156201

157-
def _migrate_old_keys(self):
158-
"""Migrate from old nested 'api_keys': {'google': ...} structure if present."""
159-
if "api_keys" in self.config and isinstance(self.config["api_keys"], dict):
160-
log.info("Migrating old 'api_keys' structure in config file.")
161-
if "google" in self.config["api_keys"] and "google_api_key" not in self.config:
162-
self.config["google_api_key"] = self.config["api_keys"]["google"]
163-
del self.config["api_keys"]
164-
self._save_config()
165-
log.info("Finished migrating 'api_keys'.")
166-
167-
# Check for old config paths and migrate if needed
168-
self._migrate_old_config_paths()
169-
170-
def _migrate_old_config_paths(self):
171-
"""Check for and migrate config from older versions with different path names."""
172-
old_paths = [
173-
Path.home() / ".config" / "gemini-code" / "config.yaml",
174-
Path.home() / ".config" / "cli-code" / "config.yaml",
175-
]
176-
177-
for old_path in old_paths:
178-
if old_path.exists() and not self.config_file.exists():
179-
log.info(f"Found old config at {old_path}. Migrating to {self.config_file}...")
180-
try:
181-
# Ensure new directory exists
182-
self.config_dir.mkdir(parents=True, exist_ok=True)
183-
184-
# Read old config
185-
with open(old_path, "r") as old_file:
186-
old_config = yaml.safe_load(old_file) or {}
187-
188-
# Update our config with old values
189-
self.config.update(old_config)
190-
191-
# Save to new location
192-
self._save_config()
193-
log.info(f"Successfully migrated config from {old_path} to {self.config_file}")
194-
except Exception as e:
195-
log.error(f"Error migrating config from {old_path}: {e}", exc_info=True)
196-
# Continue trying other paths on failure
197-
198202
def get_credential(self, provider: str) -> str | None:
199203
"""Get the credential (API key or URL) for a specific provider."""
200204
if provider == "gemini":

0 commit comments

Comments
 (0)