Advanced features

Field fallbacks

Sometimes it’s necessary for a config field to refer to other fields to find or construct its value, particularly as systems evolve over time. This is managed in a flexible way using FieldFallback objects.

from confmodel import Config
from confmodel.fields import ConfigText
from confmodel.fallbacks import SingleFieldFallback

class SimpleFallbackConfig(Config):
    """
    This config specification demonstrates the use of a SingleFieldFallback.
    """

    incantation = ConfigText(
        "The incantation to recite. (Falls back to the 'magic_word' field.)",
        required=True, fallbacks=[SingleFieldFallback("magic_word")])
    magic_word = ConfigText("*DEPRECATED* The magic word to utter.")

The above specification requires the incantation field, but of that’s not present the magic_word field will be used instead. Validation will fail if neither is present.

>>> SimpleFallbackConfig({u'incantation': u'foo'}).incantation
u'foo'
>>> SimpleFallbackConfig({u'magic_word': u'please'}).incantation
u'please'
>>> SimpleFallbackConfig({}).incantation
Traceback (most recent call last):
    ...
ConfigError: Missing required config field 'incantation'

A field used as a fallback is still a normal field in every way.

>>> print SimpleFallbackConfig({u'incantation': u'foo'}).magic_word
None
>>> SimpleFallbackConfig({u'magic_word': u'please'}).magic_word
u'please'

Multiple fallbacks

Multiple fallbacks may be used for a single field by listing then in order of preference.

>>> from confmodel import Config
>>> from confmodel.fields import ConfigText
>>> from confmodel.fallbacks import SingleFieldFallback
>>> class MultiFallbackConfig(Config):
...     """
...     This config specification demonstrates the use of multiple fallbacks.
...     """
...     incantation = ConfigText(
...         "The incantation to recite."
...         " (Falls back to the 'magic_word' and 'galdr' fields.)",
...         required=True, fallbacks=[
...             SingleFieldFallback("magic_word"),
...             SingleFieldFallback("galdr"),
...         ])
...     magic_word = ConfigText("*DEPRECATED* The magic word to utter.")
...     galdr = ConfigText("*DEPRECATED* Runes to chant.")

>>> MultiFallbackConfig({u'incantation': u'foo'}).incantation
u'foo'
>>> MultiFallbackConfig({u'magic_word': u'please'}).incantation
u'please'
>>> MultiFallbackConfig({u'galdr': u'heyri jotnar'}).incantation
u'heyri jotnar'
>>> MultiFallbackConfig({
...     u'magic_word': u'please',
...     u'galdr': u'heyri jotnar',
... }).incantation
u'please'
>>> MultiFallbackConfig({}).incantation
Traceback (most recent call last):
    ...
ConfigError: Missing required config field 'incantation'

Fallbacks with defaults

Default values for fallbacks are ignored [1] and the field’s default value is used as a last resort if no fallback values are found.

>>> from confmodel import Config
>>> from confmodel.fields import ConfigText
>>> from confmodel.fallbacks import SingleFieldFallback
>>> class FallbackDefaultsConfig(Config):
...     """
...     This config specification demonstrates fallbacks with defaults.
...     """
...     incantation = ConfigText(
...         "The incantation to recite. (Falls back to the 'magic_word' field.)",
...        default=u"xyzzy", fallbacks=[SingleFieldFallback("magic_word")])
...     magic_word = ConfigText(
...         "*DEPRECATED* The magic word to utter.", default=u"plugh")

>>> FallbackDefaultsConfig({u'incantation': u'foo'}).incantation
u'foo'
>>> FallbackDefaultsConfig({u'magic_word': u'please'}).incantation
u'please'
>>> FallbackDefaultsConfig({}).incantation
u'xyzzy'

Format string fallback

For more complex fallbacks, FormatStringFieldFallback can be used.

>>> from confmodel import Config
>>> from confmodel.fields import ConfigInt, ConfigText
>>> from confmodel.fallbacks import FormatStringFieldFallback
>>> class FormatFallbackConfig(Config):
...     """
...     This config specification demonstrates format string fallbacks.
...     """
...     url_base = ConfigText(
...         "A host:port pair.", required=True, fallbacks=[
...             FormatStringFieldFallback(u"{host}:{port}", ["host", "port"]),
...         ])
...     host = ConfigText("A hostname.")
...     port = ConfigInt("A network port.")

>>> FormatFallbackConfig({u'url_base': u'example.com:80'}).url_base
u'example.com:80'
>>> FormatFallbackConfig({u'host': u'example.org', u'port': 8080}).url_base
u'example.org:8080'
>>> FormatFallbackConfig({u'host': u'example.net'}).url_base
Traceback (most recent call last):
    ...
ConfigError: Missing required config field 'url_base'

Custom fallbacks

If your needs aren’t met by the standard fallback classes, you can subclass FieldFallback to implement custom behaviour.

TODO: Write something about custom fallback classes.

Static fields

TODO: Write something about static fields.

Footnotes

[1]Although custom FieldFallback subclasses may override this behaviour.