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.
Related
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.
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.
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)
Before posting this question, I have read through the Official Django Documentation, scouring it for a comprehensive explanation for beginners. I have read the code of the actual Model Class, and searched around on StackOverflow.
When working with databases in Django, you work with classes inheriting from the Model class in the models module. This helps programmers avoid double-typing everything, jumping between database specific syntax and python. As I have read, 'the model class that each model inherits from automatically takes care of translation'.
How does this work? How Does the Model Class convert model attributes to database columns? I suppose some methods inherited from the parent Model Class are able to use the variables specified in each new model, but would like a better explanation if possible!
Also, why write 'models.Model' if the Model class is within models.base?
LINK TO MODEL CLASS: https://docs.djangoproject.com/en/1.11/_modules/django/db/models/base/#Model
EDIT:
Figured out the reason behind why models.Model work.
How Does the Model Class convert model attributes to database columns?
The Model class doesn't really do any conversion itself. You create a subclass of Model that has some column information,
which Django's ORM uses when building the database query corresponding to your Django ORM query. The conversion is done by a database driver when it actually communicates with your specific database.
Here's a toy ORM that behaves a little like Django's Model. You can implement QuerySet for fun if you want:
class Column:
'''
Represents a database column.
This is used to create the underlying table in the database
and to translate database types to Python types.
'''
def __init__(self, type):
self.type = type
class Manager:
'''
Accessed via `YourModel.objects`. This is what constructs
a `QuerySet` object in Django.
'''
def __init__(self, model):
self.model = model
def get(self, id):
'''
Pretend `YourModel.objects.get(id=123)` queries the database directly.
'''
# Create an instance of the model. We only keep track of the model class.
instance = self.model()
# Populate the instance's attributes with the result of the database query
for name in self.model._columns:
# Pretend we load the values from the database
value = 123
setattr(instance, name, value)
# This would be done above if we actually queried the database
instance.id = id
# Finally return the instance of `self.model`
return instance
class ModelBase(type):
def __new__(cls, name, bases, attrs):
new_cls = super().__new__(cls, name, bases, attrs)
# The `Manager` instance is made a class attribute
new_cls.objects = Manager(new_cls)
# Keep track of the columns for conveniece
new_cls._columns = {}
for name, attr in attrs.items():
if isinstance(attr, Column):
new_cls._columns[name] = attr
# The class is now ready
return new_cls
class Model(metaclass=ModelBase):
'''
Django's `Model` is more complex.
This one only uses `ModelBase` as its metaclass so you can just inherit from it
'''
pass
class MyModel(Model):
id = Column(int)
column2 = Column(float)
column3 = Column(str)
if __name__ == '__main__':
print(MyModel._columns)
instance = MyModel.objects.get(id=5)
print(instance.id)
The main functionality is provided by Model having ModelBase as a metaclass. The metaclass's __new__ method is called
when Model or any subclass is created (not an instance, the class itself), which allows the metaclass to modify the class arbitrarily.
Each Model subclass contains information about its own columns and gets a objects class attribute that queries the database for it.
Also, why write 'models.Model' if the Model class is within models.base?
models/__init__.py imports Model from models/base.py so you don't have to write models.base.Model.
When you create a model class and run
python manage.py makemigrations
It creates the corresponding scripts to create a table in your database.
You can find this script in your apps "migrations" folder.
And when you run
python manage.py migrate
These scripts are mapped to the correct commands and are executed on the database by Django.
I'm buildibg some abstract model for about 10 models. I need to make, somehow, that 1 field is not declared in abstract model, but MUST be declared in inheriting models.
How to do that? Is there any way to use NotImplementedError?
I am afraid there isn't an easy way to achieve that, if possible at all, without digging deep into Django.
The main reason is that Field name "hiding" is not permitted in Django. What this means is that if you want to declare an abstract attribute in the base abstract class that is a Field instance, you will not be able to rewrite it in the child classes contrary to the normal Python class inheritance paradigm. To quote from the doc:
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.
Overriding fields in a parent model leads to difficulties in areas such as initializing new instances (specifying which field is being initialized in Model.init) and serialization. These are features which normal Python class inheritance doesn't have to deal with in quite the same way, so the difference between Django model inheritance and Python class inheritance isn't arbitrary.
This restriction only applies to attributes which are Field instances. Normal Python attributes can be overridden if you wish. It also only applies to the name of the attribute as Python sees it: if you are manually specifying the database column name, you can have the same column name appearing in both a child and an ancestor model for multi-table inheritance (they are columns in two different database tables).
Django will raise a FieldError if you override any model field in any ancestor model.
However, if the attribute is not a Field instance (very unlikely though), you will be able to achieve exactly what you want by using using #property decorator. Something like this should work:
class Person(models.Model):
def __init__(self, *args, **kwargs):
super(Person, self).__init__(*args, **kwargs)
self.last_name
first_name = models.CharField(max_length=30)
#property
def last_name(self):
raise NotImplementedError
class Meta:
abstract = True
class Student(Person):
home_group = models.CharField(max_length=5)
last_name = "Doe" # "models.CharField()" will not work!
class BadStudent(Person):
home_group = models.CharField(max_length=5)
# "NotImplmentedError" will be raised when instantiating BadStudent()
You may also want to take a look at abc.abstractproperty. I am not sure how it would work with Django's model inheritance though.
Why would you want to do it?? Which are the reasons the common field cannot be declared in the AbstractModel??
If you really want to do it, use the instructions here:
add methods in subclasses within the super class constructor