From e591f7a3c9dc790a77c9544c9bbbdbe031e3547f Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Wed, 24 Aug 2022 17:06:56 +0100 Subject: [PATCH 1/5] add test for create_model with slots --- tests/test_create_model.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_create_model.py b/tests/test_create_model.py index 42387d5b7a..e9e138afcf 100644 --- a/tests/test_create_model.py +++ b/tests/test_create_model.py @@ -1,4 +1,4 @@ -from typing import Generic, TypeVar +from typing import Generic, Optional, Tuple, TypeVar import pytest @@ -256,3 +256,8 @@ def _some_func(self): # with _init_private_attributes), so the descriptor protocol won't work. if base is object: assert a._some_func == 2 + + +def test_create_model_with_slots(): + field_definitions = {'__slots__': (Optional[Tuple[str, ...]], None), 'foobar': (Optional[int], None)} + create_model('PartialPet', **field_definitions) From 275277d28ae26eb29c434997b5344f79e43ce434 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Wed, 24 Aug 2022 17:08:11 +0100 Subject: [PATCH 2/5] add test for create_model with slots --- tests/test_create_model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_create_model.py b/tests/test_create_model.py index e9e138afcf..17e253c2c3 100644 --- a/tests/test_create_model.py +++ b/tests/test_create_model.py @@ -260,4 +260,5 @@ def _some_func(self): def test_create_model_with_slots(): field_definitions = {'__slots__': (Optional[Tuple[str, ...]], None), 'foobar': (Optional[int], None)} - create_model('PartialPet', **field_definitions) + with pytest.warns(RuntimeWarning): + create_model('PartialPet', **field_definitions) From 118f4f586be96ea00c8fba9dba299a3715d1e6ec Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Wed, 24 Aug 2022 17:17:42 +0100 Subject: [PATCH 3/5] add warning and change description --- changes/4432-samuelcolvin.md | 1 + pydantic/main.py | 5 +++++ tests/test_create_model.py | 6 ++++-- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 changes/4432-samuelcolvin.md diff --git a/changes/4432-samuelcolvin.md b/changes/4432-samuelcolvin.md new file mode 100644 index 0000000000..d1a514ef67 --- /dev/null +++ b/changes/4432-samuelcolvin.md @@ -0,0 +1 @@ +Runtime warning if `__slots__` is passed to `create_model`, `__slots__` is then ignored. diff --git a/pydantic/main.py b/pydantic/main.py index 6c1eaf8ee4..69f3b75120 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -957,6 +957,7 @@ def create_model( __module__: str = __name__, __validators__: Dict[str, 'AnyClassMethod'] = None, __cls_kwargs__: Dict[str, Any] = None, + __slots__: Optional[Tuple[str, ...]] = None, **field_definitions: Any, ) -> Type['Model']: """ @@ -967,6 +968,7 @@ def create_model( :param __module__: module of the created model :param __validators__: a dict of method names and @validator class methods :param __cls_kwargs__: a dict for class creation + :param __slots__: Deprecated, `__slots__` should not be passed to `create_model` :param field_definitions: fields of the model (or extra fields if a base is supplied) in the format `=(, )` or `=, e.g. `foobar=(str, ...)` or `foobar=123`, or, for complex use-cases, in the format @@ -974,6 +976,9 @@ def create_model( `foo=Field(datetime, default_factory=datetime.utcnow, alias='bar')` or `foo=(str, FieldInfo(title='Foo'))` """ + if __slots__ is not None: + # __slots__ will be ignored from here on + warnings.warn('__slots__ should not be passed to create_model', RuntimeWarning) if __base__ is not None: if __config__ is not None: diff --git a/tests/test_create_model.py b/tests/test_create_model.py index 17e253c2c3..b21440ec89 100644 --- a/tests/test_create_model.py +++ b/tests/test_create_model.py @@ -260,5 +260,7 @@ def _some_func(self): def test_create_model_with_slots(): field_definitions = {'__slots__': (Optional[Tuple[str, ...]], None), 'foobar': (Optional[int], None)} - with pytest.warns(RuntimeWarning): - create_model('PartialPet', **field_definitions) + with pytest.warns(RuntimeWarning, match='__slots__ should not be passed to create_model'): + model = create_model('PartialPet', **field_definitions) + + assert model.__fields__.keys() == {'foobar'} From 4268373999a549f02821106b1001f9c18ed17e58 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Wed, 24 Aug 2022 18:27:03 +0100 Subject: [PATCH 4/5] fix flakey none tests on 3.8.10 --- pydantic/typing.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pydantic/typing.py b/pydantic/typing.py index 5cd7ee4da1..afc3b62308 100644 --- a/pydantic/typing.py +++ b/pydantic/typing.py @@ -333,15 +333,16 @@ def is_none_type(type_: Any) -> bool: return type_ in NONE_TYPES elif sys.version_info[:2] == (3, 8): - # We can use the fast implementation for 3.8 but there is a very weird bug - # where it can fail for `Literal[None]`. - # We just need to redefine a useless `Literal[None]` inside the function body to fix this def is_none_type(type_: Any) -> bool: - Literal[None] # fix edge case for none_type in NONE_TYPES: if type_ is none_type: return True + # With python 3.8, specifically 3.8.10, Literal "is" check sare very flakey + # can change on very subtle changes like use of types in other modules, + # hopefully this check avoids that issue. + if is_literal_type(type_): + return all_literal_values(type_) == (None,) return False else: From 55ad6b954a80df7c72d830909a2f54cedecbda05 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Wed, 24 Aug 2022 18:38:04 +0100 Subject: [PATCH 5/5] avoid flakey coverage changes --- pydantic/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic/typing.py b/pydantic/typing.py index afc3b62308..5ccf266c93 100644 --- a/pydantic/typing.py +++ b/pydantic/typing.py @@ -341,7 +341,7 @@ def is_none_type(type_: Any) -> bool: # With python 3.8, specifically 3.8.10, Literal "is" check sare very flakey # can change on very subtle changes like use of types in other modules, # hopefully this check avoids that issue. - if is_literal_type(type_): + if is_literal_type(type_): # pragma: no cover return all_literal_values(type_) == (None,) return False