Skip to content

Commit 2025b59

Browse files
authored
Add LogOnAccess(property) and make AdvancedProperty(property), 2.1.0 (#28)
`LogOnAccess` - Property with logging on successful get/set/delete or failure. Inherit `property` instead of full implementation Cleanup last `object` subclassing
1 parent 0a203f7 commit 2025b59

13 files changed

+846
-113
lines changed

README.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ This package includes helpers for special cases:
2525

2626
* `AdvancedProperty` - property with possibility to set class wide getter.
2727

28+
* `LogOnAccess` - property with logging on successful get/set/delete or failure.
29+
2830
SeparateClassMethod
2931
-------------------
3032

@@ -124,6 +126,46 @@ Usage examples:
124126

125127
class-wide getter receives class as argument. IDE's don't know about custom descriptors and substitutes `self` by default.
126128

129+
LogOnAccess
130+
-----------
131+
132+
This special case of property is useful in cases, where a lot of properties should be logged by similar way without writing a lot of code.
133+
134+
Basic API is conform with `property`, but in addition it is possible to customize logger, log levels and log conditions.
135+
136+
Usage example:
137+
138+
.. code-block:: python
139+
140+
class Target(object):
141+
142+
def init(self, val='ok')
143+
self.val = val
144+
145+
def __repr__(self):
146+
return "{cls}(val={self.val})".format(cls=self.__class__.__name__, self=self)
147+
148+
@advanced_descriptors.LogOnAccess
149+
def ok(self):
150+
return self.val
151+
152+
@ok.setter
153+
def ok(self, val):
154+
self.val = val
155+
156+
@ok.deleter
157+
def ok(self):
158+
self.val = ""
159+
160+
ok.logger = 'test_logger'
161+
ok.log_level = logging.INFO
162+
ok.exc_level = logging.ERROR
163+
ok.log_object_repr = True # As by default
164+
ok.log_success = True # As by default
165+
ok.log_failure = True # As by default
166+
ok.log_traceback = True # As by default
167+
ok.override_name = None # As by default: use original name
168+
127169
Testing
128170
=======
129171
The main test mechanism for the package `advanced-descriptors` is using `tox`.

advanced_descriptors/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515

1616
from .separate_class_method import SeparateClassMethod
1717
from .advanced_property import AdvancedProperty
18+
from .log_on_access import LogOnAccess
1819

19-
__all__ = ("SeparateClassMethod", "AdvancedProperty")
20+
__all__ = ("SeparateClassMethod", "AdvancedProperty", "LogOnAccess")
2021

21-
__version__ = "2.0.0"
22+
__version__ = "2.1.0"
2223
__author__ = "Alexey Stepanov"
2324
__author_email__ = "penguinolog@gmail.com"
2425
__maintainers__ = {

advanced_descriptors/advanced_property.py

Lines changed: 10 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,21 @@
2020
__all__ = ("AdvancedProperty",)
2121

2222

23-
class AdvancedProperty:
23+
class AdvancedProperty(property):
2424
"""Property with class-wide getter.
2525
2626
This property allows implementation of read-only getter for classes
2727
in additional to normal set of getter/setter/deleter on instance.
2828
Implements almost full @property interface,
2929
except __doc__ due to class-wide nature.
3030
31-
.. note:
31+
.. versionadded:: 2.1.0 Inherit property
3232
33-
If class-wide setter/deleter required:
34-
use normal property in metaclass.
33+
.. note:: If class-wide setter/deleter required: use normal property in metaclass.
3534
3635
Usage examples:
3736
38-
>>> class LikeNormalProperty(object):
37+
>>> class LikeNormalProperty:
3938
... def __init__(self):
4039
... self.val = 42
4140
... @AdvancedProperty
@@ -67,7 +66,7 @@ class AdvancedProperty:
6766
...
6867
AttributeError
6968
70-
>>> class ExtendedProperty(object):
69+
>>> class ExtendedProperty:
7170
... def __init__(self):
7271
... self.val = 21
7372
... @AdvancedProperty
@@ -100,7 +99,7 @@ class AdvancedProperty:
10099
>>> ExtendedProperty.prop
101100
'self.val'
102101
103-
>>> class ClassProperty(object):
102+
>>> class ClassProperty:
104103
... def _getter(cls):
105104
... return cls
106105
... prop = AdvancedProperty(fcget=_getter) # special case
@@ -113,8 +112,6 @@ class AdvancedProperty:
113112
True
114113
"""
115114

116-
__slots__ = ("__fget", "__fset", "__fdel", "__fcget")
117-
118115
def __init__(
119116
self,
120117
fget: typing.Optional[typing.Callable[[typing.Any], typing.Any]] = None,
@@ -135,13 +132,11 @@ def __init__(
135132
136133
.. note:: doc argument is not supported due to class wide getter usage.
137134
"""
138-
self.__fget = fget
139-
self.__fset = fset
140-
self.__fdel = fdel
135+
super(AdvancedProperty, self).__init__(fget=fget, fset=fset, fdel=fdel)
141136

142137
self.__fcget = fcget
143138

144-
def __get__(self, instance: typing.Optional[typing.Any], owner: typing.Any) -> typing.Any:
139+
def __get__(self, instance: typing.Any, owner: typing.Any = None) -> typing.Any:
145140
"""Get descriptor.
146141
147142
:param instance: Owner class instance. Filled only if instance created, else None.
@@ -151,61 +146,11 @@ def __get__(self, instance: typing.Optional[typing.Any], owner: typing.Any) -> t
151146
:rtype: typing.Any
152147
:raises AttributeError: Getter is not available
153148
"""
154-
if instance is None or self.__fget is None:
149+
if owner is not None and (instance is None or self.fget is None):
155150
if self.__fcget is None:
156151
raise AttributeError()
157152
return self.__fcget(owner)
158-
return self.__fget(instance)
159-
160-
def __set__(self, instance: typing.Any, value: typing.Any) -> None:
161-
"""Set descriptor.
162-
163-
:param instance: Owner class instance. Filled only if instance created, else None.
164-
:type instance: typing.Optional
165-
:param value: Value for setter
166-
:raises AttributeError: Setter is not available
167-
"""
168-
if self.__fset is None:
169-
raise AttributeError()
170-
self.__fset(instance, value)
171-
172-
def __delete__(self, instance: typing.Any) -> None:
173-
"""Delete descriptor.
174-
175-
:param instance: Owner class instance. Filled only if instance created, else None.
176-
:type instance: typing.Optional
177-
:raises AttributeError: Deleter is not available
178-
"""
179-
if self.__fdel is None:
180-
raise AttributeError()
181-
self.__fdel(instance)
182-
183-
@property
184-
def fget(self) -> typing.Optional[typing.Callable[[typing.Any], typing.Any]]:
185-
"""Getter instance.
186-
187-
:return: Normal getter instance
188-
:rtype: typing.Optional[typing.Callable[[typing.Any, ], typing.Any]]
189-
"""
190-
return self.__fget
191-
192-
@property
193-
def fset(self) -> typing.Optional[typing.Callable[[typing.Any, typing.Any], None]]:
194-
"""Setter instance.
195-
196-
:return: Setter instance
197-
:rtype: typing.Optional[typing.Callable[[typing.Any, typing.Any], None]]
198-
"""
199-
return self.__fset
200-
201-
@property
202-
def fdel(self) -> typing.Optional[typing.Callable[[typing.Any], None]]:
203-
"""Deleter instance.
204-
205-
:return: Deletter instance
206-
:rtype: typing.Optional[typing.Callable[[typing.Any, ], None]]
207-
"""
208-
return self.__fdel
153+
return super(AdvancedProperty, self).__get__(instance, owner)
209154

210155
@property
211156
def fcget(self) -> typing.Optional[typing.Callable[[typing.Any], typing.Any]]:
@@ -216,39 +161,6 @@ def fcget(self) -> typing.Optional[typing.Callable[[typing.Any], typing.Any]]:
216161
"""
217162
return self.__fcget
218163

219-
def getter(self, fget: typing.Optional[typing.Callable[[typing.Any], typing.Any]]) -> "AdvancedProperty":
220-
"""Descriptor to change the getter on a property.
221-
222-
:param fget: new normal getter.
223-
:type fget: typing.Optional[typing.Callable[[typing.Any, ], typing.Any]]
224-
:return: AdvancedProperty
225-
:rtype: AdvancedProperty
226-
"""
227-
self.__fget = fget
228-
return self
229-
230-
def setter(self, fset: typing.Optional[typing.Callable[[typing.Any, typing.Any], None]]) -> "AdvancedProperty":
231-
"""Descriptor to change the setter on a property.
232-
233-
:param fset: new setter.
234-
:type fset: typing.Optional[typing.Callable[[typing.Any, typing.Any], None]]
235-
:return: AdvancedProperty
236-
:rtype: AdvancedProperty
237-
"""
238-
self.__fset = fset
239-
return self
240-
241-
def deleter(self, fdel: typing.Optional[typing.Callable[[typing.Any], None]]) -> "AdvancedProperty":
242-
"""Descriptor to change the deleter on a property.
243-
244-
:param fdel: New deleter.
245-
:type fdel: typing.Optional[typing.Callable[[typing.Any, ], None]]
246-
:return: AdvancedProperty
247-
:rtype: AdvancedProperty
248-
"""
249-
self.__fdel = fdel
250-
return self
251-
252164
def cgetter(self, fcget: typing.Optional[typing.Callable[[typing.Any], typing.Any]]) -> "AdvancedProperty":
253165
"""Descriptor to change the class wide getter on a property.
254166

0 commit comments

Comments
 (0)