Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn and ignore __slots__ argument to create_model #4432

Merged
merged 5 commits into from Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/4432-samuelcolvin.md
@@ -0,0 +1 @@
Runtime warning if `__slots__` is passed to `create_model`, `__slots__` is then ignored.
5 changes: 5 additions & 0 deletions pydantic/main.py
Expand Up @@ -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']:
"""
Expand All @@ -967,13 +968,17 @@ 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 `<name>=(<type>, <default default>)` or `<name>=<default value>, e.g.
`foobar=(str, ...)` or `foobar=123`, or, for complex use-cases, in the format
`<name>=<Field>` or `<name>=(<type>, <FieldInfo>)`, e.g.
`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:
Expand Down
9 changes: 5 additions & 4 deletions pydantic/typing.py
Expand Up @@ -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.
Comment on lines +341 to +343
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love to know what is compiled behind the scene to understand how this edge case arrives. Such a weird bug

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, so weird.

The line that caused the issue again was __slots__: Optional[Tuple[str, ...]] = None,, if I changed it to __slots__: 'Optional[Tuple[str, ...]]' = None, it went again. But I decided this was a safer fix.

if is_literal_type(type_): # pragma: no cover
return all_literal_values(type_) == (None,)
return False

else:
Expand Down
10 changes: 9 additions & 1 deletion tests/test_create_model.py
@@ -1,4 +1,4 @@
from typing import Generic, TypeVar
from typing import Generic, Optional, Tuple, TypeVar

import pytest

Expand Down Expand Up @@ -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'}