Skip to content

Settings

The settings module provides type-safe configuration management using Pydantic Settings.

Overview

Configuration is loaded from multiple sources in priority order: 1. Environment variables (highest priority) 2. ./config.toml (project directory) 3. ~/.config/imbi/config.toml (user directory) 4. /etc/imbi/config.toml (system directory) 5. Built-in defaults (lowest priority)

Loading Configuration

from imbi_common import settings

# Load full configuration
config = settings.load_config()

# Access individual settings sections
neo4j_config = settings.Neo4j()
clickhouse_config = settings.Clickhouse()
auth_config = settings.Auth()

API Reference

load_config

load_config() -> Configuration

Load configuration from config.toml files with environment overrides.

Checks for config files in priority order: 1. ./config.toml (project root) 2. ~/.config/imbi/config.toml (user config) 3. /etc/imbi/config.toml (system config)

Environment variables always override config file values.

Returns:

Type Description
Configuration

Configuration object with merged settings

Source code in src/imbi_common/settings.py
def load_config() -> Configuration:
    """Load configuration from config.toml files with environment overrides.

    Checks for config files in priority order:
    1. ./config.toml (project root)
    2. ~/.config/imbi/config.toml (user config)
    3. /etc/imbi/config.toml (system config)

    Environment variables always override config file values.

    Returns:
        Configuration object with merged settings

    """
    config_paths = [
        pathlib.Path.cwd() / 'config.toml',
        pathlib.Path.home() / '.config' / 'imbi' / 'config.toml',
        pathlib.Path('/etc/imbi/config.toml'),
    ]

    config_data: dict[str, typing.Any] = {}

    for config_path in config_paths:
        if config_path.exists():
            LOGGER.info('Loading configuration from %s', config_path)
            try:
                with config_path.open('rb') as f:
                    file_data = tomllib.load(f)
                    # Merge with priority to earlier files (first found wins)
                    for key, value in file_data.items():
                        if key not in config_data:
                            config_data[key] = value
                        elif isinstance(value, dict) and isinstance(
                            config_data[key], dict
                        ):
                            # Merge nested dicts
                            config_data[key] = {**value, **config_data[key]}
            except (tomllib.TOMLDecodeError, OSError) as e:
                LOGGER.warning('Failed to load %s: %s', config_path, e)
                continue

    return Configuration.model_validate(config_data)

get_auth_settings

get_auth_settings() -> Auth

Get the singleton Auth settings instance.

This ensures the JWT secret remains stable across requests when auto-generated (i.e., when IMBI_AUTH_JWT_SECRET is not set in env).

Returns:

Type Description
Auth

The singleton Auth settings instance.

Source code in src/imbi_common/settings.py
def get_auth_settings() -> Auth:
    """Get the singleton Auth settings instance.

    This ensures the JWT secret remains stable across requests when
    auto-generated (i.e., when IMBI_AUTH_JWT_SECRET is not set in env).

    Returns:
        The singleton Auth settings instance.

    """
    global _auth_settings
    if _auth_settings is None:
        _auth_settings = Auth()
    return _auth_settings

Configuration

Bases: BaseModel

Root configuration combining all shared settings sections.

Supports loading from config.toml files with environment variable overrides. Config files are checked in this priority order: 1. ./config.toml (project root) 2. ~/.config/imbi/config.toml (user config) 3. /etc/imbi/config.toml (system config)

Environment variables always take precedence over config file values.

Example config.toml: [server] environment = "production" host = "0.0.0.0" port = 8080

[neo4j]
url = "neo4j://neo4j-prod:7687"
user = "admin"

[auth]
jwt_secret = "your-secret-here"
access_token_expire_seconds = 7200

merge_env_with_config classmethod

merge_env_with_config(
    data: dict[str, Any],
) -> dict[str, typing.Any]

Merge environment variables with config file data.

For each BaseSettings submodel, instantiate it with the config file data as kwargs. This allows BaseSettings to use environment variables as defaults for any fields not provided in the config file.

Parameters:

Name Type Description Default
data dict[str, Any]

Raw config data from TOML file

required

Returns:

Type Description
dict[str, Any]

Config data with BaseSettings instances properly constructed

Source code in src/imbi_common/settings.py
@pydantic.model_validator(mode='before')
@classmethod
def merge_env_with_config(
    cls, data: dict[str, typing.Any]
) -> dict[str, typing.Any]:
    """Merge environment variables with config file data.

    For each BaseSettings submodel, instantiate it with the config file
    data as kwargs. This allows BaseSettings to use environment variables
    as defaults for any fields not provided in the config file.

    Args:
        data: Raw config data from TOML file

    Returns:
        Config data with BaseSettings instances properly constructed

    """
    settings_fields: dict[str, type[pydantic_settings.BaseSettings]] = {
        'clickhouse': Clickhouse,
        'neo4j': Neo4j,
        'server': ServerConfig,
        'auth': Auth,
    }
    for field, settings_cls in settings_fields.items():
        if field in data and data[field] is not None:
            # Skip if already an instance (e.g., from direct construction)
            if isinstance(data[field], settings_cls):
                continue
            data[field] = settings_cls(**data[field])
    return data

Neo4j

Bases: BaseSettings

extract_credentials_from_url

extract_credentials_from_url() -> Neo4j

Extract username/password from URL and strip them from the URL.

If the URL contains embedded credentials (e.g., neo4j://username:password@localhost:7687), extract them and set the user and password fields, then clean the URL.

Source code in src/imbi_common/settings.py
@pydantic.model_validator(mode='after')
def extract_credentials_from_url(self) -> 'Neo4j':
    """Extract username/password from URL and strip them from the URL.

    If the URL contains embedded credentials (e.g.,
    neo4j://username:password@localhost:7687), extract them and set
    the user and password fields, then clean the URL.

    """
    if self.url.username and not self.user:
        # Decode URL-encoded username
        self.user = parse.unquote(self.url.username)

    if self.url.password and not self.password:
        # Decode URL-encoded password
        self.password = parse.unquote(self.url.password)

    # Strip credentials from URL if present
    if self.url.username or self.url.password:
        # Rebuild URL without credentials
        scheme = self.url.scheme
        host = self.url.host or 'localhost'
        path = self.url.path or ''

        # Build URL - only include port if explicitly set
        url_parts = f'{scheme}://{host}'
        if self.url.port:
            url_parts += f':{self.url.port}'
        if path:
            url_parts += path

        self.url = pydantic.AnyUrl(url_parts)

    return self

Clickhouse

Bases: BaseSettings

ServerConfig

Bases: BaseSettings

Auth

Bases: BaseSettings

Authentication settings shared across Imbi services.

Contains only JWT and encryption configuration needed by any service that verifies tokens or handles encrypted data.

generate_encryption_key_if_missing

generate_encryption_key_if_missing() -> Auth

Generate encryption key if not provided (Phase 5).

Auto-generates a Fernet encryption key if IMBI_AUTH_ENCRYPTION_KEY is not set in the environment. Logs a warning since this key should be stable across restarts in production.

Source code in src/imbi_common/settings.py
@pydantic.model_validator(mode='after')
def generate_encryption_key_if_missing(self) -> 'Auth':
    """Generate encryption key if not provided (Phase 5).

    Auto-generates a Fernet encryption key if IMBI_AUTH_ENCRYPTION_KEY
    is not set in the environment. Logs a warning since this key should
    be stable across restarts in production.
    """
    if self.encryption_key is None:
        from cryptography import fernet

        self.encryption_key = fernet.Fernet.generate_key().decode('ascii')
        import logging

        logger = logging.getLogger(__name__)
        logger.warning(
            'Encryption key auto-generated. Set IMBI_AUTH_ENCRYPTION_KEY '
            'in production for stable key across restarts.'
        )
    return self