Skip to content

Nested Configuration Guide

1. Basic Nested Configurations of configurations

This guide explains how to work with nested configurations in SuperConf, which is useful for organizing complex configuration structures.

The most common way to create nested configurations is by defining separate configuration classes:

from superconf.configuration import Configuration
from superconf.fields import FieldString, FieldInt, FieldBool

class DatabaseConfig(Configuration):
    host = FieldString(default="localhost", help="Database host")
    port = FieldInt(default=5432, help="Database port")
    username = FieldString(help="Database username")
    password = FieldString(help="Database password")

class RedisConfig(Configuration):
    host = FieldString(default="localhost", help="Redis host")
    port = FieldInt(default=6379, help="Redis port")
    db = FieldInt(default=0, help="Redis database number")

class AppConfig(Configuration):
    debug = FieldBool(default=False, help="Enable debug mode")
    database = DatabaseConfig
    redis = RedisConfig

# Example 1: Using default values
config = AppConfig()
print(config.debug)              # False (default value)
print(config.database.host)      # "localhost" (default value)
print(config.redis.port)         # 6379 (default value)
print(config.database.username)  # None (no default value)
print(config.is_set('database.username'))  # False

# Example 2: Overriding some values
config = AppConfig(values={
    'debug': True,
    'database': {
        'host': 'db.example.com',
        'username': 'admin'
    }
})
print(config.debug)              # True (overridden)
print(config.database.host)      # "db.example.com" (overridden)
print(config.database.port)      # 5432 (still default)
print(config.database.username)  # "admin" (set value)
print(config.redis.host)         # "localhost" (default value)

2. Deep Nesting

You can nest configurations as deeply as needed:

class LoggingConfig(Configuration):
    level = FieldString(default="INFO")
    format = FieldString(default="%(levelname)s: %(message)s")

class ServiceConfig(Configuration):
    url = FieldString(help="Service URL")
    timeout = FieldInt(default=30)
    logging = LoggingConfig

class AppConfig(Configuration):
    name = FieldString(default="myapp")
    service_a = ServiceConfig
    service_b = ServiceConfig

Accessing Nested Values:

config = AppConfig()
print(config.service_a.logging.level)  # "INFO"
print(config.is_set('service_a.url'))  # False (required field not set)

4. Dynamic Field Configurations

For cases where you need to handle configurations with unknown fields or dynamic structures, SuperConf provides the FieldConf field type with the children_class parameter.

from superconf.configuration import Configuration
from superconf.fields import FieldString, FieldInt, FieldConf

class ServiceConfig(Configuration):
    url = FieldString()
    timeout = FieldInt(default=30)

class DynamicConfig(Configuration):
    # Will accept any number of services as key-value pairs
    services_as_dict = FieldDict(children_class=ServiceConfig)
    services_as_list = FieldList(children_class=ServiceConfig)

# Usage example
config = DynamicConfig(values={
    'services_as_dict': {
        'auth': {'url': 'https://auth.example.com', 'timeout': 60},
        'api': {'url': 'https://api.example.com'},
        'cache': {'url': 'https://cache.example.com', 'timeout': 15}
    },
    'services_as_list': [
        {'url': 'https://auth.example.com', 'timeout': 60},
        {'url': 'https://api.example.com'},
        {'url': 'https://cache.example.com', 'timeout': 15}
    ]
})

print(config.services_as_dict['auth'].timeout)  # 60
print(config.services_as_dict['api'].timeout)   # 30 (default value)
print(config.services_as_dict['cache'].url)     # "https://cache.example.com"

print(config.services_as_list[0].timeout)  # 60
print(config.services_as_list[1].timeout)   # 30 (default value)
print(config.services_as_list[2].url)     # "https://cache.example.com"

5. Dynamic Field Configurations with custom classes

Dictionary of Configurations

To create a dictionary of configuration objects with dynamic keys:

from superconf.configuration import Configuration
from superconf.fields import FieldString, FieldInt, FieldConf

class ServiceConfig(Configuration):
    url = FieldString()
    timeout = FieldInt(default=30)

class DynamicConfig(Configuration):
    # Will accept any number of services as key-value pairs
    services = FieldConf(children_class=ServiceConfig)

# Usage example
config = DynamicConfig(values={
    'services': {
        'auth': {'url': 'https://auth.example.com', 'timeout': 60},
        'api': {'url': 'https://api.example.com'},
        'cache': {'url': 'https://cache.example.com', 'timeout': 15}
    }
})

print(config.services['auth'].timeout)  # 60
print(config.services['api'].timeout)   # 30 (default value)
print(config.services['cache'].url)     # "https://cache.example.com"

List of Configurations

For handling lists of configuration objects:

from superconf.configuration import Configuration
from superconf.fields import FieldString, FieldInt, FieldBool, FieldConf

class EndpointConfig(Configuration):
    host = FieldString()
    port = FieldInt(default=80)
    secure = FieldBool(default=False)

class LoadBalancerConfig(ConfigurationList):
    # Will accept a list of endpoints
    endpoints = FieldConf(children_class=EndpointConfig)

# Usage example
config = LoadBalancerConfig(values=[
        {'host': 'server1.example.com', 'port': 443, 'secure': True},
        {'host': 'server2.example.com'},
        {'host': 'server3.example.com', 'port': 8080}
    ]
)

print(config.endpoints[0].secure)  # True
print(config.endpoints[1].port)    # 80 (default value)
print(config.endpoints[2].host)    # "server3.example.com"

The FieldConf with children_class maintains all the benefits of SuperConf's type checking, validation, and environment variable support while providing flexibility for dynamic configurations. Use is_list=True when you need a list of configurations, and omit it for dictionary-style configurations.