diff --git a/stdlib/@tests/stubtest_allowlists/py310.txt b/stdlib/@tests/stubtest_allowlists/py310.txt index 93e595839d40..78fa22149873 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 4841231d6eb6..f01d6ea4d42f 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 9ae160e7e819..6d77c8d111b9 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 4049026a4c37..b8ce8c9fc297 100644 --- a/stdlib/@tests/stubtest_allowlists/py313.txt +++ b/stdlib/@tests/stubtest_allowlists/py313.txt @@ -68,8 +68,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 b513bd77468a..e3afb0cf6322 100644 --- a/stdlib/types.pyi +++ b/stdlib/types.pyi @@ -583,8 +583,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], None] | None + fdel: Callable[[Any], None] | None + overwrite_doc: bool + __isabstractmethod__: bool + def __init__( + self, + fget: Callable[[Any], Any] | None = None, + fset: Callable[[Any, Any], None] | None = None, + fdel: Callable[[Any], None] | 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], None]) -> DynamicClassAttribute: ... + def deleter(self, fdel: Callable[[Any], None]) -> DynamicClassAttribute: ... _Fn = TypeVar("_Fn", bound=Callable[..., object]) _R = TypeVar("_R")