Reputation: 1088
I have 2 classes:
class UserCreate(BaseModel):
avatar: HttpUrl = Field(..., description="Avatar", example="https://picsum.photos/200")
name: str = Field(..., max_length=20, description="A single word", example='Ivan')
birthdate: datetime_date = Field(..., description="Two digits", example='1980-1-1')
comment: Optional[str] = Field(..., max_length=512, description="lorem ipsum about a user", example='blah blah')
and I want to create a UserUpdate
class that will inherit every field from the parent class and make it Optional
.
a Result class must look like:
class UserUpdate(BaseModel):
avatar: typing.Optional[HttpUrl] = Field(..., description="Avatar", example="https://picsum.photos/200")
name: typing.Optional[str] = Field(..., max_length=20, description="A single word", example='Ivan')
birthdate: typing.Optional[datetime_date] = Field(..., description="Two digits", example='1980-1-1')
comment: typing.Optional[str] = Field(..., max_length=512, description="lorem ipsum about a user", example='blah blah')
But obviously, I want to make it automatically, like:
class UserUpdate(UserCreate):
def foo(fields_from_user_create):
for fields in fields_from_user_create:
field = typing.Optional(field)
Upvotes: 3
Views: 4935
Reputation: 1797
Based on my answer from here:
from typing import Optional, Type, Any, Tuple
from copy import deepcopy
from pydantic import BaseModel, create_model
from pydantic.fields import FieldInfo
def partial_model(model: Type[BaseModel]):
def make_field_optional(field: FieldInfo, default: Any = None) -> Tuple[Any, FieldInfo]:
new = deepcopy(field)
new.default = default
new.annotation = Optional[field.annotation] # type: ignore
return new.annotation, new
return create_model(
f'Partial{model.__name__}',
__base__=model,
__module__=model.__module__,
**{
field_name: make_field_optional(field_info)
for field_name, field_info in model.__fields__.items()
}
)
Usage:
@partial_model
class Model(BaseModel):
i: int
f: float
s: str
Model(i=1)
Upvotes: 0
Reputation: 1088
A solution directly from PrettyWood
Here is a way of doing it
from copy import deepcopy
from typing import Optional, Type, TypeVar
from pydantic import BaseModel, create_model
BaseModelT = TypeVar('BaseModelT', bound=BaseModel)
def to_optional(model: Type[BaseModelT], name: Optional[str] = None) -> Type[BaseModelT]:
"""
Create a new BaseModel with the exact same fields as `model`
but making them all optional
"""
field_definitions = {}
for name, field in model.__fields__.items():
optional_field_info = deepcopy(field.field_info)
# Do not change default value of fields that are already optional
if optional_field_info.default is ...:
optional_field_info.default = None
field_type = model.__annotations__.get(name, field.outer_type_)
field_definitions[name] = (field_type, optional_field_info)
return create_model(name or f'Optional{model.__name__}', **field_definitions) # type: ignore[arg-type]
Hope it helps ;)
Upvotes: 2
Reputation: 1512
I am using the following approach (credit goes to Aron Podrigal):
import inspect
from pydantic import BaseModel
def optional(*fields):
"""Decorator function used to modify a pydantic model's fields to all be optional.
Alternatively, you can also pass the field names that should be made optional as arguments
to the decorator.
Taken from https://github.com/samuelcolvin/pydantic/issues/1223#issuecomment-775363074
"""
def dec(_cls):
for field in fields:
_cls.__fields__[field].required = False
return _cls
if fields and inspect.isclass(fields[0]) and issubclass(fields[0], BaseModel):
cls = fields[0]
fields = cls.__fields__
return dec(cls)
return dec
In your example you'd use it like this:
@optional
class UserUpdate(UserCreate):
pass
Upvotes: 3