Skip to content

Commit

Permalink
fix dataclass containing Any
Browse files Browse the repository at this point in the history
  • Loading branch information
DetachHead committed Aug 9, 2022
1 parent f6c74a5 commit e90e882
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 18 deletions.
1 change: 1 addition & 0 deletions changes/4356-detachhead.md
@@ -0,0 +1 @@
remove `Any` types from the `dataclass` decorator so it can be used with the `disallow_any_expr` mypy option
12 changes: 6 additions & 6 deletions pydantic/config.py
Expand Up @@ -2,7 +2,7 @@
from enum import Enum
from typing import TYPE_CHECKING, Any, Callable, Dict, ForwardRef, Optional, Tuple, Type, Union

from .typing import AnyCallable
from .typing import AnyArgAnyCallable, AnyCallable
from .utils import GetterDict
from .version import compiled

Expand Down Expand Up @@ -62,10 +62,10 @@ class ConfigDict(TypedDict, total=False):
getter_dict: Type[GetterDict]
alias_generator: Optional[Callable[[str], str]]
keep_untouched: Tuple[type, ...]
schema_extra: Union[Dict[str, Any], 'SchemaExtraCallable']
json_loads: Callable[[str], Any]
json_dumps: Callable[..., str]
json_encoders: Dict[Type[Any], AnyCallable]
schema_extra: Union[Dict[str, object], 'SchemaExtraCallable']
json_loads: Callable[[str], object]
json_dumps: AnyArgAnyCallable[str]
json_encoders: Dict[Type[object], AnyCallable]
underscore_attrs_are_private: bool

# whether or not inherited models as fields should be reconstructed as base model
Expand Down Expand Up @@ -144,7 +144,7 @@ def prepare_field(cls, field: 'ModelField') -> None:
pass


def get_config(config: Union[ConfigDict, Type[BaseConfig], None]) -> Type[BaseConfig]:
def get_config(config: Union[ConfigDict, Type[object], None]) -> Type[BaseConfig]:
if config is None:
return BaseConfig

Expand Down
26 changes: 14 additions & 12 deletions pydantic/dataclasses.py
Expand Up @@ -66,7 +66,7 @@ class Dataclass:
__pydantic_validate_values__: ClassVar[Callable[['Dataclass'], None]]
__pydantic_has_field_info_default__: ClassVar[bool] # whether a `pydantic.Field` is used as default value

def __init__(self, *args: Any, **kwargs: Any) -> None:
def __init__(self, *args: object, **kwargs: object) -> None:
pass

@classmethod
Expand All @@ -86,6 +86,8 @@ def __validate__(cls: Type['DataclassT'], v: Any) -> 'DataclassT':
'make_dataclass_validator',
]

_T = TypeVar('_T')

if sys.version_info >= (3, 10):

@__dataclass_transform__(kw_only_default=True, field_descriptors=(Field, FieldInfo))
Expand All @@ -98,24 +100,24 @@ def dataclass(
order: bool = False,
unsafe_hash: bool = False,
frozen: bool = False,
config: Union[ConfigDict, Type[Any], None] = None,
config: Union[ConfigDict, Type[object], None] = None,
validate_on_init: Optional[bool] = None,
kw_only: bool = ...,
) -> Callable[[Type[Any]], 'DataclassClassOrWrapper']:
) -> Callable[[Type[_T]], 'DataclassClassOrWrapper']:
...

@__dataclass_transform__(kw_only_default=True, field_descriptors=(Field, FieldInfo))
@overload
def dataclass(
_cls: Type[Any],
_cls: Type[_T],
*,
init: bool = True,
repr: bool = True,
eq: bool = True,
order: bool = False,
unsafe_hash: bool = False,
frozen: bool = False,
config: Union[ConfigDict, Type[Any], None] = None,
config: Union[ConfigDict, Type[object], None] = None,
validate_on_init: Optional[bool] = None,
kw_only: bool = ...,
) -> 'DataclassClassOrWrapper':
Expand All @@ -133,42 +135,42 @@ def dataclass(
order: bool = False,
unsafe_hash: bool = False,
frozen: bool = False,
config: Union[ConfigDict, Type[Any], None] = None,
config: Union[ConfigDict, Type[object], None] = None,
validate_on_init: Optional[bool] = None,
) -> Callable[[Type[Any]], 'DataclassClassOrWrapper']:
) -> Callable[[Type[_T]], 'DataclassClassOrWrapper']:
...

@__dataclass_transform__(kw_only_default=True, field_descriptors=(Field, FieldInfo))
@overload
def dataclass(
_cls: Type[Any],
_cls: Type[_T],
*,
init: bool = True,
repr: bool = True,
eq: bool = True,
order: bool = False,
unsafe_hash: bool = False,
frozen: bool = False,
config: Union[ConfigDict, Type[Any], None] = None,
config: Union[ConfigDict, Type[object], None] = None,
validate_on_init: Optional[bool] = None,
) -> 'DataclassClassOrWrapper':
...


@__dataclass_transform__(kw_only_default=True, field_descriptors=(Field, FieldInfo))
def dataclass(
_cls: Optional[Type[Any]] = None,
_cls: Optional[Type[_T]] = None,
*,
init: bool = True,
repr: bool = True,
eq: bool = True,
order: bool = False,
unsafe_hash: bool = False,
frozen: bool = False,
config: Union[ConfigDict, Type[Any], None] = None,
config: Union[ConfigDict, Type[object], None] = None,
validate_on_init: Optional[bool] = None,
kw_only: bool = False,
) -> Union[Callable[[Type[Any]], 'DataclassClassOrWrapper'], 'DataclassClassOrWrapper']:
) -> Union[Callable[[Type[_T]], 'DataclassClassOrWrapper'], 'DataclassClassOrWrapper']:
"""
Like the python standard lib dataclasses but with type validation.
The result is either a pydantic dataclass that will validate input data
Expand Down
6 changes: 6 additions & 0 deletions pydantic/typing.py
Expand Up @@ -19,6 +19,7 @@
Set,
Tuple,
Type,
TypeVar,
Union,
_eval_type,
cast,
Expand Down Expand Up @@ -76,9 +77,14 @@ def get_all_type_hints(obj: Any, globalns: Any = None, localns: Any = None) -> A
return get_type_hints(obj, globalns, localns, include_extras=True)


_T = TypeVar('_T')

AnyCallable = TypingCallable[..., Any]
NoArgAnyCallable = TypingCallable[[], Any]

# workaround for https://github.com/python/mypy/issues/9496
AnyArgAnyCallable = TypingCallable[..., _T]


# Annotated[...] is implemented by returning an instance of one of these classes, depending on
# python/typing_extensions version.
Expand Down
23 changes: 23 additions & 0 deletions tests/mypy/configs/mypy-plugin-strict-no-any.ini
@@ -0,0 +1,23 @@
[mypy]
plugins = pydantic.mypy

follow_imports = silent
strict_optional = True
warn_redundant_casts = True
warn_unused_ignores = True
disallow_any_generics = True
check_untyped_defs = True
no_implicit_reexport = True
disallow_untyped_defs = True
disallow_any_decorated = True
disallow_any_expr = True
disallow_any_explicit = True
disallow_any_unimported = True
disallow_subclassing_any = True
warn_return_any = True

[pydantic-mypy]
init_forbid_extra = True
init_typed = True
warn_required_dynamic_aliases = True
warn_untyped_fields = True
11 changes: 11 additions & 0 deletions tests/mypy/modules/no_any.py
@@ -0,0 +1,11 @@
from pydantic.dataclasses import dataclass


@dataclass
class Foo:
...


@dataclass(config={})
class Bar:
...
1 change: 1 addition & 0 deletions tests/mypy/test_mypy.py
Expand Up @@ -32,6 +32,7 @@
('mypy-default.ini', 'fail3.py', 'fail3.txt'),
('mypy-default.ini', 'fail4.py', 'fail4.txt'),
('mypy-default.ini', 'plugin_success.py', 'plugin_success.txt'),
('mypy-plugin-strict-no-any.ini', 'no_any.py', None),
('pyproject-default.toml', 'success.py', None),
('pyproject-default.toml', 'fail1.py', 'fail1.txt'),
('pyproject-default.toml', 'fail2.py', 'fail2.txt'),
Expand Down

0 comments on commit e90e882

Please sign in to comment.