lightbulb.config

This module contains utilities for parsing your application configuration from various file types. Along with basic deserializing to Python objects, it implements environment variable substitution expressions to allow “dynamic” values within the configuration.

These utilities were originally implemented within lightbulb, but since have been extracted and are now provided by the confspec library (PyPI).

Environment Variable Substitution

The basic syntax for defining a variable substitution is as follows:

${VARIABLE_NAME}

This would be replaced by the loader with the value of the environment variable VARIABLE_NAME while parsing the configuration. If the variable is not set, then this will evaluate to an empty string.

Expanding on this, you can also specify default values for when a variable is not set:

${VARIABLE_NAME:default-value}

This will expand to the value of VARIABLE_NAME, if it is set, otherwise it will expand to whatever you specified as the default value.

Sometimes you may want to include text that uses the ${NAME} format directly, without any substitution. This can be achieved by escaping the substitution expression with an additional $ symbol:

$${VARIABLE_NAME}

This will not be expanded using the environment variables, and instead one of the leading $ symbols will be stripped, causing it to evaluate as ${VARIABLE_NAME}.

confspec provides additional substitution features, you should consult its documentation for further details.

Usage

confspec supports parsing configuration into a variety of formats - standard dictionaries, msgspec Structs, or pydantic models. The below example will use msgspec Structs, but you can choose whichever you are most comfortable with. Just ensure the correct dependencies are installed first.

You should create a configuration file for your application - supported formats are yaml, toml and json - and a msgspec.Struct-derived class that your configuration will be deserialized to.

config.yaml

foo: bar
baz: ${INT_VAR}
bork:
  qux: quux

config.py

class BorkConfig(msgspec.Struct):
    qux: str

class Config(msgspec.Struct):
    foo: str
    baz: int
    bork: BorkConfig

Then to parse the config file into your models, simply call the load() method:

import lightbulb
# your configuration models
from app import config

parsed_cfg = lightbulb.config.load("path/to/config.yaml", cls=config.Config)
# prints "bar"
print(parsed_cfg.foo)
load(path: str | Path, /, *, cls: type[pydantic.BaseModel | Struct] | None = None, strict: bool = False, dec_hook: Callable[[type[t.Any], t.Any], t.Any] | None = None) dict[str, t.Any] | pydantic.BaseModel | Struct[source]

Loads arbitrary configuration from the given path, performing environment variable substitutions, and parses it into the given class (or to a dictionary if no class was provided).

Currently supported formats are: yaml, toml and JSON. Additional formats can be supported by creating your own custom implementation of Parser and registering it with parser_registry.

Parameters:
  • path – The path to the configuration file.

  • cls – The pydantic BaseModel, or msgspec Struct to parse the configuration into. If None, the configuration will be parsed into a dictionary. Defaults to None.

  • strict – Whether the parsing behaviour of pydantic/msgspec should be in strict mode. Defaults to False. If True, then parsers will not perform type coercion (e.g. digit string to int).

  • dec_hook – Optional decode hook for msgspec to use when parsing to allow supporting additional types.

Returns:

The parsed configuration.

Raises:
  • NotImplementedError – If a file with an unrecognized format is specified.

  • ValueError – If the file cannot be parsed to a dictionary (e.g. the top level object is an array).

  • ImportError – If a required dependency is not installed.

loads(raw: str | bytes, fmt: KnownFormats | str, /, *, cls: type[pydantic.BaseModel | Struct] | None = None, strict: bool = False, dec_hook: Callable[[type[t.Any], t.Any], t.Any] | None = None) dict[str, t.Any] | pydantic.BaseModel | Struct[source]

Like load(), but loads the configuration from the given string or bytes object instead. You must pass a format when using this method so that the library knows which parser to use. All other arguments have the same meaning as in load().