Source code for honeyhive.config.models.base

"""Base configuration models for HoneyHive SDK.

This module provides the base Pydantic models that contain common fields
shared across different domain-specific configurations. This approach
eliminates duplication while maintaining type safety and validation.

The models follow graceful degradation principles - invalid values are logged
as warnings and replaced with safe defaults to prevent crashing the host application.
"""

# pylint: disable=duplicate-code
# Note: Pydantic model configuration patterns are intentionally similar
# across config modules for consistency. These provide standardized
# validation and environment variable handling.

import logging
import os
from typing import Any, Optional

from pydantic import AliasChoices, Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

# Module logger for graceful degradation warnings
logger = logging.getLogger(__name__)


[docs] class ServerURLMixin: # pylint: disable=too-few-public-methods """Mixin for server URL configuration with HH_API_URL environment variable support. This mixin provides the server_url field with proper environment variable loading for classes that need to support custom HoneyHive server URLs. It can be used by both APIClientConfig and TracerConfig to avoid field duplication. Environment Variables: HH_API_URL: Custom HoneyHive server URL Examples: >>> class MyConfig(BaseHoneyHiveConfig, ServerURLMixin): ... pass >>> config = MyConfig() # Loads from HH_API_URL if set """ server_url: str = Field( default="https://api.honeyhive.ai", description="Custom HoneyHive server URL", validation_alias=AliasChoices("HH_API_URL", "server_url"), examples=["https://api.honeyhive.ai", "https://custom.honeyhive.com"], )
[docs] @field_validator("server_url", mode="before") @classmethod def validate_server_url(cls, v: Any) -> str: """Validate server URL format with graceful degradation. Args: v: The server URL to validate Returns: The validated and normalized server URL, or default if invalid """ if v is None: return "https://api.honeyhive.ai" validated = _safe_validate_url( v, "server_url", allow_none=False, default="https://api.honeyhive.ai" ) # Remove trailing slash for consistency return validated.rstrip("/") if validated else "https://api.honeyhive.ai"
def _safe_validate_string( value: Any, field_name: str, allow_none: bool = True, default: Optional[str] = None ) -> Optional[str]: """Safely validate string values with graceful degradation. Args: value: Value to validate field_name: Name of the field for logging allow_none: Whether None values are allowed default: Default value to return on validation failure Returns: Validated string or safe default """ if value is None: return None if allow_none else default if not isinstance(value, str): logger.warning( "Invalid %s: expected string, got %s. Using default.", field_name, type(value).__name__, extra={ "honeyhive_data": { "field": field_name, "invalid_type": type(value).__name__, } }, ) return default value = value.strip() if len(value) == 0: logger.warning( "Empty %s provided. Using default.", field_name, extra={"honeyhive_data": {"field": field_name}}, ) return default return value # type: ignore[no-any-return] def _safe_validate_url( value: Any, field_name: str, allow_none: bool = True, default: Optional[str] = None ) -> Optional[str]: """Safely validate URL values with graceful degradation. Args: value: Value to validate field_name: Name of the field for logging allow_none: Whether None values are allowed default: Default value to return on validation failure Returns: Validated URL or safe default """ validated = _safe_validate_string(value, field_name, allow_none, default) if validated is None or validated == default: return validated if not validated.startswith(("http://", "https://")): logger.warning( "Invalid %s: must be HTTP/HTTPS URL. Using default.", field_name, extra={"honeyhive_data": {"field": field_name, "invalid_url": validated}}, ) return default return validated
[docs] class BaseHoneyHiveConfig(BaseSettings): """Base configuration model with common HoneyHive fields. This base class contains fields that are commonly used across different parts of the SDK (tracer, API client, evaluation, etc.) to avoid duplication and ensure consistent validation. Common Fields: - api_key: HoneyHive API key for authentication - project: Project name (required by backend API) - test_mode: Enable test mode (no data sent to backend) - verbose: Enable verbose logging Example: This class is not used directly but inherited by domain-specific configs: >>> class TracerConfig(BaseHoneyHiveConfig): ... session_name: Optional[str] = None ... source: str = "dev" >>> >>> config = TracerConfig(api_key="hh_...", project="my-project") >>> print(config.api_key) # Inherited from base hh_... """ api_key: Optional[str] = Field( # type: ignore[call-overload,pydantic-alias] default=None, description="HoneyHive API key for authentication", validation_alias=AliasChoices("HH_API_KEY", "api_key"), examples=["hh_1234567890abcdef"], ) project: Optional[str] = Field( # type: ignore[call-overload,pydantic-alias] default=None, description="Project name (required by backend API)", validation_alias=AliasChoices("HH_PROJECT", "project"), examples=["my-llm-project", "chatbot-v2"], ) test_mode: bool = Field( # type: ignore[call-overload,pydantic-alias] default=False, description="Enable test mode (no data sent to backend)", validation_alias=AliasChoices("HH_TEST_MODE", "test_mode"), ) verbose: bool = Field( # type: ignore[call-overload,pydantic-alias] default=False, description="Enable verbose logging output and debug mode", validation_alias=AliasChoices("HH_VERBOSE", "verbose"), ) model_config = SettingsConfigDict( validate_assignment=True, extra="forbid", # Prevent accidental typos in field names case_sensitive=False, ) def __init__(self, **data: Any) -> None: """Initialize base config with unified verbose/debug mode handling.""" # Handle verbose mode from HH_VERBOSE environment variable if "verbose" not in data: # Check HH_VERBOSE environment variable verbose_env = os.getenv("HH_VERBOSE", "").lower() # Set verbose=True if HH_VERBOSE is true if verbose_env in ("true", "1", "yes", "on"): data["verbose"] = True super().__init__(**data)
[docs] @field_validator("api_key", mode="before") @classmethod def validate_api_key(cls, v: Any) -> Optional[str]: """Validate API key format with graceful degradation. Args: v: The API key value to validate Returns: The validated and normalized API key, or None if invalid """ validated = _safe_validate_string(v, "api_key", allow_none=True, default=None) if validated is not None: # Basic format validation - should start with 'hh_' for HoneyHive keys if not validated.startswith(("hh_", "sk-")): # Warning: not an error to maintain backwards compatibility logger.debug( "API key does not follow standard format (hh_* or sk_*): %s...", validated[:8], extra={ "honeyhive_data": { "api_key_prefix": validated[:3] if validated else None } }, ) return validated
[docs] @field_validator("project", mode="before") @classmethod def validate_project(cls, v: Any) -> Optional[str]: """Validate project name format with graceful degradation. Args: v: The project name to validate Returns: The validated and normalized project name, or None if invalid """ validated = _safe_validate_string(v, "project", allow_none=True, default=None) if validated is not None: # Basic validation - no special characters that could cause issues invalid_chars = ["/", "\\", "?", "#", "&"] if any(char in validated for char in invalid_chars): logger.warning( "Project name contains invalid characters. Using None.", extra={ "honeyhive_data": { "project": validated, "invalid_chars": invalid_chars, } }, ) return None return validated
[docs] @field_validator("test_mode", "verbose", mode="before") @classmethod def validate_boolean_fields(cls, v: Any) -> bool: """Validate boolean fields with graceful degradation. Args: v: The value to validate as boolean Returns: The validated boolean value, or False if invalid """ if v is None: return False if isinstance(v, bool): return v if isinstance(v, str): # Handle common boolean string representations lower_v = v.lower().strip() if lower_v in ("true", "1", "yes", "on", "enabled"): return True if lower_v in ("false", "0", "no", "off", "disabled", ""): return False # Invalid boolean string - log warning and return default logger.warning( "Invalid boolean value: %s. Using False as default.", v, extra={"honeyhive_data": {"invalid_boolean": v}}, ) return False # For non-string, non-bool types, log warning and return default logger.warning( "Invalid boolean type: %s. Using False as default.", type(v).__name__, extra={ "honeyhive_data": {"invalid_type": type(v).__name__, "value": str(v)} }, ) return False