Skip to content

ccproxy.config

ccproxy.config

Configuration module for Claude Proxy API Server.

AuthSettings

Bases: BaseModel

Combined authentication and credentials configuration.

validate_oauth classmethod

validate_oauth(v)

Validate and convert OAuth configuration.

Source code in ccproxy/config/auth.py
@field_validator("oauth", mode="before")
@classmethod
def validate_oauth(cls, v: Any) -> Any:
    """Validate and convert OAuth configuration."""
    if v is None:
        return OAuthSettings()

    # If it's already an OAuthSettings instance, return as-is
    if isinstance(v, OAuthSettings):
        return v

    # If it's a dict, create OAuthSettings from it
    if isinstance(v, dict):
        return OAuthSettings(**v)

    # Try to convert to dict if possible
    if hasattr(v, "model_dump"):
        return OAuthSettings(**v.model_dump())
    elif hasattr(v, "__dict__"):
        return OAuthSettings(**v.__dict__)

    return v

validate_storage classmethod

validate_storage(v)

Validate and convert storage configuration.

Source code in ccproxy/config/auth.py
@field_validator("storage", mode="before")
@classmethod
def validate_storage(cls, v: Any) -> Any:
    """Validate and convert storage configuration."""
    if v is None:
        return CredentialStorageSettings()

    # If it's already a CredentialStorageSettings instance, return as-is
    if isinstance(v, CredentialStorageSettings):
        return v

    # If it's a dict, create CredentialStorageSettings from it
    if isinstance(v, dict):
        return CredentialStorageSettings(**v)

    # Try to convert to dict if possible
    if hasattr(v, "model_dump"):
        return CredentialStorageSettings(**v.model_dump())
    elif hasattr(v, "__dict__"):
        return CredentialStorageSettings(**v.__dict__)

    return v

CredentialStorageSettings

Bases: BaseModel

Settings for credential storage locations.

OAuthSettings

Bases: BaseModel

OAuth-specific settings.

DockerSettings

Bases: BaseModel

Docker configuration settings for running Claude commands in containers.

validate_docker_volumes classmethod

validate_docker_volumes(v)

Validate Docker volume mount format.

Source code in ccproxy/config/docker_settings.py
@field_validator("docker_volumes")
@classmethod
def validate_docker_volumes(cls, v: list[str]) -> list[str]:
    """Validate Docker volume mount format."""
    return validate_volumes_list(v)

validate_docker_home_directory classmethod

validate_docker_home_directory(v)

Validate and normalize Docker home directory (host path).

Source code in ccproxy/config/docker_settings.py
@field_validator("docker_home_directory")
@classmethod
def validate_docker_home_directory(cls, v: str | None) -> str | None:
    """Validate and normalize Docker home directory (host path)."""
    if v is None:
        return None
    return validate_host_path(v)

validate_docker_workspace_directory classmethod

validate_docker_workspace_directory(v)

Validate and normalize Docker workspace directory (host path).

Source code in ccproxy/config/docker_settings.py
@field_validator("docker_workspace_directory")
@classmethod
def validate_docker_workspace_directory(cls, v: str | None) -> str | None:
    """Validate and normalize Docker workspace directory (host path)."""
    if v is None:
        return None
    return validate_host_path(v)

setup_docker_configuration

setup_docker_configuration()

Set up Docker volumes and user mapping configuration.

Source code in ccproxy/config/docker_settings.py
@model_validator(mode="after")
def setup_docker_configuration(self) -> "DockerSettings":
    """Set up Docker volumes and user mapping configuration."""
    # Set up Docker volumes based on home and workspace directories
    if (
        not self.docker_volumes
        and not self.docker_home_directory
        and not self.docker_workspace_directory
    ):
        # Use XDG config directory for Claude CLI data
        claude_config_dir = get_claude_docker_home_dir()
        home_host_path = str(claude_config_dir)
        workspace_host_path = os.path.expandvars("$PWD")

        self.docker_volumes = [
            f"{home_host_path}:/data/home",
            f"{workspace_host_path}:/data/workspace",
        ]

    # Update environment variables to point to container paths
    if "CLAUDE_HOME" not in self.docker_environment:
        self.docker_environment["CLAUDE_HOME"] = "/data/home"
    if "CLAUDE_WORKSPACE" not in self.docker_environment:
        self.docker_environment["CLAUDE_WORKSPACE"] = "/data/workspace"

    # Set up user mapping with auto-detection if enabled but not configured
    if self.user_mapping_enabled and os.name == "posix":
        # Auto-detect current user UID/GID if not explicitly set
        if self.user_uid is None:
            self.user_uid = os.getuid()
        if self.user_gid is None:
            self.user_gid = os.getgid()
    elif self.user_mapping_enabled and os.name != "posix":
        # Disable user mapping on non-Unix systems (Windows)
        self.user_mapping_enabled = False

    return self

ConfigLoader

ConfigLoader()

Load configuration from multiple sources.

Source code in ccproxy/config/loader.py
def __init__(self) -> None:
    self._cached_config: dict[str, Any] | None = None

load

load(config_file=None)

Load configuration from multiple sources.

Priority: ENV > config file > defaults

Parameters:

Name Type Description Default
config_file Path | None

Optional path to config file

None

Returns:

Type Description
Settings

Settings instance with loaded configuration

Raises:

Type Description
ConfigurationError

If config file is invalid or cannot be loaded

Source code in ccproxy/config/loader.py
def load(self, config_file: Path | None = None) -> Settings:
    """Load configuration from multiple sources.

    Priority: ENV > config file > defaults

    Args:
        config_file: Optional path to config file

    Returns:
        Settings instance with loaded configuration

    Raises:
        ConfigurationError: If config file is invalid or cannot be loaded
    """
    config_data = self._load_config_file(config_file)

    # Environment variables take precedence over config file
    return Settings(**config_data) if config_data else Settings()

clear_cache

clear_cache()

Clear cached configuration.

Source code in ccproxy/config/loader.py
def clear_cache(self) -> None:
    """Clear cached configuration."""
    self._cached_config = None

ReverseProxySettings

Bases: BaseModel

Reverse proxy configuration settings.

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.

is_development property

is_development

Check if running in development mode.

validate_server classmethod

validate_server(v)

Validate and convert server settings.

Source code in ccproxy/config/settings.py
@field_validator("server", mode="before")
@classmethod
def validate_server(cls, v: Any) -> Any:
    """Validate and convert server settings."""
    if v is None:
        return ServerSettings()
    if isinstance(v, ServerSettings):
        return v
    if isinstance(v, dict):
        return ServerSettings(**v)
    return v

validate_security classmethod

validate_security(v)

Validate and convert security settings.

Source code in ccproxy/config/settings.py
@field_validator("security", mode="before")
@classmethod
def validate_security(cls, v: Any) -> Any:
    """Validate and convert security settings."""
    if v is None:
        return SecuritySettings()
    if isinstance(v, SecuritySettings):
        return v
    if isinstance(v, dict):
        return SecuritySettings(**v)
    return v

validate_cors classmethod

validate_cors(v)

Validate and convert CORS settings.

Source code in ccproxy/config/settings.py
@field_validator("cors", mode="before")
@classmethod
def validate_cors(cls, v: Any) -> Any:
    """Validate and convert CORS settings."""
    if v is None:
        return CORSSettings()
    if isinstance(v, CORSSettings):
        return v
    if isinstance(v, dict):
        return CORSSettings(**v)
    return v

validate_claude classmethod

validate_claude(v)

Validate and convert Claude settings.

Source code in ccproxy/config/settings.py
@field_validator("claude", mode="before")
@classmethod
def validate_claude(cls, v: Any) -> Any:
    """Validate and convert Claude settings."""
    if v is None:
        return ClaudeSettings()
    if isinstance(v, ClaudeSettings):
        return v
    if isinstance(v, dict):
        return ClaudeSettings(**v)
    return v

validate_reverse_proxy classmethod

validate_reverse_proxy(v)

Validate and convert reverse proxy settings.

Source code in ccproxy/config/settings.py
@field_validator("reverse_proxy", mode="before")
@classmethod
def validate_reverse_proxy(cls, v: Any) -> Any:
    """Validate and convert reverse proxy settings."""
    if v is None:
        return ReverseProxySettings()
    if isinstance(v, ReverseProxySettings):
        return v
    if isinstance(v, dict):
        return ReverseProxySettings(**v)
    return v

validate_auth classmethod

validate_auth(v)

Validate and convert auth settings.

Source code in ccproxy/config/settings.py
@field_validator("auth", mode="before")
@classmethod
def validate_auth(cls, v: Any) -> Any:
    """Validate and convert auth settings."""
    if v is None:
        return AuthSettings()
    if isinstance(v, AuthSettings):
        return v
    if isinstance(v, dict):
        return AuthSettings(**v)
    return v

validate_docker_settings classmethod

validate_docker_settings(v)

Validate and convert Docker settings.

Source code in ccproxy/config/settings.py
@field_validator("docker", mode="before")
@classmethod
def validate_docker_settings(cls, v: Any) -> Any:
    """Validate and convert Docker settings."""
    if v is None:
        return DockerSettings()

    # If it's already a DockerSettings instance, return as-is
    if isinstance(v, DockerSettings):
        return v

    # If it's a dict, create DockerSettings from it
    if isinstance(v, dict):
        return DockerSettings(**v)

    # Try to convert to dict if possible
    if hasattr(v, "model_dump"):
        return DockerSettings(**v.model_dump())
    elif hasattr(v, "__dict__"):
        return DockerSettings(**v.__dict__)

    return v

validate_observability classmethod

validate_observability(v)

Validate and convert observability settings.

Source code in ccproxy/config/settings.py
@field_validator("observability", mode="before")
@classmethod
def validate_observability(cls, v: Any) -> Any:
    """Validate and convert observability settings."""
    if v is None:
        return ObservabilitySettings()
    if isinstance(v, ObservabilitySettings):
        return v
    if isinstance(v, dict):
        return ObservabilitySettings(**v)
    return v

validate_scheduler classmethod

validate_scheduler(v)

Validate and convert scheduler settings.

Source code in ccproxy/config/settings.py
@field_validator("scheduler", mode="before")
@classmethod
def validate_scheduler(cls, v: Any) -> Any:
    """Validate and convert scheduler settings."""
    if v is None:
        return SchedulerSettings()
    if isinstance(v, SchedulerSettings):
        return v
    if isinstance(v, dict):
        return SchedulerSettings(**v)
    return v

validate_pricing classmethod

validate_pricing(v)

Validate and convert pricing settings.

Source code in ccproxy/config/settings.py
@field_validator("pricing", mode="before")
@classmethod
def validate_pricing(cls, v: Any) -> Any:
    """Validate and convert pricing settings."""
    if v is None:
        return PricingSettings()
    if isinstance(v, PricingSettings):
        return v
    if isinstance(v, dict):
        return PricingSettings(**v)
    return v

setup_claude_cli_path

setup_claude_cli_path()

Set up Claude CLI path in environment if provided or found.

Source code in ccproxy/config/settings.py
@model_validator(mode="after")
def setup_claude_cli_path(self) -> "Settings":
    """Set up Claude CLI path in environment if provided or found."""
    # If not explicitly set, try to find it
    if not self.claude.cli_path:
        found_path, found_in_path = self.claude.find_claude_cli()
        if found_path:
            self.claude.cli_path = found_path
            # Only add to PATH if it wasn't found via which()
            if not found_in_path:
                cli_dir = str(Path(self.claude.cli_path).parent)
                current_path = os.environ.get("PATH", "")
                if cli_dir not in current_path:
                    os.environ["PATH"] = f"{cli_dir}:{current_path}"
    elif self.claude.cli_path:
        # If explicitly set, always add to PATH
        cli_dir = str(Path(self.claude.cli_path).parent)
        current_path = os.environ.get("PATH", "")
        if cli_dir not in current_path:
            os.environ["PATH"] = f"{cli_dir}:{current_path}"
    return self

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()

load_toml_config classmethod

load_toml_config(toml_path)

Load configuration from a TOML file.

Parameters:

Name Type Description Default
toml_path Path

Path to the TOML configuration file

required

Returns:

Name Type Description
dict dict[str, Any]

Configuration data from the TOML file

Raises:

Type Description
ValueError

If the TOML file is invalid or cannot be read

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.

    Args:
        toml_path: Path to the TOML configuration file

    Returns:
        dict: Configuration data from the TOML file

    Raises:
        ValueError: If the TOML file is invalid or cannot be read
    """
    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.

Parameters:

Name Type Description Default
config_path Path

Path to the configuration file

required

Returns:

Name Type Description
dict dict[str, Any]

Configuration data from the file

Raises:

Type Description
ValueError

If the file format is unsupported or invalid

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.

    Args:
        config_path: Path to the configuration file

    Returns:
        dict: Configuration data from the file

    Raises:
        ValueError: If the file format is unsupported or invalid
    """
    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.

Parameters:

Name Type Description Default
toml_path Path | None

Path to TOML configuration file. If None, auto-discovers file.

None
**kwargs Any

Additional keyword arguments to override config values

{}

Returns:

Name Type Description
Settings Settings

Configured Settings instance

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.

    Args:
        toml_path: Path to TOML configuration file. If None, auto-discovers file.
        **kwargs: Additional keyword arguments to override config values

    Returns:
        Settings: Configured Settings instance
    """
    # Use the more generic from_config method
    return cls.from_config(config_path=toml_path, **kwargs)

from_config classmethod

from_config(config_path=None, **kwargs)

Create Settings instance from configuration file.

Parameters:

Name Type Description Default
config_path Path | str | None

Path to configuration file. Can be: - None: Auto-discover config file or use CONFIG_FILE env var - Path or str: Use this specific config file

None
**kwargs Any

Additional keyword arguments to override config values

{}

Returns:

Name Type Description
Settings Settings

Configured Settings instance

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

    Args:
        config_path: Path to configuration file. Can be:
            - None: Auto-discover config file or use CONFIG_FILE env var
            - Path or str: Use this specific config file
        **kwargs: Additional keyword arguments to override config values

    Returns:
        Settings: Configured Settings instance
    """
    # Check for CONFIG_FILE environment variable first
    if config_path is None:
        config_path_env = os.environ.get("CONFIG_FILE")
        if config_path_env:
            config_path = Path(config_path_env)

    # Convert string to Path if needed
    if isinstance(config_path, str):
        config_path = Path(config_path)

    # Auto-discover config file if not provided
    if config_path is None:
        config_path = find_toml_config_file()

    # Load config if found
    config_data = {}
    if config_path and config_path.exists():
        config_data = cls.load_config_file(config_path)

    # Merge config with kwargs (kwargs take precedence)
    merged_config = {**config_data, **kwargs}

    # Create Settings instance with merged config
    return cls(**merged_config)

ConfigValidationError

Bases: Exception

Configuration validation error.

load_config

load_config(config_file=None)

Load configuration using the global loader.

Parameters:

Name Type Description Default
config_file Path | None

Optional path to config file

None

Returns:

Type Description
Settings

Settings instance with loaded configuration

Source code in ccproxy/config/loader.py
def load_config(config_file: Path | None = None) -> Settings:
    """Load configuration using the global loader.

    Args:
        config_file: Optional path to config file

    Returns:
        Settings instance with loaded configuration
    """
    return config_loader.load(config_file)

get_settings

get_settings(config_path=None)

Get the global settings instance with configuration file support.

Parameters:

Name Type Description Default
config_path Path | str | None

Optional path to configuration file. If None, uses CONFIG_FILE env var or auto-discovers config file.

None

Returns:

Name Type Description
Settings Settings

Configured Settings instance

Source code in ccproxy/config/settings.py
def get_settings(config_path: Path | str | None = None) -> Settings:
    """Get the global settings instance with configuration file support.

    Args:
        config_path: Optional path to configuration file. If None, uses CONFIG_FILE env var
                    or auto-discovers config file.

    Returns:
        Settings: Configured Settings instance
    """
    try:
        # Check for CLI overrides from environment variable
        cli_overrides = {}
        cli_overrides_json = os.environ.get("CCPROXY_CONFIG_OVERRIDES")
        if cli_overrides_json:
            with contextlib.suppress(json.JSONDecodeError):
                cli_overrides = json.loads(cli_overrides_json)

        return Settings.from_config(config_path=config_path, **cli_overrides)
    except Exception as e:
        # If settings can't be loaded (e.g., missing API key),
        # this will be handled by the caller
        raise ValueError(f"Configuration error: {e}") from e

validate_config_dict

validate_config_dict(config)

Validate configuration dictionary.

Parameters:

Name Type Description Default
config dict[str, Any]

Configuration dictionary to validate

required

Returns:

Type Description
dict[str, Any]

The validated configuration dictionary

Raises:

Type Description
ConfigValidationError

If configuration is invalid

Source code in ccproxy/config/validators.py
def validate_config_dict(config: dict[str, Any]) -> dict[str, Any]:
    """Validate configuration dictionary.

    Args:
        config: Configuration dictionary to validate

    Returns:
        The validated configuration dictionary

    Raises:
        ConfigValidationError: If configuration is invalid
    """
    if not isinstance(config, dict):
        raise ConfigValidationError("Configuration must be a dictionary")

    validated_config: dict[str, Any] = {}

    # Validate specific fields if present
    if "host" in config:
        validated_config["host"] = validate_host(config["host"])

    if "port" in config:
        validated_config["port"] = validate_port(config["port"])

    if "target_url" in config:
        validated_config["target_url"] = validate_url(config["target_url"])

    if "log_level" in config:
        validated_config["log_level"] = validate_log_level(config["log_level"])

    if "cors_origins" in config:
        validated_config["cors_origins"] = validate_cors_origins(config["cors_origins"])

    if "timeout" in config:
        validated_config["timeout"] = validate_timeout(config["timeout"])

    # Copy other fields without validation
    for key, value in config.items():
        if key not in validated_config:
            validated_config[key] = value

    return validated_config

validate_cors_origins

validate_cors_origins(origins)

Validate CORS origins.

Parameters:

Name Type Description Default
origins list[str]

List of origin URLs to validate

required

Returns:

Type Description
list[str]

The validated list of origins

Raises:

Type Description
ConfigValidationError

If any origin is invalid

Source code in ccproxy/config/validators.py
def validate_cors_origins(origins: list[str]) -> list[str]:
    """Validate CORS origins.

    Args:
        origins: List of origin URLs to validate

    Returns:
        The validated list of origins

    Raises:
        ConfigValidationError: If any origin is invalid
    """
    if not isinstance(origins, list):
        raise ConfigValidationError("CORS origins must be a list")

    validated_origins = []
    for origin in origins:
        if origin == "*":
            validated_origins.append(origin)
        else:
            validated_origins.append(validate_url(origin))

    return validated_origins

validate_host

validate_host(host)

Validate host address.

Parameters:

Name Type Description Default
host str

Host address to validate

required

Returns:

Type Description
str

The validated host address

Raises:

Type Description
ConfigValidationError

If host is invalid

Source code in ccproxy/config/validators.py
def validate_host(host: str) -> str:
    """Validate host address.

    Args:
        host: Host address to validate

    Returns:
        The validated host address

    Raises:
        ConfigValidationError: If host is invalid
    """
    if not host:
        raise ConfigValidationError("Host cannot be empty")

    # Allow localhost, IP addresses, and domain names
    if host in ["localhost", "0.0.0.0", "127.0.0.1"]:
        return host

    # Basic IP address validation
    if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", host):
        parts = host.split(".")
        if all(0 <= int(part) <= 255 for part in parts):
            return host
        raise ConfigValidationError(f"Invalid IP address: {host}")

    # Basic domain name validation
    if re.match(r"^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", host):
        return host

    return host  # Allow other formats for flexibility

validate_log_level

validate_log_level(level)

Validate log level.

Parameters:

Name Type Description Default
level str

Log level to validate

required

Returns:

Type Description
str

The validated log level

Raises:

Type Description
ConfigValidationError

If log level is invalid

Source code in ccproxy/config/validators.py
def validate_log_level(level: str) -> str:
    """Validate log level.

    Args:
        level: Log level to validate

    Returns:
        The validated log level

    Raises:
        ConfigValidationError: If log level is invalid
    """
    valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
    level = level.upper()

    if level not in valid_levels:
        raise ConfigValidationError(
            f"Invalid log level: {level}. Must be one of: {valid_levels}"
        )

    return level

validate_path

validate_path(path)

Validate file path.

Parameters:

Name Type Description Default
path str | Path

Path to validate

required

Returns:

Type Description
Path

The validated Path object

Raises:

Type Description
ConfigValidationError

If path is invalid

Source code in ccproxy/config/validators.py
def validate_path(path: str | Path) -> Path:
    """Validate file path.

    Args:
        path: Path to validate

    Returns:
        The validated Path object

    Raises:
        ConfigValidationError: If path is invalid
    """
    if isinstance(path, str):
        path = Path(path)

    if not isinstance(path, Path):
        raise ConfigValidationError(f"Path must be a string or Path object: {path}")

    return path

validate_port

validate_port(port)

Validate port number.

Parameters:

Name Type Description Default
port int | str

Port number to validate

required

Returns:

Type Description
int

The validated port number

Raises:

Type Description
ConfigValidationError

If port is invalid

Source code in ccproxy/config/validators.py
def validate_port(port: int | str) -> int:
    """Validate port number.

    Args:
        port: Port number to validate

    Returns:
        The validated port number

    Raises:
        ConfigValidationError: If port is invalid
    """
    if isinstance(port, str):
        try:
            port = int(port)
        except ValueError as e:
            raise ConfigValidationError(f"Port must be a valid integer: {port}") from e

    if not isinstance(port, int):
        raise ConfigValidationError(f"Port must be an integer: {port}")

    if port < 1 or port > 65535:
        raise ConfigValidationError(f"Port must be between 1 and 65535: {port}")

    return port

validate_timeout

validate_timeout(timeout)

Validate timeout value.

Parameters:

Name Type Description Default
timeout int | float

Timeout value to validate

required

Returns:

Type Description
int | float

The validated timeout value

Raises:

Type Description
ConfigValidationError

If timeout is invalid

Source code in ccproxy/config/validators.py
def validate_timeout(timeout: int | float) -> int | float:
    """Validate timeout value.

    Args:
        timeout: Timeout value to validate

    Returns:
        The validated timeout value

    Raises:
        ConfigValidationError: If timeout is invalid
    """
    if not isinstance(timeout, int | float):
        raise ConfigValidationError(f"Timeout must be a number: {timeout}")

    if timeout <= 0:
        raise ConfigValidationError(f"Timeout must be positive: {timeout}")

    return timeout

validate_url

validate_url(url)

Validate URL format.

Parameters:

Name Type Description Default
url str

URL to validate

required

Returns:

Type Description
str

The validated URL

Raises:

Type Description
ConfigValidationError

If URL is invalid

Source code in ccproxy/config/validators.py
def validate_url(url: str) -> str:
    """Validate URL format.

    Args:
        url: URL to validate

    Returns:
        The validated URL

    Raises:
        ConfigValidationError: If URL is invalid
    """
    if not url:
        raise ConfigValidationError("URL cannot be empty")

    try:
        result = urlparse(url)
        if not result.scheme or not result.netloc:
            raise ConfigValidationError(f"Invalid URL format: {url}")
    except Exception as e:
        raise ConfigValidationError(f"Invalid URL: {url}") from e

    return url