Skip to content

Commit

Permalink
Partial Fix: init=False in dataclass fields ignored
Browse files Browse the repository at this point in the history
Why
- Previously, setting `init=False` in std field and pydantic's Field had
no effect, the field still appears as a parameter in the generated `__init__`

What
- Added `init` attribute to `FieldInfo` and `Field`
- Corrected docstring for `init_var`
- Added check for `init` in `make_pydantic_fields_compatible`
- Added test to confirm `init=False` fields don't show up in the signature

Notes
- Unfortunately, can still use the `init=False` as parameters in init...
I think this is because of pydantic allowing the "extra" params that aren't
explicitly defined
- Even setting `ConfigDict(extra='forbid')` doesn't help here. From some
debugging, it doesn't look like the model schema understands the `init=False`.
So more work needed to actually throw an error.
  • Loading branch information
tigeryy2 committed Jan 13, 2024
1 parent 2e459bb commit eea1b40
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 2 deletions.
4 changes: 4 additions & 0 deletions pydantic/dataclasses.py
Expand Up @@ -172,6 +172,10 @@ def make_pydantic_fields_compatible(cls: type[Any]) -> None:
if field_value.repr is not True:
field_args['repr'] = field_value.repr

# Set `init` attribute if it's explicitly specified to be `False`.
if field_value.init is False:
field_args['init'] = field_value.init

setattr(cls, field_name, dataclasses.field(**field_args))

def create_dataclass(cls: type[Any]) -> type[PydanticDataclass]:
Expand Down
14 changes: 12 additions & 2 deletions pydantic/fields.py
Expand Up @@ -64,6 +64,7 @@ class _FromFieldInfoInputs(typing_extensions.TypedDict, total=False):
frozen: bool | None
validate_default: bool | None
repr: bool
init: bool | None
init_var: bool | None
kw_only: bool | None

Expand Down Expand Up @@ -101,7 +102,9 @@ class FieldInfo(_repr.Representation):
frozen: Whether the field is frozen.
validate_default: Whether to validate the default value of the field.
repr: Whether to include the field in representation of the model.
init_var: Whether the field should be included in the constructor of the dataclass.
init: If true (the default), this field is included as a parameter to the generated `__init__` of the dataclass
init_var: Whether the field is an "init-only" field: used as a parameter to the generated `__init__` method, but
not included as a field of the resulting model.
kw_only: Whether the field should be a keyword-only argument in the constructor of the dataclass.
metadata: List of metadata constraints.
"""
Expand All @@ -122,6 +125,7 @@ class FieldInfo(_repr.Representation):
frozen: bool | None
validate_default: bool | None
repr: bool
init: bool | None
init_var: bool | None
kw_only: bool | None
metadata: list[Any]
Expand All @@ -143,6 +147,7 @@ class FieldInfo(_repr.Representation):
'frozen',
'validate_default',
'repr',
'init',
'init_var',
'kw_only',
'metadata',
Expand Down Expand Up @@ -203,6 +208,7 @@ def __init__(self, **kwargs: Unpack[_FieldInfoInputs]) -> None:
self.validate_default = kwargs.pop('validate_default', None)
self.frozen = kwargs.pop('frozen', None)
# currently only used on dataclasses
self.init = kwargs.pop('init', None)
self.init_var = kwargs.pop('init_var', None)
self.kw_only = kwargs.pop('kw_only', None)

Expand Down Expand Up @@ -623,6 +629,7 @@ def Field( # noqa: C901
frozen: bool | None = _Unset,
validate_default: bool | None = _Unset,
repr: bool = _Unset,
init: bool | None = _Unset,
init_var: bool | None = _Unset,
kw_only: bool | None = _Unset,
pattern: str | None = _Unset,
Expand Down Expand Up @@ -668,8 +675,10 @@ def Field( # noqa: C901
validate_default: If `True`, apply validation to the default value every time you create an instance.
Otherwise, for performance reasons, the default value of the field is trusted and not validated.
repr: A boolean indicating whether to include the field in the `__repr__` output.
init_var: Whether the field should be included in the constructor of the dataclass.
init: Whether the field should be included in the generated constructor of the dataclass.
(Only applies to dataclasses.)
init_var: Whether the field is `init-only`. Used as a parameter to the generated `__init__` method, but not
included as a field of the resulting model. (Only applies to dataclasses.)
kw_only: Whether the field should be a keyword-only argument in the constructor of the dataclass.
(Only applies to dataclasses.)
strict: If `True`, strict validation is applied to the field.
Expand Down Expand Up @@ -777,6 +786,7 @@ def Field( # noqa: C901
pattern=pattern,
validate_default=validate_default,
repr=repr,
init=init,
init_var=init_var,
kw_only=kw_only,
strict=strict,
Expand Down
17 changes: 17 additions & 0 deletions tests/test_dataclasses.py
Expand Up @@ -616,6 +616,23 @@ class Outer:
}


@pytest.mark.parametrize(
'init_false_field', [dataclasses.field(init=False, default=-1), pydantic.dataclasses.Field(init=False, default=-1)]
)
def test_init_false(init_false_field):
@pydantic.dataclasses.dataclass(config=ConfigDict(extra='forbid'))
class MyDataclass:
a: int = init_false_field
b: int = pydantic.dataclasses.Field(default=2)

signature = inspect.signature(MyDataclass)
# `a` should not be in the __init__
assert 'a' not in signature.parameters.keys()
assert 'b' in signature.parameters.keys()

assert isinstance(MyDataclass(b=2), MyDataclass)


def test_initvar():
@pydantic.dataclasses.dataclass
class TestInitVar:
Expand Down

0 comments on commit eea1b40

Please sign in to comment.