Is there an elegant way to define a model with all fields optional, and then to define a second model which is identical but with all fields required? Or, alternatively, to modify the required/optional status of all fields on the first model?
An example:
class PersonalInformation(BaseModel):
title: Optional[str]
forename: Optional[str]
surname: Optional[str]
When doing a PATCH update (via FastAPI) on the DB data associated with this model, I want all the fields to be optional, so that the API user can supply only the fields they want to update. But later, in business logic, I want to be sure that all the fields are present. Specifying all the field names and base types twice seems like overkill, though: is there a way to do this without specifying two separate models in their entirety (as below)?
class PersonalInformationUpdate(BaseModel):
title: Optional[str]
forename: Optional[str]
surname: Optional[str]
class PersonalInformation(BaseModel):
title: str
forename: str
surname: str
The motivation is that, for larger models with complex validation, separate models leads to a fair amount of code duplication and a higher possibility of errors creeping in.
Hello @garry-jeromson
You could go with something like this
from typing import Optional, Type
from pydantic import BaseModel, create_model
class PersonalInformation(BaseModel):
title: Optional[str]
forename: Optional[str]
surname: Optional[str]
def to_required(model: Type[BaseModel]) -> Type[BaseModel]:
required_fields = {
field_name: (model_field.type_, ...)
for field_name, model_field in model.__fields__.items()
}
return create_model(f'Required{model.__name__}', **required_fields)
PersonalInformation()
# OK
RequiredPersonalInformation = to_required(PersonalInformation)
RequiredPersonalInformation()
# title
# field required (type=value_error.missing)
# forename
# field required (type=value_error.missing)
# surname
# field required (type=value_error.missing)
class MandatoryPersonalInformation(BaseModel):
title: str
forename: str
surname: str
def to_optional(model: Type[BaseModel]) -> Type[BaseModel]:
optional_fields = {
field_name: (model_field.type_, None)
for field_name, model_field in model.__fields__.items()
}
return create_model(f'Optional{model.__name__}', **optional_fields)
MandatoryPersonalInformation()
# title
# field required (type=value_error.missing)
# forename
# field required (type=value_error.missing)
# surname
# field required (type=value_error.missing)
OptionalPersonalInformation = to_optional(MandatoryPersonalInformation)
OptionalPersonalInformation()
# OK
You will need to make some tweaks if you have more complex fields.
Hope it helps !
A gentleman and a scholar. Thanks!
Sorry for the noise on a closed issue, but it seems preferable to opening another issue on a topic that already exists in multiple forms.
@PrettyWood You have suggested this approach more than once, is there a reason to do it your way rather than the following?
class MandatoryPersonalInformation(BaseModel):
title: str
forename: str
surname: str
def to_optional(model: Type[BaseModel]) -> Type[BaseModel]:
baseclass: Any = model
class Partial(baseclass):
pass
for field in Partial.__fields__.values():
field.required = False
Partial.__name__ = f"Partial{model.__name__}"
return Partial
OptionalPersonalInformation = to_optional(MandatoryPersonalInformation)
OptionalPersonalInformation()
With your solution I had trouble with mypy when using the resulting model.
Hi @dashavoo,
It's just the first solution that came to my mind but of course your solution works as well.
I didn't even check how it would work out with mypy ;)
Thanks for sharing
Most helpful comment
Hello @garry-jeromson
You could go with something like this
You will need to make some tweaks if you have more complex fields.
Hope it helps !