-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Support for Descriptors in Model Fields #11148
Comments
A proposed solution: introduce
With my proposed changes, the above example can be re-written as: from pydantic import ModelFieldDescriptor
from pydantic import BaseModel, Field
from pydantic.fields import FieldInfo
from pydantic_core import PydanticUndefined
from typing import Any
class FieldDescriptor(ModelFieldDescriptor):
def __init__(self, /, default: Any = PydanticUndefined, field=None):
super().__init__(default=default, field=field)
self.__values = {}
def __get__(self, instance, owner):
if instance is not None:
try:
return self.__values[id(instance)][self.name]
except KeyError:
return self.field.default
return self
def __set__(self, instance, value):
self.__values.setdefault(id(instance), {})[self.name] = value
class Foo(BaseModel):
s: str = FieldDescriptor(field=Field("Nick", alias="String"))
i: int
@property
def ii(self) -> int:
return self.i
>>> f = Foo(i=123)
>>> f
Foo(i=123, s='Nick')
>>> f.__dict__
{'i': 123}
>>> f.s
'Nick'
>>> f.i
123
>> f.model_dump()
{'i': 123, 's': 'Nick'}
>> Foo.__pydantic_descriptor_fields__
{'s'} |
After discussion with @Vilicos, it's clear that more debate on descriptors is needed. In case anyone wants such functionality in the meantime, you can find a stop-gap solution here and here - caveat emptor! |
Initial Checks
Description
[Raising this issue as a prelude to submitting PRs for pydantic and pydantic_core, with some suggested solutions]
Pydantic has features which break the use of descriptors for model fields. Principally:
__init__()
callsself.__pydantic_validator__.validate_python
, which ends up settingself.__dict__
directlyBaseMode.__setattr__()
sets model fields viaself.__dict__[name] = ...
model_dump()
andmodel_dump_json()
access__dict__
directly (viaModelSerializer.get_inner_value()
in pydantic_core)__repr_args__
,__iter__
and__eq__
for BaseModel access model fields viaself.__dict__
collect_model_fields()
will actually delete descriptors in some casesYou can see this behaviour with the following code:
However, if we use a dataclass and TypeAdapter, it all works beautifully!
I presume the existing implementation and its reliance on direct access to
__dict__
was done for a reason, so I'm trying to make this work without wholesale changes. My approach is to add__pydantic_descriptor_fields__
, to identify those which use a descriptor, and add functionality slightly in the mould of__pydantic_extra__
.PRs to follow ...
Affected Components
.model_dump()
and.model_dump_json()
model_construct()
, pickling, private attributes, ORM modeThe text was updated successfully, but these errors were encountered: