Ir al contenido principal

This is my blog, more about me at marianoguerra.github.io

🦋 @marianoguerra.org 🐘 @marianoguerra@hachyderm.io 🐦 @warianoguerra

metawtf

jugando un poco con metaclases y decoradores para hacer los plugins de emesene mas seguros.

el problema que intento resolver es el siguiente.

dada una función o un método que pueden llegar a fallar:

def function(a, b):
return a / b

class Object(object):
def __init__(self, num):
self.num = num

def method(self, a, b):
'''return a / b * self.num'''
return a / b * self.num

print "function(1, 2) = ", function(1, 2)
print "Object(10).method(1, 2) = ", Object(10).method(1, 2)

print "trying function(1, 0)"
try:
function(1, 0)
except ZeroDivisionError, error:
print error

print "trying Object(10).method(1, 0)"
try:
Object(10).method(1, 0)
except ZeroDivisionError, error:
print error


cuya salida es:


function(1, 2) = 0
Object(10).method(1, 2) = 0
trying function(1, 0)
integer division or modulo by zero
trying Object(10).method(1, 0)
integer division or modulo by zero


usando el modulo sandbox podemos hacer:

import sandbox

def callback(function, exception_type, exception):
print "exception raised by %s: %s" % (function, exception)

@sandbox.sandbox(callback, Exception)
def safe_function(a, b):
return a / b

class SafeObject(object):
__metaclass__ = sandbox.meta_decorator(sandbox.sandbox,
callback, Exception)

def __init__(self, num):
self.num = num

def method(self, a, b):
'''return a / b * self.num'''
return a / b * self.num

print "safe_function(1, 2) = ", safe_function(1, 2)
print "SafeObject(10).method(1, 2) = ", SafeObject(10).method(1, 2)

print "trying safe_function(1, 0)"
try:
safe_function(1, 0)
except ZeroDivisionError, error:
print error

print "trying SafeObject(10).method(1, 0)"
try:
SafeObject(10).method(1, 0)
except ZeroDivisionError, error:
print error


la salida es:


safe_function(1, 2) = 0
SafeObject(10).method(1, 2) = 0
trying safe_function(1, 0)
exception raised by safe_function: integer division or modulo by zero
trying SafeObject(10).method(1, 0)
exception raised by method: integer division or modulo by zero


la salida es la salida del callback

otra cosa turbia que se puede hacer, es decorar metodos de objetos, no de clases, aca hay un ejemplo:

obj = Object(10)
sandbox.decorate_object(obj, sandbox.sandbox, callback,
ZeroDivisionError)

print "trying obj.method(1, 0)"
obj.method(1, 0)


notese que decora el objeto, no es que lo copia y devuelve uno similar o algo asi.

lo que hace es decorar un objeto del tipo Objeto (que no es seguro).
la salida:


trying obj.method(1, 0)
exception raised by method: integer division or modulo by zero


para que sirve todo esto? basicamente para asegurarme de que los plugins que cargo no emitan excepciones, y que si lo hacen, un metodo que yo defina decida que hacer con ese plugin (pararlo, reportar el error, seguir o preguntarle al usuario que desea hacer con el mismo).
Todo esto sin requerir que el usuario escriba una linea extra de codigo (lo cual no suelen hacer de todas formas, mientras el plugin les funcione a ellos :P)

el modulo permite crear metaclases que decoren con cualquier decorador que reciba una cantidad arbitraria de parametros, no solo el decorador de sandobox, lo mismo con decorate_object, por lo que pueden ser usados para decorar cualquier cosa con cualquier decorador.

para los curiosos que quieren ver el codigo, aca esta:

import types
import inspect
import functools

class sandbox(object):
'''decorator that will catch the exceptions of type
exception_type and call the callback passing the function, the
exception type and the exception object as parameters'''

def __init__(self, callback, exception_type=Exception):
self.callback = callback
self.exception_type= exception_type

def __call__(self, function):
@functools.wraps(function)
def wrapper(*args, **kwds):
try:
return function(*args, **kwds)
except self.exception_type, exception:
self.callback(function, self.exception_type, exception)

return wrapper

# wtf?
def meta_decorator(decorator, *args, **kwds):
'''return a metaclass that used on a class will decorate
all the methods of the *class* with the decorator
passing args and kwds to the decorator'''

class MetaDecorator(type):
def __init__(cls, name, bases, dct):
type.__init__(cls, name, bases, dct)
methods = [x for x in dct if isinstance(dct[x],
types.FunctionType)]

dec = decorator(*args, **kwds)

for method in methods:
setattr(cls, method, dec(getattr(cls, method)))

return MetaDecorator

class MethodSandbox(object):
'''wrap a method with the sandbox decorator and return a callable
object that is almost identical to the method'''

def __init__(self, method, callback, exception_type=Exception):
functools.update_wrapper(self, method)
self.method = method
self.callback = callback
self.exception_type = exception_type

def __call__(self, *args, **kwds):
try:
return self.method(*args, **kwds)
except self.exception_type, exception:
self.callback(self.method, self.exception_type, exception)

def decorate_object(obj, decorator, *args, **kwds):
'''wrap all the obj methods with the sandbox decorator,
and calling the callback parameter when an exception is raised
it decorates all the methods on an *object*'''
dec = decorator(*args, **kwds)

[setattr(obj, method, dec(getattr(obj, method)))\
for method in dir(obj) if inspect.ismethod(getattr(obj, method))]