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)
Related
I would like to generate a Pydantic model that inherits from a parent class, but only has a subset of that parent model's fields.
E.g. ModelB should inherit only field_b from ModelA:
from pydantic import BaseModel
class ModelA(BaseModel):
field_a: str
field_b: str
class ModelB(ModelA):
pass
As far as I know, there is no built-in mechanism for this in Pydantic.
Difficult solutions
You could start messing around with the internals like __fields__
and __fields_set__, but I would strongly advise against it. I think this may be less than trivial because you need to take into account validators that are already registered and maybe a bunch of other stuff that happens internally, one a field is defined on a model.
You could also go the route of e.g. defining your own __init_subclass__ on ModelA or even subclassing ModelMetaclass, but this will likely lead to the same difficulties. Unless you are very familiar with the intricacies of Pydantic models and are prepared to rework your code, if something fundamentally changes on their end, I would not recommend this.
I can think of a few workarounds though.
Potential workarounds
The simplest one in my opinion is simply factoring out the fields that you want to share into their own model:
from pydantic import BaseModel
class ModelWithB(BaseModel):
field_b: str
class ModelA(ModelWithB):
field_a: str
class ModelB(ModelWithB):
pass
This obviously doesn't work, if you have no control over ModelA. It also may mess up the order of the fields on ModelA (in this case field_b would come before field_a), which may or may not be important to you. Validation for example depends on the order in which fields were defined.
Another possible workaround would be to override the unneeded field in ModelB and make it optional with a None default and exclude it from dict and json exports:
from pydantic import BaseModel, Field
class ModelA(BaseModel):
field_a: str
field_b: str
class ModelB(ModelA):
field_a: str | None = Field(default=None, exclude=True)
b = ModelB(field_b="foo")
print(b.json())
Output:
{"field_b": "foo"}
Note that this does not actually get rid of the field. It is still there and by default still visible in the model's string representation for example, as well as in the model schema. But at least you never need to pass a value for field_a and it is not present, when calling dict or json by default.
Note also that you may run into addtional problems, if you have custom validators for field_a that don't work with a None value.
If you provide more details, I might amend this answer, but so far I hope this helps a little.
It was enough for me to hard copy the field and to adjust the extras I had defined. Here is a snippet from my code:
import copy
from pydantic import BaseModel
def copy_primary_field(
model_from: BaseModel,
model_to: BaseModel,
primary_key: str,
) -> BaseModel:
new_field_name = f"{model_from.__name__}" + "_" + primary_key
model_to.__fields__[new_field_name] = copy.deepcopy(
model_from.__fields__[primary_key]
)
model_to.__fields__[new_field_name].name = new_field_name
model_to.__fields__[new_field_name].field_info.extra["references"] = (
f"{model_from.__name__}" + ":" + primary_key
)
return model_to
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.
Is there a way to assign all fields from a base dataclass to another one such that MyPy understands which fields are present?
from dataclasses import dataclass
#dataclass
class TypeA:
name: str
age: int
#dataclass
class TypeB(TypeA):
more: bool
def upgrade(a: TypeA) -> TypeB:
return TypeB(
more=False,
**a, # this is syntax I'm uncertain of
)
I can use ** on a dataclasses.asdict or the __dict__ field, but that erases the type checking. For example, MyPy doesn't complain about this:
return TypeB(
**a.__dict__
)
Despite more being missing. It appears that MyPy loses type information on __dict__ (as well as dataclasses.asdict).
I don't want to list individual fields for copying for two reasons:
It's redundant
I may miss an optional field which would not be caught
I may later add an optiona/default field which would not be detected
I have a (dynamic) definition of a simple class, like so:
class Simple:
val: int = 1
I intend to use this definition to build a pydantic.BaseModel, so it can be defined from the Simple class; basically doing this, but via type, under a metaclass structure where the Simple class is retrieved from.
from pydantic import BaseModel
class SimpleModel(Simple, BaseModel):
pass
# Actual ways tried:
SimpleModel = type('SimpleModel', (Simple, BaseModel), {})
# or
SimpleModel = type('SimpleModel', (BaseModel, ), Simple.__annotations__)
However, this approach was not returning a model class with the parameters from the Simple class.
I understand that the BaseModel already uses a rather complex metaclass under the hood, however, my intended implementation is also under a metaclass, where I intend to dynamically transfer the Simple class into a BaseModel from pydantic.
Your suggestions will be kindly appreciated.
I managed to get this working by first, casting my Simple class to be a dataclass from pydantic, then getting a pydantic model from it.
I am not an expert in pydantic, so would not mind your views on the approach.
from pydantic.dataclasses import dataclass
SimpleModel = dataclass(Simple).__pydantic_model__
The trouble I did however find (same with an answer provided by #jsbueno), that when declaring annotation for data type for pathlib.Path (as an example) with BaseModel directly, the string value provided gets coerced to the annotation data type. But with my or #jsbueno approaches, the data type remains original (no coercion).
You can simply call type passing a dictionary made of SimpleModel's __dict__ attribute - that will contain your fileds default values and the __annotations__ attribute, which are enough information for Pydantic to do its thing.
I just would just take the extra step of deleting the __weakref__ attribute that is created by default in the plain "SimpleModel" before doing that - to avoid it pointing to the wrong class.
from pydantic import BaseModel
class Simple:
val: int = 1
new_namespace = dict(Simple.__dict__) # copies the class dictproxy into a plain dictionary
del new_namespace["__weakref__"]
SimpleModel = type("SimpleModel", (BaseModel,), new_namespace)
and we have
In [58]: SimpleModel.schema()
Out[58]:
{'title': 'Simple',
'type': 'object',
'properties': {'one_val': {'title': 'One Val',
'default': 1,
'type': 'integer'}}}
That works - but since Pydantic is complex, to make it more futureproof, it might be better to use the Pydantic's metaclass supplied namespace object instead of a plain dictionary - the formal way to do that is by using
the helper functions in the types model:
import types
from pydantic import BaseModel
class Simple:
val: int = 1
SimpleModel = types.new_class(
"SimpleModel",
(BaseModel,),
exec_body=lambda ns:ns.update(
{key: val for key, val in Simple.__dict__.items()
if not key.startswith("_")}
)
)
The new_type call computes the appropriate metaclass, and pass the correct namespace object to the callback in the exec_body argument. There, we just fill it with the contents of the dict on your dynamic class.
Here, I opted to update the namespace and filter all "_" values in a single line, but you can define the function passes to "exec_body" as a full multiline function and filter the contents you want out more carefully.
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.