Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

decouple types.DynamicClassAttribute from property #13276

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions stdlib/@tests/stubtest_allowlists/py310.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions stdlib/@tests/stubtest_allowlists/py311.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions stdlib/@tests/stubtest_allowlists/py312.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions stdlib/@tests/stubtest_allowlists/py313.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions stdlib/@tests/test_cases/check_types.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import sys
import types
from collections import UserDict
Expand Down Expand Up @@ -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
23 changes: 21 additions & 2 deletions stdlib/types.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want the special semantics where you can write @foo.setter later without type checkers complaining about a duplicate definition. That may be hard to achieve without aliasing property.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I'll put together a test to find out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that it works as expected in mypy but not pyright.

def deleter(self, fdel: Callable[[Any], None]) -> DynamicClassAttribute: ...

_Fn = TypeVar("_Fn", bound=Callable[..., object])
_R = TypeVar("_R")
Expand Down
Loading