Skip to content

ccproxy.config.settings

ccproxy.config.settings

ConfigurationError

Bases: Exception

Raised when configuration loading or validation fails.

Settings

Bases: BaseSettings

Configuration settings for the Claude Proxy API Server.

Settings are loaded from environment variables, .env files, and TOML configuration files. Environment variables take precedence over .env file values. TOML configuration files are loaded in the following order: 1. .ccproxy.toml in current directory 2. ccproxy.toml in git repository root 3. config.toml in XDG_CONFIG_HOME/ccproxy/

server_url property

server_url

Get the complete server URL.

LLMSettings

Bases: BaseModel

LLM-specific feature toggles and defaults.

model_dump_safe

model_dump_safe()

Dump model data with sensitive information masked.

Returns:

Name Type Description
dict dict[str, Any]

Configuration with sensitive data masked

Source code in ccproxy/config/settings.py
def model_dump_safe(self) -> dict[str, Any]:
    """
    Dump model data with sensitive information masked.

    Returns:
        dict: Configuration with sensitive data masked
    """
    return self.model_dump(mode="json")

load_toml_config classmethod

load_toml_config(toml_path)

Load configuration from a TOML file.

Source code in ccproxy/config/settings.py
@classmethod
def load_toml_config(cls, toml_path: Path) -> dict[str, Any]:
    """Load configuration from a TOML file."""
    try:
        with toml_path.open("rb") as f:
            return tomllib.load(f)
    except OSError as e:
        raise ValueError(f"Cannot read TOML config file {toml_path}: {e}") from e
    except tomllib.TOMLDecodeError as e:
        raise ValueError(f"Invalid TOML syntax in {toml_path}: {e}") from e

load_config_file classmethod

load_config_file(config_path)

Load configuration from a file based on its extension.

Source code in ccproxy/config/settings.py
@classmethod
def load_config_file(cls, config_path: Path) -> dict[str, Any]:
    """Load configuration from a file based on its extension."""
    suffix = config_path.suffix.lower()

    if suffix in [".toml"]:
        return cls.load_toml_config(config_path)
    else:
        raise ValueError(
            f"Unsupported config file format: {suffix}. "
            "Only TOML (.toml) files are supported."
        )

from_toml classmethod

from_toml(toml_path=None, **kwargs)

Create Settings instance from TOML configuration.

Source code in ccproxy/config/settings.py
@classmethod
def from_toml(cls, toml_path: Path | None = None, **kwargs: Any) -> "Settings":
    """Create Settings instance from TOML configuration."""
    return cls.from_config(config_path=toml_path, **kwargs)

from_config classmethod

from_config(config_path=None, cli_context=None, **kwargs)

Create Settings instance from configuration file with env precedence and safe merging.

Source code in ccproxy/config/settings.py
@classmethod
def from_config(
    cls,
    config_path: Path | str | None = None,
    cli_context: dict[str, Any] | None = None,
    **kwargs: Any,
) -> "Settings":
    """Create Settings instance from configuration file with env precedence and safe merging."""
    logger = get_logger(__name__)

    global _CONFIG_MISSING_LOGGED

    if config_path is None:
        config_path_env = os.environ.get("CONFIG_FILE")
        if config_path_env:
            config_path = Path(config_path_env)

    if isinstance(config_path, str):
        config_path = Path(config_path)

    if config_path is None:
        config_path = find_toml_config_file()

    config_data: dict[str, Any] = {}
    if config_path and config_path.exists():
        config_data = cls.load_config_file(config_path)
        logger.debug(
            "config_file_loaded",
            path=str(config_path),
            category="config",
        )
    elif not _CONFIG_MISSING_LOGGED:
        suggestion = f"ccproxy config init --output-dir {get_ccproxy_config_dir()}"
        log_kwargs: dict[str, Any] = {
            "category": "config",
            "suggested_command": suggestion,
        }
        if config_path is not None:
            log_kwargs["path"] = str(config_path)
        logger.warning("config_file_missing", **log_kwargs)
        _CONFIG_MISSING_LOGGED = True

    cls._validate_deprecated_keys(config_data)

    # Start from env + .env via BaseSettings
    settings = cls()

    # Merge file-based configuration with env-var precedence
    for key, value in config_data.items():
        if not hasattr(settings, key):
            continue

        if key == "plugins" and isinstance(value, dict):
            current_plugins = getattr(settings, key, {})
            merged_plugins = cls._merge_plugins(current_plugins, value)
            setattr(settings, key, merged_plugins)
            continue

        current_attr = getattr(settings, key)

        if isinstance(value, dict) and isinstance(current_attr, BaseModel):
            merged_model = cls._merge_model(current_attr, value, f"{key.upper()}__")
            setattr(settings, key, merged_model)
        else:
            # Only set top-level simple types if there is no top-level env override
            env_key = key.upper()
            if os.getenv(env_key) is None:
                setattr(settings, key, value)

    # Smart default: if no config file exists and enabled_plugins is still None,
    # set a curated default list of core plugins
    if not config_path or not config_path.exists():
        if settings.enabled_plugins is None:
            settings.enabled_plugins = DEFAULT_ENABLED_PLUGINS

    # Apply direct kwargs overrides (highest precedence within process)
    if kwargs:
        cls._apply_overrides(settings, kwargs)

    # Apply CLI context (explicit flags)
    if cli_context:
        # Store raw CLI context for plugin access
        settings.cli_context = cli_context

        # Apply common serve CLI overrides directly to settings
        server_overrides: dict[str, Any] = {}
        if cli_context.get("host") is not None:
            server_overrides["host"] = cli_context["host"]
        if cli_context.get("port") is not None:
            server_overrides["port"] = cli_context["port"]
        if cli_context.get("reload") is not None:
            server_overrides["reload"] = cli_context["reload"]

        logging_overrides: dict[str, Any] = {}
        if cli_context.get("log_level") is not None:
            logging_overrides["level"] = cli_context["log_level"]
        if cli_context.get("log_file") is not None:
            logging_overrides["file"] = cli_context["log_file"]

        security_overrides: dict[str, Any] = {}
        if cli_context.get("auth_token") is not None:
            security_overrides["auth_token"] = cli_context["auth_token"]

        if server_overrides:
            cls._apply_overrides(settings, {"server": server_overrides})
        if logging_overrides:
            cls._apply_overrides(settings, {"logging": logging_overrides})
        if security_overrides:
            cls._apply_overrides(settings, {"security": security_overrides})

        # Apply plugin enable/disable lists if provided
        enabled_plugins = cli_context.get("enabled_plugins")
        disabled_plugins = cli_context.get("disabled_plugins")
        if enabled_plugins is not None:
            settings.enabled_plugins = list(enabled_plugins)
        if disabled_plugins is not None:
            settings.disabled_plugins = list(disabled_plugins)

    return settings

get_cli_context

get_cli_context()

Get CLI context for plugin access.

Source code in ccproxy/config/settings.py
def get_cli_context(self) -> dict[str, Any]:
    """Get CLI context for plugin access."""
    return self.cli_context