Specific types behavior#

Builtin loaders and dumpers designed to work well with JSON data processing. If you are working with a different format, you may need to override the default behavior, see Retort recipe for details.

Mostly predefined loaders accept value only a single type; if it’s a string, it strings in a single format. You can disable the strict_coercion parameter of Retort to allow all conversions that the corresponding constructor can perform.

Scalar types#

Basic types#

Values of these types are loaded using their constructor. If strict_coercion is enabled, the loader will pass only values of appropriate types listed at the Allowed strict origins row.

Type

Allowed strict origins

Dumping to

int

int

no conversion

float

float, int

no conversion

str

str

no conversion

bool

bool

no conversion

Decimal

str, Decimal

str

Fraction

str, Fraction

str

complex

str, complex

str

Any and object#

Value is passed as is, without any conversion.

None#

Loader accepts only None, dumper produces no conversion.

bytes-like#

Exact list: bytes, bytearray, ByteString.

Value is represented as base64 encoded string.

BytesIO and IO[bytes]#

Value is represented as base64 encoded string.

re.Pattern#

The loader accepts a string that will be compiled into a regex pattern. Dumper extracts the original string from a compiled pattern.

Path-like#

Exact list: PurePath, Path, PurePosixPath, PosixPath, PureWindowsPath, WindowsPath, PathLike[str].

Loader takes any string accepted by the constructor, dumper serialize value via __fspath__ method.

PathLike[str] loader produces Path instance

IP addresses and networks#

Exact list: IPv4Address, IPv6Address, IPv4Network, IPv6Network, IPv4Interface, IPv6Interface.

Loader takes any string accepted by the constructor, dumper serialize value via __str__ method.

UUID#

Loader takes any hex string accepted by the constructor, dumper serialize value via __str__ method.

date, time and datetime#

Value is represented as an isoformat string.

timedelta#

Loader accepts instance of int, float or Decimal representing seconds, dumper serialize value via total_seconds method.

Flag subclasses#

Flag members by default are represented by their value. Note that flags with skipped bits and negative values are not supported, so it is highly recommended to define flag values via enum.auto() instead of manually specifying them. Besides, adaptix provides another way to process flags: by list using their names. See flag_by_member_names for details.

Other Enum subclasses#

Enum members are represented by their value without any conversion.

LiteralString#

Loader and dumper have same behaviour as builtin one’s of str type

Compound types#

NewType#

All NewType’s are treated as origin types.

For example, if you create MyNewModel = NewType('MyNewModel', MyModel), MyNewModel will share loader, dumper and name_mapping with MyModel. This also applies to user-defined providers.

You can override providers only for NewType if you pass MyNewModel directly as a predicate.

Metadata types#

The types such as Final, Annotated, ClassVar and InitVar are processed the same as wrapped types.

Literal#

Loader accepts only values listed in Literal. If strict_coercion is enabled, the loader will distinguish equal bool and int instances, otherwise, they will be considered as same values. Enum instances will be loaded via its loaders. Enum loaders have a higher priority over others, that is, they will be applied first.

If the input value could be interpreted as several Literal members, the result will be undefined.

Dumper will return value without any processing excluding Enum instances, they will be processed via the corresponding dumper.

Be careful when you use a 0, 1, False and True as Literal members. Due to type hint caching Literal[0, 1] sometimes returns Literal[False, True]. It was fixed only at Python 3.9.1.

Union#

Loader calls loader of each union case and returns a value of the first loader that does not raise LoadError. Therefore, for the correct operation of a union loader, there must be no value that would be accepted by several union case loaders.

from dataclasses import dataclass
from typing import Union

from adaptix import Retort


@dataclass
class Cat:
    name: str
    breed: str


@dataclass
class Dog:
    name: str
    breed: str


retort = Retort()
retort.load({"name": "Tardar Sauce", "breed": "mixed"}, Union[Cat, Dog])

The return value in this example is undefined, it can be either a Cat instance or a Dog instance. This problem could be solved if the model will contain a designator (tag) that can uniquely determine the type.

from dataclasses import dataclass
from typing import Literal, Union

from adaptix import Retort


@dataclass
class Cat:
    name: str
    breed: str

    kind: Literal["cat"] = "cat"


@dataclass
class Dog:
    name: str
    breed: str

    kind: Literal["dog"] = "dog"


retort = Retort()
data = {"name": "Tardar Sauce", "breed": "mixed", "kind": "cat"}
cat = retort.load(data, Union[Cat, Dog])
assert cat == Cat(name="Tardar Sauce", breed="mixed")
assert retort.dump(cat) == data

This example shows how to add a type designator to the model. Be careful, this example does not work if name_mapping.omit_default is applied to tag field.

Be careful if one model is a superset of another model. By default, all unknown fields are skipped, this does not allow distinct such models.

from dataclasses import dataclass
from typing import Union

from adaptix import Retort


@dataclass
class Vehicle:
    speed: float


@dataclass
class Bike(Vehicle):
    wheel_count: int


retort = Retort()
data = {"speed": 10, "wheel_count": 3}
assert retort.load(data, Bike) == Bike(speed=10, wheel_count=3)
assert retort.load(data, Vehicle) == Vehicle(speed=10)
retort.load(data, Union[Bike, Vehicle])  # result is undefined

This can be avoided by inserting a type designator like in the example above. Processing of unknown fields could be customized via name_mapping.extra_in.

Dumper finds appropriate dumper using object type. This means that it does not distinguish List[int] and List[str]. For objects of types that are not listed in the union, but which are a subclass of some union case, the base class dumper is used. If there are several parents, it will be the selected class that appears first in .mro() list.

Also, builtin dumper can work only with class type hints and Literal. For example, type hints like LiteralString | int can not be dumped.

Iterable subclasses#

If strict_coercion is enabled, the loader takes any iterable excluding str and Mapping. If strict_coercion is disabled, any iterable are accepted.

Dumper produces the same iterable with dumped elements.

If you require a dumper or loader for abstract type, a minimal suitable type will be used. For example, if you need a dumper for type Iterable[int], retort will use tuple. So if a field with Iterable[int] type will contain List[int], the list will be converted to a tuple while dumping.

Tuple of dynamic length like *tuple[int, ...] isn’t supported yet. This doesn’t applies for tuples like *tuple[int, str] (constant length tuples).

Dict and Mapping#

Loader accepts any other Mapping and makes dict instances. Dumper also constructs dict with converted keys and values.

DefaultDict#

Loader makes instances of defaultdict with the default_factory parameter set to None. To customize this behavior, there are factory default_dict that have default_dict.default_factory parameter that can be overridden.

Models#

Models are classes that have a predefined set of fields. By default, models are loading from dict, with keys equal field names, but this behavior could be precisely configured via name_mapping mechanism. Also, the model could be loaded from the list.

Dumper works similarly and produces dict (or list).

See Supported model kinds for exact list of supported model.