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/pydantic/typing.py b/pydantic/typing.py index 5cd7ee4da1..5ccf266c93 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_): # pragma: no cover + return all_literal_values(type_) == (None,) return False else: diff --git a/tests/test_create_model.py b/tests/test_create_model.py index 42387d5b7a..b21440ec89 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,11 @@ 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)} + with pytest.warns(RuntimeWarning, match='__slots__ should not be passed to create_model'): + model = create_model('PartialPet', **field_definitions) + + assert model.__fields__.keys() == {'foobar'}