Skip to content

Instantly share code, notes, and snippets.

@rmariano
Last active May 1, 2024 15:45
Show Gist options
  • Save rmariano/0c401b79add51bca844429ca5a303e06 to your computer and use it in GitHub Desktop.
Save rmariano/0c401b79add51bca844429ca5a303e06 to your computer and use it in GitHub Desktop.
"""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()
"""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