Source code for tchannel.thrift.module

# Copyright (c) 2016 Uber Technologies, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

from __future__ import (
    absolute_import, division, print_function, unicode_literals
)

import inspect
import types

from tchannel.deprecate import deprecated
from tchannel.errors import ValueExpectedError
from tchannel.errors import OneWayNotSupportedError
from tchannel.serializer.thrift import ThriftSerializer

from .reflection import get_service_methods, get_module_name


@deprecated(
    "thrift_request_builder is deprecated and will be removed in 0.19.0. "
    "please switch usage to tchannel.thrift.load."
)
[docs]def thrift_request_builder(service, thrift_module, hostport=None, thrift_class_name=None): """Provide TChannel compatibility with Thrift-generated modules. The service this creates is meant to be used with TChannel like so: .. code-block:: python from tchannel import TChannel, thrift_request_builder from some_other_service_thrift import some_other_service tchannel = TChannel('my-service') some_service = thrift_request_builder( service='some-other-service', thrift_module=some_other_service ) resp = tchannel.thrift( some_service.fetchPotatoes() ) .. deprecated:: 0.18.0 Please switch to :py:func:`tchannel.thrift.load`. .. warning:: This API is deprecated and will be removed in a future version. :param string service: Name of Thrift service to call. This is used internally for grouping and stats, but also to route requests over Hyperbahn. :param thrift_module: The top-level module of the Apache Thrift generated code for the service that will be called. :param string hostport: When calling the Thrift service directly, and not over Hyperbahn, this 'host:port' value should be provided. :param string thrift_class_name: When the Apache Thrift generated Iface class name does not match thrift_module, then this should be provided. """ # start with a request maker instance maker = ThriftRequestMaker( service=service, thrift_module=thrift_module, hostport=hostport, thrift_class_name=thrift_class_name ) # create methods that mirror thrift client # and each return ThriftRequest methods = _create_methods(thrift_module) # then attach to instane for name, method in methods.iteritems(): method = types.MethodType(method, maker, ThriftRequestMaker) setattr(maker, name, method) return maker
class ThriftRequestMaker(object): def __init__(self, service, thrift_module, hostport=None, thrift_class_name=None): self.service = service self.thrift_module = thrift_module self.hostport = hostport if thrift_class_name is not None: self.thrift_class_name = thrift_class_name else: self.thrift_class_name = get_module_name(self.thrift_module) def _make_request(self, method_name, args, kwargs): result_type = self._get_result_type(method_name) if result_type is None: raise OneWayNotSupportedError( 'TChannel+Thrift does not currently support oneway ' 'procedures.' ) endpoint = self._get_endpoint(method_name) call_args = self._get_call_args(method_name, args, kwargs) request = ThriftRequest( service=self.service, endpoint=endpoint, result_type=result_type, call_args=call_args, hostport=self.hostport ) return request def _get_endpoint(self, method_name): endpoint = '%s::%s' % (self.thrift_class_name, method_name) return endpoint def _get_args_type(self, method_name): args_type = getattr(self.thrift_module, method_name + '_args') return args_type def _get_result_type(self, method_name): # if None then result_type is oneway result_type = getattr( self.thrift_module, method_name + '_result', None ) return result_type def _get_call_args(self, method_name, args, kwargs): args_type = self._get_args_type(method_name) params = inspect.getcallargs( getattr(self.thrift_module.Iface, method_name), self, *args, **kwargs ) params.pop('self') # self is already known call_args = args_type() for name, value in params.items(): setattr(call_args, name, value) return call_args class ThriftRequest(object): __slots__ = ( 'service', 'endpoint', 'result_type', 'call_args', 'hostport', '_serializer', ) # TODO - implement __repr__ def __init__(self, service, endpoint, result_type, call_args, hostport=None, serializer=None): self.service = service self.endpoint = endpoint self.result_type = result_type self.call_args = call_args self.hostport = hostport if not serializer: serializer = ThriftSerializer(self.result_type) self._serializer = serializer def get_serializer(self): return self._serializer def read_body(self, body): """Handles the response body for this request. If the response body includes a result, returns the result unwrapped from the response union. If the response contains an exception, raises that exception. """ result_spec = self.result_type.thrift_spec # raise application exception, if present for exc_spec in result_spec[1:]: exc = getattr(body, exc_spec[2]) if exc is not None: raise exc # success - non-void if len(result_spec) >= 1 and result_spec[0] is not None: # value expected, but got none # TODO - server side should use this same logic if body.success is None: raise ValueExpectedError( 'Expected a value to be returned for %s, ' 'but recieved None - only void procedures can ' 'return None.' % self.endpoint ) return body.success # success - void else: return None def _create_methods(thrift_module): # TODO - this method isn't needed, instead, do: # # for name in get_service_methods(...): # method = _create_method(...) # # ... # methods = {} method_names = get_service_methods(thrift_module.Iface) for method_name in method_names: method = _create_method(method_name) methods[method_name] = method return methods def _create_method(method_name): # TODO - copy over entire signature using @functools.wraps(that_function) # or wrapt on Iface.<method> def method(self, *args, **kwargs): # TODO switch to __make_request return self._make_request(method_name, args, kwargs) return method