1
2#
3# spyne - Copyright (C) Spyne contributors.
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Lesser General Public
7# License as published by the Free Software Foundation; either
8# version 2.1 of the License, or (at your option) any later version.
9#
10# This library is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with this library; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
18#
19
20"""
21This module contains the :class:`Service` class and its helper objects.
22"""
23
24import logging
25logger = logging.getLogger(__name__)
26
27from spyne.util.six.moves.collections_abc import Sequence
28
29from spyne.evmgr import EventManager
30from spyne.util import six
31from spyne.util.oset import oset
32
33
34class ServiceBaseMeta(type):
35    """Adds event managers."""
36
37    def __init__(self, cls_name, cls_bases, cls_dict):
38        super(ServiceBaseMeta, self).__init__(cls_name, cls_bases, cls_dict)
39
40        self.public_methods = {}
41        self.event_manager = EventManager(self,
42                                      self.__get_base_event_handlers(cls_bases))
43
44    def __get_base_event_handlers(self, cls_bases):
45        handlers = {}
46
47        for base in cls_bases:
48            evmgr = getattr(base, 'event_manager', None)
49            if evmgr is None:
50                continue
51
52            for k, v in evmgr.handlers.items():
53                handler = handlers.get(k, oset())
54                for h in v:
55                    handler.add(h)
56                handlers[k] = handler
57
58        return handlers
59
60class ServiceMeta(ServiceBaseMeta):
61    """Creates the :class:`spyne.MethodDescriptor` objects by iterating over
62    tagged methods.
63    """
64
65    def __init__(self, cls_name, cls_bases, cls_dict):
66        super(ServiceMeta, self).__init__(cls_name, cls_bases, cls_dict)
67
68        self.__has_aux_methods = self.__aux__ is not None
69        has_nonaux_methods = None
70
71        for k, v in cls_dict.items():
72            if not hasattr(v, '_is_rpc'):
73                continue
74
75            descriptor = v(_default_function_name=k, _service_class=self)
76
77            # these two lines are needed for staticmethod wrapping to work
78            setattr(self, k, staticmethod(descriptor.function))
79            descriptor.reset_function(getattr(self, k))
80
81            try:
82                getattr(self, k).descriptor = descriptor
83            except AttributeError:
84                pass
85                # FIXME: this fails with builtins. Temporary hack while we
86                # investigate whether we really need this or not
87
88            self.public_methods[k] = descriptor
89            if descriptor.aux is None and self.__aux__ is None:
90                has_nonaux_methods = True
91            else:
92                self.__has_aux_methods = True
93
94            if self.__has_aux_methods and has_nonaux_methods:
95                raise Exception("You can't mix primary and "
96                                "auxiliary methods in a single service definition.")
97
98    def is_auxiliary(self):
99        return self.__has_aux_methods
100
101
102# FIXME: To be renamed to ServiceBase in Spyne 3
103@six.add_metaclass(ServiceBaseMeta)
104class ServiceBaseBase(object):
105    __in_header__ = None
106    """The incoming header object that the methods under this service definition
107    accept."""
108
109    __out_header__ = None
110    """The outgoing header object that the methods under this service definition
111    accept."""
112
113    __service_name__ = None
114    """The name of this service definition as exposed in the interface document.
115    Defaults to the class name."""
116
117    __service_module__ = None
118    """This is used for internal idenfitication of the service class,
119    to override the ``__module__`` attribute."""
120
121    __port_types__ = ()
122    """WSDL-Specific portType mappings"""
123
124    __aux__ = None
125    """The auxiliary method type. When set, the ``aux`` property of every method
126    defined under this service is set to this value. The _aux flag in the @srpc
127    decorator overrides this."""
128
129    @classmethod
130    def get_service_class_name(cls):
131        return cls.__name__
132
133    @classmethod
134    def get_service_name(cls):
135        if cls.__service_name__ is None:
136            return cls.__name__
137        else:
138            return cls.__service_name__
139
140    @classmethod
141    def get_service_module(cls):
142        if cls.__service_module__ is None:
143            return cls.__module__
144        else:
145            return cls.__service_module__
146
147    @classmethod
148    def get_internal_key(cls):
149        return "%s.%s" % (cls.get_service_module(), cls.get_service_name())
150
151    @classmethod
152    def get_port_types(cls):
153        return cls.__port_types__
154
155    @classmethod
156    def _has_callbacks(cls):
157        """Determines if this service definition has callback methods or not."""
158
159        for method in cls.public_methods.values():
160            if method.is_callback:
161                return True
162
163        return False
164
165    @classmethod
166    def get_context(cls):
167        """Returns a user defined context. Override this in your ServiceBase
168        subclass to customize context generation."""
169        return None
170
171    @classmethod
172    def call_wrapper(cls, ctx, args=None):
173        """Called in place of the original method call. You can override this to
174        do your own exception handling.
175
176        :param ctx: The method context.
177
178        The overriding function must call this function by convention.
179        """
180
181        if ctx.function is not None:
182            if args is None:
183                args = ctx.in_object
184
185                assert not isinstance(args, six.string_types)
186
187                # python3 wants a proper sequence as *args
188                if not isinstance(args, Sequence):
189                    args = tuple(args)
190
191                if not ctx.descriptor.no_ctx:
192                    args = (ctx,) + tuple(args)
193
194            return ctx.function(*args)
195
196    @classmethod
197    def initialize(cls, app):
198        pass
199
200
201@six.add_metaclass(ServiceMeta)
202class Service(ServiceBaseBase):
203    """The ``Service`` class is the base class for all service definitions.
204
205    The convention is to have public methods defined under a subclass of this
206    class along with common properties of public methods like header classes or
207    auxiliary processors. The :func:`spyne.decorator.srpc` decorator or its
208    wrappers should be used to flag public methods.
209
210    This class is designed to be subclassed just once. You're supposed to
211    combine Service subclasses in order to get the public method mix you
212    want.
213
214    It is a natural abstract base class, because it's of no use without any
215    method definitions, hence the 'Base' suffix in the name.
216
217    This class supports the following events:
218        * ``method_call``
219            Called right before the service method is executed
220
221        * ``method_return_object``
222            Called right after the service method is executed
223
224        * ``method_exception_object``
225            Called when an exception occurred in a service method, before the
226            exception is serialized.
227
228        * ``method_accept_document``
229            Called by the transport right after the incoming stream is parsed to
230            the incoming protocol's document type.
231
232        * ``method_return_document``
233            Called by the transport right after the outgoing object is
234            serialized to the outgoing protocol's document type.
235
236        * ``method_exception_document``
237            Called by the transport right before the outgoing exception object
238            is serialized to the outgoing protocol's document type.
239
240        * ``method_return_string``
241            Called by the transport right before passing the return string to
242            the client.
243
244        * ``method_exception_string``
245            Called by the transport right before passing the exception string to
246            the client.
247    """
248
249
250# FIXME: To be deleted in Spyne 3
251ServiceBase = Service
252