-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Add support for stringified annotations when using PrivateAttr with Annotated
#10157
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
Deploying pydantic-docs with
|
| Latest commit: |
298c4d2
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://b3b63e10.pydantic-docs.pages.dev |
| Branch Preview URL: | https://10153.pydantic-docs.pages.dev |
sydney-runkle
left a comment
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.
Quick question about namespaces.
| if isinstance(ann_type, str): | ||
| # Walking up the frames to get the module namespace where the model is defined | ||
| # (as the model class wasn't created yet, we unfortunately can't use `cls.__module__`): | ||
| module = inspect.getmodule(sys._getframe(2)) | ||
| if module is not None: | ||
| ann_type = eval_type_backport(ForwardRef(ann_type), globalns=module.__dict__) |
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.
What if the model is defined in a function?
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.
It will fail indeed. Not having access to the current model class makes things annoying as stated in my comment. Not sure if we can address this issue? As I said in the original issue, the Annotated pattern isn't recommended anyway.
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 think we should consider many of the questions we've been thinking about for our other recent namespace management changes - how are things different if we rebuild or import a model in a new file?
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.
Should we be filtering the namespace with our new logic in _typing_extra.py?
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.
how are things different if we rebuild or import a model in a new file?
In these cases we have access to the class' module, so it helps as we can easily combine the module namespace and parent namespace (if necessary).
I'll note that because the way inspect.getmodule works, the following is ok:
from typing import Annotated
from pydantic import BaseModel, PrivateAttr
def func():
class Model(BaseModel):
_foo: 'Annotated[int, PrivateAttr()]'
func()(however, it does not if PrivateAttr and/or Annotated is imported inside func).
We can consider many scenarios where even our current annotation evaluation for normal fields (i.e. after cls is created in the metaclass, where all the logic to get the parent ns and merge it with the module ns is done) does not work as expected:
from __future__ import annotations
from pydantic import BaseModel
def test():
from typing import Annotated
def inner():
from pydantic import Field
class Model(BaseModel):
a: Annotated[int, Field(default=1)]
return Model
Model = inner()
test()Trying to support these cases would probably complicate things (and also maybe increase memory/cpu usage). I'm also wondering how much of these use cases can be covered by PEP 649.
CodSpeed Performance ReportMerging #10157 will not alter performanceComparing Summary
|
| module = inspect.getmodule(sys._getframe(2)) | ||
| if module is not None: | ||
| ann_type = eval_type_backport(ForwardRef(ann_type), globalns=module.__dict__) |
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.
| module = inspect.getmodule(sys._getframe(2)) | |
| if module is not None: | |
| ann_type = eval_type_backport(ForwardRef(ann_type), globalns=module.__dict__) | |
| frame = sys._getframe(2) | |
| if frame is not None: | |
| ann_type = eval_type_backport(ForwardRef(ann_type), globalns=frame.f_globals, localns=frame.f_locals) |
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.
Agreed with a movement towards this approach, but should we just use
pydantic/pydantic/_internal/_typing_extra.py
Line 188 in 7212484
| def parent_frame_namespace(*, parent_depth: int = 2) -> dict[str, Any] | None: |
If we go with that, then you can ignore all of my above comments, bc I think this would address them.
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.
parent_frame_namespace only considers locals so I went with the frame locals and globals
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.
Sounds good to me. Thanks for following up!
Co-authored-by: Alex Hall <[email protected]>
sydney-runkle
left a comment
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.
Nice work!

Change Summary
Related issue number
Checklist