"""Provide some tools to patch thrift handler."""
from __future__ import absolute_import
import logging
from functools import wraps
from thrift.Thrift import TApplicationException, TException
from thrift.protocol.TBase import TExceptionBase
from thriftpool import thriftpool
from thriftpool.signals import handler_method_guarded
from thriftpool.exceptions import WrappingError
logger = logging.getLogger(__name__)
[docs]def maybe_wraps(method):
"""Ignore wrapping exceptions."""
def inner_wrapper(fn):
try:
return wraps(method)(fn)
except AttributeError:
return fn
return inner_wrapper
[docs]class guarded_method(object):
"""Create guarded method for handler."""
def __init__(self, name, doc=None):
self.__name__ = name
self.__doc__ = doc
def __create_method(self, obj):
"""Create bounded method."""
handler = obj._handler
service_name = obj._service_name
method = getattr(handler, self.__name__)
stack = thriftpool.request_stack
allowed_exceptions = (TException, TExceptionBase)
# Apply all returned by signal decorators.
for sender, decorator in handler_method_guarded.send(sender=handler,
fn=method):
if decorator is not None:
method = decorator(method)
@maybe_wraps(method)
def inner_method(*args, **kwargs):
"""Method that handle unknown exception correctly."""
stack.add(handler, method, args, kwargs, service_name)
with stack:
try:
return method(*args, **kwargs)
except allowed_exceptions:
raise
except Exception as exc:
# Catch all exceptions here, process they here. Write
# application exception to thrift transport.
logger.exception(exc)
code = TApplicationException.INTERNAL_ERROR
msg = "{0}({1})".format(type(exc).__name__, str(exc))
raise TApplicationException(code, msg)
return inner_method
def __get__(self, obj, type=None):
if obj is None:
return self
try:
return obj.__dict__[self.__name__]
except KeyError:
value = obj.__dict__[self.__name__] = self.__create_method(obj)
return value
[docs]class BaseWrappedHandler(object):
"""Abstract base for wrapped handler."""
_handler_cls = None
_service_name = None
_wrapped_methods = None
def __init__(self, handler):
self._handler = handler
# Force methods creation.
for item in self._wrapped_methods:
getattr(self, item)
def __getattr__(self, name):
"""All unknown attribute access go to wrapped handler."""
return getattr(self._handler, name)
[docs]class WrappedHandlerMeta(type):
"""Metaclass that create handler with decorated methods."""
def __new__(mcs, name, bases, attrs):
# Add base class to bases.
if BaseWrappedHandler not in bases:
bases = (BaseWrappedHandler,) + bases
# Ensure that service name provided.
if '_service_name' not in attrs:
raise WrappingError('Missing attribute "_service_name" in'
' class "{0}"'.format(name))
# Get original handler class.
try:
handler_cls = attrs['_handler_cls']
except KeyError:
raise WrappingError('Missing attribute "_handler_cls" in'
' class "{0}"'.format(name))
# Try to find methods of service.
included = set()
stack = set(handler_cls.__bases__)
while stack:
# Collect all bases and try to find all classes with name 'Iface'.
# Methods with collected names will be decorated.
base = stack.pop()
if base is object:
continue
stack.update(base.__bases__)
if base.__name__ == 'Iface':
included.update(vars(base).keys())
# No interface found, fail.
if not included:
raise WrappingError('No methods to wrap found in class "{0}"'.
format(name))
# Find attributes that must be decorated.
included = included - set(vars(type).keys() + ['__weakref__'])
attrs['_wrapped_methods'] = included
# Decorate them!
for attr in included:
method = guarded_method(attr, getattr(handler_cls, attr).__doc__)
attrs[attr] = method
# Create new class.
return super(WrappedHandlerMeta, mcs).__new__(mcs, name, bases, attrs)