Pydantic models

The library can also handle Pydantic’s models, and map to them and from them.

It supports all the other features discussed in Supported features or Enum mappings.

It supports both the old Pydantic version 1.x and the Rust rewrite 2.x.

Pydantic v2

For performance reasons it will use Pydantic’s .model_construct class method to construct objects. However it will fall back to the normal, slow initializer, when required (e.g. when the Pydantic model has validators that modify the model).

Additionally it can work with alias fields, and also with the populate_by_name configuration.

>>> class Animal(BaseModel):
...     name: str
...     greeting: str = Field(alias="greetingSound")
...
...     @field_validator("greeting")
...     def repeat_greeting(cls, v):
...         return " ".join([v] * 3)
>>>
>>> @mapper(Animal)
... @dataclass
... class Pet:
...     name: str
...     greeting: str
>>>
>>> rocky = Pet(name="Rocky", greeting="Woof")
>>> map_to(rocky, Animal)
Animal(name='Rocky', greeting='Woof Woof Woof')

Pydantic also remembers which optional fields are set, and which are unset (with default None). This might be useful, if you want to distinguish if user explicitely set the value None, or if they didn’t set it all all (e.g. setting it explicitely could mean deleting the value in a database). This library will remember which fields are set, and are unset.

>>> class Foo(BaseModel):
...     x: Optional[float] = None
...     y: Optional[int] = None
...     z: Optional[bool] = None
>>>
>>> @mapper(Foo)
... class Bar(BaseModel):
...     x: Optional[float] = None
...     y: Optional[int] = None
...     z: Optional[bool] = None
>>>
>>> bar = Bar(x=1.23, z=None)
>>> sorted(bar.model_fields_set)
['x', 'z']
>>> foo = map_to(bar, Foo)
>>> foo
Foo(x=1.23, y=None, z=None)
>>> sorted(foo.model_fields_set)
['x', 'z']

This is also used for updates, even with other dataclasses. It will only update the set fields, however it’s only possible if both fields are optional.

>>> @dataclass
... class Customer:
...    name: Optional[str]
...    age: Optional[int]
...    discount_amount: Optional[float]
...
>>> @mapper(Customer)
... class CustomerUpdate(BaseModel):
...    name: Optional[str] = None
...    age: Optional[int] = None
...    discount_amount: Optional[float] = None
...
>>> customer = Customer(name="John Doe", age=41, discount_amount=10.0)
>>> map_to(CustomerUpdate(age=42, discount_amount=None), customer)
>>> customer
Customer(name='John Doe', age=42, discount_amount=None)

Pydantic v1

For performance reasons it will use Pydantic’s .construct class method to construct objects. However it will fall back to the normal, slow initializer, when required (e.g. when the Pydantic model has validators that modify the model).

Additionally it can work with alias fields, and also with the allow_population_by_field_name configuration.

>>> class Animal(BaseModel):
...     name: str
...     greeting: str = Field(alias="greetingSound")
...
...     @validator("greeting")
...     def repeat_greeting(cls, v):
...         return " ".join([v] * 3)
>>>
>>> @mapper(Animal)
... @dataclass
... class Pet:
...     name: str
...     greeting: str
>>>
>>> rocky = Pet(name="Rocky", greeting="Woof")
>>> map_to(rocky, Animal)
Animal(name='Rocky', greeting='Woof Woof Woof')

Pydantic also remembers which optional fields are set, and which are unset (with default None). This might be useful, if you want to distinguish if user explicitely set the value None, or if they didn’t set it all all (e.g. setting it explicitely could mean deleting the value in a database). This library will remember which fields are set, and are unset.

>>> class Foo(BaseModel):
...     x: Optional[float]
...     y: Optional[int]
...     z: Optional[bool]
>>>
>>> @mapper(Foo)
... class Bar(BaseModel):
...     x: Optional[float] = None
...     y: Optional[int] = None
...     z: Optional[bool] = None
>>>
>>> bar = Bar(x=1.23, z=None)
>>> sorted(bar.__fields_set__)
['x', 'z']
>>> foo = map_to(bar, Foo)
>>> foo
Foo(x=1.23, y=None, z=None)
>>> sorted(foo.__fields_set__)
['x', 'z']

This is also used for updates, even with other dataclasses. It will only update the set fields, however it’s only possible if both fields are optional.

>>> @dataclass
... class Customer:
...    name: Optional[str]
...    age: Optional[int]
...    discount_amount: Optional[float]
...
>>> @mapper(Customer)
... class CustomerUpdate(BaseModel):
...    name: Optional[str] = None
...    age: Optional[int] = None
...    discount_amount: Optional[float] = None
...
>>> customer = Customer(name="John Doe", age=41, discount_amount=10.0)
>>> map_to(CustomerUpdate(age=42, discount_amount=None), customer)
>>> customer
Customer(name='John Doe', age=42, discount_amount=None)