diff --git a/stdlib/_typeshed/__init__.pyi b/stdlib/_typeshed/__init__.pyi index 68ac2a9b1900..33af534311d1 100644 --- a/stdlib/_typeshed/__init__.pyi +++ b/stdlib/_typeshed/__init__.pyi @@ -8,9 +8,10 @@ import mmap import pickle import sys from collections.abc import Awaitable, Callable, Iterable, Set as AbstractSet +from dataclasses import Field from os import PathLike from types import FrameType, TracebackType -from typing import Any, AnyStr, Generic, Protocol, TypeVar, Union +from typing import Any, AnyStr, ClassVar, Generic, Protocol, TypeVar, Union from typing_extensions import Final, Literal, LiteralString, TypeAlias, final _KT = TypeVar("_KT") @@ -304,3 +305,10 @@ ProfileFunction: TypeAlias = Callable[[FrameType, str, Any], object] # Objects suitable to be passed to sys.settrace, threading.settrace, and similar TraceFunction: TypeAlias = Callable[[FrameType, str, Any], TraceFunction | None] + +# experimental +# Might not work as expected for pyright, see +# https://github.com/python/typeshed/pull/9362 +# https://github.com/microsoft/pyright/issues/4339 +class DataclassInstance(Protocol): + __dataclass_fields__: ClassVar[dict[str, Field[Any]]] diff --git a/stdlib/dataclasses.pyi b/stdlib/dataclasses.pyi index 3b7327137ec5..797093895bc9 100644 --- a/stdlib/dataclasses.pyi +++ b/stdlib/dataclasses.pyi @@ -1,9 +1,10 @@ import enum import sys import types +from _typeshed import DataclassInstance from builtins import type as Type # alias to avoid name clashes with fields named "type" from collections.abc import Callable, Iterable, Mapping -from typing import Any, ClassVar, Generic, Protocol, TypeVar, overload +from typing import Any, Generic, Protocol, TypeVar, overload from typing_extensions import Literal, TypeAlias, TypeGuard if sys.version_info >= (3, 9): @@ -30,10 +31,7 @@ __all__ = [ if sys.version_info >= (3, 10): __all__ += ["KW_ONLY"] -class _DataclassInstance(Protocol): - __dataclass_fields__: ClassVar[dict[str, Field[Any]]] - -_DataclassT = TypeVar("_DataclassT", bound=_DataclassInstance) +_DataclassT = TypeVar("_DataclassT", bound=DataclassInstance) # define _MISSING_TYPE as an enum within the type stubs, # even though that is not really its type at runtime @@ -49,13 +47,13 @@ if sys.version_info >= (3, 10): class KW_ONLY: ... @overload -def asdict(obj: _DataclassInstance) -> dict[str, Any]: ... +def asdict(obj: DataclassInstance) -> dict[str, Any]: ... @overload -def asdict(obj: _DataclassInstance, *, dict_factory: Callable[[list[tuple[str, Any]]], _T]) -> _T: ... +def asdict(obj: DataclassInstance, *, dict_factory: Callable[[list[tuple[str, Any]]], _T]) -> _T: ... @overload -def astuple(obj: _DataclassInstance) -> tuple[Any, ...]: ... +def astuple(obj: DataclassInstance) -> tuple[Any, ...]: ... @overload -def astuple(obj: _DataclassInstance, *, tuple_factory: Callable[[list[Any]], _T]) -> _T: ... +def astuple(obj: DataclassInstance, *, tuple_factory: Callable[[list[Any]], _T]) -> _T: ... if sys.version_info >= (3, 8): # cls argument is now positional-only @@ -223,13 +221,13 @@ else: metadata: Mapping[Any, Any] | None = None, ) -> Any: ... -def fields(class_or_instance: _DataclassInstance | type[_DataclassInstance]) -> tuple[Field[Any], ...]: ... +def fields(class_or_instance: DataclassInstance | type[DataclassInstance]) -> tuple[Field[Any], ...]: ... @overload -def is_dataclass(obj: _DataclassInstance | type[_DataclassInstance]) -> Literal[True]: ... +def is_dataclass(obj: DataclassInstance | type[DataclassInstance]) -> Literal[True]: ... @overload -def is_dataclass(obj: type) -> TypeGuard[type[_DataclassInstance]]: ... +def is_dataclass(obj: type) -> TypeGuard[type[DataclassInstance]]: ... @overload -def is_dataclass(obj: object) -> TypeGuard[_DataclassInstance | type[_DataclassInstance]]: ... +def is_dataclass(obj: object) -> TypeGuard[DataclassInstance | type[DataclassInstance]]: ... class FrozenInstanceError(AttributeError): ...