Skip to content

Commit

Permalink
v1.2.3 introduced optional 'typecheck' argument
Browse files Browse the repository at this point in the history
  • Loading branch information
sg495 committed Sep 23, 2024
1 parent e2ed3eb commit e3696be
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 7 deletions.
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
# built documents.
#
# The full version, including alpha/beta/rc tags.
release = "1.2.1"
release = "1.2.3"
# The short X.Y version.
version = "1.2.1"
version = "1.2.3"


# -- General configuration ---------------------------------------------------
Expand Down
14 changes: 14 additions & 0 deletions test/test_01_attr.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,17 @@ def __init__(self, x: int, y: int) -> None:
C(x, y)
with pytest.raises(ValueError):
C(x, x+1).y = y

@pytest.mark.parametrize("decorator", [False, True])
@pytest.mark.parametrize("x", [0, "hello"])
def test_single_attr_no_typecheck(x: Any, decorator: bool) -> None:
class C:
if decorator:
@Attr.validator(typecheck=False)
def x(self, value: int) -> bool:
return True
else:
x = Attr(int, typecheck=False)
def __init__(self, x: int) -> None:
self.x = x
C(x)
14 changes: 14 additions & 0 deletions test/test_02_prop.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,17 @@ def x(self) -> int:
c.y = y
with pytest.raises(TypeError):
assert c.x == len(y)


@pytest.mark.parametrize("decorator", [False, True])
@pytest.mark.parametrize("x", [0, "hello"])
def test_single_prop_no_typecheck(x: int, decorator: bool) -> None:
class C:
if not decorator:
@cached_property(typecheck=False)
def x(self) -> int:
return x
else:
x = Prop(int, lambda _: x, typecheck=False) # type: ignore
c = C()
assert c.x == x
14 changes: 13 additions & 1 deletion typed_descriptors/attr.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def validator(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> Attr[T]: ...

@staticmethod
Expand All @@ -156,6 +157,7 @@ def validator(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> ValidatedAttrFactory: ...

@staticmethod
Expand All @@ -167,6 +169,7 @@ def validator(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> ValidatedAttrFactory | Attr[T]:
"""
Decorator used to create an :class:`Attr` from a validator function,
Expand Down Expand Up @@ -217,6 +220,7 @@ def w(self, value: Sequence[int]) -> bool:
backed_by=backed_by,
use_dict=use_dict,
use_slots=use_slots,
typecheck=typecheck,
)

def _validated_attr(validator_fun: ValidatorFunction[_T]) -> Attr[_T]:
Expand All @@ -226,11 +230,13 @@ def _validated_attr(validator_fun: ValidatorFunction[_T]) -> Attr[_T]:
backed_by=backed_by,
use_dict=use_dict,
use_slots=use_slots,
typecheck=typecheck,
)

return _validated_attr

__readonly: bool
__typecheck: bool
__validator_fun: Optional[ValidatorFunction[T]]

@overload
Expand All @@ -244,6 +250,7 @@ def __init__(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> None:
# pylint: disable = redefined-builtin
...
Expand All @@ -259,6 +266,7 @@ def __init__(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> None:
# pylint: disable = redefined-builtin
...
Expand All @@ -273,13 +281,15 @@ def __init__(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> None:
"""
Creates a new attribute with the given type and optional validator.
:param ty: the type of the attribute
:param validator: an optional validator function for the attribute
:param readonly: whether the attribute is read-only
:param typecheck: whether to perform dynamic typechecks (default: True)
:raises TypeError: if the type is not a valid type
:raises TypeError: if the validator is not callable
Expand All @@ -298,6 +308,7 @@ def __init__(
self.__doc__ = validator.__doc__
self.__validator_fun = validator
self.__readonly = bool(readonly)
self.__typecheck = bool(typecheck)

@final
@property
Expand Down Expand Up @@ -380,7 +391,8 @@ def __set__(self, instance: Any, value: T) -> None:
raise AttributeError(
f"Attribute {self} is readonly: it can only be set once."
)
validate(value, self.type)
if self.__typecheck:
validate(value, self.type)
validator = self.__validator_fun
if validator is not None:
try:
Expand Down
3 changes: 1 addition & 2 deletions typed_descriptors/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ def name_unmangle(owner: type, attr_name: str) -> str:
T_co = TypeVar("T_co", covariant=True)
""" Invariant type variable for generic values. """


@runtime_checkable
class TypedDescriptor(Protocol[T_co]):
"""
Expand Down Expand Up @@ -237,7 +236,7 @@ def __init__(
use_slots: Optional[Literal[True]] = None,
) -> None:
"""
Creates a new descriptor with the given type and optional validator.
Creates a new descriptor with the given type.
:param type: the type of the descriptor.
:param backed_by: name for the backing attribute (optional, default name
Expand Down
21 changes: 19 additions & 2 deletions typed_descriptors/prop.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def value(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> Prop[T]: ...

@staticmethod
Expand All @@ -121,6 +122,7 @@ def value(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> PropFactory: ...

@staticmethod
Expand All @@ -131,6 +133,7 @@ def value(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> PropFactory | Prop[T]:
"""
An alias for :func:`cached_property`.
Expand All @@ -144,9 +147,11 @@ def value(
backed_by=backed_by,
use_dict=use_dict,
use_slots=use_slots,
typecheck=typecheck,
)

__value_fun: ValueFunction[T]
__typecheck: bool

@overload
def __init__(
Expand All @@ -158,6 +163,7 @@ def __init__(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> None:
# pylint: disable = redefined-builtin
...
Expand All @@ -172,6 +178,7 @@ def __init__(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> None:
# pylint: disable = redefined-builtin
...
Expand All @@ -185,6 +192,7 @@ def __init__(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> None:
"""
Creates a new property with the given type and value function.
Expand All @@ -193,6 +201,7 @@ def __init__(
:param type: the type of the property
:param attr_name: the name of the backing attribute for the property
cache, or :obj:`None` to use a default name
:param typecheck: whether to perform dynamic typechecks (default: True)
:raises TypeError: if the type is not a valid type
:raises TypeError: if the value function is not callable
Expand All @@ -211,6 +220,7 @@ def __init__(
raise TypeError(f"Expected callable 'value', got {value!r}.")
self.__value_fun = value
self.__doc__ = value.__doc__
self.__typecheck = bool(typecheck)

@final
@property
Expand Down Expand Up @@ -244,7 +254,8 @@ def cache_on(self, instance: Any) -> None:
if self._is_set_on(instance):
raise AttributeError(f"Property {self} is already cached.")
value = self.value_fun(instance)
validate(value, self.type)
if self.__typecheck:
validate(value, self.type)
self._set_on(instance, value)

@overload
Expand All @@ -270,7 +281,8 @@ def __get__(self, instance: Any, _: Type[Any]) -> T | Self:
return self._get_on(instance)
except AttributeError:
value = self.value_fun(instance)
validate(value, self.type)
if self.__typecheck:
validate(value, self.type)
self._set_on(instance, value)
return value

Expand Down Expand Up @@ -345,6 +357,7 @@ def cached_property(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> Prop[T]: ...


Expand All @@ -356,6 +369,7 @@ def cached_property(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> PropFactory: ...


Expand All @@ -366,6 +380,7 @@ def cached_property(
backed_by: Optional[str] = None,
use_dict: Optional[Literal[True]] = None,
use_slots: Optional[Literal[True]] = None,
typecheck: bool = True,
) -> PropFactory | Prop[T]:
"""
Decorator used to create a cached property from a value function,
Expand Down Expand Up @@ -415,6 +430,7 @@ def x(self) -> Sequence[str]:
backed_by=backed_by,
use_dict=use_dict,
use_slots=use_slots,
typecheck=typecheck,
)

def _cached_property(value_fun: ValueFunction[_T]) -> Prop[_T]:
Expand All @@ -423,6 +439,7 @@ def _cached_property(value_fun: ValueFunction[_T]) -> Prop[_T]:
backed_by=backed_by,
use_dict=use_dict,
use_slots=use_slots,
typecheck=typecheck,
)

return _cached_property

0 comments on commit e3696be

Please sign in to comment.