diff --git a/changes/4356-detachhead.md b/changes/4356-detachhead.md new file mode 100644 index 00000000000..d8eac7ec168 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/pydantic/config.py b/pydantic/config.py index b7389c559a0..7764f0b79fc 100644 --- a/pydantic/config.py +++ b/pydantic/config.py @@ -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 @@ -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 @@ -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 diff --git a/pydantic/dataclasses.py b/pydantic/dataclasses.py index 1a9008cc6ce..a6dbcf155bf 100644 --- a/pydantic/dataclasses.py +++ b/pydantic/dataclasses.py @@ -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 @@ -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)) @@ -98,16 +100,16 @@ 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, @@ -115,7 +117,7 @@ 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 = ..., ) -> 'DataclassClassOrWrapper': @@ -133,15 +135,15 @@ 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, @@ -149,7 +151,7 @@ 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, ) -> 'DataclassClassOrWrapper': ... @@ -157,7 +159,7 @@ def dataclass( @__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, @@ -165,10 +167,10 @@ 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 = 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 diff --git a/pydantic/typing.py b/pydantic/typing.py index f2ceaaa6169..a05742d12b6 100644 --- a/pydantic/typing.py +++ b/pydantic/typing.py @@ -19,6 +19,7 @@ Set, Tuple, Type, + TypeVar, Union, _eval_type, cast, @@ -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. diff --git a/tests/mypy/configs/mypy-plugin-strict-no-any.ini b/tests/mypy/configs/mypy-plugin-strict-no-any.ini new file mode 100644 index 00000000000..9dd2e87e1a4 --- /dev/null +++ b/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 diff --git a/tests/mypy/modules/no_any.py b/tests/mypy/modules/no_any.py new file mode 100644 index 00000000000..019ab7188f8 --- /dev/null +++ b/tests/mypy/modules/no_any.py @@ -0,0 +1,11 @@ +from pydantic.dataclasses import dataclass + + +@dataclass +class Foo: + ... + + +@dataclass(config={}) +class Bar: + ... diff --git a/tests/mypy/test_mypy.py b/tests/mypy/test_mypy.py index c976069e03e..dac84b987e2 100644 --- a/tests/mypy/test_mypy.py +++ b/tests/mypy/test_mypy.py @@ -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'),