Object Instantiation

The configuration system supports constructing objects and calling factory functions to generate the values returned by specific configuration paths. This can be used to retrieve fully-configured instances of classes and functions from the configuration that are immediately ready to be used.

Note: At present, return values are not cached, so multiple calls to config.get or resolving multiple expressions within a file may return different instances. This behavior should not, however, be relied upon, as caching may be introduced as the default behavior in the future.

Object Construction

The configuration can construct attrs classes, dataclasses and Pydantic models.

To construct an object, add the _target_ key to a mapping whose value is the fully-qualified class name. The remaining keys in that mapping will be set as attributes on the constructed object with values coerced to match the type of each attribute. Attributes too complex to be coerced can use a nested mapping with its own _target_ key. All nested values will be resolved before constructing the parent value.

For instance, database returns an instantiated DatabaseConfig object via this configuration:

database:
  _target_: my_package.db.DatabaseConfig
  url: ${env:DATABASE_URL}
  pool_size: 20
db = config.get("database")   # a DatabaseConfig instance

In this case, all of the keys except _target_ are set on the instantiated DatabaseConfig object. Only attributes you want configurable need to be specified; all others will use their default values.

Factory Functions

The configuration can also call factory functions to resolve the value for a configuration path.

To resolve a value via a factory function, add the _target_ key to a mapping whose value is the fully-qualified import name of the function. The remaining keys in that mapping will be passed as keyword parameters to the function with values coerced to match the type of each parameter. Parameters too complex to be coerced can use a nested mapping with its own _target_ key. All nested values will be resolved before the factory function is called.

For instance, the cache path is defined to return a configured cache when config.get("cache") is called:

cache:
  _target_: my_package.cache.build_cache
  backend: ${services.backend}
  database:
    url: ${env:DATABASE_URL}
    pool_size: 20
  ttl: 3600

In this case, build_cache is called with three named parameters:

  • backend, whose value is resolved from services.backend elsewhere in the config.
  • database, whose mapping will be coerced into a DatabaseConfig instance with its url set to the environment variable DATABASE_URL and its pool_size set to 20.
  • ttl, whose value will be set to the integer 3600.

If build_cache takes a parameter whose type is Config, it will also be passed, even though it doesn’t appear in the YAML above; see Config Injection below. The value returned will be whatever the function produces.

Config Injection

Factory functions and classes frequently need access to the configuration itself, e.g., for a factory that reads other settings from the config. Because referring to the configuration within the configuration itself can cause issues when fingerprinting or exporting the configuration, any _target_ parameter annotated with the Config type is automatically injected with the active configuration, so it does not need to be (and generally should not be) set in the YAML.

For instance, if the build_cache factory declares a config: Config parameter:

from strataconf import Config

def build_cache(config: Config, backend: str, database: DatabaseConfig, ttl: int):
    ...

Nothing sets config in the YAML below; the config system supplies it when the factory is instantiated:

cache:
  _target_: my_package.cache.build_cache
  backend: redis
  database: ${database}
  ttl: 3600

An explicitly-provided value always takes precedence over injection, so you can still pass a specific Config in the rare case you need to. Because injected parameters are not written in the YAML, they are also excluded from the configuration fingerprint, which avoids circular reference issues when generating hashes of configuration values.