fix: resolve CI/CD issues with proper package structure and imports
This commit is contained in:
133
src/local_api_docs_search/utils/config.py
Normal file
133
src/local_api_docs_search/utils/config.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
"""Configuration management for the application."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
"""Configuration management class supporting env vars and YAML config."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
config_path: Optional[Path] = None,
|
||||||
|
env_path: Optional[Path] = None,
|
||||||
|
):
|
||||||
|
self._config: dict[str, Any] = {}
|
||||||
|
self._config_path = config_path or Path.cwd() / "config.yaml"
|
||||||
|
self._load_env(env_path)
|
||||||
|
self._load_config()
|
||||||
|
|
||||||
|
def _load_env(self, env_path: Optional[Path] = None) -> None:
|
||||||
|
"""Load environment variables from .env file."""
|
||||||
|
env_file = env_path or Path.cwd() / ".env"
|
||||||
|
if env_file.exists():
|
||||||
|
load_dotenv(env_file)
|
||||||
|
|
||||||
|
def _load_config(self) -> None:
|
||||||
|
"""Load configuration from YAML file."""
|
||||||
|
if self._config_path.exists():
|
||||||
|
with open(self._config_path, "r") as f:
|
||||||
|
self._config = yaml.safe_load(f) or {}
|
||||||
|
else:
|
||||||
|
self._config = {}
|
||||||
|
|
||||||
|
def get(self, key: str, default: Any = None) -> Any:
|
||||||
|
"""Get configuration value with environment variable override."""
|
||||||
|
env_key = f"API_DOCS_{key.upper()}"
|
||||||
|
env_value = os.environ.get(env_key)
|
||||||
|
|
||||||
|
if env_value is not None:
|
||||||
|
return self._cast_env_value(env_value)
|
||||||
|
|
||||||
|
return self._config.get(key, default)
|
||||||
|
|
||||||
|
def _cast_env_value(self, value: str) -> Any:
|
||||||
|
"""Cast environment variable string to appropriate type."""
|
||||||
|
if value.lower() in ("true", "false"):
|
||||||
|
return value.lower() == "true"
|
||||||
|
try:
|
||||||
|
return int(value)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
return float(value)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def index_path(self) -> Path:
|
||||||
|
"""Get the documentation index path."""
|
||||||
|
return Path(self.get("index_path", "./docs"))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def model_name(self) -> str:
|
||||||
|
"""Get the embedding model name."""
|
||||||
|
return self.get("model_name", "all-MiniLM-L6-v2")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def embedding_device(self) -> str:
|
||||||
|
"""Get the embedding device."""
|
||||||
|
return self.get("embedding_device", "cpu")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def chroma_persist_dir(self) -> Path:
|
||||||
|
"""Get the ChromaDB persistence directory."""
|
||||||
|
return Path(self.get("chroma_persist_dir", ".api-docs/chroma"))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_limit(self) -> int:
|
||||||
|
"""Get the default search result limit."""
|
||||||
|
return int(self.get("default_limit", 10))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def verbose(self) -> bool:
|
||||||
|
"""Get verbose mode setting."""
|
||||||
|
return self.get("verbose", False)
|
||||||
|
|
||||||
|
def set(self, key: str, value: Any) -> None:
|
||||||
|
"""Set a configuration value."""
|
||||||
|
self._config[key] = value
|
||||||
|
|
||||||
|
def save(self) -> None:
|
||||||
|
"""Save configuration to YAML file."""
|
||||||
|
with open(self._config_path, "w") as f:
|
||||||
|
yaml.dump(self._config, f, default_flow_style=False)
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
"""Reset configuration to defaults."""
|
||||||
|
self._config = {}
|
||||||
|
if self._config_path.exists():
|
||||||
|
self._config_path.unlink()
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"""Return configuration as dictionary."""
|
||||||
|
return {
|
||||||
|
"index_path": str(self.index_path),
|
||||||
|
"model_name": self.model_name,
|
||||||
|
"embedding_device": self.embedding_device,
|
||||||
|
"chroma_persist_dir": str(self.chroma_persist_dir),
|
||||||
|
"default_limit": self.default_limit,
|
||||||
|
"verbose": self.verbose,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_config: Optional[Config] = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_config(config_path: Optional[Path] = None) -> Config:
|
||||||
|
"""Get or create the global configuration instance."""
|
||||||
|
global _config
|
||||||
|
if _config is None:
|
||||||
|
_config = Config(config_path)
|
||||||
|
return _config
|
||||||
|
|
||||||
|
|
||||||
|
def reset_config() -> None:
|
||||||
|
"""Reset the global configuration instance."""
|
||||||
|
global _config
|
||||||
|
_config = None
|
||||||
Reference in New Issue
Block a user