Skip to content
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 recursive dataclasses #5760

Merged
merged 5 commits into from
May 17, 2023
Merged

Support recursive dataclasses #5760

merged 5 commits into from
May 17, 2023

Conversation

dmontagu
Copy link
Contributor

@dmontagu dmontagu commented May 15, 2023

Closes #5721. This is also a precursor to work on generic dataclasses.

I'll open the PR now, but I think there's a variety of things to establish some consensus on before merging:

  • Do we want to unify the model-not-fully-defined and (analogous) dataclass-not-fully-defined usage errors?
    • Actually, currently in this PR, I have removed model-not-fully-defined and replaced it with a common class-not-fully-defined error, but would be happy to revert this.
    • If we do revert this change, is dataclass-not-fully-defined an acceptable name?
      • Would we essentially fully duplicate the documentation of model-not-fully-defined considering it's largely similar and we have a docs section for each usage error code?
  • I have renamed BaseModel.__pydantic_model_complete__ to BaseModel.__pydantic_complete__ and used the same property on pydantic dataclasses. Any objections?
  • With BaseModels, to do an explicit rebuild you call the BaseModel.model_rebuild method. I have exported an analogous function called pydantic.dataclasses.rebuild_dataclass.
    • Is this name (and import path) acceptable?
    • Also, the function is defined in pydantic._internal._dataclasses and re-exported in pydantic.dataclasses. Is this acceptable?
    • (Actually, I think Adrian wanted to add a method called __pydantic_dataclass_rebuild__ or similar to pydantic dataclasses — is that still the case? If so, what should the name of that method be?)

I'll be happy to make any requested changes.

Selected Reviewer: @samuelcolvin

@dmontagu
Copy link
Contributor Author

please review

pydantic/main.py Outdated Show resolved Hide resolved
if _parent_namespace_depth > 0:
frame_parent_ns = _typing_extra.parent_frame_namespace(parent_depth=_parent_namespace_depth) or {}
# cls_parent_ns = cls.__pydantic_parent_namespace__ or {}
# cls.__pydantic_parent_namespace__ = {**cls_parent_ns, **frame_parent_ns}
Copy link
Contributor Author

@dmontagu dmontagu May 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we want to store the original namespace on the dataclasses like we do for BaseModel.

This helps in the scenario where rebuilds are necessary when importing from a separate module and there was a ForwardRef to a type in the module in which the class was originally defined.

This may also be necessary when parametrizing generic cyclically-recursive dataclasses outside the module in which they were defined.

I've left these lines present and commented as a reminder to decide about this. I'm okay either way.

Note: related, it may also be a good idea to make the _types_namespace argument here and/or on BaseModel "public". (For context: It currently has the leading _ because we wanted to discourage its usage because picking up references from the current namespace should generally work, but we still included the argument in BaseModel.model_rebuild to make it possible to more explicitly control what ForwardRefs evaluate to if you know what you are doing.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty neutral / would defer to you on it.

My preference is "simple wins", so I think I'd prefer not to store the namespace, but don't really mind.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a chance to speak with Adrian about this briefly, and I think we agreed it would be nice to keep the behavior as similar as possible between BaseModel and pydantic dataclass.

I do see your point about keeping things simple, but I also vaguely recall that I had added that __pydantic_parent_namespace__ in the BaseModel case for the purpose of dealing with recursive generics.

Before proceeding to merge this branch, I'm going to try to work on top of this branch and get recursive generic dataclasses working. If it becomes clear that I will need the __pydantic_parent_namespace__ thing to get that to behave nicely, it might make this a moot point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, given there is a lot of complexity related to generic dataclasses and how exactly we should handle them, and not handling it properly is not a regression from v1 (as it wasn't handled properly there), my new plan is to not fiddle with the parent namespace, and also not support generic dataclasses in v2 (but perhaps in v2.1, or maybe sooner if we happen to get it all working in time.. but the pressure is off.)

docs/usage/errors.md Outdated Show resolved Hide resolved
Copy link
Member

@samuelcolvin samuelcolvin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otherwise LGTM.

docs/usage/errors.md Outdated Show resolved Hide resolved
docs/usage/errors.md Outdated Show resolved Hide resolved
pydantic/_internal/_dataclasses.py Outdated Show resolved Hide resolved
if _parent_namespace_depth > 0:
frame_parent_ns = _typing_extra.parent_frame_namespace(parent_depth=_parent_namespace_depth) or {}
# cls_parent_ns = cls.__pydantic_parent_namespace__ or {}
# cls.__pydantic_parent_namespace__ = {**cls_parent_ns, **frame_parent_ns}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty neutral / would defer to you on it.

My preference is "simple wins", so I think I'd prefer not to store the namespace, but don't really mind.

pydantic/dataclasses.py Outdated Show resolved Hide resolved
@cloudflare-pages
Copy link

cloudflare-pages bot commented May 15, 2023

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2ae81be
Status: ✅  Deploy successful!
Preview URL: https://d657bb80.pydantic-docs2.pages.dev
Branch Preview URL: https://dataclasses-rebuild.pydantic-docs2.pages.dev

View logs

Copy link
Member

@samuelcolvin samuelcolvin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

conflicts, otherwise LGTM.

Merge when passing.

@samuelcolvin
Copy link
Member

please update.

@dmontagu dmontagu enabled auto-merge (squash) May 17, 2023 20:34
@dmontagu dmontagu merged commit 9aed15b into main May 17, 2023
41 checks passed
@dmontagu dmontagu deleted the dataclasses-rebuild branch May 17, 2023 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ForwarRef of dataclasses
2 participants