From 3e1ef3829c2a9c0da8b424c622eb931a51ba5584 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Sat, 12 Aug 2023 21:04:05 +0200 Subject: [PATCH 1/8] Do not require `validate_assignment` to use `Field.frozen` --- docs/errors/validation_errors.md | 5 +---- docs/usage/fields.md | 10 ++-------- pydantic/main.py | 7 +++++++ tests/test_main.py | 14 +++++++++++++- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/docs/errors/validation_errors.md b/docs/errors/validation_errors.md index 320ebb4349..d34cedcac4 100644 --- a/docs/errors/validation_errors.md +++ b/docs/errors/validation_errors.md @@ -606,8 +606,7 @@ except ValidationError as exc: ## `frozen_field` -This error is raised when `model_config['validate_assignment'] == True` and you attempt to assign -a value to a field with `frozen=True`: +This error is raised when you attempt to assign a value to a field with `frozen=True`: ```py from pydantic import BaseModel, ConfigDict, Field, ValidationError @@ -616,8 +615,6 @@ from pydantic import BaseModel, ConfigDict, Field, ValidationError class Model(BaseModel): x: str = Field('test', frozen=True) - model_config = ConfigDict(validate_assignment=True) - model = Model() try: diff --git a/docs/usage/fields.md b/docs/usage/fields.md index 3f282780bd..5d1f882b9f 100644 --- a/docs/usage/fields.md +++ b/docs/usage/fields.md @@ -627,8 +627,6 @@ from pydantic import BaseModel, ConfigDict, Field, ValidationError class User(BaseModel): - model_config = ConfigDict(validate_assignment=True) # (1)! - name: str = Field(frozen=True) age: int @@ -636,7 +634,7 @@ class User(BaseModel): user = User(name='John', age=42) try: - user.name = 'Jane' # (2)! + user.name = 'Jane' # (1)! except ValidationError as e: print(e) """ @@ -646,11 +644,7 @@ except ValidationError as e: """ ``` -1. The `model_config` field is used to enable the validation of assignments. - - See the [Validate Assignment] section for more details. - -2. Since `validate_assignment` is enabled, and the `name` field is frozen, the assignment is not allowed. +1. Since `name` field is frozen, the assignment is not allowed. ## Exclude diff --git a/pydantic/main.py b/pydantic/main.py index e3a34ed43b..76b679dac8 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -748,6 +748,13 @@ def __setattr__(self, name: str, value: Any) -> None: 'input': value, } raise pydantic_core.ValidationError.from_exception_data(self.__class__.__name__, [error]) + elif name in self.model_fields and self.model_fields[name].frozen: + error: pydantic_core.InitErrorDetails = { + 'type': 'frozen_field', + 'loc': (name,), + 'input': value, + } + raise pydantic_core.ValidationError.from_exception_data(self.__class__.__name__, [error]) attr = getattr(self.__class__, name, None) if isinstance(attr, property): diff --git a/tests/test_main.py b/tests/test_main.py index 78211b4b9d..76a43cd58b 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -503,6 +503,18 @@ class FrozenModel(BaseModel): ] +def test_frozen_field(): + class FrozenModel(BaseModel): + a: int = 10 + + m = FrozenModel() + + with pytest.raises(ValidationError) as exc_info: + m.a = 11 + assert exc_info.value.errors(include_url=False) == [ + {'type': 'frozen_field', 'loc': ('a',), 'msg': 'Field is frozen', 'input': 11} + ] + def test_not_frozen_are_not_hashable(): class TestModel(BaseModel): a: int = 10 @@ -1660,7 +1672,7 @@ class M(BaseModel): get_type_hints(type(M.model_config)) -def test_frozen_field(): +def test_frozen_field_with_validate_assignment(): """assigning a frozen=True field should raise a TypeError""" class Entry(BaseModel): From 166bef8db54f9948e1b245874eff626f85fc6196 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Sat, 12 Aug 2023 21:22:07 +0200 Subject: [PATCH 2/8] fix lint --- tests/test_main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_main.py b/tests/test_main.py index 76a43cd58b..615b3b28ca 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -515,6 +515,7 @@ class FrozenModel(BaseModel): {'type': 'frozen_field', 'loc': ('a',), 'msg': 'Field is frozen', 'input': 11} ] + def test_not_frozen_are_not_hashable(): class TestModel(BaseModel): a: int = 10 From 8812c792906249f73a53c3c6c4c3756bf8a381f0 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Sat, 12 Aug 2023 21:58:48 +0200 Subject: [PATCH 3/8] fix test --- tests/test_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_main.py b/tests/test_main.py index 615b3b28ca..767c22e6ce 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -505,7 +505,7 @@ class FrozenModel(BaseModel): def test_frozen_field(): class FrozenModel(BaseModel): - a: int = 10 + a: int = 10 = Field(frozen=True) m = FrozenModel() From 9dd60d76da25e13f09c7bd03369a02f1b12e3977 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Sat, 12 Aug 2023 22:03:30 +0200 Subject: [PATCH 4/8] Fix syntax --- tests/test_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_main.py b/tests/test_main.py index 767c22e6ce..2c30433f2f 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -505,7 +505,7 @@ class FrozenModel(BaseModel): def test_frozen_field(): class FrozenModel(BaseModel): - a: int = 10 = Field(frozen=True) + a: int = Field(10, frozen=True) m = FrozenModel() From 4a6d06411daea9c316410c512a035d3f9046c736 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Sun, 13 Aug 2023 08:47:18 +0200 Subject: [PATCH 5/8] fix unused import --- docs/errors/validation_errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/errors/validation_errors.md b/docs/errors/validation_errors.md index d34cedcac4..483ae7f263 100644 --- a/docs/errors/validation_errors.md +++ b/docs/errors/validation_errors.md @@ -609,7 +609,7 @@ except ValidationError as exc: This error is raised when you attempt to assign a value to a field with `frozen=True`: ```py -from pydantic import BaseModel, ConfigDict, Field, ValidationError +from pydantic import BaseModel, Field, ValidationError class Model(BaseModel): From 3c128159e00e5d6d6c1153f135874336aabd0b71 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Sun, 13 Aug 2023 08:47:48 +0200 Subject: [PATCH 6/8] Fix unused import --- docs/usage/fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/fields.md b/docs/usage/fields.md index 5d1f882b9f..d017df3c0d 100644 --- a/docs/usage/fields.md +++ b/docs/usage/fields.md @@ -623,7 +623,7 @@ assigned a new value after the model is created (immutability). See the [frozen dataclass documentation] for more details. ```py -from pydantic import BaseModel, ConfigDict, Field, ValidationError +from pydantic import BaseModel, Field, ValidationError class User(BaseModel): From e5fbc9d03613a7cb302fafdf7c3f9fd21df09777 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Mon, 14 Aug 2023 17:49:31 +0200 Subject: [PATCH 7/8] Use `getattr` --- pydantic/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic/main.py b/pydantic/main.py index 76b679dac8..cbcc69c42f 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -748,7 +748,7 @@ def __setattr__(self, name: str, value: Any) -> None: 'input': value, } raise pydantic_core.ValidationError.from_exception_data(self.__class__.__name__, [error]) - elif name in self.model_fields and self.model_fields[name].frozen: + elif getattr(self.model_fields.get(name), "frozen", False) error: pydantic_core.InitErrorDetails = { 'type': 'frozen_field', 'loc': (name,), From 6c1e02e74ddef66a5eec3ba074eb843ad1695bc5 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Mon, 14 Aug 2023 18:56:42 +0200 Subject: [PATCH 8/8] fix typos --- pydantic/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic/main.py b/pydantic/main.py index cbcc69c42f..18c03ced7c 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -748,7 +748,7 @@ def __setattr__(self, name: str, value: Any) -> None: 'input': value, } raise pydantic_core.ValidationError.from_exception_data(self.__class__.__name__, [error]) - elif getattr(self.model_fields.get(name), "frozen", False) + elif getattr(self.model_fields.get(name), 'frozen', False): error: pydantic_core.InitErrorDetails = { 'type': 'frozen_field', 'loc': (name,),