Last active
May 1, 2024 15:45
-
-
Save rmariano/0c401b79add51bca844429ca5a303e06 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Descriptors & Decorators | |
Example 2: A decorator that changes the signature of the function. | |
""" | |
from functools import wraps | |
class DomainObject: | |
"""Dummy object that requires the common parameters for processing""" | |
def __init__(self, *args): | |
self.args = args | |
def process(self): | |
"""format all arguments passed by""" | |
return ', '.join(self.args) | |
task1 = task2 = process | |
def task(self, task_no): | |
"""Dummy task""" | |
result = self.process() | |
return f"Task {task_no}: {result}" | |
def resolver_function(root, args, context, info): | |
"""A function that always requires these parameters for constructing an | |
object and operating with it. | |
>>> resolver_function('root', 'args', 'context', 'info') | |
'root, args, context, info' | |
""" | |
helper = DomainObject(root, args, context, info) | |
helper.process() | |
helper.task1() | |
helper.task2() | |
return helper.task1() | |
class DomainArgs: | |
"""The first attempt of a decorator will work for regular functions, but | |
doesn't handle the case for methods. | |
""" | |
def __init__(self, func): | |
self.func = func | |
wraps(func)(self) | |
def __call__(self, root, args, context, info): | |
"""Changes the signature of the wrapped function. Exposes the same old | |
4 parameters, but passes the required object to the internal function, | |
that can assume that's already processed by this decorator. | |
""" | |
helper = DomainObject(root, args, context, info) | |
return self.func(helper) | |
class DomainArgsInjector(DomainArgs): | |
"""By implementing ``__get__``, with the above logic, this can handle the | |
case of being called from a class/instance. | |
""" | |
def __get__(self, instance, owner): | |
mapped = self.func.__get__(instance, owner) | |
return self.__class__(mapped) | |
@DomainArgs | |
def resolver_function2(helper): | |
"""The first decorator works for regular functions. | |
>>> resolver_function2('root', 'args', 'context', 'info') | |
'root, args, context, info' | |
""" | |
helper.task1() | |
helper.task2() | |
return helper.process() | |
class ViewResolver: | |
"""An object that contains method, that also require the logic of using a | |
helper with the parameters they receive. | |
""" | |
@DomainArgs | |
def resolve_method(self, helper): | |
"""With the first decorator, this method fails. | |
>>> vr = ViewResolver() | |
>>> vr.resolve_method('root', 'args', 'context', 'info') | |
Traceback (most recent call last): | |
... | |
TypeError: resolve_method() missing 1 required positional argument: 'helper' | |
""" | |
response = helper.process() | |
return f"Method: {response}" | |
@DomainArgsInjector | |
def method_resolver(self, helper): | |
"""The enhanced decorator can work for methods like this one. | |
>>> vr = ViewResolver() | |
>>> vr.method_resolver('root2', 'args2', 'context2', 'info2') | |
'Method resolver: root2, args2, context2, info2' | |
""" | |
response = helper.process() | |
return f"Method resolver: {response}" | |
if __name__ == '__main__': | |
import doctest | |
doctest.testmod() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Descriptors & Decorators | |
Example 1: class methods | |
""" | |
from functools import wraps | |
class decorator: | |
def __init__(self, func): | |
self.func = func | |
wraps(func)(self) | |
def __call__(self, *args, **kwargs): | |
result = self.func(*args, **kwargs) | |
return f"decorated {result}" | |
class decorator2(decorator): | |
def __get__(self, instance, owner): | |
mapped = self.func.__get__(instance, owner) | |
return self.__class__(mapped) | |
@decorator | |
def function(): | |
""" | |
>>> function() | |
'decorated function' | |
""" | |
return 'function' | |
class Object: | |
""" | |
>>> Object.class_method() | |
Traceback (most recent call last): | |
... | |
TypeError: 'classmethod' object is not callable | |
>>> Object.class_method2() | |
'decorated second class method' | |
>>> Object.base_class_method() | |
Traceback (most recent call last): | |
... | |
TypeError: 'classmethod' object is not callable | |
>>> Object.correct() | |
'decorated this works' | |
""" | |
@decorator | |
@classmethod | |
def class_method(cls): | |
return 'class method' | |
@decorator2 | |
@classmethod | |
def class_method2(cls): | |
return 'second class method' | |
@classmethod | |
def base_class_method(cls): | |
return 'base class method' | |
base_class_method = decorator(base_class_method) | |
@classmethod | |
@decorator | |
def correct(cls): | |
return 'this works' | |
if __name__ == '__main__': | |
import doctest | |
doctest.testmod(verbose=True) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment