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
Related
I am trying to change the alias_generator and the allow_population_by_field_name properties of the Config class of a Pydantic model during runtime. I am expecting it to cascade from the parent model to the child models.
class ParentModel(BaseModel):
class Config:
alias_generator = to_camel
allow_population_by_field_name = True
class ChildModel(ParentModel):
first_name: str
class ChildModel2(ParentModel):
data: ChildModel
What I want to do is to change the following during runtime.
alias_generator = to_pascal
How would I be able to do that?
I tried something like this:
class ParentModel(BaseModel):
def __init__(self, is_camel = True, **data):
self.Config.alias_generator = to_camel if is_camel else to_pascal
class Config:
allow_population_by_field_name = True
class ChildModel(ParentModel):
first_name: str
class ChildModel2(ParentModel):
def __init__(self, is_camel, **data):
super.__init__(is_camel, **data)
data: ChildModel
test = ChildModel2(is_camel=False, data=ChildModel(first_name="test"))
So the goal is to convert the pydantic models to json in either camel case or pascal case or snake case when the fast api returns the response.
I have code, which don't work:
class OrderValidator:
#validator("product_id")
def product_exists(cls, product_id):
print(cls)
session = create_session()
order = session.query(Order).get(product_id)
if order is None:
raise ValueError(f"Product with the ID: {product_id} is not found")
return product_id
#validator("product_quantity")
def check_quantity(cls, product_quantity, values):
session = create_session()
product = session.query(Product).get(values["product_id"])
if product.quantity < product_quantity:
raise ValueError(
f"This product does not have such a quantity: {product_quantity}"
)
return product_quantity
class CreateOrder(BaseModel, OrderValidator):
product_id: int
product_quantity: int
contact_whatsapp: str
contact_telegram: str
contact_phone: str
contact_name: str
contact_city: str
status = 0
class EditOrder(BaseModel, OrderValidator):
product_id: Optional[int]
product_quantity: Optional[int]
contact_whatsapp: Optional[str]
contact_telegram: Optional[str]
contact_phone: Optional[str]
contact_name: Optional[str]
contact_city: Optional[str]
status: Optional[int]
I want to create all the validation logic in one class that pydantic models will inherit. But my code doesn't work.
P.S.: If one of the validation points is shoved directly into the model, then everything will work fine
Due to the way the Pydantic ModelMetaclass is set up, validators will only ever be inherited from parent classes that are themselves models, i.e. inherit from BaseModel. (Source)
This means you can easily solve your problem by just having OrderValidator inherit from BaseModel. Beware however that since the fields that it validates aren't defined on it, you'll need to set check_fields=False on each validator.
I suppose you might as well turn it into your base model for the others at this point, so that you don't have to inherit from BaseModel again. But you know better what the inheritence logic should be.
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
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
In my code, I have a few models with multiple custom properties:
#dataclass
class Value:
amount: float
currency: str
class MyModel(models.Model):
...
#property
def v1(self) -> Value:
...
#property
def v2(self) -> Value:
...
#property
def v3(self) -> Value:
...
An I have the following serializer:
class MyModelBaseSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = [..., "v1", "v2", "v3"]
When serializing any MyModel instance, a TypeError raises: TypeError: Object of type Value is not JSON serializable.
I know this can be solved by explicitly adding v1, v2, v3 as fields inside MyModelBaseSerializer, but I don't want to do that: I have many models with Value properties. I want the default DRF serializer to know how to serialize Value instances.
I tried overriding to_representation, but that didn't seem to work.
I want something similar to overriding the JSONEncoder.default(o) method, but I don't see how to tell DRF which encoder to use.
Add a custom serializer for Value. For example (not tested).
class ValueSerializer(serializers.Serializer):
amount = serializers.FloatField()
currency = serializers.CharField()
class MyModelBaseSerializer(serializers.ModelSerializer):
v1 = ValueSerializer()
v2 = ValueSerializer()
v3 = ValueSerializer()
...
Or, since Value is a dataclass, take a look at https://github.com/oxan/djangorestframework-dataclasses.
EDIT
A quick and dirty DRY approach can be to override build_property_field on the serializer (see docs).
class MyModelBaseSerializer(serializers.ModelSerializer):
def build_property_field(self, field_name, model_class):
if field_name in ["v1", "v2", "v3"]:
return ValueSerializer, {}
return super().build_property_field(field_name, model_class)
Overriding serializer_field_mapping is another option.