Skip to content

Commit ec86d01

Browse files
committed
Use internal mechanisms instead of NamedTuple
Mostly to prevent Mypy from looking at our code, but sadly it wasn't enough courtesy of enums for some reason.
1 parent fbe6fda commit ec86d01

7 files changed

Lines changed: 212 additions & 110 deletions

File tree

docs/api.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,10 @@ Helpers
197197
.. doctest::
198198

199199
>>> @define
200-
... class C:
200+
... class CInspect:
201201
... pass
202-
>>> attrs.inspect(C)
203-
ClassProps(is_exception=False, is_slotted=True, has_weakref_slot=True, is_frozen=False, kw_only=ClassProps.KeywordOnly.NO, collect_by_mro=False, init=True, repr=True, eq=True, order=True, hash=ClassProps.Hashability.UNHASHABLE, match_args=True, str=False, getstate_setstate=False, on_setattr=None, field_transformer=None)
202+
>>> attrs.inspect(CInspect) # doctest: +ELLIPSIS
203+
ClassProps(is_exception=False, is_slotted=True, has_weakref_slot=True, is_frozen=False, kw_only=<KeywordOnly.NO: 'no'>, collect_by_mro=True, init=True, repr=True, eq=True, order=False, hash=<Hashability.UNHASHABLE: 'unhashable'>, match_args=True, str=False, getstate_setstate=True, on_setattr=<function pipe.<locals>.wrapped_pipe at ...>, field_transformer=None)
204204

205205
.. autoclass:: attrs.ClassProps
206206
.. autoclass:: attrs.ClassProps.Hashability

src/attr/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
from . import converters, exceptions, filters, setters, validators
1111
from ._cmp import cmp_using
1212
from ._config import get_run_validators, set_run_validators
13-
from ._funcs import asdict, assoc, astuple, has, resolve_types
13+
from ._funcs import asdict, assoc, astuple, has, inspect, resolve_types
1414
from ._make import (
1515
NOTHING,
1616
Attribute,
17+
ClassProps,
1718
Converter,
1819
Factory,
1920
_Nothing,
@@ -26,7 +27,6 @@
2627
validate,
2728
)
2829
from ._next_gen import define, field, frozen, mutable
29-
from ._props import ClassProps, inspect
3030
from ._version_info import VersionInfo
3131

3232

src/attr/__init__.pyi

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ from . import filters as filters
2020
from . import setters as setters
2121
from . import validators as validators
2222
from ._cmp import cmp_using as cmp_using
23-
from ._funcs import inspect as inspect
24-
from ._props import ClassProps as ClassProps
23+
2524
from ._typing_compat import AttrsInstance_
2625
from ._version_info import VersionInfo
2726
from attrs import (
@@ -142,6 +141,58 @@ class Attribute(Generic[_T]):
142141

143142
def evolve(self, **changes: Any) -> "Attribute[Any]": ...
144143

144+
class ClassProps:
145+
"""
146+
XXX: somehow defining/using enums Mypy starts looking at our own code
147+
and causes tons of errors.
148+
"""
149+
150+
# class Hashability(enum.Enum): ...
151+
# class KeywordOnly(enum.Enum): ...
152+
153+
is_exception: bool
154+
is_slotted: bool
155+
has_weakref_slot: bool
156+
is_frozen: bool
157+
# kw_only: ClassProps.KeywordOnly
158+
kw_only: Any
159+
collect_by_mro: bool
160+
init: bool
161+
repr: bool
162+
eq: bool
163+
order: bool
164+
# hash: ClassProps.Hashability
165+
hash: Any
166+
match_args: bool
167+
str: bool
168+
getstate_setstate: bool
169+
on_setattr: _OnSetAttrType
170+
field_transformer: Callable[[Attribute[Any]], Attribute[Any]]
171+
172+
def __init__(
173+
self,
174+
is_exception: bool,
175+
is_slotted: bool,
176+
has_weakref_slot: bool,
177+
is_frozen: bool,
178+
# kw_only: ClassProps.KeywordOnly
179+
kw_only: Any,
180+
collect_by_mro: bool,
181+
init: bool,
182+
repr: bool,
183+
eq: bool,
184+
order: bool,
185+
# hash: ClassProps.Hashability
186+
hash: Any,
187+
match_args: bool,
188+
str: bool,
189+
getstate_setstate: bool,
190+
on_setattr: _OnSetAttrType,
191+
field_transformer: Callable[[Attribute[Any]], Attribute[Any]],
192+
) -> None: ...
193+
@property
194+
def is_hashable(self) -> bool: ...
195+
145196
# NOTE: We had several choices for the annotation to use for type arg:
146197
# 1) Type[_T]
147198
# - Pros: Handles simple cases correctly
@@ -376,6 +427,7 @@ def astuple(
376427
retain_collection_types: bool = ...,
377428
) -> tuple[Any, ...]: ...
378429
def has(cls: type) -> TypeGuard[type[AttrsInstance]]: ...
430+
def inspect(cls: type) -> ClassProps: ...
379431
def assoc(inst: _T, **changes: Any) -> _T: ...
380432
def evolve(inst: _T, **changes: Any) -> _T: ...
381433

src/attr/_funcs.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from ._compat import get_generic_base
77
from ._make import _OBJ_SETATTR, NOTHING, fields
8-
from .exceptions import AttrsAttributeNotFoundError
8+
from .exceptions import AttrsAttributeNotFoundError, NotAnAttrsClassError
99

1010

1111
_ATOMIC_TYPES = frozenset(
@@ -379,6 +379,28 @@ def has(cls):
379379
return False
380380

381381

382+
def inspect(cls):
383+
"""
384+
Inspect the class and return it's effective build parameters.
385+
386+
Args:
387+
cls: The class to inspect.
388+
389+
Returns:
390+
The effective build parameters of the class.
391+
392+
Raises:
393+
NotAnAttrsClassError: If the class is not an *attrs*-decorated class.
394+
395+
.. versionadded:: 25.4.0
396+
"""
397+
try:
398+
return cls.__dict__["__attrs_props__"]
399+
except KeyError:
400+
msg = f"{cls!r} is not an attrs-decorated class."
401+
raise NotAnAttrsClassError(msg) from None
402+
403+
382404
def assoc(inst, **changes):
383405
"""
384406
Copy *inst* and apply *changes*.

src/attr/_make.py

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
from functools import cached_property
1919
from typing import Any, NamedTuple, TypeVar
2020

21-
from attr._props import ClassProps
22-
2321
# We need to import _compat itself in addition to the _compat members to avoid
2422
# having the thread-local in the globals here.
2523
from . import _compat, _config, setters
@@ -2798,6 +2796,127 @@ def default(self, meth):
27982796
_CountingAttr = _add_eq(_add_repr(_CountingAttr))
27992797

28002798

2799+
class ClassProps:
2800+
"""
2801+
Effective class properties as derived from parameters to attr.s() or
2802+
define() decorators.
2803+
2804+
.. versionadded:: 25.4.0
2805+
"""
2806+
2807+
class Hashability(enum.Enum):
2808+
"""
2809+
The hashability of a class.
2810+
2811+
.. versionadded:: 25.4.0
2812+
"""
2813+
2814+
HASHABLE = "hashable"
2815+
"""Write a ``__hash__``."""
2816+
HASHABLE_CACHED = "hashable_cache"
2817+
"""Write a ``__hash__`` and cache the hash."""
2818+
UNHASHABLE = "unhashable"
2819+
"""Set ``__hash__`` to ``None``."""
2820+
LEAVE_ALONE = "leave_alone"
2821+
"""Don't touch ``__hash__``."""
2822+
2823+
class KeywordOnly(enum.Enum):
2824+
"""
2825+
How attributes should be treated regarding keyword-only parameters.
2826+
2827+
.. versionadded:: 25.4.0
2828+
"""
2829+
2830+
NO = "no"
2831+
"""Attributes are not keyword-only."""
2832+
YES = "yes"
2833+
"""Attributes in current class without kw_only=False are keyword-only."""
2834+
FORCE = "force"
2835+
"""All attributes are keyword-only."""
2836+
2837+
__slots__ = ( # noqa: RUF023 -- order matters for __init__
2838+
"is_exception",
2839+
"is_slotted",
2840+
"has_weakref_slot",
2841+
"is_frozen",
2842+
"kw_only",
2843+
"collect_by_mro",
2844+
"init",
2845+
"repr",
2846+
"eq",
2847+
"order",
2848+
"hash",
2849+
"match_args",
2850+
"str",
2851+
"getstate_setstate",
2852+
"on_setattr",
2853+
"field_transformer",
2854+
)
2855+
2856+
def __init__(
2857+
self,
2858+
is_exception,
2859+
is_slotted,
2860+
has_weakref_slot,
2861+
is_frozen,
2862+
kw_only,
2863+
collect_by_mro,
2864+
init,
2865+
repr,
2866+
eq,
2867+
order,
2868+
hash,
2869+
match_args,
2870+
str,
2871+
getstate_setstate,
2872+
on_setattr,
2873+
field_transformer,
2874+
):
2875+
self.is_exception = is_exception
2876+
self.is_slotted = is_slotted
2877+
self.has_weakref_slot = has_weakref_slot
2878+
self.is_frozen = is_frozen
2879+
self.kw_only = kw_only
2880+
self.collect_by_mro = collect_by_mro
2881+
self.init = init
2882+
self.repr = repr
2883+
self.eq = eq
2884+
self.order = order
2885+
self.hash = hash
2886+
self.match_args = match_args
2887+
self.str = str
2888+
self.getstate_setstate = getstate_setstate
2889+
self.on_setattr = on_setattr
2890+
self.field_transformer = field_transformer
2891+
2892+
@property
2893+
def is_hashable(self):
2894+
return (
2895+
self.hash is ClassProps.Hashability.HASHABLE
2896+
or self.hash is ClassProps.Hashability.HASHABLE_CACHED
2897+
)
2898+
2899+
2900+
_cas = [
2901+
Attribute(
2902+
name=name,
2903+
default=NOTHING,
2904+
validator=None,
2905+
repr=True,
2906+
cmp=None,
2907+
eq=True,
2908+
order=False,
2909+
hash=True,
2910+
init=True,
2911+
inherited=False,
2912+
alias=_default_init_alias_for(name),
2913+
)
2914+
for name in ClassProps.__slots__
2915+
]
2916+
2917+
ClassProps = _add_eq(_add_repr(ClassProps, attrs=_cas), attrs=_cas)
2918+
2919+
28012920
class Factory:
28022921
"""
28032922
Stores a factory callable.

src/attr/_props.py

Lines changed: 0 additions & 99 deletions
This file was deleted.

0 commit comments

Comments
 (0)