From 18f90fc7ebf03c5869bcbbc1177ded9a693de4d4 Mon Sep 17 00:00:00 2001 From: rhuille Date: Sun, 30 Aug 2020 15:27:38 +0200 Subject: [PATCH] Generate a hash function when `allow_mutation` is `False` The generated hash is the hash of the dict_value of the BaseModel.__dict__ attribute. closes #1880 --- docs/usage/model_config.md | 2 +- pydantic/main.py | 1 + tests/test_main.py | 25 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/usage/model_config.md b/docs/usage/model_config.md index ade95eac1d9..4d61d12f42d 100644 --- a/docs/usage/model_config.md +++ b/docs/usage/model_config.md @@ -24,7 +24,7 @@ Options: and `'allow'` will assign the attributes to the model. **`allow_mutation`** -: whether or not models are faux-immutable, i.e. whether `__setattr__` is allowed (default: `True`) +: whether or not models are faux-immutable, i.e. whether `__setattr__` is allowed (default: `True`). `False` will make the model immutable and hashable i.e. a default `__hash__` function is generated for you, but you can still overwrite it. **`use_enum_values`** : whether to populate models with the `value` property of enums, rather than the raw enum. diff --git a/pydantic/main.py b/pydantic/main.py index c872f1e3b50..7e13e173357 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -313,6 +313,7 @@ def __new__(mcs, name, bases, namespace, **kwargs): # noqa C901 '__schema_cache__': {}, '__json_encoder__': staticmethod(json_encoder), '__custom_root_type__': _custom_root_type, + '__hash__': (lambda self: hash(self.__dict__.values())) if not config.allow_mutation else None, **{n: v for n, v in namespace.items() if n not in fields}, } diff --git a/tests/test_main.py b/tests/test_main.py index 19d17e6d43b..9253bbede55 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -355,6 +355,19 @@ class Config: assert '"TestModel" object has no field "b"' in exc_info.value.args[0] +def test_mutable_are_not_hashable(): + class TestModel(BaseModel): + a: int = 10 + + class Config: + allow_mutation = True + + m = TestModel() + with pytest.raises(TypeError) as exc_info: + hash(m) + assert "unhashable type: 'TestModel'" in exc_info.value.args[0] + + def test_immutability(): class TestModel(BaseModel): a: int = 10 @@ -373,6 +386,18 @@ class Config: assert '"TestModel" object has no field "b"' in exc_info.value.args[0] +def test_immutable_are_hashable(): + class TestModel(BaseModel): + a: int = 10 + + class Config: + allow_mutation = False + + m = TestModel() + assert m.__hash__ is not None + assert isinstance(hash(m), int) + + def test_const_validates(): class Model(BaseModel): a: int = Field(3, const=True)