descriptors2_args0.py (Source)

"""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()