Customizing Converters

When working with attrs or dataclasses, you can choose between two converters: the built-in TSConverter and a cattrs.Converter.

The built-in converter allows you to use Typed Settings with fewer (or even no) dependencies but it cannot be customized easily.

However, customization is one of cattrs’ many strengths. This section provides only a few recipes, so you may want to read cattrs’ full Customizing (Un-)structuring docs.

Enums

Typed Settings structures enum.Enum instances by name and enum.StrEnum as well as enum.IntEnum instances by value.

If you don’t like this behavior, you can reconfigure the converter with the help of typed_settings.converters.to_enum_by_name() and typed_settings.converters.to_enum_by_value():

example.py
import enum
import functools

import typed_settings as ts


class LeEnum(enum.Enum):
    spam = "Le spam"
    eggs = "Le eggs"


@ts.settings
class Settings:
    option: LeEnum


converter = ts.converters.get_default_cattrs_converter()
converter.register_structure_hook(LeEnum, ts.converters.to_enum_by_value)

settings = ts.load_settings(
    Settings,
    ts.default_loaders("myapp"),
    converter=converter
)
print(settings)
$ MYAPP_OPTION="Le spam" python example.py
Settings(option=<LeEnum.spam: 'Le spam'>)

Why different behaviors?

  • Sqlalchemy (de)structures enums by name when used as column type.

  • Pydantic and Cattrs (de)structure enums by value – probably because it is more human readable?

  • The first versions of Typed Settings only supported enum.Enum.

  • It used “by name” because I deemed it safer / less error prone, especially when used as command line options (e.g., via click.Choice).

  • Support for enum.StrEnum and enum.IntEnum was added in 25.0.

  • I decided to follow the example of Cattrs/Pydantic and structure them by value. This also makes sense because they inherit str/int and their value is what you get when you call str/int on them.

  • I didn’t change the behavior for enum.Enum for backwards compatibility.