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

Dataclass extra #3393

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/3393-DetachHead.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dataclasses now support the `extra` config option
13 changes: 11 additions & 2 deletions pydantic/dataclasses.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Type, TypeVar, Union, overload
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Type, TypeVar, Union, get_type_hints, overload

from .class_validators import gather_all_validators
from .error_wrappers import ValidationError
from .errors import DataclassTypeError
from .fields import Field, FieldInfo, Required, Undefined
from .main import create_model, validate_model
from .main import Extra, create_model, validate_model
from .typing import resolve_annotations
from .utils import ClassAttribute

Expand Down Expand Up @@ -188,6 +188,15 @@ def _process_class(
cls.__name__, __config__=config, __module__=_cls.__module__, __validators__=validators, **field_definitions
)

if getattr(config, 'extra', Extra.forbid) is not Extra.forbid:
def allow_extra_init(self, *args, **kwargs):
self.__original_init__(*args, **{
k: v for k, v in kwargs.items() if k in get_type_hints(type(self))
})

cls.__original_init__ = cls.__init__
Copy link
Member

Choose a reason for hiding this comment

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

Looks like this is never called. We need a test for this.

cls.__init__ = allow_extra_init
Copy link
Member

Choose a reason for hiding this comment

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

@PrettyWood I remember we had lots of problems with overriding __init__ at some points.

Do you think this will break anything?

What about conflicts with #2557, will it just be code conflicts, or does this approach need to be rethought?

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 sorry @DetachHead but I felt like it was way easier to add it in 30b58f3 rather than handling conflicts :/

@samuelcolvin IMO if the commit above is good we can close this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@PrettyWood looks good. though i can't find the branch that commit is on? is there a PR for it?


cls.__initialised__ = False
cls.__validate__ = classmethod(_validate_dataclass) # type: ignore[assignment]
cls.__get_validators__ = classmethod(_get_validators) # type: ignore[assignment]
Expand Down
29 changes: 28 additions & 1 deletion tests/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import pytest

import pydantic
from pydantic import BaseModel, ValidationError, validator
from pydantic import BaseModel, Extra, ValidationError, validator


def test_simple():
Expand Down Expand Up @@ -945,3 +945,30 @@ def __new__(cls, *args, **kwargs):
instance = cls(a=test_string)
assert instance._special_property == 1
assert instance.a == test_string


def test_allow_extra():
class C:
extra = Extra.ignore

@pydantic.dataclasses.dataclass(config=C)
class Foo:
a: str
b: str

Foo(**{"a": "bar", "b": "foo", "c": 1})
Copy link
Member

Choose a reason for hiding this comment

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

  • single quotes
  • Foo(a='bar'...) would be cleaner
  • you need to add assert not hasattr(foo, 'c')
  • you need to add another test for Extra.allow



def test_allow_extra_subclass():
class C:
extra = Extra.ignore

@pydantic.dataclasses.dataclass(config=C)
class Foo:
a: str

@pydantic.dataclasses.dataclass(config=C)
class Bar(Foo):
b: str

Bar(**{"a": "bar", "b": "foo", "c": 1})