Kaydet (Commit) c8604a14 authored tarafından Batuhan Taşkaya's avatar Batuhan Taşkaya

value hook and excluding

üst 84c0c01f
# Catlizor
## Installation
You can install catlizor from PyPI.
```
$ pip(3) install catlizor
```
## Examples
```py
from catlizor import catlizor
from functools import partial
def fancy_print(st, name, *args, **kwargs):
print(f"{st:8} {name} call with {args} and {kwargs}")
options = {
"watch": [
{
"attribs": ["name"],
"hooks": {
"pre": [partial(fancy_print, 'pre')],
"post": [partial(fancy_print, 'post')],
"value": [partial(fancy_print, 'value')]
},
"exclude": {
"pre": ["set", "del"],
"post": ["get"],
"value": ["set", "del"]
}
},
{
"attribs": ["age"],
"hooks": {
"value": [lambda name, *a, **k: print(f"Got value of {name}({', '.join(a[1:])}), {k['value']}")]
},
"exclude": {
"value": ["set", "del"]
}
}
]
}
@catlizor(**options)
class MyClass:
def __init__(self, name, age=15):
self.name = "batuhan"
self.age = age
def __repr__(self):
return "MyClass instance"
mc = MyClass("batuhan")
mc.__dict__["birth"] = 2019 - mc.age
info = f"{mc.name} {mc.age} years old."
del mc.name
```
and output is
```
post __setattr__ call with (MyClass instance, 'name', 'batuhan') and {}
Got value of __getattribute__(age), 15
pre __getattribute__ call with (MyClass instance, 'name') and {}
value __getattribute__ call with (MyClass instance, 'name') and {'value': 'batuhan'}
Got value of __getattribute__(age), 15
post __delattr__ call with (MyClass instance, 'name') and {}
```
from collections import defaultdict
from collections import defaultdict, ChainMap
from functools import wraps, partial
from contextlib import contextmanager
WATCH_ATTRIB_METHS = {'__getattribute__', '__setattr__', '__delattr__'}
WATCH_ATTRIB_METHS = {"__getattribute__", "__setattr__", "__delattr__"}
WATCH_ATTRIB_ALIAS = {
"get": "__getattribute__",
"set": "__setattr__",
"del": "__delattr__",
}
def attrib_wrapper(hooker, f):
@wraps(f)
def wrapper(*args, **kwargs):
with hooker.catch(f.__name__, *args, **kwargs):
val = f(*args, **kwargs)
with hooker.catch(f.__name__, *args, **kwargs) as c:
val = c(f(*args, **kwargs))
return val
return wrapper
class AttribWatchHooks:
def __init__(self, *hooks):
def __init__(self, *watches):
self.pre_hooks = defaultdict(list)
self.post_hooks = defaultdict(list)
for hook in hooks:
for attr in hook['attribs']:
if 'pre_hooks' in hook:
self.pre_hooks[attr].extend(hook['pre_hooks'])
if 'post_hooks' in hook:
self.post_hooks[attr].extend(hook['post_hooks'])
self.value_hooks = defaultdict(list)
self.exclude = defaultdict(list)
self._last_val = None
for watch in watches:
for attr in watch["attribs"]:
if "hooks" in watch:
for hook, fns in watch["hooks"].items():
getattr(self, f"{hook}_hooks")[attr].extend(fns)
if "exclude" in watch:
for hook, aliases in watch["exclude"].items():
self.exclude[f"{hook}_{attr}"].extend(
map(WATCH_ATTRIB_ALIAS.get, aliases)
)
def exc(self, cond, name, *args, **kwargs):
hooks = getattr(self, f"{cond}_hooks")[args[1]]
for hook in hooks:
hook(name, *args, **kwargs)
excluded = self.exclude.get(f"{cond}_{args[1]}")
if (not excluded) or (name not in excluded):
hooks = getattr(self, f"{cond}_hooks")[args[1]]
for hook in hooks:
hook(name, *args, **kwargs)
@contextmanager
def catch(self, name, *args, **kwargs):
try:
self.exc('pre', name, *args, **kwargs)
yield
self.exc("pre", name, *args, **kwargs)
yield self
self.exc(
"value", name, *args, **ChainMap(kwargs, {"value": self._last_val})
)
self._last_val = None
finally:
self.exc('post', name, *args, **kwargs)
self.exc("post", name, *args, **kwargs)
def __call__(self, value):
self._last_val = value
return value
def catlizor(cls=None, *, watch=None):
def wrapper(cls):
if watch:
watch_hooks = AttribWatchHooks(*watch)
hooked_wrapper = partial(attrib_wrapper, watch_hooks)
for meth in WATCH_ATTRIB_METHS:
setattr(cls, meth, hooked_wrapper(getattr(cls, meth)))
cls.__catlized = True
return cls
if cls is None:
return wrapper
return wrapper(cls)
from catlizor import catlizor
from functools import partial
def fancy_print(st, name, *args, **kwargs):
print(f"{st:8} {name} call with {args} and {kwargs}")
options = {
"watch": [
{
"attribs": ["name"],
"hooks": {
"pre": [partial(fancy_print, "pre")],
"post": [partial(fancy_print, "post")],
"value": [partial(fancy_print, "value")],
},
"exclude": {
"pre": ["set", "del"],
"post": ["get"],
"value": ["set", "del"],
},
},
{
"attribs": ["age"],
"hooks": {
"value": [
lambda name, *a, **k: print(
f"Got value of {name}({', '.join(a[1:])}), {k['value']}"
)
]
},
"exclude": {"value": ["set", "del"]},
},
]
}
@catlizor(**options)
class MyClass:
def __init__(self, name, age=15):
self.name = "batuhan"
self.age = age
def __repr__(self):
return "MyClass instance"
mc = MyClass("batuhan")
mc.__dict__["birth"] = 2019 - mc.age
info = f"{mc.name} {mc.age} years old."
del mc.name
......@@ -9,31 +9,6 @@ class TestCatlizor(unittest.TestCase):
self.age = age
self.cls = MyClass
def test_watch_attrib(self):
total_call = []
def hook(name, *a, **k):
total_call.append(a[1])
options = {
"watch": [
{
"attribs": ["name", "age"],
"post_hooks": [hook],
},
{
"attribs": ["__dict__"],
"pre_hooks": [hook],
}
]
}
new_cls = catlizor(self.cls, **options)
mc = new_cls("batuhan", 13)
mc.__dict__['name']
print(total_call)
self.assertEqual(total_call, ['name', 'age', '__dict__'])
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment