Skip to content

Configuration

Python API reference for the tolomeo.config package.

load_config reads and validates the TOML configuration file into a Config; get_config returns the one loaded for the running process. For the file format and every key, see the Configuration Reference.


Loading

tolomeo.config.load_config(path=None)

Load a configuration, without touching the process-level cache.

Parameters:

Name Type Description Default
path str | Path | None

An explicit file to read. Outranks every other source. When omitted the precedence order applies: TOLOMEO_CONFIG, then the search paths, then the built-in defaults.

None

Returns:

Type Description
Config

The loaded configuration, or the built-in defaults when no file exists.

Raises:

Type Description
ConfigError

The file is unreadable or malformed, or its contents fail validation. Validation failures report every offending key at once.

Source code in tolomeo/config/loader.py
def load_config(path: str | Path | None = None) -> Config:
    """Load a configuration, without touching the process-level cache.

    Args:
        path: An explicit file to read. Outranks every other source. When
            omitted the precedence order applies: ``TOLOMEO_CONFIG``, then the
            search paths, then the built-in defaults.

    Returns:
        The loaded configuration, or the built-in defaults when no file exists.

    Raises:
        ConfigError: The file is unreadable or malformed, or its contents fail
            validation. Validation failures report every offending key at once.

    """
    resolved, source = _resolve_path(path)
    if resolved is None:
        config = Config()
        logger.info("No configuration file found; using built-in defaults")
        logger.info("Configuration in effect: %s", describe(config))
        return config

    raw = _read_toml(resolved)
    try:
        config = Config(**raw)
    except ValidationError as exc:
        raise ConfigError(str(exc), path=resolved) from exc

    logger.info("Configuration loaded from %s (%s)", resolved, source)
    logger.info("Configuration in effect: %s", describe(config))
    return config

tolomeo.config.search_paths()

Return the configuration search paths, highest precedence first.

Resolved on each call rather than at import, so the working directory and home directory are read when the configuration is actually loaded.

Returns:

Type Description
list[Path]

Candidate configuration file paths in precedence order.

Source code in tolomeo/config/loader.py
def search_paths() -> list[Path]:
    """Return the configuration search paths, highest precedence first.

    Resolved on each call rather than at import, so the working directory and
    home directory are read when the configuration is actually loaded.

    Returns:
        Candidate configuration file paths in precedence order.

    """
    return [
        Path.cwd() / CONFIG_FILENAME,
        Path.home() / ".config" / "tolomeo" / CONFIG_FILENAME,
        SYSTEM_CONFIG_DIR / CONFIG_FILENAME,
    ]

tolomeo.config.get_config() cached

Return the service configuration, loading it on first use.

One process runs one service with one configuration, so the file is read once and every component reads the same values thereafter. Point a service at its own file with the TOLOMEO_CONFIG environment variable.

Returns:

Type Description
Config

The service configuration, or the built-in defaults when no file exists.

Raises:

Type Description
ConfigError

A configuration file was found but cannot be used.

Source code in tolomeo/config/process.py
@lru_cache(maxsize=1)
def get_config() -> Config:
    """Return the service configuration, loading it on first use.

    One process runs one service with one configuration, so the file is read
    once and every component reads the same values thereafter. Point a service
    at its own file with the ``TOLOMEO_CONFIG`` environment variable.

    Returns:
        The service configuration, or the built-in defaults when no file exists.

    Raises:
        ConfigError: A configuration file was found but cannot be used.

    """
    return load_config()

tolomeo.config.reset_config()

Clear the cached configuration, so the next read reloads it.

Intended for tests; the suite resets between tests so that a configuration cannot leak from one test into another.

Source code in tolomeo/config/process.py
def reset_config() -> None:
    """Clear the cached configuration, so the next read reloads it.

    Intended for tests; the suite resets between tests so that a configuration
    cannot leak from one test into another.

    """
    get_config.cache_clear()

Models

tolomeo.config.Config

Bases: BaseModel

A fully loaded configuration.

extra="forbid" rejects unknown top-level sections, and applies inside nats and service too. It deliberately does not reach inside a plugin's table: a plugin validates its own table against the model it declares as Meta.config_model.

hide_input_in_errors=True drops the offending value from a validation error's rendering. This is the model whose validation pass constructs the ValidationError that load_config turns into a ConfigError, so it is the model this must be set on: the same setting on a nested model such as NATSConfig has no effect on the error that actually gets raised. Without it, a failed cross-field check such as _reject_multiple_authentication_styles would echo the raw, still-unwrapped [nats] table — including a plaintext password or token — into the message that ends up in a startup log.

Attributes:

Name Type Description
nats NATSConfig

Broker connection settings.

service ServiceConfig

Service-level settings.

plugins Dict[str, Dict[str, Any]]

Per-plugin configuration tables, keyed by plugin id. A plugin normally reads through PluginBase.options rather than this directly, so its table is validated against the model it declares.

Source code in tolomeo/config/models.py
class Config(BaseModel):
    """A fully loaded configuration.

    ``extra="forbid"`` rejects unknown top-level sections, and applies inside
    ``nats`` and ``service`` too. It deliberately does not reach inside a
    plugin's table: a plugin validates its own table against the model it
    declares as ``Meta.config_model``.

    ``hide_input_in_errors=True`` drops the offending value from a validation
    error's rendering. This is the model whose validation pass constructs the
    ``ValidationError`` that ``load_config`` turns into a ``ConfigError``, so it
    is the model this must be set on: the same setting on a nested model such as
    ``NATSConfig`` has no effect on the error that actually gets raised. Without
    it, a failed cross-field check such as
    ``_reject_multiple_authentication_styles`` would echo the raw, still-unwrapped
    ``[nats]`` table — including a plaintext password or token — into the message
    that ends up in a startup log.

    Attributes:
        nats: Broker connection settings.
        service: Service-level settings.
        plugins: Per-plugin configuration tables, keyed by plugin id. A plugin
            normally reads through ``PluginBase.options`` rather than this
            directly, so its table is validated against the model it declares.

    """

    model_config = ConfigDict(extra="forbid", frozen=True, hide_input_in_errors=True)

    nats: NATSConfig = Field(default_factory=NATSConfig)
    service: ServiceConfig = Field(default_factory=ServiceConfig)
    plugins: Dict[str, Dict[str, Any]] = Field(default_factory=dict)

    # --- Concrete Methods ---

    def plugin_config(self, plugin_id: str) -> Dict[str, Any]:
        """Return the configuration table declared for a plugin.

        The result is a copy, so a caller that mutates it cannot reconfigure
        later readers in the process.

        Args:
            plugin_id: The plugin's own id, as assigned during ``connect``.

        Returns:
            The plugin's configuration table, or an empty mapping when no table
            is declared for that id.

        """
        return copy.deepcopy(dict(self.plugins.get(plugin_id, {})))

plugin_config(plugin_id)

Return the configuration table declared for a plugin.

The result is a copy, so a caller that mutates it cannot reconfigure later readers in the process.

Parameters:

Name Type Description Default
plugin_id str

The plugin's own id, as assigned during connect.

required

Returns:

Type Description
Dict[str, Any]

The plugin's configuration table, or an empty mapping when no table

Dict[str, Any]

is declared for that id.

Source code in tolomeo/config/models.py
def plugin_config(self, plugin_id: str) -> Dict[str, Any]:
    """Return the configuration table declared for a plugin.

    The result is a copy, so a caller that mutates it cannot reconfigure
    later readers in the process.

    Args:
        plugin_id: The plugin's own id, as assigned during ``connect``.

    Returns:
        The plugin's configuration table, or an empty mapping when no table
        is declared for that id.

    """
    return copy.deepcopy(dict(self.plugins.get(plugin_id, {})))

tolomeo.config.NATSConfig

Bases: BaseModel

Broker connection settings.

Every attribute except url and reconnect_time_wait_s defaults to None meaning unset. An unset attribute is omitted from the connect call rather than passed as a value the framework guessed at, so the client library's own default applies.

Numeric attributes are strict: a boolean is rejected rather than coerced to an integer, which pydantic's default mode would otherwise do.

Attributes:

Name Type Description
url str

Broker to connect to.

user str | None

Username for user/password authentication.

password SecretStr | None

Password for user/password authentication. Wrapped so it cannot leak through repr or a traceback.

token SecretStr | None

Token for token authentication. Wrapped so it cannot leak through repr or a traceback.

credentials_file str | None

Path to an nkey/JWT credentials file.

tls_ca_file str | None

Path to a CA bundle, verified readable at load time.

tls_hostname str | None

Hostname the broker's certificate is expected to present.

reconnect_time_wait_s StrictInt | StrictFloat

Seconds to wait between reconnection attempts.

max_reconnect_attempts StrictInt | None

Retry ceiling. Unset means the client library's own default, which is 60 attempts rather than unlimited.

Source code in tolomeo/config/models.py
class NATSConfig(BaseModel):
    """Broker connection settings.

    Every attribute except ``url`` and ``reconnect_time_wait_s`` defaults to
    ``None`` meaning unset. An unset attribute is omitted from the connect call
    rather than passed as a value the framework guessed at, so the client
    library's own default applies.

    Numeric attributes are strict: a boolean is rejected rather than coerced to
    an integer, which pydantic's default mode would otherwise do.

    Attributes:
        url: Broker to connect to.
        user: Username for user/password authentication.
        password: Password for user/password authentication. Wrapped so it
            cannot leak through ``repr`` or a traceback.
        token: Token for token authentication. Wrapped so it cannot leak
            through ``repr`` or a traceback.
        credentials_file: Path to an nkey/JWT credentials file.
        tls_ca_file: Path to a CA bundle, verified readable at load time.
        tls_hostname: Hostname the broker's certificate is expected to present.
        reconnect_time_wait_s: Seconds to wait between reconnection attempts.
        max_reconnect_attempts: Retry ceiling. Unset means the client library's
            own default, which is 60 attempts rather than unlimited.

    """

    model_config = ConfigDict(extra="forbid", frozen=True)

    url: str = DEFAULT_NATS_URL
    user: str | None = None
    password: SecretStr | None = None
    token: SecretStr | None = None
    credentials_file: str | None = None
    tls_ca_file: str | None = None
    tls_hostname: str | None = None
    reconnect_time_wait_s: StrictInt | StrictFloat = DEFAULT_RECONNECT_TIME_WAIT_S
    max_reconnect_attempts: StrictInt | None = None

    # --- Validators ---

    @model_validator(mode="after")
    def _reject_multiple_authentication_styles(self) -> NATSConfig:
        """Reject more than one authentication style.

        Preferring one style over another silently would hide a deployment
        mistake, so a combination is refused rather than resolved.

        Returns:
            The validated settings.

        Raises:
            ValueError: More than one authentication style is configured.

        """
        styles = {
            "user/password": bool(self.user or self.password),
            "token": bool(self.token),
            "credentials_file": bool(self.credentials_file),
        }
        configured = sorted(name for name, present in styles.items() if present)
        if len(configured) > 1:
            raise ValueError(f"only one authentication style may be configured, found {' and '.join(configured)}")
        return self

    @model_validator(mode="after")
    def _verify_tls_ca_file(self) -> NATSConfig:
        """Verify the CA bundle is readable, so a bad one fails at startup.

        The message must name the CA file: callers rely on the path appearing in
        the error.

        Returns:
            The validated settings.

        Raises:
            ValueError: The CA file does not exist or could not be loaded.

        """
        if self.tls_ca_file is None:
            return self

        ca_path = Path(self.tls_ca_file)
        if not ca_path.is_file():
            raise ValueError(f"TLS CA file does not exist: {ca_path}")
        try:
            ssl.create_default_context().load_verify_locations(cafile=str(ca_path))
        except (OSError, ssl.SSLError) as exc:
            raise ValueError(f"TLS CA file could not be loaded: {ca_path}: {exc}") from exc
        return self

    # --- Properties ---

    @cached_property
    def tls_context(self) -> ssl.SSLContext | None:
        """SSL context built from ``tls_ca_file``, or ``None`` when TLS is unset.

        The client library takes a prepared context rather than a CA path. Built
        on first access and cached; the file was already verified readable at
        load time, so this cannot fail for a configuration that loaded.

        """
        if self.tls_ca_file is None:
            return None
        context = ssl.create_default_context()
        context.load_verify_locations(cafile=self.tls_ca_file)
        return context

tls_context cached property

SSL context built from tls_ca_file, or None when TLS is unset.

The client library takes a prepared context rather than a CA path. Built on first access and cached; the file was already verified readable at load time, so this cannot fail for a configuration that loaded.

tolomeo.config.ServiceConfig

Bases: BaseModel

Service-level settings.

Attributes:

Name Type Description
heartbeat_interval_ms StrictInt

Heartbeat cadence in milliseconds.

Source code in tolomeo/config/models.py
class ServiceConfig(BaseModel):
    """Service-level settings.

    Attributes:
        heartbeat_interval_ms: Heartbeat cadence in milliseconds.

    """

    model_config = ConfigDict(extra="forbid", frozen=True)

    heartbeat_interval_ms: StrictInt = DEFAULT_HEARTBEAT_INTERVAL_MS

Errors

tolomeo.config.ConfigError

Bases: Exception

Raised when a configuration cannot be used.

Always raised at load time, never mid-run, and names the offending file. Validation failures carry pydantic's report, which names every offending key at once.

Parameters:

Name Type Description Default
message str

Description of what is wrong.

required
path str | Path | None

Configuration file the problem was found in, if any.

None

Attributes:

Name Type Description
path

The offending file as a string, or None.

Source code in tolomeo/config/exceptions.py
class ConfigError(Exception):
    """Raised when a configuration cannot be used.

    Always raised at load time, never mid-run, and names the offending file.
    Validation failures carry pydantic's report, which names every offending
    key at once.

    Args:
        message: Description of what is wrong.
        path: Configuration file the problem was found in, if any.

    Attributes:
        path: The offending file as a string, or ``None``.

    """

    def __init__(self, message: str, *, path: str | Path | None = None) -> None:
        self.path = str(path) if path is not None else None
        super().__init__(f"{self.path}: {message}" if self.path is not None else message)