New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom Root Types - validate(), dict() not as expected #1193
Comments
Reference: #730 ( |
Yes, |
Regarding If there are problems with fastAPI, please create an issue there. |
I think this may be related, apologies if not... But I would really love to be able to instantiate a model that uses This is particularly in the case where the rooted model is a child attr of the parent model you are instantiating. I realise there is one case which works, you can: class RootedModel(BaseModel):
__root__: Dict[str, str]
RootedModel.parse_obj({"dynamic_field": "some value"}) But this fails as soon as you want to instantiate the parent: >>> class ParentModel(BaseModel):
>>> rooted: RootedModel
>>>
>>> ParentModel.parse_obj({"rooted": {"dynamic_field": "some value"}})
ValidationError: 1 validation error for ParentModel
rooted -> __root__
field required (type=value_error.missing) I don't really like that the It would be great if the existing special case, for |
Well, it seems that an easy workaround in this example is to eliminate class ParentModel(BaseModel):
rooted: Dict[str, str]
ParentModel.parse_obj({"rooted": {"dynamic_field": "some value"}}) ...in which case I am not really sure what the point of Well, on large models it allows to separate the validation etc more logically. Or maybe you want to reuse the definition of |
Weird, I had the opposite issue: able to instantiate via a parent class but not directly. To overcome this, I'm now detecting direct instantiations and fixing the parameters passed to BaseModel, explicitly setting the class MovieGenre(BaseModel):
class MovieGenreEnum(str, Enum):
Action = "Action"
Drama = "Drama"
__root__: MovieGenreEnum
def __init__(self, *args, **kwargs):
if len(args) == 1 and type(args[0]) == str:
# a genre was passed as a parameter - this is a direct instantiation
genre = args[0]
super().__init__(__root__=MovieGenreEnum(genre), *args[1:], **kwargs)
else:
super().__init__(*args, **kwargs) A similar approach might solve your issue too. I wonder if there's a more elegant solution though. |
Regarding validation, from typing import Dict
from pydantic import BaseModel, validate_model
class StrDict(BaseModel):
__root__: Dict[str, str]
value, fields_set, error = validate_model(StrDict, {'foo': 'bar'})
print(error) yields
Is this the intended behavior? |
yes, with |
Okay. The feeling I'm getting is that the output of foo: BaseModel = ...
value, fields_set, error = validate_model(foo.__class__, foo.dict())
assert error is None for any This seems to be true currently, and if it is meant to be true generally, this indicates a validation bug that mirrors the from typing import Dict
from pydantic import BaseModel, validate_model
class StrDict(BaseModel):
__root__: Dict[str, str]
class NestedDict(BaseModel):
v: StrDict
value, fields_set, error = validate_model(NestedDict, {'v': {'foo': 'bar'}})
print(error) |
Hi! @classmethod
def validate(cls: Type['Model'], value: Any) -> 'Model':
- if isinstance(value, dict):
+ if cls.__custom_root_type__:
+ return cls.parse_obj(value)
+ elif isinstance(value, dict):
return cls(**value)
elif isinstance(value, cls):
return value.copy()
elif cls.__config__.orm_mode:
return cls.from_orm(value)
- elif cls.__custom_root_type__:
- return cls.parse_obj(value)
else:
try:
value_as_dict = dict(value) |
@PrettyWood This doesn't seem to be fixed at all. Edit: I've just realized that the issue with As a workaround I've created a sub class now to change the behavior and be in sync with class BaseModel(PydanticBaseModel):
def dict(self, *args, **kwargs):
data = super().dict(*args, **kwargs)
return data[ROOT_KEY] if self.__custom_root_type__ else data |
Pydantic v2 class IdsOrAll(RootModel):
root: Union[list[int], Literal["all"]
class DTO(BaseModel):
applicable_to_location_ids: IdsOrAll
dto = DTO(applicable_to_location_ids=IdsOrAll(root="all"))
dto.model_dump() # produces {"applicable_to_location_ids": "all"}, should be {"applicable_to_location_ids": IdsOrAll} Is that expected? |
pydantic offers a feature called "Custom Root Type": https://pydantic-docs.helpmanual.io/usage/models/#custom-root-types
For now it looks like there is general poor support for such objects across available BaseModel methods.
validate()
function used byfastapi
does not consider this setting at all:This should pass, however
pydantic.errors.DictError: value is not a valid dict
is raised instead.validate()
function also isn't documented at all.While such thing is implemented by
parse_obj()
it does not implement other features thatvalidate()
has, for examplecls.__config__.orm_mode
.Also these two functions looks pretty the same, what are the differences between them?
dict()
method used byfastapi
returns value other than expectedWhile this is documented, and probably
dict()
should not return anything other thandict
at the moment there is no function opposite toparse_obj()
, eg. returning whatdict()
returns for normal models, and direct__root__
value for custom root type objects. Maybeserialize_obj()
should be added?Custom Root Type could probably use single
__init__
parameter.This would allow to completly hide root argument
Custom Root Type models should be a separate classes?
As handling them is a separate thing, and putting
if __custom_root_type__
everywhere does not seem to be reasonable.The text was updated successfully, but these errors were encountered: