-
Notifications
You must be signed in to change notification settings - Fork 243
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
feat: Computed field serialization for TypedDict #1018
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #1018 +/- ##
==========================================
+ Coverage 93.12% 93.15% +0.03%
==========================================
Files 106 106
Lines 15952 15994 +42
Branches 35 35
==========================================
+ Hits 14855 14899 +44
+ Misses 1090 1088 -2
Partials 7 7
... and 2 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
CodSpeed Performance ReportMerging #1018 will improve performances by 13.01%Comparing Summary
Benchmarks breakdown
|
please review |
caa7cf9
to
a1bba74
Compare
e7d7a9a
to
8525d28
Compare
8525d28
to
264e6ac
Compare
54203aa
to
4aa13c4
Compare
4aa13c4
to
6dc985f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks generally like a fine approach, with a couple of small thoughts. I wonder how this looks when applied to a TypedDict
? I'm unsure if it's actually valid to have methods on a TypedDict
?
We compute based on the serialization function provided in the schema instead of adding a |
At runtime yes. But type checkers hate it (insert meme reference). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 so I think then in principle we can allow these fields on TypedDict
and users just have to type: ignore
them?
// Backwards compatiability. | ||
let mut legacy_attr_error: Option<PyErr> = None; | ||
let legacy_result = match ob_type_lookup.get_type(input_value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's fair to call this "backwards compatibility" when this is still expected to be the main code path for models and dataclasses.
I wonder if there might be a more unified way. For a model or dataclass A
with computed field b
, the analogous functionality really seems to be A.b.__get__(instance)
. For a TypedDict
it looks like that also works:
>>> class Bar(TypedDict):
... @property
... def y(self):
... return 434
...
>>> Bar.y.__get__({})
434
So maybe what we really want, in all cases, is
let property_value = input_value.get_type().getattr(field.property_name_py.as_ref(py))?.call_method1("__get__", (input_value,))?;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thinking was that a serialization function should be provided to a computed field, doesn't matter if the input type is a Model
, TypedDict
, Dataclass
, etc.
The default behavior is to compute the computed value from the function provided in the serialization schema and then it gets set in the output_dict
:
pydantic-core/src/serializers/computed_fields.rs
Lines 144 to 154 in 866eb2d
let value = self | |
.serializer | |
.to_python(next_value, next_include, next_exclude, extra)?; | |
if extra.exclude_none && value.is_none(py) { | |
return Ok(()); | |
} | |
let key = match extra.by_alias { | |
true => self.alias_py.as_ref(py), | |
false => property_name_py, | |
}; | |
output_dict.set_item(key, value)?; |
This seems more generalizable to all computed fields instead of relying on the computed field defined as an attribute on the input value.
However, I am probably missing some context on how computed fields are used by https://github.com/pydantic/pydantic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not following all that closely but my 2c is that ideally we extract the function from the thing in pydantic
and not in pydantic-core
so that:
- We have more flexibility. It's easier to hack things (like rebuild
__mro__
based on__orig_bases__
which we do for TypedDict) - It ensures that we do this at schema build time and not runtime
The con of that last one is that in theory someone could want us to use the method on a subclass they pass in as a value, which doesn't apply to TypedDict but also is not what we do for BaseModel and no one has complained 😄
@davidhewitt, do you expect us to pick back up with this soon? |
Change Summary
Updated computed field serialization to use the serialization function directly (if one exists) instead of always looking up a model attribute.
Related issue number
fix #657
Checklist
pydantic-core
(except for expected changes)Selected Reviewer: @samuelcolvin