-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Only evaluate FieldInfo annotations if required during schema building
#10769
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
Conversation
| @@ -1215,11 +1215,15 @@ def _common_field_schema( # C901 | |||
| ) -> _CommonField: | |||
| # Update FieldInfo annotation if appropriate: | |||
| FieldInfo = import_cached_field_info() | |||
| if has_instance_in_type(field_info.annotation, (ForwardRef, str)): | |||
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.
The interesting part is that we remove the call to has_instance_in_type here.
Deploying pydantic-docs with
|
| Latest commit: |
076da3c
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://bf7a63be.pydantic-docs.pages.dev |
| Branch Preview URL: | https://fieldinfo-evaluated.pydantic-docs.pages.dev |
| @@ -1338,12 +1342,13 @@ def _type_alias_type_schema(self, obj: TypeAliasType) -> CoreSchema: | |||
| return maybe_schema | |||
|
|
|||
| origin: TypeAliasType = get_origin(obj) or obj | |||
|
|
|||
| annotation = origin.__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.
Moved below, as accessing the __value__ attribute will trigger the evaluation of the annotation, and thus this can raise a NameError. The eval_type call is still necessary though, as you can have explicit string references inside a a type alias type.
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.
Maybe I'm misunderstanding - was this moved below? Or do we not need this specific line anymore?
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.
Basically, here is what can happen with type aliases:
type A = Int
annotation = origin.__value__
# NameError, 'Int' is not defined
type B = 'Int'
annotation = origin.__value__
assert annotation == 'Int'
eval_type(B, ..., ...)
# NameError, 'Int' is not definedSo everything was moved in a single try..except block:
try:
annotation = _typing_extra.eval_type(origin.__value__, *self._types_namespace)
except ...: ...
CodSpeed Performance ReportMerging #10769 will improve performances by 8.54%Comparing Summary
Benchmarks breakdown
|
367f48a to
88aa322
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.
Alrighty, left some initial comments on the implementation.
The tuple return doesn't feel like the cleanest implementation, but I can't come up with anything better off the top of my head. Let's chat first thing tomorrow morning about alternatives. I'm not opposed to this approach, but want to think about other options before we move forward 👍
| '`eval_type_lenient` is deprecated, use `eval_type` with `lenient=True` instead.', | ||
| '`eval_type_lenient` is deprecated, use `try_eval_type` instead.', |
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.
Hmm, this feels like an anti-pattern - if we already deprecated this, there's probably a reason why we didn't want to have two functions. Even if we disagree with this deprecation now, seems like a lot of churn to basically reintroduce the function and redirect for the deprecated case...
I believe my suggestion above re always returning a second value, evaluated, even in the case of eval_type with lenient=False might help, and we can leave this be?
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.
A few more follow ups:
- This is a very internal function - can we just deprecate this?
- If we were to bring back a second function, to reduce churn, can we call it
eval_type_lenient?
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 had to keep this function during the types namespaces refactor because fastapi makes use of it 🫤. I added the deprecated decorator so that type checking fails in the fastapi CI and they can use the correct function (or best, do not rely on our internals because I don't think the _typing_extra module is stable enough, we might apply other changes here in the future).
I believe my suggestion above re always returning a second value,
evaluated, even in the case ofeval_typewithlenient=Falsemight help, and we can leave this be?
Same issue as https://github.com/pydantic/pydantic/pull/10769/files#r1832947088, we can't have a single function because we want to catch the NameError to reraise PydanticUndefinedAnnotation
| @@ -1338,12 +1342,13 @@ def _type_alias_type_schema(self, obj: TypeAliasType) -> CoreSchema: | |||
| return maybe_schema | |||
|
|
|||
| origin: TypeAliasType = get_origin(obj) or obj | |||
|
|
|||
| annotation = origin.__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.
Maybe I'm misunderstanding - was this moved below? Or do we not need this specific line anymore?
Wow @Viicos, nice work here. Didn't realize this would have that significant of an impact. We can chat about potential other APIs for this, but we should definitely move forward with this change :). Does this unblock the |
|
Another follow up - what if we returned something like this: from python import TypedDict
class TypeEvaluationResult(TypedDict):
evaluated: bool
result: Any
error: NameError | NoneOr something like: from python import TypedDict, TypeAlias
class TypeEvaluationSuccess(TypedDict):
evaluated: Literal[True]
result: Any
class TypeEvaluationFailure(TypedDict):
evaluated: Literal[False]
error: NameError
type TypeEvaluationResult = TypeEvaluationSuccess | TypeEvaluationFailureDoesn't have to be either of those exactly, but I think you get the point :). Then, we could have one function and preserve relevant errors, but also intuitively handle attempted evals? Maybe this isn't better, but I think it does help us avoid the overloads, though admittedly there's more conditional logic required after a function call, as the result can be many things. Could even do a named tuple. |
|
Seems like both the overload and structured return value have different drawbacks: what is Maybe it's worth revisiting the API, if we want to make this module more stable and maybe expose it publicly, as it is used already by some other projects. |
|
Yeah, fair point. Let's not go with the structured return. One other idea - could we revert to that single signature idea, and manufacture a |
|
You mean something like |
|
Or even like |
|
but the issue is we currently raise using
|
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.
Looking over this again, seems reasonable, given the constraints we've discussed re typing and overloads.
|
Going to go ahead and merge. We can cherry pick relevant stuff for the v2.10 official release (not including this). |
By adding a new `evaluated` attribute that can be set during model fields collection. Also refactor a bit the utility functions to eval such annotations.
ed6e232 to
076da3c
Compare


By adding a new
evaluatedattribute that can be set during model fields collection. Also refactor a bit the utility functions to eval such annotations.Change Summary
Related issue number
Checklist