Skip to content

Configuration Reference

Py ToloMEO reads deployment configuration from a single TOML file, tolomeo.toml, loaded once per process. Every key is optional; with no file, the defaults below apply.

Precedence

The first source that exists wins:

Order Source
1 An explicit path given to the loader
2 The TOLOMEO_CONFIG environment variable
3 ./tolomeo.toml
4 ~/.config/tolomeo/tolomeo.toml
5 /etc/tolomeo/tolomeo.toml
6 Built-in defaults

A missing file is not an error. A path named explicitly — as an argument or through TOLOMEO_CONFIG — that does not exist is an error.

[nats]

Key Type Default Meaning
url string nats://localhost:4222 Broker to connect to
user string unset Username for user/password authentication
password string unset Password for user/password authentication
token string unset Token authentication
credentials_file string unset Path to an nkey/JWT credentials file
tls_ca_file string unset Path to a CA bundle
tls_hostname string unset Expected certificate hostname
reconnect_time_wait_s number 2 Seconds between reconnection attempts
max_reconnect_attempts integer unset Retry ceiling; unset means the client library's own default of 60

[service]

Key Type Default Meaning
heartbeat_interval_ms integer 10000 Heartbeat cadence in milliseconds

[plugins.<plugin_id>]

Keyed by the plugin's own id — the value it assigns to self.id during connect. A plugin with no declared table reads an empty mapping.

[plugins.thermostat]
endpoint = "http://localhost:9000"
samples = 4

Valid keys are the plugin's own business, declared as a Pydantic model in its Meta.config_model:

class ThermostatOptions(BaseModel):
    model_config = ConfigDict(extra="forbid", frozen=True, hide_input_in_errors=True)

    endpoint: str = "http://localhost:9000"
    samples: PositiveInt = 4


class ThermostatPlugin(SensorPlugin):
    class Meta:
        config_model = ThermostatOptions

The plugin then reads self.options.samples instead of parsing a dict.

A plugin id that is not a bare word must be quoted

Unquoted TOML keys allow only letters, digits, underscores and dashes. A BLE plugin's id is the device address, so its table name has to be quoted:

[plugins."AA:BB:CC:DD:EE:FF"]
notify_interval_s = 2.0

Validation happens in two stages

[nats] and [service] are validated when the file loads. Plugin tables are validated against their config_model at attach time, which setup forces eagerly:

ConfigError: invalid [plugins.thermostat] table: 1 validation error for ThermostatOptions
samples_count
  Extra inputs are not permitted

A plugin that declares no config_model is not validated; reading self.options on one is a programming error. It can still read the raw table through self.plugin_config.

Options extend, they do not replace

A plugin deriving from another inherits its model and adds options by extending it:

class SWUpdateOptions(OTAOptions):
    socket_path: Path = Path("/run/swupdate.sock")


class SWUpdatePlugin(OTAPlugin):
    class Meta:
        config_model = SWUpdateOptions

An unrelated model raises TypeError at class-definition time: base methods read fields the base declared, so replacing the model would break them.

[plugins.<id>] keys of the shipped plugins

OTAPlugin, and therefore anything deriving from it:

Key Type Default Meaning
download_dir string /data/tolomeo Directory the update package goes to
download_filename string ota.pkg Name the package is saved under
checkpoint_file string /data/tolomeo/ota-service.yml Where the OTA state snapshot is kept

/data/tolomeo when /data exists, otherwise the same path under the system temp directory. checkpoint_file's default is pinned to that default download directory, not to a configured download_dir — setting download_dir alone does not move the checkpoint.

SensorPlugin and PluginBase declare no options. The examples/ directory ships a commented .toml file per service.

Semantics

Unset means "not passed"

An omitted key is left out of the client library's connect call entirely, rather than passed as the value the framework believes the library defaults to. A library upgrade that changes a default is therefore picked up, not overridden.

The retry ceiling is not unlimited

The client library's max_reconnect_attempts default is 60 attempts, not unlimited. Leaving the key unset preserves that; it does not mean "retry forever".

The one exception is reconnect_time_wait_s, always passed explicitly as 2.

One authentication style at a time

user/password, token and credentials_file are mutually exclusive. Configuring more than one is a configuration error.

TLS is verified at load time

The client library takes a prepared SSL context, not a CA path. The bundle named by tls_ca_file is verified at load and the context prepared on first connect, so an unreadable or invalid CA file fails at startup, not mid-run.

Errors surface at startup

A malformed file, an unknown section, an unknown key, or a wrongly typed value in [nats] or [service] raises at load, naming the file and every offending key — all mistakes at once, not one per restart. Plugin tables are checked at attach; see Validation happens in two stages.

Numeric keys are strict: heartbeat_interval_ms = true is an error, not the integer 1.

What gets logged

At INFO, loading reports the file read and its source — an explicit path, TOLOMEO_CONFIG, or the search path — or that no file was found. A second line summarises what is in effect; connecting reports the broker and whether any setting came from an explicit argument:

INFO  tolomeo.config.loader: Configuration loaded from /etc/tolomeo/tolomeo.toml (TOLOMEO_CONFIG)
INFO  tolomeo.config.loader: Configuration in effect: broker=nats://broker.prod:4222 auth=user/password
      tls=CA /etc/nats/ca.pem reconnect_wait=2s max_reconnect=10 heartbeat=5000ms plugins=[thermostat]
INFO  my_service: Connecting to NATS at nats://broker.prod:4222: auth=user/password tls=on ...
INFO  my_service: Connected to NATS server at broker.prod:4222

Secrets are never logged

Logs name the authentication style, never its value, and list plugin ids, never a table's contents — plugin options may hold secrets. Search paths are logged at DEBUG.

A plugin logs its id and the configured keys, never model or class names, field names, or values. An empty table logs [].

INFO  my_plugin: Options set for 'thermostat': ['endpoint', 'samples']

An options model must set hide_input_in_errors=True, as the shipped models do; without it a validation failure prints the offending value into the ConfigError that reaches the startup log.

Python API

from tolomeo.config import get_config, load_config, reset_config
Function Purpose
get_config() The service configuration, loaded on first use and cached thereafter
load_config(path=None) Load a configuration without touching the cache
reset_config() Clear the cache so the next read reloads; intended for tests

Components read through get_config(), and each accepts an explicit override so a test can inject instead:

from tolomeo.config import Config, NATSConfig
from tolomeo.messaging.client import NATSClientManager

manager = NATSClientManager(logger, url="nats://explicit:4222")
manager = NATSClientManager(logger, config=Config(nats=NATSConfig(url="nats://injected:4222")))

Pointing a service at its own file

Set TOLOMEO_CONFIG in the service's environment. One process runs one service with one configuration.

A note on scope

This is deployment configuration: read-only, loaded once, never mutated at runtime. Distinct from ConfigManagerABC in tolomeo.services.configuration, a mutable manager for state a service persists at runtime — the BLE device list, for instance, which lives in its own .devices.yml. Override new_config_manager to point it elsewhere, as the ble_mock example does.

See also