You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-3-lib/pym/calculate/contrib/spyne/application.py

322 lines
12 KiB

#
# spyne - Copyright (C) Spyne contributors.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
#
import logging
logger = logging.getLogger(__name__)
logger_client = logging.getLogger('.'.join([__name__, 'client']))
logger_server = logging.getLogger('.'.join([__name__, 'server']))
from pprint import pformat
from spyne import BODY_STYLE_EMPTY, BODY_STYLE_BARE, BODY_STYLE_WRAPPED, \
EventManager
from spyne.error import Fault, Redirect, RespawnError, InvalidRequestError
from spyne.interface import Interface
from spyne.util import six
from spyne.util.appreg import register_application
class MethodAlreadyExistsError(Exception):
def __init__(self, what):
super(MethodAlreadyExistsError, self) \
.__init__("Method key %r already exists", what)
def get_fault_string_from_exception(e):
# haha.
return "Internal Error"
def return_traceback_in_unhandled_exceptions():
"""Call this function first thing in your main function to return original
python errors to your clients in case of unhandled exceptions.
"""
global get_fault_string_from_exception
import traceback
def _get_fault_string_from_exception(e):
return traceback.format_exc()
get_fault_string_from_exception = _get_fault_string_from_exception
class Application(object):
"""The Application class is the glue between one or more service
definitions, input and output protocols.
:param services: An iterable of Service subclasses that defines
the exposed services.
:param tns: The targetNamespace attribute of the exposed
service.
:param name: The optional name attribute of the exposed service.
The default is the name of the application class
which is by default 'Application'.
:param in_protocol: A ProtocolBase instance that denotes the input
protocol. It's only optional for NullServer transport.
:param out_protocol: A ProtocolBase instance that denotes the output
protocol. It's only optional for NullServer transport.
:param config: An arbitrary python object to store random global data.
Supported events:
* ``method_call``:
Called right before the service method is executed
* ``method_return_object``:
Called right after the service method is executed
* ``method_exception_object``:
Called when an exception occurred in a service method, before the
exception is serialized.
* ``method_context_created``:
Called from the constructor of the MethodContext instance.
* ``method_context_closed``:
Called from the ``close()`` function of the MethodContext instance,
which in turn is called by the transport when the response is fully
sent to the client (or in the client case, the response is fully
received from server).
"""
transport = None
def __init__(self, services, tns, name=None,
in_protocol=None, out_protocol=None, config=None, classes=()):
self.services = tuple(services)
self.tns = tns
self.name = name
self.config = config
self.classes = classes
print("SPYNE DEBUG")
print(__name__)
if self.name is None:
self.name = self.__class__.__name__.split('.')[-1]
logger.info("Initializing application {%s}%s...", self.tns, self.name)
self.event_manager = EventManager(self)
self.error_handler = None
self.in_protocol = in_protocol
self.out_protocol = out_protocol
if self.in_protocol is None:
from spyne.protocol import ProtocolBase
self.in_protocol = ProtocolBase()
if self.out_protocol is None:
from spyne.protocol import ProtocolBase
self.out_protocol = ProtocolBase()
self.check_unique_method_keys() # is this really necessary nowadays?
# this needs to be after protocol assignments to give _static_when
# functions as much info as possible about the application
self.interface = Interface(self)
# set_app needs to be after interface init because the protocols use it.
self.in_protocol.set_app(self)
# FIXME: this normally is another parameter to set_app but it's kept
# separate for backwards compatibility reasons.
self.in_protocol.message = self.in_protocol.REQUEST
self.out_protocol.set_app(self)
# FIXME: this normally is another parameter to set_app but it's kept
# separate for backwards compatibility reasons.
self.out_protocol.message = self.out_protocol.RESPONSE
register_application(self)
def process_request(self, ctx):
"""Takes a MethodContext instance. Returns the response to the request
as a native python object. If the function throws an exception, it
returns None and sets the exception object to ctx.out_error.
Overriding this method would break event management. So this is not
meant to be overridden unless you know what you're doing.
"""
try:
ctx.fire_event('method_call')
# in object is always a sequence of incoming values. We need to fix
# that for bare mode.
if ctx.descriptor.body_style is BODY_STYLE_BARE:
ctx.in_object = [ctx.in_object]
elif ctx.descriptor.body_style is BODY_STYLE_EMPTY:
ctx.in_object = []
# call user method
ctx.out_object = self.call_wrapper(ctx)
# out object is always a sequence of return values. see
# MethodContext docstrings for more info
if ctx.descriptor.body_style is not BODY_STYLE_WRAPPED or \
len(ctx.descriptor.out_message._type_info) <= 1:
# if it's not a wrapped method, OR there's just one return type
# we wrap it ourselves
ctx.out_object = [ctx.out_object]
# Now that the processing is switched to the outgoing message,
# point ctx.protocol to ctx.out_protocol
ctx.protocol = ctx.outprot_ctx
ctx.fire_event('method_return_object')
except Redirect as e:
try:
e.do_redirect()
ctx.out_object = [None]
# Now that the processing is switched to the outgoing message,
# point ctx.protocol to ctx.out_protocol
# point ctx.protocol to ctx.out_protocol
ctx.protocol = ctx.outprot_ctx
ctx.fire_event('method_redirect')
except Exception as e:
logger_server.exception(e)
ctx.out_error = Fault('Server',
get_fault_string_from_exception(e))
ctx.fire_event('method_redirect_exception')
except Fault as e:
if e.faultcode == 'Client' or e.faultcode.startswith('Client.'):
logger_client.exception(e)
else:
logger.exception(e)
ctx.out_error = e
ctx.fire_event('method_exception_object')
# we don't catch BaseException because we actually don't want to catch
# "system-exiting" exceptions. See:
# https://docs.python.org/2/library/exceptions.html#exceptions.Exception
except Exception as e:
logger_server.critical(e, **{'exc_info': 1})
ctx.out_error = Fault('Server', get_fault_string_from_exception(e))
ctx.fire_event('method_exception_object')
def call_wrapper(self, ctx):
"""This method calls the call_wrapper method in the service definition.
This can be overridden to make an application-wide custom exception
management.
"""
# no function
if ctx.function is None:
logger.debug("Skipping user code call as ctx.function is None.")
return None
# @rpc inside service class
if ctx.descriptor.no_self:
assert ctx.descriptor.service_class is not None
return ctx.descriptor.service_class.call_wrapper(ctx)
# from here on it's @mrpc in a (parent) class
cls = ctx.descriptor.parent_class
if cls.__orig__ is not None:
cls = cls.__orig__
filters = {}
inst = cls.__respawn__(ctx, filters)
if inst is None:
raise RespawnError('{%s}%s with params %r' %
(cls.get_namespace(), cls.get_type_name(), filters))
in_cls = ctx.descriptor.in_message
args = tuple(ctx.in_object)
if args is None:
args = ()
elif ctx.descriptor.body_style is BODY_STYLE_WRAPPED and \
len(in_cls.get_flat_type_info(in_cls)) <= 1:
args = ()
else:
args = args[1:]
# check whether this is a valid request according to the prerequisite
# function (the callable that was passed in the _when argument to @mrpc)
if ctx.descriptor.when is not None:
if not ctx.descriptor.when(inst, ctx):
raise InvalidRequestError("Invalid object state for request")
if ctx.descriptor.no_ctx:
args = (inst,) + args
else:
args = (inst, ctx,) + args
if ctx.descriptor.service_class is None:
retval = ctx.function(*args)
else:
retval = ctx.descriptor.service_class.call_wrapper(ctx, args=args)
return retval
def _has_callbacks(self):
return self.interface._has_callbacks()
def reinitialize(self, server):
"""This is normally called on transport instantiation by ServerBase"""
seen = set()
from spyne import MethodDescriptor
for d in self.interface.method_id_map.values():
assert isinstance(d, MethodDescriptor)
if d.aux is not None and not id(d.aux) in seen:
d.aux.initialize(server)
seen.add(id(d.aux))
if d.service_class is not None and not id(d.service_class) in seen:
d.service_class.initialize(server)
seen.add(id(d.service_class))
def __hash__(self):
return hash(tuple((id(s) for s in self.services)))
def check_unique_method_keys(self):
keys = {}
for s in self.services:
#DEBUG
logger.debug("service s: %s", s)
logger.debug("service public methods: %s", s.public_methods.values())
for mdesc in s.public_methods.values():
other_mdesc = keys.get(mdesc.internal_key, None)
if other_mdesc is not None:
logger.error(
'Methods keys for "%s.%s" and "%s.%s" conflict',
mdesc.function.__module__,
six.get_function_name(mdesc.function),
other_mdesc.function.__module__,
six.get_function_name(other_mdesc.function))
raise MethodAlreadyExistsError(mdesc.internal_key)
keys[mdesc.internal_key] = mdesc