Skip to content
Jay Lee edited this page Jun 14, 2021 · 19 revisions

Welcome to Decko wiki!

Decko is a decorator-based library designed for the following purposes

  1. Make the creation of decorators simple and reduce boilerplate
  2. Change the behavior of existing classes and function with minimal modifications
  3. Manage the state
  4. Provide a rich set of pre-made and tested decorators.

Updates / News

Self-contained decorators that can be used without creating a Decko instance is under development.

Install

Install and update using pip:

pip install -U decko

Uninstall

Uninstall using pip:

pip uninstall decko

API Overview

Decko's API is divided into two main parts.

1. decorators.py:

This provides a rich set of pre-built decorators that can be used immediately in your projects. The main decorator is called deckorator, a utility decorator for creating decorators. To demonstrate its power, the code below is a simple slower_than() decorator that calls a callback if a function is slower than threshold time, without deckorator.

from decko import deckorator
from time import process_time
import typing as t


def slower_than(threshold_time: t.Union[int, float], callback: t.Callable):
    # Do something if wrapped function runs slower than threshold time

    if not isinstance(threshold_time, (int, float)):
        raise TypeError("threshold_time must be either an int or float")

    if not isinstance(callback, t.Callable):
        raise TypeError("callback must be a callable object")

    def wrapper(func):

        def inner(*args, **kwargs):
            start = process_time() * 1000
            output = func(*args, **kwargs)
            elapsed = (process_time() * 1000) - start
            if elapsed > threshold_time:
                callback(elapsed, threshold_time)
            return output

        return inner

    return wrapper


def gogo(input_data):
    input_size, milliseconds = input_data

    def handle_slow_function(elapsed, threshold_time):
        print(f"We exceeded threshold time: {threshold_time} ms. "
              f"Time elapsed: {elapsed:.2f} ms")

    @slower_than(milliseconds, handle_slow_function)
    def long_func(n):
        x = 0
        for i in range(n):
            x += i
        return x

    long_func(input_size)


if __name__ == "__main__":
    gogo((10000000, 100.0))

Now, we will create the decorator using deckorator. Replace the existing slower_than implementation with the following

# REPLACE THIS
# def slower_than(threshold_time: t.Union[int, float], callback: t.Callable):
#     # Do something if wrapped function runs slower than threshold time
# 
#     if not isinstance(threshold_time, (int, float)):
#         raise TypeError("threshold_time must be either an int or float")
# 
#     if not isinstance(callback, t.Callable):
#         raise TypeError("callback must be a callable object")
# 
#     def wrapper(func):
# 
#         def inner(*args, **kwargs):
#             start = process_time() * 1000
#             output = func(*args, **kwargs)
#             elapsed = (process_time() * 1000) - start
#             if elapsed > threshold_time:
#                 callback(elapsed, threshold_time)
#             return output
# 
#         return inner
# 
#     return wrapper

# With
@deckorator((float, int), t.Callable)
def slower_than(wrapped_function: t.Callable,
                threshold_time: t.Union[int, float],
                callback: t.Callable,
                *args, **kwargs):
    # Much shorter and cleaner
    start = process_time() * 1000
    output = wrapped_function(*args, **kwargs)
    elapsed = (process_time() * 1000) - start
    if elapsed > threshold_time:
        callback(elapsed, threshold_time)
    return output

Note that the nested function is now gone. This helps reduce boilerplate when writing custom decorators. Note that in @deckorator((float, int), t.Callable), the arguments ((float, int), t.Callable) indicates that the new decorator slower_than accepts two arguments. The first argument can be of either type int or float. The second argument is of type t.Callable. If the passed in arguments do not match the type, deckorator will raise an error.

deckorator makes it easy to create decorators that decorate functions and classes, and also accept arguments. Users can provide templates so that errors are thrown if the decorator is defined using more than or fewer arguments than what was specified. For example ...

from decko import deckorator
import time
import typing as t


@deckorator(t.Callable)
def timer(
          # first argument is always the wrapped function
          wrapped_function,
          # After the wrapped function, the arguments that follow are 
          # the type template arguments. 
          # this decorator accepts one argument of type <t.Callable> 
          # this is required by the "timer" decorator
          # if type is not callable, deckorator will raise an error.
          callback,
          # Since one template arg of type callable is specified, 
          # *args, **kwargs refers to arguments passed into 
          # decorated function. In this example, it would be create_list(n)
          *args,
          **kwargs):
    start_time = time.time()
    output = wrapped_function(*args, **kwargs)
    elapsed = time.time() - start_time
    # Here, callback accepts one string argument 
    # In decorated create_list(), print is the callback function
    callback(f"Time elapsed: {elapsed}")
    return output
if __name__ == "__main__":
    @timer(print)
    def create_list(n):
        return list(range(n))
    create_list(1000000)

If we try to provide a non-callable object such as an int to the timer decorator, we will get an error, because deckorator performs type checking when creating the decorator by default.

if __name__ == "__main__":
    # Remember, our decorator only accepts callable objects ... 
    @timer(10)
    def create_list(n):
        return list(range(n))

    create_list(1000000)

------------------------------------ Console -------------------------------------

Traceback (most recent call last):
  File "..., line 21, in <module>
    @timer(10)
  File "...", line 228, in returned_obj
    raise TypeError(f"Passed invalid type: {type(decorator_arg)}. "
TypeError: Passed invalid type: <class 'int'>. Expected type: 'typing.Callable'
----------------------------------------------------------------------------------

2. Decko

This class provides state management for decorators and allows users to track the statistics for decorators created using decko for extending and debugging purposes.

Status: This wiki will be updated periodically as new features are added.

Updated on: May 30/05/21

Clone this wiki locally