Skip to content

API Reference

The main function

as_dict_rec.as_dict_rec(obj, *, skip_null=False, overrides=None)

Reduce a complex Python structure, e.g. a dataclass, to basic types.

Useful for e.g. serializing to JSON, TOML, etc.

Source code in src/as_dict_rec/impl.py
def as_dict_rec(  # noqa: C901,PLR0911  # we handle many cases
    obj: Any,  # noqa: ANN401  # it could really be anything
    *,
    skip_null: bool = False,
    overrides: Overrides | None = None,
) -> Any:  # noqa: ANN401  # ...and we can turn it into anything
    """Reduce a complex Python structure, e.g. a dataclass, to basic types.

    Useful for e.g. serializing to JSON, TOML, etc.
    """

    def recurse(value: Any) -> Any:  # noqa: ANN401  # it could still be anything
        """Recurse with the same parameters."""
        return as_dict_rec(value, skip_null=skip_null, overrides=overrides)

    def nonnull(values: Iterable[Any]) -> Iterator[Any]:
        """Only return null values if they are not specifically forbidden."""
        for value in values:
            if value is not None or not skip_null:
                yield value

    if overrides is None:
        overrides = Overrides()

    for base_type, handler in overrides.as_dict_handlers.items():
        if isinstance(obj, base_type):
            return handler(obj)

    if isinstance(obj, overrides.basic_types):
        return obj

    if isinstance(obj, dict):
        return {
            name: recurse(value)
            for name, value in obj.items()
            if value is not None or not skip_null
        }

    if dataclasses.is_dataclass(obj) and not isinstance(obj, type):
        return recurse(dataclasses.asdict(obj))

    if isinstance(obj, overrides.list_types):
        return [recurse(value) for value in nonnull(obj)]

    if isinstance(obj, overrides.sorted_list_types):
        return recurse(sorted(nonnull(obj)))

    if isinstance(obj, overrides.str_types):
        return str(obj)

    # This is a weird fallback, but it works for some cases.
    obj_dict = getattr(obj, "__dict__", None)
    if obj_dict is not None:
        if obj_dict is dict:
            return recurse(obj_dict)

        return recurse(dict(obj_dict))

    raise NotImplementedError(repr(obj))

as_dict_rec.Overrides dataclass

Override some of the default handlers.

Source code in src/as_dict_rec/impl.py
@dataclasses.dataclass(frozen=True)
class Overrides:
    """Override some of the default handlers."""

    as_dict_handlers: dict[type[Any], Callable[[Any], Any]] = dataclasses.field(
        default_factory=lambda: AS_DICT_HANDLERS,
    )
    """The types that we handle in special ways before anything else."""

    basic_types: tuple[type, ...] = dataclasses.field(default_factory=lambda: BASIC_TYPES)
    """The types that can usually be serialized directly."""

    list_types: tuple[type[Iterable[Any]], ...] = dataclasses.field(
        default_factory=lambda: LIST_TYPES,
    )
    """The types that we represent as a list."""

    sorted_list_types: tuple[type[Iterable[Any]], ...] = dataclasses.field(
        default_factory=lambda: SORTED_LIST_TYPES,
    )
    """The types that we represent as a list, but we sort it."""

    str_types: tuple[type[Any], ...] = dataclasses.field(default_factory=lambda: STR_TYPES)
    """The types that we stringify."""

as_dict_handlers = dataclasses.field(default_factory=(lambda: AS_DICT_HANDLERS)) class-attribute instance-attribute

The types that we handle in special ways before anything else.

basic_types = dataclasses.field(default_factory=(lambda: BASIC_TYPES)) class-attribute instance-attribute

The types that can usually be serialized directly.

list_types = dataclasses.field(default_factory=(lambda: LIST_TYPES)) class-attribute instance-attribute

The types that we represent as a list.

sorted_list_types = dataclasses.field(default_factory=(lambda: SORTED_LIST_TYPES)) class-attribute instance-attribute

The types that we represent as a list, but we sort it.

str_types = dataclasses.field(default_factory=(lambda: STR_TYPES)) class-attribute instance-attribute

The types that we stringify.

The various lists of type conversions

as_dict_rec.AS_DICT_HANDLERS = {logging.Logger: lambda log: f'Logger("{log.name}", {logging.getLevelName(log.level)})', re.Pattern: lambda pattern: pattern.pattern, tempfile._TemporaryFileWrapper: lambda tempf: tempf.path} module-attribute

The types that we handle in special ways before anything else.

as_dict_rec.BASIC_TYPES = (float, int, str, types.NoneType) module-attribute

The types that can usually be serialized directly.

as_dict_rec.LIST_TYPES = (list, tuple) module-attribute

The types that we represent as a list.

as_dict_rec.SORTED_LIST_TYPES = (set,) module-attribute

The types that we represent as a list, but we sort it.

as_dict_rec.STR_TYPES = (enum.Enum, ipaddress.IPv4Address, ipaddress.IPv6Address, numbers.Number, pathlib.Path, uuid.UUID) module-attribute

The types that we stringify.