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
postgres_config = settings.Postgres()
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 libraries/common/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

    """
    return Configuration.model_validate(load_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 libraries/common/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

get_config_settings

get_config_settings() -> ConfigSecrets

Get the singleton ConfigSecrets settings instance.

This ensures the config encryption key remains stable across requests when auto-generated (i.e., when IMBI_CONFIG_ENCRYPTION_KEY is not set in env).

Returns:

Type Description
ConfigSecrets

The singleton ConfigSecrets settings instance.

Source code in libraries/common/src/imbi/common/settings.py
def get_config_settings() -> ConfigSecrets:
    """Get the singleton ConfigSecrets settings instance.

    This ensures the config encryption key remains stable across requests
    when auto-generated (i.e., when IMBI_CONFIG_ENCRYPTION_KEY is not set in
    env).

    Returns:
        The singleton ConfigSecrets settings instance.

    """
    global _config_settings
    if _config_settings is None:
        _config_settings = ConfigSecrets()
    return _config_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: [postgres] url = "postgresql://postgres:secret@db-prod:5432/imbi"

[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 libraries/common/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]] = {
        'auth': Auth,
        'clickhouse': Clickhouse,
        'embeddings': Embeddings,
        'plugins': Plugins,
        'postgres': Postgres,
        'releases': Releases,
        'ssl': SSL,
        'valkey': Valkey,
    }
    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

Postgres

Bases: BaseSettings

PostgreSQL connection settings.

Clickhouse

Bases: BaseSettings

cluster_name class-attribute instance-attribute

cluster_name: str | None = None

Name of the ClickHouse cluster (CLICKHOUSE_CLUSTER_NAME).

When set, setup_schema() injects ON CLUSTER <cluster_name> into the CREATE DDL statements loaded from schemata.toml so the schema is created across every node of the cluster. Leave unset for a single-node deployment.

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.

validate_secrets

validate_secrets() -> Auth

Enforce explicit secrets outside development; auto-generate in dev.

When ENVIRONMENT is anything other than development (the default), both IMBI_AUTH_JWT_SECRET and IMBI_AUTH_ENCRYPTION_KEY must be configured explicitly: booting with auto-generated per-process values would silently invalidate every issued token (and make every encrypted value undecryptable) on the next restart, so it is refused. In development the historical auto-generation behavior is preserved.

model_fields_set distinguishes an explicitly-supplied value (env var, config file, or kwarg) from a default, and is inspected before the dev-mode auto-generation below mutates it.

Source code in libraries/common/src/imbi/common/settings.py
@pydantic.model_validator(mode='after')
def validate_secrets(self) -> Auth:
    """Enforce explicit secrets outside development; auto-generate in dev.

    When ``ENVIRONMENT`` is anything other than ``development`` (the
    default), both ``IMBI_AUTH_JWT_SECRET`` and
    ``IMBI_AUTH_ENCRYPTION_KEY`` must be configured explicitly: booting
    with auto-generated per-process values would silently invalidate
    every issued token (and make every encrypted value undecryptable)
    on the next restart, so it is refused. In development the historical
    auto-generation behavior is preserved.

    ``model_fields_set`` distinguishes an explicitly-supplied value
    (env var, config file, or kwarg) from a default, and is inspected
    before the dev-mode auto-generation below mutates it.
    """
    if os.getenv('ENVIRONMENT', 'development').lower() != 'development':
        missing = sorted(
            name
            for name in ('jwt_secret', 'encryption_key')
            if name not in self.model_fields_set
        )
        if missing:
            raise ValueError(
                'These IMBI_AUTH_ settings must be set explicitly when '
                'ENVIRONMENT is not "development": ' + ', '.join(missing)
            )

    if self.encryption_key is None:
        from cryptography import fernet

        self.encryption_key = fernet.Fernet.generate_key().decode('ascii')
        LOGGER.warning(
            'Encryption key auto-generated. Set IMBI_AUTH_ENCRYPTION_KEY '
            'in production for stable key across restarts.'
        )
    return self

ConfigSecrets

Bases: BaseSettings

Encryption settings for persisted configuration secrets.

Provides a dedicated Fernet key, separate from :class:Auth, used to encrypt sensitive configuration values (e.g. external MCP server credentials) at rest. Keeping it distinct from the JWT/token encryption key lets the two be rotated independently.

validate_secrets

validate_secrets() -> ConfigSecrets

Enforce an explicit key outside development; auto-generate in dev.

When ENVIRONMENT is anything other than development (the default), IMBI_CONFIG_ENCRYPTION_KEY must be configured explicitly: booting with an auto-generated per-process value would silently make every encrypted configuration value undecryptable on the next restart, so it is refused. In development the historical auto-generation behavior is preserved.

model_fields_set distinguishes an explicitly-supplied value (env var, config file, or kwarg) from a default, and is inspected before the dev-mode auto-generation below mutates it.

Source code in libraries/common/src/imbi/common/settings.py
@pydantic.model_validator(mode='after')
def validate_secrets(self) -> ConfigSecrets:
    """Enforce an explicit key outside development; auto-generate in dev.

    When ``ENVIRONMENT`` is anything other than ``development`` (the
    default), ``IMBI_CONFIG_ENCRYPTION_KEY`` must be configured
    explicitly: booting with an auto-generated per-process value would
    silently make every encrypted configuration value undecryptable on
    the next restart, so it is refused. In development the historical
    auto-generation behavior is preserved.

    ``model_fields_set`` distinguishes an explicitly-supplied value
    (env var, config file, or kwarg) from a default, and is inspected
    before the dev-mode auto-generation below mutates it.
    """
    if os.getenv('ENVIRONMENT', 'development').lower() != 'development':
        if 'encryption_key' not in self.model_fields_set:
            raise ValueError(
                'IMBI_CONFIG_ENCRYPTION_KEY must be set explicitly when '
                'ENVIRONMENT is not "development"'
            )

    if self.encryption_key is None:
        from cryptography import fernet

        self.encryption_key = fernet.Fernet.generate_key().decode('ascii')
        LOGGER.warning(
            'Config encryption key auto-generated. Set '
            'IMBI_CONFIG_ENCRYPTION_KEY in production for a stable key '
            'across restarts.'
        )
    return self

SSL

Bases: BaseSettings

SSL configuration settings.

configure

configure() -> None

Configure the default SSL context if cert_dir is set.

Patches ssl.create_default_context and ssl._create_default_https_context to load CA certificates from cert_dir on every call.

Source code in libraries/common/src/imbi/common/settings.py
def configure(self) -> None:
    """Configure the default SSL context if cert_dir is set.

    Patches ssl.create_default_context and
    ssl._create_default_https_context to load CA certificates from
    cert_dir on every call.
    """
    if self.cert_dir is None:
        return
    import ssl

    cert_dir = str(self.cert_dir)
    _orig = ssl.create_default_context

    def _patched(*args: object, **kwargs: object) -> ssl.SSLContext:
        ctx = _orig(*args, **kwargs)  # type: ignore[arg-type]
        ctx.load_verify_locations(capath=cert_dir)
        return ctx

    ssl.create_default_context = _patched
    ssl._create_default_https_context = _patched