How to make Pydantic recognize attributes from non-model parent class? - python

I have a normal Python class:
class NormalClass:
a: str
b: bool
I don't want NormalClass to inherit from pydantic.BaseModel class, but I still want another class with the same attributes as NormalClass and it being a Pydantic model. So here's what I try:
class ModelClass(BaseModel, NormalClass):
pass
Unfortunately, when I try to use this ModelClass to validate server response in FastAPI, I always get {}. What happens here?

I believe that you cannot expect to inherit the features of a pydantic model (including fields) from a class that is not a pydantic model.
a and b in NormalClass are class attributes. Although the fields of a pydantic model are usually defined as class attributes, that does not mean that any class attribute is automatically a field. NormalClass is not a pydantic model because it does not inherit from BaseModel. ModelClass has no fields because it does not define any fields by itself and has not inherited any fields from a pydantic model.

Related

Class inheritance python

In the case creating a model, for example
class Student(models.Model)
name=models.charfield(),roll=models.integerfield()
similarly,
In the case creating a form, class newform(forms.Form)
name=forms.charfield(),roll=forms.integerfield()
similarly,
In the case creating a serializer, class serial(serializers.Serializer)
name=serializers.charfield(),roll=serializers.integerfield()
I understood that in each classes,a base class is inherited but i am confused that if different objects of different classes are created inside a class in each scenario then what is the meaning of inheriting models.model, forms.Form,serializers.Serializer what these inherited classes do?
Django uses inheritance as well as object composition which are techniques of OOP for reusability.
Let us take your first class as example (I have only kept one field for simplicity):
Student(models.Model):
name = models.CharField(max_length=100)
Inheritance:
The first line Student(model.Model): does inheritance by inheriting from Model class using which you are getting methods like save(), delete(), clean_fields e.t.c. Now your Student class can reuse those methods.
Composition
The second line name = models.CharField(max_length=100) does object composition by creating object namely name of class CharField using which you get methods like check, get_internal_type e.t.c.
All of those Inbuilt classes (Model, CharField e.t.c) are defined in file namely models.py so when you do models.Model you are getting Model class from file models.py and models.CharField gives you CharField class from same file.
By inheriting from other classes, you have access to their methods;
Class A(object):
def _print(self):
print('Class A')
Class B(A):
def other_print(self):
print('Class B')
if __name__ == "__main__":
a, b = A(), B()
a._print()
b._print()
b.other_print()
When inheriting from model, forms, etc... You inherit from an object that is already integrated in the framework and thus has specific methods to work with the framework. For example the model will be registered to the database, the form 'knows' how to render properly, etc...
When you inherit from these classes, you already have an pre-built object with all these methods.

Pydantic - Dynamically create a model with multiple base classes?

From the pydantic docs I understand this:
import pydantic
class User(pydantic.BaseModel):
id: int
name: str
class Student(pydantic.BaseModel):
semester: int
# this works as expected
class Student_User(User, Student):
building: str
print(Student_User.__fields__.keys())
#> dict_keys(['semester', 'id', 'name', 'building'])
However, when I want to create a similar object dynamically (following the section dynamic-model-creation):
# this results in a TypeError
pydantic.create_model("Student_User2", __base__=(User, Student))
I get:
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
Question: How to dynamically create a class like Student_User
Its not the answer to the original question, but if you are like me and all you care about is having a model which holds fields of other models, this should be a solutions.
Student_User = pydantic.create_model("Student_User", **{
**{key: (value.type_, value.default) for key, value in User.__fields__.items()},
**{key: (value.type_, value.default) for key, value in Student.__fields__.items()},
**{"building": (str, '')},
})
Essentially, we are dynamically creating a new pydantic model and we are setting its fields to be the fields of our other models plus an additional custom field.
Note:
OP included these lines in his question:
print(Student_User.__fields__.keys())
#> dict_keys(['semester', 'id', 'name', 'building'])
So, my guess is that his end goal was copying the fields from the other models and having a model created from multiple bases was just a method of achieving it.
As of pydantic==1.9.2,
Student_User2 = pydantic.create_model("Student_User2", __base__=(User, Student), building=(str, ...))
runs successfully and
print(Student_User2.__fields__.keys())
returns
dict_keys(['semester', 'id', 'name', 'building'])
Your problem is not with pydantic but with how python handles multiple inheritances. I am assuming in the above code, you created a class which has both the fields of User as well as Student, so a better way to do that is
class User(pydantic.BaseModel):
id: int
name: str
class Student(User):
semester: int
class Student_User(Student):
building: str
This gets your job done. So, now if you want to create these models dynamically, you would do
pydantic.create_model("Student_User2", building=(str, ...), __base__=Student)
Obviously, building is the new model's field, so you can change that as you want
So, the final complete code would look something like this
import pydantic
class User(pydantic.BaseModel):
id: int
name: str
class Student(User):
semester: int
class Student_User(Student):
building: str
print(Student_User.__fields__.keys())
model = pydantic.create_model("Student_User2", building=(str, ...), __base__=Student)

provide Enum at DB creation time

Let's say I have a generic Food sqlalchemy model that I want to reuse for different apps.
In each app, I have a FoodType enum which contains the different types of food I'll use.
I want to be able to pass this app-specific Enum to my generic model. Any idea on how to do that?
Here is my food model:
class Food(Base):
type = Column(Enum(FoodType, name="l=food_type"))
I tried to define an empty enum in my generic model so that it could be overwritten in each app but that apparently doesn't work, it crashes on:
sqlalchemy.exc.StatementError: (builtins.LookupError) "PIZZA" is not among the defined enum values
Make Food a mixin instead of a concrete model, and use declared_attr to define type:
class FoodMixin:
#declared_attr
def type(cls):
return Column(Enum(cls.food_type, name="food_type"))
Then in your application create the concrete model as:
class Food(FoodMixin, Base):
food_type = FoodType
Another way would be to define a model factory for Food that takes the enum type as an argument and produces a model:
def food_maker(FoodType, Base):
class Food(Base):
type = Column(Enum(FoodType, name="food_type"))
return Food
and in the app:
Food = food_maker(FoodType, Base)
...Or make the factory return a mixin and inherit from that.

Model subclass: override CharField max_length

I have a supplied database schema for which I want to create a Django application. Many of the tables in the schema share a common set of columns, such as name and date_created. That prompted me to create an abstract Standard_model class containing those columns, and subclass the relevant models from it.
Unfortunately, some of the tables have a name column with a different max_length. I'm trying to come up with a way for the subclassed model to pass the max_length value to the abstract base class, but I'm drawing a blank.
Any ideas?
class Standard_model(models.Model):
name = models.CharField(max_length=50)
date_created = models.DateTimeField()
class Meta:
abstract = True
class MyModel(Standard_model):
name = models.CharField(max_length=80) # Can't do this.
No, you cannot override the name field definition:
In normal Python class inheritance, it is permissible for a child
class to override any attribute from the parent class. In Django, this
is not permitted for attributes that are Field instances (at least,
not at the moment). If a base class has a field called author, you
cannot create another model field called author in any class that
inherits from that base class.
See also:
In Django - Model Inheritance - Does it allow you to override a parent model's attribute?
And, FYI, according to the model naming convention, it should be called StandardModel.

combining object with Meta and django models.Model

some newbie question here, I have a model like so:
from django.db import models
class SomeCommons(object):
# some fields here
# ...
class Meta:
abstract=True
class SomeDjangoModels(SomeCommons,models.Model):
pass
is it the same as the following model :
from django.db import models
class SomeModels(models.Model):
# some fields here
# ...
class Meta:
abstract=True
What I know that when doing like so SomeDjangoModels(SomeCommons,models.Model) the attribute from SomeCommons will be available in SomeDjangoModels, but the question is if the SomeCommons contains django Meta class will the Meta class also available in SomeDjangoModels? if it is, is there a way to prove it (the Meta class does exists)?
thanx
Yes, meta classes are inherited...
Meta inheritance
When an abstract base class is created, Django makes any Meta inner class you declared in the base class available as an attribute. If a child class does not declare its own Meta class, it will inherit the parent’s Meta. If the child wants to extend the parent’s Meta class, it can subclass it.
But in your case it does nothing as absrtact is set to False on the inheriting child class.
Django does make one adjustment to the Meta class of an abstract base class: before installing the Meta attribute, it sets abstract=False.
Asa result SomeModels will be abstract, but SomeDjangoModels will not.
No, those two definitions are not quite the same.
By default, a subclass will inherit its parent's Meta, but it will not inherit the abstract=True property (as the common use case is that subclasses will not be abstract).
If you do wish to inherit that, you must explicitly override the meta class, as shown in the documentation. (It appears from the question that you do wish SomeDjangoModels to also be abstract, but it's not entirely clear.)
If you do want a concrete (cf meta) subclass, then for all practical purposes the definitions are identical.

Categories