#!/usr/bin/env python

from functools import wraps

def decorator(func):
    """Decorator which authenticates the incoming API call.
    """
    @wraps(func)
    # wraps, preserves metadata (__name__ and __doc__) of the wrapping function.
    def wrapper(*args, **kwargs):
        # we send all positional arguments and keyword arguments because
        # we don't know what all arguments are going to be in func.
        # perform any actual authentication here and return the func as is.
        print("in decorator() wrapper function")
        return func(*args, **kwargs)

    return wrapper

@decorator
def do():
    print("in do() function")

do()

# MRO (method resolution order)
# hasattr
# class inheritance
# 
import time

def time_it(func):
    @wraps(func)
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        return str(end - start)
    return wrapper

@time_it
def long_operation():
    """Performs some time taking operation."""
    numbers = []
    for num in range(0, 100000):
        numbers.append(num * 2)
    print("Sum = " + str(sum(numbers)))

print(long_operation())
print(long_operation.__name__) # see the result with wraps or without wraps
print(long_operation.__doc__) # see the result with wraps or without wraps
print(long_operation.__module__) # ?? check

# Decorating class methods
def make_uppercase(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        self = args[0]
        self.name = self.name.upper()
        self.family = self.family.upper()
        return func(*args, **kwargs)
    return wrapper

class Person:
    def __init__(self):
        self.name = "John"
        self.family = "Doe"

    @make_uppercase
    def fullname(self):
        return self.name + " " + self.family

p = Person()
print(p.fullname())
