openwebui-cli/openwebui_cli/config.py
Danny Stocker 80510a7082 Fix code review issues: P0 bugs, style, and features
**P0 BUGS FIXED:**
-  chat_id now sent to API (critical bug - conversations couldn't continue)
-  OPENWEBUI_TOKEN env var honored (enables CI/headless workflows)
-  CLIError exit codes properly applied (correct status codes 0-5)

**DUPLICATE FUNCTION FIXED:**
-  Renamed duplicate delete() functions in rag.py to delete_file() and delete_collection()

**RUFF STYLE FIXES (25 → 0 issues):**
-  Removed unused imports (Live, Text from chat.py)
-  Fixed import sorting across all modules
-  Fixed all line-too-long errors (broke long strings, extracted variables)
-  All ruff checks now passing

**FUNCTIONAL IMPROVEMENTS:**
-  --format flag now respected in streaming mode (was only non-streaming)
-  Model parameter now optional, falls back to config.defaults.model
-  Better error message when no model specified

**PYDANTIC V2 COMPATIBILITY:**
-  Fixed ConfigDict → SettingsConfigDict for BaseSettings
-  Added types-PyYAML to dev dependencies for mypy

**ENTRY POINT UPDATE:**
-  Changed entry point from main:app to main:cli for proper error handling

**CODE QUALITY:**
- All 14 tests still passing
- All ruff style checks passing
- Mypy type coverage improved (some minor issues remain)

**IMPACT:**
- Fixes 3 critical bugs blocking production use
- Eliminates all style warnings (25 ruff issues → 0)
- Adds missing functionality (format flag, default model)
- Improves CI/automation workflows (env token support)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 20:51:50 +01:00

110 lines
3 KiB
Python

"""Configuration management for OpenWebUI CLI."""
import os
from pathlib import Path
import yaml
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict
def get_config_dir() -> Path:
"""Get the configuration directory path (XDG-compliant)."""
if os.name == "nt": # Windows
base = Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
else: # Unix/Linux/macOS
base = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"))
return base / "openwebui"
def get_config_path() -> Path:
"""Get the configuration file path."""
return get_config_dir() / "config.yaml"
class ProfileConfig(BaseModel):
"""Configuration for a single server profile."""
uri: str = "http://localhost:8080"
# Token stored in keyring, not in config file
class DefaultsConfig(BaseModel):
"""Default settings for CLI commands."""
model: str | None = None
format: str = "text"
stream: bool = True
timeout: int = 30
class OutputConfig(BaseModel):
"""Output formatting preferences."""
colors: bool = True
progress_bars: bool = True
timestamps: bool = False
class Config(BaseModel):
"""Main configuration model."""
version: int = 1
default_profile: str = "default"
profiles: dict[str, ProfileConfig] = Field(default_factory=lambda: {"default": ProfileConfig()})
defaults: DefaultsConfig = Field(default_factory=DefaultsConfig)
output: OutputConfig = Field(default_factory=OutputConfig)
class Settings(BaseSettings):
"""Environment-based settings that override config file."""
model_config = SettingsConfigDict(env_prefix="", case_sensitive=False)
openwebui_uri: str | None = None
openwebui_token: str | None = None
openwebui_profile: str | None = None
def load_config() -> Config:
"""Load configuration from file, with defaults for missing values."""
config_path = get_config_path()
if config_path.exists():
with open(config_path) as f:
data = yaml.safe_load(f) or {}
return Config(**data)
else:
return Config()
def save_config(config: Config) -> None:
"""Save configuration to file."""
config_path = get_config_path()
config_path.parent.mkdir(parents=True, exist_ok=True)
with open(config_path, "w") as f:
yaml.dump(config.model_dump(), f, default_flow_style=False, sort_keys=False)
def get_effective_config(
profile: str | None = None,
uri: str | None = None,
) -> tuple[str, str | None]:
"""
Get effective URI and profile name, respecting precedence:
CLI flags > env vars > config file > defaults
"""
config = load_config()
settings = Settings()
# Determine profile
effective_profile = profile or settings.openwebui_profile or config.default_profile
# Get profile config
profile_config = config.profiles.get(effective_profile, ProfileConfig())
# Determine URI with precedence
effective_uri = uri or settings.openwebui_uri or profile_config.uri
return effective_uri, effective_profile