diff --git a/stdlib/@tests/stubtest_allowlists/py310.txt b/stdlib/@tests/stubtest_allowlists/py310.txt index b9117eba1d2e..d809618c6df0 100644 --- a/stdlib/@tests/stubtest_allowlists/py310.txt +++ b/stdlib/@tests/stubtest_allowlists/py310.txt @@ -25,8 +25,6 @@ posixpath.join ntpath.join os.path.join -types.DynamicClassAttribute..* # In the stub we pretend it's an alias for property, but it has positional-only differences - # typing.IO uses positional-or-keyword arguments, but in the stubs we prefer # to mark these as positional-only for compatibility with existing sub-classes. typing(_extensions)?\.BinaryIO\.write diff --git a/stdlib/@tests/stubtest_allowlists/py311.txt b/stdlib/@tests/stubtest_allowlists/py311.txt index aa5ab8aaf936..26f846f339a3 100644 --- a/stdlib/@tests/stubtest_allowlists/py311.txt +++ b/stdlib/@tests/stubtest_allowlists/py311.txt @@ -46,8 +46,6 @@ posixpath.join ntpath.join os.path.join -types.DynamicClassAttribute..* # In the stub we pretend it's an alias for property, but it has positional-only differences - # typing.IO uses positional-or-keyword arguments, but in the stubs we prefer # to mark these as positional-only for compatibility with existing sub-classes. typing(_extensions)?\.BinaryIO\.write diff --git a/stdlib/@tests/stubtest_allowlists/py312.txt b/stdlib/@tests/stubtest_allowlists/py312.txt index 9d3ed5487085..1e48d68093a0 100644 --- a/stdlib/@tests/stubtest_allowlists/py312.txt +++ b/stdlib/@tests/stubtest_allowlists/py312.txt @@ -45,8 +45,6 @@ posixpath.join ntpath.join os.path.join -types.DynamicClassAttribute..* # In the stub we pretend it's an alias for property, but it has positional-only differences - # typing.IO uses positional-or-keyword arguments, but in the stubs we prefer # to mark these as positional-only for compatibility with existing sub-classes. typing(_extensions)?\.BinaryIO\.write diff --git a/stdlib/@tests/stubtest_allowlists/py313.txt b/stdlib/@tests/stubtest_allowlists/py313.txt index 2aa1f8eaa79b..25f56e73e2bc 100644 --- a/stdlib/@tests/stubtest_allowlists/py313.txt +++ b/stdlib/@tests/stubtest_allowlists/py313.txt @@ -45,8 +45,6 @@ posixpath.join ntpath.join os.path.join -types.DynamicClassAttribute..* # In the stub we pretend it's an alias for property, but it has positional-only differences - # typing.IO uses positional-or-keyword arguments, but in the stubs we prefer # to mark these as positional-only for compatibility with existing sub-classes. typing(_extensions)?\.BinaryIO\.write diff --git a/stdlib/@tests/test_cases/check_types.py b/stdlib/@tests/test_cases/check_types.py index 7dcf31923bec..8ae5b1641abb 100644 --- a/stdlib/@tests/test_cases/check_types.py +++ b/stdlib/@tests/test_cases/check_types.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys import types from collections import UserDict @@ -39,3 +41,20 @@ assert_type(item_3, Union[int, str]) # Default isn't accepted as a keyword argument. mp.get(4, default="default") # type: ignore + + +# test: `types.DynamicClassAttribute` +class DCAtest: + _value: int | None = None + + @types.DynamicClassAttribute + def foo(self) -> int | None: + return self._value + + @foo.setter + def foo(self, value: int) -> None: + self._value = value + + @foo.deleter + def foo(self) -> None: + self._value = None diff --git a/stdlib/types.pyi b/stdlib/types.pyi index d41ca0d1c367..57d1ec935a76 100644 --- a/stdlib/types.pyi +++ b/stdlib/types.pyi @@ -615,8 +615,27 @@ def prepare_class( if sys.version_info >= (3, 12): def get_original_bases(cls: type, /) -> tuple[Any, ...]: ... -# Actually a different type, but `property` is special and we want that too. -DynamicClassAttribute = property +# Does not actually inherit from property, but saying it does makes sure that +# pyright handles this class correctly. +class DynamicClassAttribute(property): + fget: Callable[[Any], Any] | None + fset: Callable[[Any, Any], object] | None # type: ignore[assignment] + fdel: Callable[[Any], object] | None # type: ignore[assignment] + overwrite_doc: bool + __isabstractmethod__: bool + def __init__( + self, + fget: Callable[[Any], Any] | None = None, + fset: Callable[[Any, Any], object] | None = None, + fdel: Callable[[Any], object] | None = None, + doc: str | None = None, + ) -> None: ... + def __get__(self, instance: Any, ownerclass: type | None = None) -> Any: ... + def __set__(self, instance: Any, value: Any) -> None: ... + def __delete__(self, instance: Any) -> None: ... + def getter(self, fget: Callable[[Any], Any]) -> DynamicClassAttribute: ... + def setter(self, fset: Callable[[Any, Any], object]) -> DynamicClassAttribute: ... + def deleter(self, fdel: Callable[[Any], object]) -> DynamicClassAttribute: ... _Fn = TypeVar("_Fn", bound=Callable[..., object]) _R = TypeVar("_R")