Pydantic set attributes with a default function - python

Is it possible to pass function setters for immutable Pydantic Models.
For example:
from uuid import uuid4, UUID
from pydantic import BaseModel
from datetime import datetime
def generate_uuid():
return uuid4()
def get_datetimenow():
return datetime.now()
class Item(BaseModel):
class Config:
allow_mutation = False
extra = "forbid"
id: UUID
created_at: datetime
I want the methods generate_uuid and get_datetimenow to set the attributes.
>>> Item()
ValidationError: 2 validation errors for Item
id
field required (type=value_error.missing)
created_at
field required (type=value_error.missing)
While I want to get an Item object with id and created_at automatically set. Identical result as when you run:
>>> Item(id=generate_uuid(), created_at=get_datetimenow())
Item(id=UUID('8f898730-3fad-4ca9-9667-c090f62a2954'), created_at=datetime.datetime(2021, 1, 19, 21, 13, 7, 58051))

You can use default_factory parameter of Field with an arbitrary function. Like so:
from uuid import uuid4, UUID
from pydantic import BaseModel, Field
from datetime import datetime
class Item(BaseModel):
class Config:
allow_mutation = False
extra = "forbid"
id: UUID = Field(default_factory=uuid4)
created_at: datetime = Field(default_factory=datetime.now)

Related

Pydantic model inside model

So i am trying to use an existing pydantic model in another pydantic model, as i've seen on the examples.
Those are my pydantic models:
class DriverCategoryOut(BaseModel):
internal_id: int
category: str
class Config:
orm_mode = True
class DocListOut(BaseModel):
driver_categories: DriverCategoryOut
class Config:
orm_mode = True
This is my route code:
#router.get('/document', response_model=shemas.DocListOut)
def get_doc_list(db: Session = Depends(get_db)):
driver_categories = db.query(DriverCategory).first()
return driver_categories
I'm getting the error:
pydantic.error_wrappers.ValidationError: 1 validation error for DocListOut
response -> driver_categories
field required (type=value_error.missing)
If I change to response_model=shemas.DriverCategoryOut it works just fine. What is wrong with my DocListOut model?
This line driver_categories = db.query(DriverCategory).first() retrieves (and parses a record as a) DriverCategory. Assuming that maps perfectly to DriverCategoryOut, why would this be automatically parsed to an object that has a property that is of type DriverCategoryOut?
You might want to try the following:
#router.get('/document', response_model=shemas.DocListOut)
def get_doc_list(db: Session = Depends(get_db)):
driver_categories = db.query(DriverCategory).first()
return {'driver_categories': driver_categories}
Now, that JSON will be parsed to your response_model (in this case, a DocListOut).
there is another way to inheritance your model
class DocListOut(DriverCategoryOut):
class Config:
orm_mode = True
# if you want to exclude some fields
fields = {
"internal_id": {'exclude': True},
}
if your output are lists
class DocListOut(BaseModel):
driver_category: List[DriverCategoryOut] = []
class Config:
orm_mode=True

Random model values inside list subfactory elements

I cannot find an answer to my question. The thing is that I want to generate an User factory model where inside will be a subfactory List with Addresses. Each Addresses element must have different/random values (I mean that each element has non-repeatable unique values).
In my current implementation, all of the elements have the same values (maybe seeding is necessary?)
Actual code:
from pydantic import BaseModel
from factory import Factory, List, Subfactory
class Address(BaseModel):
Name: str
class User(BaseModel):
Addresses: list[Address]
class AddressFactory(Factory):
Name = fake.name()
class Meta:
model = Address
class UserFactory(Factory):
Addresses = List([SubFactory(AddressFactory) for _ in range(3)])
class Meta:
model = User
Actual output:
> UserFactory()
> User(Addresses=[Address(Name='Isa Merkx'), Address(Name='Isa Merkx'), Address(Name='Isa Merkx')])
Desired Output:
> UserFactory()
> User(Addresses=[Address(Name='Isa Merkx'), Address(Name='John Smith'), Address(Name='Elon Musk')])
In the code you provided, you have to replace fake.name() by Faker("name"), where Faker is imported from factory at the beginning of your file.
Indeed, fake.name() is called only once, when the class is defined, whereas you want it to happen each time you instantiate a factory.
You'll find a more detailed answer to your question here.
You should use the LazyAttribute to get a different value each time.
from typing import List
from pydantic import BaseModel
import factory
from faker import Faker
fake = Faker('en_GB') # <-- missing from the original example
class Address(BaseModel):
Street: str
HouseNumber: str
City: str
Postcode: str
class AddressFactory(factory.Factory):
Street = factory.LazyAttribute(lambda _: fake.street_name()) # <-- Lazy load the attribute values
HouseNumber = factory.LazyAttribute(lambda _: fake.building_number())
City = factory.LazyAttribute(lambda _: fake.city())
Postcode = factory.LazyAttribute(lambda _: fake.postcode())
class Meta:
model = Address
class User(BaseModel):
Addresses: List[Address]
class UserFactory(factory.Factory):
Addresses = factory.List([factory.SubFactory(AddressFactory) for _ in range(3)])
class Meta:
model = User
user = UserFactory()
user
The output:
User(Addresses=[Address(Street='Jade rapids', HouseNumber='3', City='Vanessaville', Postcode='B6H 2XA'), Address(Street='Wendy grove', HouseNumber='76', City='West Patricia', Postcode='WR5 0GL'), Address(Street='Smith ramp', HouseNumber='3', City='New Leslie', Postcode='L6 6JF')])

Pydantic/SQLAlchemy: How to work with enums?

What is the best way to convert a sqlalchemy model to a pydantic schema (model) if it includes an enum field?
Sqlalchemy
import enum
from sqlalchemy import Enum, Column, String
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class StateEnum(enum.Enum):
CREATED = 'CREATED'
UPDATED = 'UPDATED'
class Adapter(Base):
__tablename__ = 'adapters'
id = Column(String, primary_key=True)
friendly_name = Column(String(256), nullable=False)
state: StateEnum = Column(Enum(StateEnum))
Pydantic
from pydantic import BaseModel
from enum import Enum
class StateEnumDTO(str, Enum):
CREATED = 'CREATED'
UPDATED = 'UPDATED'
class AdapterDTO(BaseModel):
friendly_name: str
state: StateEnumDTO # This currently cannot be converted?
class Config:
allow_population_by_field_name = True
orm_mode = True
use_enum_values = True
Conversion
AdapterDTO.from_orm(Adapter(friendly_name='test', state=StateEnum.CREATED))
This leads to the error
value is not a valid enumeration member; permitted: 'CREATED', 'UPDATED' (type=type_error.enum; enum_values=[<StateEnumDTO.CREATED: 'CREATED'>, <StateEnumDTO.UPDATED: 'UPDATED'>])
How can I configure either
a.) the serialization with the from_orm method?
or
b.) the creation of the state field?
c.) How to convert it the other way around?
Is there a native way to do this with pydantic or how is this typically done?
Update:
Test case
def test_enum_conversion_to_dto():
adapter = Adapter(id='1', friendly_name='test', state=StateEnum.CREATED)
adapter_dto = AdapterDTO.from_orm(adapter)
assert adapter_dto.state == StateEnumDTO.CREATED
assert adapter_dto.state.value == StateEnum.CREATED.value
Pydantic requires that both enum classes have the same type definition.
In your case, StateEnum inherits from enum.Enum, but StateEnumDTO inherits from both str and enum.Enum.
You can fix this issue by changing your SQLAlchemy enum definition:
class StateEnum(str, enum.Enum):
CREATED = 'CREATED'
UPDATED = 'UPDATED'
You must add arbitrary_types_allowed = True
To the model Config class.
from pydantic import BaseModel
from enum import Enum
class StateEnumDTO(str, Enum):
CREATED = 'CREATED'
UPDATED = 'UPDATED'
class AdapterDTO(BaseModel):
friendly_name: str
state: StateEnumDTO # This currently cannot be converted?
class Config:
allow_population_by_field_name = True
orm_mode = True
use_enum_values = True
arbitrary_types_allowed = True

Is it possible in Pydantic to dynamically change representation of object?

I'm interested if there is any way to set an attribute on the Pydantic Model class the will only be used when constructing the output representation of the object.
Something that works like Django Rest Framework SerializerMethodField. It is highly useful when you need to construct output depending on the environment or many other conditions.
It's possible to have a similar mechanism as django's SerializerMethodField by overriding pydantic BaseModel class
from copy import deepcopy
from pydantic import BaseModel, Field
class Person:
def __init__(self, name, likes_cake):
self.name = name
self.likes_cake = likes_cake
class CustomPydanticModel(BaseModel):
#classmethod
def from_orm(cls, obj, getter_binding=None):
getter_binding = getter_binding or {}
obj = deepcopy(obj)
for field in cls.__fields__:
method = getter_binding.get(field)
if method is None:
method = getattr(cls, f"get_{field}", None)
if method is not None and callable(method):
setattr(obj, field, method(obj))
return super().from_orm(obj)
class PersonModel(CustomPydanticModel):
name: str
status: str | None = None
#staticmethod
def get_status(obj):
return "I like cake" if obj.likes_cake else "Hello"
class Config:
orm_mode = True
obj = Person("Patrick", True)
pydantic_obj = PersonModel.from_orm(obj)
Note that the get_status method should be static or classmethod

FastAPI - ENUM type models not populated

Below is my fastAPI code
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl, Field
from enum import Enum
app = FastAPI()
class Status(Enum):
RECEIVED = 'RECEIVED'
CREATED = 'CREATED'
CREATE_ERROR = 'CREATE_ERROR'
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
status: Status = None
#app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Below is the swagger doc generated. The Status is not shown. I am new to pydantic and i am not sure on how to show status in the docs
create the Status class by inheriting from both str and Enum
class Status(str, Enum):
RECEIVED = 'RECEIVED'
CREATED = 'CREATED'
CREATE_ERROR = 'CREATE_ERROR'
References
Working with Python enumerations--(FastAPI doc)
[BUG] docs don't show nested enum attribute for body--(Issue #329)

Categories