-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Welcome to Decko wiki!
Decko is a decorator-based library designed for the following purposes
- Make the creation of decorators simple and reduce boilerplate
- Change the behavior of existing classes and function with minimal modifications
- Manage the state
- Provide a rich set of pre-made and tested decorators.
Self-contained decorators that can be used without creating a Decko
instance is under development.
Install and update using pip:
pip install -U decko
Uninstall using pip:
pip uninstall decko
Decko's API is divided into two main parts.
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'
----------------------------------------------------------------------------------
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