Skip to content

Commit

Permalink
1. Added support for __dict__-based backing attribute access.
Browse files Browse the repository at this point in the history
2. Introduced decorator `Attr.validator` to define `Attr`ibutes from validator methods.
3. Introduced decorators `cached_property` and `Prop.value` (an alias) to define `Prop`erties from value methods.
  • Loading branch information
sg495 committed Jan 18, 2024
1 parent 0a71d95 commit 33999f9
Show file tree
Hide file tree
Showing 14 changed files with 892 additions and 298 deletions.
19 changes: 18 additions & 1 deletion docs/api/typed_descriptors.attr.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,33 @@ Attr
.. autoclass:: typed_descriptors.attr.Attr
:show-inheritance:
:members:
:special-members: __init__, __set__, __get__
:special-members: __init__, __set_name__, __set__, __get__

T_contra
--------

.. autodata:: typed_descriptors.attr.T_contra

ValidatedAttrFactory
--------------------

.. autoclass:: typed_descriptors.attr.ValidatedAttrFactory
:show-inheritance:
:members:

ValidatorFunction
-----------------

.. autoclass:: typed_descriptors.attr.ValidatorFunction
:show-inheritance:
:members:

validate_validator_fun
----------------------

.. autofunction:: typed_descriptors.attr.validate_validator_fun

validator_fun_value_type
------------------------

.. autofunction:: typed_descriptors.attr.validator_fun_value_type
12 changes: 11 additions & 1 deletion docs/api/typed_descriptors.base.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,19 @@ DescriptorBase
.. autoclass:: typed_descriptors.base.DescriptorBase
:show-inheritance:
:members:
:special-members: __init__, __get__
:special-members: __init__, __set_name__, __get__

T
-

.. autodata:: typed_descriptors.base.T

name_mangle
-----------

.. autofunction:: typed_descriptors.base.name_mangle

name_unmangle
-------------

.. autofunction:: typed_descriptors.base.name_unmangle
24 changes: 23 additions & 1 deletion docs/api/typed_descriptors.prop.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ Prop
.. autoclass:: typed_descriptors.prop.Prop
:show-inheritance:
:members:
:special-members: __init__, __get__
:special-members: __init__, __set_name__, __get__

PropFactory
-----------

.. autoclass:: typed_descriptors.prop.PropFactory
:show-inheritance:
:members:

T_co
----
Expand All @@ -22,3 +29,18 @@ ValueFunction
.. autoclass:: typed_descriptors.prop.ValueFunction
:show-inheritance:
:members:

cached_property
---------------

.. autofunction:: typed_descriptors.prop.cached_property

validate_value_fun
------------------

.. autofunction:: typed_descriptors.prop.validate_value_fun

value_fun_return_type
---------------------

.. autofunction:: typed_descriptors.prop.value_fun_return_type
1 change: 1 addition & 0 deletions docs/api/typed_descriptors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ The following members were explicitly reexported using ``__all__``:

- :py:class:`typed_descriptors.attr.Attr`
- :py:class:`typed_descriptors.prop.Prop`
- :py:func:`typed_descriptors.prop.cached_property`
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.1.0"
release = "1.2.0"
# The short X.Y version.
version = "1.1.0"
version = "1.2.0"


# -- General configuration ---------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from __future__ import annotations
86 changes: 37 additions & 49 deletions test/test_00_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,58 @@
from typing import Optional
import pytest
from typed_descriptors import Attr
from typed_descriptors.base import name_mangle

@pytest.mark.parametrize("attr_name", [None, "_x", "_y", "__x", "__y"])
def test_base_set_name(attr_name: Optional[str]) -> None:
@pytest.mark.parametrize("backed_by", [None, "x", "_x", "_y", "__x", "__y"])
def test_base_set_name(backed_by: Optional[str]) -> None:
if backed_by is None:
backing_attr = "x"
else:
backing_attr = backed_by
class C:
x = Attr(int, lambda _, x: x >= 0, backed_by=backed_by)
def __init__(self, x: int) -> None:
self.x = x
c = C(0)
assert name_mangle(C, backing_attr) in c.__dict__

@pytest.mark.parametrize("backed_by", [None, "_x", "_y", "__x", "__y"])
def test_base_set_name_slots(backed_by: Optional[str]) -> None:
if backed_by is None:
backing_attr = "__x"
else:
backing_attr = backed_by
class C:
x = Attr(int, lambda _, x: x >= 0, attr_name=attr_name)
x = Attr(int, lambda _, x: x >= 0, backed_by=backed_by)
__slots__ = (backing_attr,)
def __init__(self, x: int) -> None:
self.x = x
C(0)
c = C(0)
assert hasattr(c, name_mangle(C, backing_attr))

@pytest.mark.parametrize("attr_name", [None, "_x", "_y", "__x", "__y"])
def test_base_set_name_slots(attr_name: Optional[str]) -> None:
if attr_name is not None:
slot_name = attr_name
@pytest.mark.parametrize("backed_by", [None, "x", "_x", "_y", "__x", "__y"])
def test_base_set_name_slots_dict(backed_by: Optional[str]) -> None:
if backed_by is None:
backing_attr = "x"
else:
slot_name = "__x"
backing_attr = backed_by
class C:
x = Attr(int, lambda _, x: x >= 0, attr_name=attr_name)
__slots__ = (slot_name,)
x = Attr(int, lambda _, x: x >= 0, backed_by=backed_by)
__slots__ = ("__dict__",)
def __init__(self, x: int) -> None:
self.x = x
C(0)
c = C(0)
assert name_mangle(C, backing_attr) in c.__dict__

name_slot_error = (
AttributeError
TypeError
if sys.version_info[1] >= 12
else RuntimeError
)

@pytest.mark.parametrize("attr_name", [None, "_x", "_y", "__x", "__y"])
def test_base_set_name_slots_error(attr_name: Optional[str]) -> None:
@pytest.mark.parametrize("backed_by", [None, "x", "_x", "_y", "__x", "__y"])
def test_base_set_name_slots_error(backed_by: Optional[str]) -> None:
with pytest.raises(name_slot_error):
class C:
x = Attr(int, lambda _, x: x >= 0, attr_name=attr_name)
x = Attr(int, lambda _, x: x >= 0, backed_by=backed_by)
__slots__ = ()
class D:
x = Attr(int, lambda _, x: x >= 0, attr_name=attr_name)
__slots__ = ("__dict__",)
def __init__(self, x: int) -> None:
self.x = x
D(0)

@pytest.mark.parametrize("attr_name", [None, "x", "y", "_y", "__x", "__y"])
def test_base_set_name_slots_protected(attr_name: Optional[str]) -> None:
if attr_name is not None:
slot_name = attr_name
else:
slot_name = "__x"
class C:
_x = Attr(int, lambda _, x: x >= 0, attr_name=attr_name)
__slots__ = (slot_name,)
def __init__(self, x: int) -> None:
self._x = x
C(0)


@pytest.mark.parametrize("attr_name", [None, "x", "y", "_x" "_y", "__y", "___x"])
def test_base_set_name_slots_private(attr_name: Optional[str]) -> None:
if attr_name is not None:
slot_name = attr_name
else:
slot_name = "___x"
class C:
__x = Attr(int, lambda _, x: x >= 0, attr_name=attr_name)
__slots__ = (slot_name,)
def __init__(self, x: int) -> None:
self.__x = x
C(0)
Loading

0 comments on commit 33999f9

Please sign in to comment.