spyne-2.11.0/0000755000175000001440000000000012352131452012702 5ustar plqusers00000000000000spyne-2.11.0/spyne/0000755000175000001440000000000012352131452014040 5ustar plqusers00000000000000spyne-2.11.0/spyne/auxproc/0000755000175000001440000000000012352131452015521 5ustar plqusers00000000000000spyne-2.11.0/spyne/auxproc/__init__.py0000644000175000001440000000322512342627562017647 0ustar plqusers00000000000000 # # 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 # """The ``spyne.auxproc`` package contains backends to process auxiliary method contexts. "Auxiliary Methods" are methods that run asyncronously once the primary method returns (either successfully or not). There can be only one primary method for a given method identifier but zero or more auxiliary methods. To define multiple auxiliary methods for a given main method, you must use separate :class:`ServiceBase` subclasses that you pass to the :class:`spyne.application.Application` constructor. Auxiliary methods are a useful abstraction for a variety of asyncronous execution methods like persistent or non-persistent queueing, async execution in another thread, process or node. Classes from this package will have the ``AuxProc`` suffix, short for "Auxiliary Processor". This package is EXPERIMENTAL. """ from spyne.auxproc._base import process_contexts from spyne.auxproc._base import AuxProcBase spyne-2.11.0/spyne/auxproc/_base.py0000644000175000001440000000560512342627562017165 0ustar plqusers00000000000000 # # 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 # """Base class and other helper methods for Auxiliary Method Processors ('AuxProc's for short). AuxProcs define how an auxiliary method is going to be executed. """ import logging logger = logging.getLogger(__name__) from spyne import AuxMethodContext def process_contexts(server, contexts, p_ctx, error=None): """Method to be called in the auxiliary context.""" for ctx in contexts: ctx.descriptor.aux.initialize_context(ctx, p_ctx, error) if error is None or ctx.descriptor.aux.process_exceptions: ctx.descriptor.aux.process_context(server, ctx) class AuxProcBase(object): def __init__(self, process_exceptions=False): """Abstract Base class shared by all AuxProcs. :param process_exceptions: If false, does not execute auxiliary methods when the main method throws an exception. """ self.methods = [] self.process_exceptions = process_exceptions def process(self, server, ctx, *args, **kwargs): """The method that does the actual processing. This should be called from the auxiliary context. """ server.get_in_object(ctx) if ctx.in_error is not None: logger.exception(ctx.in_error) return ctx.in_error server.get_out_object(ctx) if ctx.out_error is not None: logger.exception(ctx.out_error) return ctx.out_error server.get_out_string(ctx) for s in ctx.out_string: pass ctx.close() def process_context(self, server, ctx, p_ctx, p_error): """Override this to implement your own auxiliary processor.""" raise NotImplementedError() def initialize(self, server): """Override this method to make arbitrary initialization of your AuxProc. It's called once, 'as late as possible' into the Application initialization.""" def initialize_context(self, ctx, p_ctx, error): """Override this method to alter thow the auxiliary method context is initialized. It's called every time the method is executed. """ ctx.aux = AuxMethodContext(p_ctx, error) spyne-2.11.0/spyne/auxproc/sync.py0000644000175000001440000000207712342627562017070 0ustar plqusers00000000000000 # # 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__) from spyne.auxproc import AuxProcBase class SyncAuxProc(AuxProcBase): """SyncAuxProc processes auxiliary methods synchronously. It's useful for testing purposes. """ def process_context(self, server, ctx): self.process(server, ctx) spyne-2.11.0/spyne/auxproc/thread.py0000644000175000001440000000340412345433230017344 0ustar plqusers00000000000000 # # 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__) from multiprocessing.pool import ThreadPool from spyne.auxproc import AuxProcBase class ThreadAuxProc(AuxProcBase): """ThreadAuxProc processes auxiliary methods asynchronously in another thread using the undocumented ``multiprocessing.pool.ThreadPool`` class. This is available in Python 2.7. It's possibly there since 2.6 as well but it's hard to tell since it's not documented. :param pool_size: Max. number of threads that can be used to process methods in auxiliary queue in parallel. """ def __init__(self, pool_size=1): super(ThreadAuxProc, self).__init__() self.pool = None self.__pool_size = pool_size @property def pool_size(self): return self.__pool_size def process_context(self, server, ctx, *args, **kwargs): self.pool.apply_async(self.process, (server, ctx) + args, kwargs) def initialize(self, server): self.pool = ThreadPool(self.__pool_size) spyne-2.11.0/spyne/client/0000755000175000001440000000000012352131452015316 5ustar plqusers00000000000000spyne-2.11.0/spyne/client/twisted/0000755000175000001440000000000012352131452017001 5ustar plqusers00000000000000spyne-2.11.0/spyne/client/twisted/__init__.py0000644000175000001440000001053612345433230021120 0ustar plqusers00000000000000 # # 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 # """The Twisted Http Client transport.""" from spyne import __version__ as VERSION from spyne.client import Service from spyne.client import RemoteProcedureBase from spyne.client import ClientBase from zope.interface import implements from twisted.internet import reactor from twisted.internet.defer import Deferred from twisted.internet.protocol import Protocol from twisted.web import error as werror from twisted.web.client import Agent from twisted.web.client import ResponseDone from twisted.web.iweb import IBodyProducer from twisted.web.iweb import UNKNOWN_LENGTH from twisted.web.http_headers import Headers class _Producer(object): implements(IBodyProducer) _deferred = None def __init__(self, body): """:param body: an iterable of strings""" self.__paused = False # check to see if we can determine the length try: len(body) # iterator? self.length = sum([len(fragment) for fragment in body]) self.body = iter(body) except TypeError: self.length = UNKNOWN_LENGTH self._deferred = Deferred() def startProducing(self, consumer): self.consumer = consumer self.resumeProducing() return self._deferred def resumeProducing(self): self.__paused = False for chunk in self.body: self.consumer.write(chunk) if self.__paused: break else: self._deferred.callback(None) # done producing forever def pauseProducing(self): self.__paused = True def stopProducing(self): self.__paused = True class _Protocol(Protocol): def __init__(self, ctx): self.ctx = ctx self.deferred = Deferred() def dataReceived(self, bytes): self.ctx.in_string.append(bytes) def connectionLost(self, reason): if reason.check(ResponseDone): self.deferred.callback(None) else: self.deferred.errback(reason) class _RemoteProcedure(RemoteProcedureBase): def __call__(self, *args, **kwargs): # there's no point in having a client making the same request more than # once, so if there's more than just one context, it's rather a bug. # The comma-in-assignment trick is a pedantic way of getting the first # and the only variable from an iterable. so if there's more than one # element in the iterable, it'll fail miserably. self.ctx, = self.contexts self.get_out_object(self.ctx, args, kwargs) self.get_out_string(self.ctx) self.ctx.in_string = [] agent = Agent(reactor) d = agent.request( 'POST', self.url, Headers({'User-Agent': ['Spyne Twisted Http Client %s' % VERSION]}), _Producer(self.ctx.out_string) ) def _process_response(_, response): # this sets ctx.in_error if there's an error, and ctx.in_object if # there's none. self.get_in_object(self.ctx) if self.ctx.in_error is not None: raise self.ctx.in_error elif response.code >= 400: raise werror.Error(response.code) return self.ctx.in_object def _cb_request(response): p = _Protocol(self.ctx) response.deliverBody(p) return p.deferred.addCallback(_process_response, response) return d.addCallback(_cb_request) class TwistedHttpClient(ClientBase): def __init__(self, url, app): super(TwistedHttpClient, self).__init__(url, app) self.service = Service(_RemoteProcedure, url, app) spyne-2.11.0/spyne/client/__init__.py0000644000175000001440000000176612342627562017454 0ustar plqusers00000000000000 # # 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 # """The ``spyne.client`` package contains the client transports.""" from spyne.client._base import Factory from spyne.client._base import Service from spyne.client._base import ClientBase from spyne.client._base import RemoteProcedureBase spyne-2.11.0/spyne/client/_base.py0000644000175000001440000001465312345433230016753 0ustar plqusers00000000000000 # # 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 # """Contains the ClientBase class and its helper objects.""" from spyne._base import MethodContext from spyne.model.primitive import string_encoding class Factory(object): def __init__(self, app): self.__app = app def create(self, object_name): return self.__app.interface.get_class_instance(object_name) class Service(object): def __init__(self, rpc_class, url, app, *args, **kwargs): self.__app = app self.__url = url self.out_header = None self.rpc_class = rpc_class self.args = args self.kwargs = kwargs def __getattr__(self, key): return self.rpc_class(self.__url, self.__app, key, self.out_header, *self.args, **self.kwargs) class RemoteProcedureBase(object): """Abstract base class that handles all (de)serialization. Child classes must implement the client transport in the __call__ method using the following method signature: :: def __call__(self, *args, **kwargs): :param url: The url for the server endpoint. :param app: The application instance the client belongs to. :param name: The string identifier for the remote method. :param out_header: The header that's going to be sent with the remote call. """ def __init__(self, url, app, name, out_header=None): self.url = url self.app = app initial_ctx = MethodContext(self) initial_ctx.method_request_string = name initial_ctx.out_header = out_header self.contexts = initial_ctx.out_protocol.generate_method_contexts(initial_ctx) def __call__(self, *args, **kwargs): """Serializes its arguments, sends them, receives and deserializes the response and returns it.""" raise NotImplementedError() def get_out_object(self, ctx, args, kwargs): """Serializes the method arguments to output document. :param args: Sequential arguments. :param kwargs: Name-based arguments. """ assert ctx.out_object is None request_raw_class = ctx.descriptor.in_message request_type_info = request_raw_class._type_info request_raw = request_raw_class() for i in range(len(request_type_info)): if i < len(args): setattr(request_raw, request_type_info.keys()[i], args[i]) else: setattr(request_raw, request_type_info.keys()[i], None) for k in request_type_info: if k in kwargs: setattr(request_raw, k, kwargs[k]) ctx.out_object = list(request_raw) def get_out_string(self, ctx): """Serializes the output document to a bytestream.""" assert ctx.out_document is None assert ctx.out_string is None ctx.out_protocol.serialize(ctx, ctx.out_protocol.REQUEST) if ctx.service_class != None: if ctx.out_error is None: ctx.service_class.event_manager.fire_event( 'method_return_document', ctx) else: ctx.service_class.event_manager.fire_event( 'method_exception_document', ctx) ctx.out_protocol.create_out_string(ctx, string_encoding) if ctx.service_class != None: if ctx.out_error is None: ctx.service_class.event_manager.fire_event( 'method_return_string', ctx) else: ctx.service_class.event_manager.fire_event( 'method_exception_string', ctx) if ctx.out_string is None: ctx.out_string = [""] def get_in_object(self, ctx): """Deserializes the response bytestream first as a document and then as a native python object. """ assert ctx.in_string is not None assert ctx.in_document is None self.app.in_protocol.create_in_document(ctx) if ctx.service_class != None: ctx.service_class.event_manager.fire_event( 'method_accept_document', ctx) # sets the ctx.in_body_doc and ctx.in_header_doc properties self.app.in_protocol.decompose_incoming_envelope(ctx, message=self.app.in_protocol.RESPONSE) # this sets ctx.in_object self.app.in_protocol.deserialize(ctx, message=self.app.in_protocol.RESPONSE) type_info = ctx.descriptor.out_message._type_info # TODO: Non-Wrapped Object Support if len(ctx.descriptor.out_message._type_info) == 1: wrapper_attribute = type_info.keys()[0] ctx.in_object = getattr(ctx.in_object, wrapper_attribute, None) class ClientBase(object): """The base class for all client applications. ``self.service`` attribute should be initialized in the constructor of the child class. """ def __init__(self, url, app): self.factory = Factory(app) def set_options(self, **kwargs): """Sets call options. :param out_header: Sets the header object that's going to be sent with the remote procedure call. :param soapheaders: A suds-compatible alias for out_header. """ if ('soapheaders' in kwargs) and ('out_header' in kwargs): raise ValueError('you should specify only one of "soapheaders" or ' '"out_header" keyword arguments.') self.service.out_header = kwargs.get('soapheaders', None) if self.service.out_header is None: self.service.out_header = kwargs.get('out_header', None) spyne-2.11.0/spyne/client/django.py0000644000175000001440000000521412345433230017135 0ustar plqusers00000000000000 # # 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 # """The Django client transport for testing Spyne apps the way you'd test Django apps.""" from __future__ import absolute_import from django.test.client import Client from spyne.client import Service from spyne.client import ClientBase from spyne.client import RemoteProcedureBase class _RemoteProcedure(RemoteProcedureBase): def __call__(self, *args, **kwargs): response = self.get_django_response(*args, **kwargs) code = response.status_code self.ctx.in_string = [response.content] # this sets ctx.in_error if there's an error, and ctx.in_object if # there's none. self.get_in_object(self.ctx) if not (self.ctx.in_error is None): raise self.ctx.in_error elif code >= 400: raise self.ctx.in_error else: return self.ctx.in_object def get_django_response(self, *args, **kwargs): """Return Django ``HttpResponse`` object as RPC result.""" # there's no point in having a client making the same request more than # once, so if there's more than just one context, it is a bug. # the comma-in-assignment trick is a general way of getting the first # and the only variable from an iterable. so if there's more than one # element in the iterable, it'll fail miserably. self.ctx, = self.contexts # sets ctx.out_object self.get_out_object(self.ctx, args, kwargs) # sets ctx.out_string self.get_out_string(self.ctx) out_string = ''.join(self.ctx.out_string) # Hack client = Client() return client.post(self.url, content_type='text/xml', data=out_string) class DjangoTestClient(ClientBase): """The Django test client transport.""" def __init__(self, url, app): super(DjangoTestClient, self).__init__(url, app) self.service = Service(_RemoteProcedure, url, app) spyne-2.11.0/spyne/client/http.py0000644000175000001440000000522312345433230016652 0ustar plqusers00000000000000 # # 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 # """The HTTP (urllib2) client transport.""" try: from urllib2 import Request from urllib2 import urlopen from urllib2 import HTTPError except ImportError: # Python 3 from urllib.request import Request from urllib.request import urlopen from urllib.error import HTTPError from spyne.client import Service from spyne.client import ClientBase from spyne.client import RemoteProcedureBase class _RemoteProcedure(RemoteProcedureBase): def __call__(self, *args, **kwargs): # there's no point in having a client making the same request more than # once, so if there's more than just one context, it is a bug. # the comma-in-assignment trick is a general way of getting the first # and the only variable from an iterable. so if there's more than one # element in the iterable, it'll fail miserably. self.ctx, = self.contexts # sets ctx.out_object self.get_out_object(self.ctx, args, kwargs) # sets ctx.out_string self.get_out_string(self.ctx) out_string = ''.join(self.ctx.out_string) # FIXME: just send the iterable to the http stream. request = Request(self.url, out_string) code = 200 try: response = urlopen(request) self.ctx.in_string = [response.read()] except HTTPError as e: code = e.code self.ctx.in_string = [e.read()] # this sets ctx.in_error if there's an error, and ctx.in_object if # there's none. self.get_in_object(self.ctx) if not (self.ctx.in_error is None): raise self.ctx.in_error elif code >= 400: raise self.ctx.in_error else: return self.ctx.in_object class HttpClient(ClientBase): def __init__(self, url, app): super(HttpClient, self).__init__(url, app) self.service = Service(_RemoteProcedure, url, app) spyne-2.11.0/spyne/client/zeromq.py0000644000175000001440000000331412345433230017207 0ustar plqusers00000000000000 # # 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 # """The ZeroMQ (zmq.REQ) client transport.""" import zmq from spyne.client import Service from spyne.client import RemoteProcedureBase from spyne.client import ClientBase context = zmq.Context() class _RemoteProcedure(RemoteProcedureBase): def __call__(self, *args, **kwargs): self.ctx = self.contexts[0] self.get_out_object(self.ctx, args, kwargs) self.get_out_string(self.ctx) out_string = b''.join(self.ctx.out_string) socket = context.socket(zmq.REQ) socket.connect(self.url) socket.send(out_string) self.ctx.in_string = [socket.recv()] self.get_in_object(self.ctx) if not (self.ctx.in_error is None): raise self.ctx.in_error else: return self.ctx.in_object class ZeroMQClient(ClientBase): def __init__(self, url, app): super(ZeroMQClient, self).__init__(url, app) self.service = Service(_RemoteProcedure, url, app) spyne-2.11.0/spyne/const/0000755000175000001440000000000012352131452015166 5ustar plqusers00000000000000spyne-2.11.0/spyne/const/__init__.py0000644000175000001440000000434212345433230017303 0ustar plqusers00000000000000 # # 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 # """The ``spyne.const`` package contains miscellanous constant values needed in various parts of Spyne.""" MAX_STRING_FIELD_LENGTH = 64 """Maximum length of a string field for :func:`spyne.util.log_repr`""" MAX_ARRAY_ELEMENT_NUM = 2 """Maximum number of array elements for :func:`spyne.util.log_repr`""" MAX_FIELD_NUM = 10 """Maximum number of complex model fields for :func:`spyne.util.log_repr`""" ARRAY_PREFIX = '' """The prefix for Array wrapper objects. You may want to set this to 'ArrayOf' and the ARRAY_SUFFIX to '' for compatibility with some SOAP deployments.""" ARRAY_SUFFIX = 'Array' """The suffix for Array wrapper objects.""" REQUEST_SUFFIX = '' """The suffix for function response objects.""" RESPONSE_SUFFIX = 'Response' """The suffix for function response objects.""" RESULT_SUFFIX = 'Result' """The suffix for function response wrapper objects.""" TYPE_SUFFIX = 'Type' """The suffix for primitives with unnamed constraints.""" PARENT_SUFFIX = 'Parent' """The suffix for parent classes of primitives with unnamed constraints.""" MANDATORY_PREFIX = 'Mandatory' """The prefix for types created with the :func:`spyne.model.Mandatory`.""" MANDATORY_SUFFIX = '' """The suffix for types created with the :func:`spyne.model.Mandatory`.""" DEFAULT_DECLARE_ORDER = 'random' """Order of complex type attrs of :class:`spyne.model.complex.ComplexModel`.""" def add_request_suffix(string): """Concatenates REQUEST_SUFFIX to end of string""" return string + REQUEST_SUFFIX spyne-2.11.0/spyne/const/ansi_color.py0000644000175000001440000000411712342627562017706 0ustar plqusers00000000000000 # # 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 # """You can use the constants in this package to add colour to your logs. You can use the "colorama" package to get ANSI colors working on windows. """ DARK_RED = "" """ANSI colour value for dark red if colours are enabled, empty string otherwise.""" LIGHT_GREEN = "" """ANSI colour value for light green if colours are enabled, empty string otherwise.""" LIGHT_RED = "" """ANSI colour value for light red if colours are enabled, empty string otherwise.""" LIGHT_BLUE = "" """ANSI colour value for light blue if colours are enabled, empty string otherwise.""" END_COLOR = "" """ANSI colour value for end color marker if colours are enabled, empty string otherwise.""" def enable_color(): """Enable colors by setting colour code constants to ANSI color codes.""" global LIGHT_GREEN LIGHT_GREEN = "\033[1;32m" global LIGHT_RED LIGHT_RED = "\033[1;31m" global LIGHT_BLUE LIGHT_BLUE = "\033[1;34m" global DARK_RED DARK_RED = "\033[0;31m" global END_COLOR END_COLOR = "\033[0m" def disable_color(): """Disable colours by setting colour code constants to empty strings.""" global LIGHT_GREEN LIGHT_GREEN = "" global LIGHT_RED LIGHT_RED = "" global LIGHT_BLUE LIGHT_BLUE = "" global DARK_RED DARK_RED = "" global END_COLOR END_COLOR = "" enable_color() spyne-2.11.0/spyne/const/http.py0000644000175000001440000001035412345433230016523 0ustar plqusers00000000000000 # # 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 # """The ``spyne.const.http module contains the Http response status codes.""" HTTP_200 = '200 OK' HTTP_201 = '201 Created' HTTP_202 = '202 Accepted' HTTP_203 = '203 Non-Authoritative Information' # (since HTTP/1.1) HTTP_204 = '204 No Content' HTTP_205 = '205 Reset Content' HTTP_206 = '206 Partial Content' HTTP_207 = '207 Multi-Status' # (WebDAV; RFC 4918) HTTP_208 = '208 Already Reported' # (WebDAV; RFC 5842) HTTP_226 = '226 IM Used' # (RFC 3229) HTTP_300 = '300 Multiple Choices' HTTP_301 = '301 Moved Permanently' HTTP_302 = '302 Found' HTTP_303 = '303 See Other' # (since HTTP/1.1) HTTP_304 = '304 Not Modified' HTTP_305 = '305 Use Proxy' # (since HTTP/1.1) HTTP_306 = '306 Switch Proxy' HTTP_307 = '307 Temporary Redirect' # (since HTTP/1.1) HTTP_308 = '308 Permanent Redirect' # (approved as experimental RFC])[11] HTTP_400 = '400 Bad Request' HTTP_401 = '401 Unauthorized' HTTP_402 = '402 Payment Required' HTTP_403 = '403 Forbidden' HTTP_404 = '404 Not Found' HTTP_405 = '405 Method Not Allowed' HTTP_406 = '406 Not Acceptable' HTTP_407 = '407 Proxy Authentication Required' HTTP_408 = '408 Request Timeout' HTTP_409 = '409 Conflict' HTTP_410 = '410 Gone' HTTP_411 = '411 Length Required' HTTP_412 = '412 Precondition Failed' HTTP_413 = '413 Request Entity Too Large' HTTP_414 = '414 Request-URI Too Long' HTTP_415 = '415 Unsupported Media Type' HTTP_416 = '416 Requested Range Not Satisfiable' HTTP_417 = '417 Expectation Failed' HTTP_418 = "418 I'm a teapot" # (RFC 2324) HTTP_420 = '420 Enhance Your Calm' # (Twitter) HTTP_422 = '422 Unprocessable Entity' # (WebDAV; RFC 4918) HTTP_423 = '423 Locked' # (WebDAV; RFC 4918) HTTP_424 = '424 Failed Dependency' # (WebDAV; RFC 4918) HTTP_425 = '425 Unordered Collection' # (Internet draft) HTTP_426 = '426 Upgrade Required' # (RFC 2817) HTTP_428 = '428 Precondition Required' # (RFC 6585) HTTP_429 = '429 Too Many Requests' # (RFC 6585) HTTP_431 = '431 Request Header Fields Too Large' # (RFC 6585) HTTP_444 = '444 No Response' # (Nginx) HTTP_449 = '449 Retry With' # (Microsoft) HTTP_450 = '450 Blocked by Windows Parental Controls' # (Microsoft) HTTP_451 = '451 Unavailable For Legal Reasons' # (Internet draft) HTTP_494 = '494 Request Header Too Large' # (Nginx) HTTP_495 = '495 Cert Error' # (Nginx) HTTP_496 = '496 No Cert' # (Nginx) HTTP_497 = '497 HTTP to HTTPS' # (Nginx) HTTP_499 = '499 Client Closed Request' # (Nginx) HTTP_500 = '500 Internal Server Error' HTTP_501 = '501 Not Implemented' HTTP_502 = '502 Bad Gateway' HTTP_503 = '503 Service Unavailable' HTTP_504 = '504 Gateway Timeout' HTTP_505 = '505 HTTP Version Not Supported' HTTP_506 = '506 Variant Also Negotiates' # (RFC 2295) HTTP_507 = '507 Insufficient Storage' # (WebDAV; RFC 4918) HTTP_508 = '508 Loop Detected' # (WebDAV; RFC 5842) HTTP_509 = '509 Bandwidth Limit Exceeded' # (Apache bw/limited extension) HTTP_510 = '510 Not Extended' # (RFC 2774) HTTP_511 = '511 Network Authentication Required' # (RFC 6585) HTTP_598 = '598 Network read timeout error' # (Unknown) HTTP_599 = '599 Network connect timeout error' # (Unknown) def gen_body_redirect(code, location): from lxml.html.builder import E from lxml.html import tostring return tostring(E.HTML( E.HEAD( E.meta(**{ "http-equiv": "content-type", "content": "text/html;charset=utf-8", }), E.TITLE(code), ), E.BODY( E.H1(code), E.P("The document has moved"), E.A("here", HREF=location), ".", ) )) spyne-2.11.0/spyne/const/xml_ns.py0000644000175000001440000000410512345433230017041 0ustar plqusers00000000000000 # # 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 # """The ``spyne.const.xml`` module contains various XML-related constants like namespace prefixes, namespace values and schema uris. """ #SHOULDHAVEFIXEDME: These are constants, so should have been all UPPERCASE. xml = 'http://www.w3.org/XML/1998/namespace' xsd = 'http://www.w3.org/2001/XMLSchema' xsi = 'http://www.w3.org/2001/XMLSchema-instance' wsa = 'http://schemas.xmlsoap.org/ws/2003/03/addressing' xop = 'http://www.w3.org/2004/08/xop/include' soap = 'http://schemas.xmlsoap.org/wsdl/soap/' wsdl = 'http://schemas.xmlsoap.org/wsdl/' plink = 'http://schemas.xmlsoap.org/ws/2003/05/partner-link/' soap_enc = 'http://schemas.xmlsoap.org/soap/encoding/' soap_env = 'http://schemas.xmlsoap.org/soap/envelope/' soap12_env = 'http://www.w3.org/2003/05/soap-envelope/' soap12_enc = 'http://www.w3.org/2003/05/soap-encoding/' const_nsmap = { 'xml': xml, 'xs': xsd, 'xsi': xsi, 'plink': plink, 'soap': soap, 'wsdl': wsdl, 'senc': soap_enc, 'senv': soap_env, 'soap12env': soap12_env, 'soap12enc': soap12_enc, 'wsa': wsa, 'xop': xop, } const_prefmap = None def regen_prefmap(): global const_prefmap const_prefmap = dict([(b, a) for a, b in const_nsmap.items()]) regen_prefmap() schema_location = { xsd: 'http://www.w3.org/2001/XMLSchema.xsd', } class DEFAULT_NS(object): pass spyne-2.11.0/spyne/interface/0000755000175000001440000000000012352131452016000 5ustar plqusers00000000000000spyne-2.11.0/spyne/interface/wsdl/0000755000175000001440000000000012352131452016751 5ustar plqusers00000000000000spyne-2.11.0/spyne/interface/wsdl/__init__.py0000644000175000001440000000175312342627562021103 0ustar plqusers00000000000000 # # 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 # """The ``spyne.interface.wsdl`` package contains an implementation of the subset Wsdl 1.1 standard, and awaits for volunteers for implementing the brand new Wsdl 2.0 standard. """ from spyne.interface.wsdl.wsdl11 import Wsdl11 spyne-2.11.0/spyne/interface/wsdl/defn.py0000644000175000001440000000540612345433230020245 0ustar plqusers00000000000000 from spyne.util.six import add_metaclass from spyne.const import xml_ns from spyne.model.primitive import Unicode from spyne.model.complex import XmlAttribute from spyne.model.complex import ComplexModelBase from spyne.model.complex import ComplexModelMeta from spyne.interface.xml_schema.defn import XmlSchema10 @add_metaclass(ComplexModelMeta) class Wsdl11Base(ComplexModelBase): __namespace__ = xml_ns.wsdl @add_metaclass(ComplexModelMeta) class Soap11Base(ComplexModelBase): __namespace__ = xml_ns.soap class Types(Wsdl11Base): schema = XmlSchema10.customize(max_occurs="unbounded") class MessagePart(Wsdl11Base): element = XmlAttribute(Unicode) name = XmlAttribute(Unicode) class Message(Wsdl11Base): part = MessagePart name = XmlAttribute(Unicode) class SoapBodyDefinition(Wsdl11Base): use = XmlAttribute(Unicode) class SoapHeaderDefinition(Wsdl11Base): use = XmlAttribute(Unicode) message = XmlAttribute(Unicode) part = XmlAttribute(Unicode) class OperationMode(Wsdl11Base): name = XmlAttribute(Unicode) message = XmlAttribute(Unicode) soap_body = SoapBodyDefinition.customize(sub_ns=xml_ns.soap, sub_name="body") soap_header = SoapHeaderDefinition.customize(sub_ns=xml_ns.soap, sub_name="header") class SoapOperation(Wsdl11Base): soapAction = XmlAttribute(Unicode) style = XmlAttribute(Unicode) class Operation(Wsdl11Base): input = OperationMode output = OperationMode soap_operation = SoapOperation.customize(sub_ns=xml_ns.soap, sub_name="operation") parameterOrder = XmlAttribute(Unicode) class PortType(Wsdl11Base): name = XmlAttribute(Unicode) operation = Operation.customize(max_occurs="unbounded") class SoapBinding(Soap11Base): style = XmlAttribute(Unicode) transport = XmlAttribute(Unicode) class Binding(Wsdl11Base): name = XmlAttribute(Unicode) type = XmlAttribute(Unicode) location = XmlAttribute(Unicode) soap_binding = SoapBinding.customize(sub_ns=xml_ns.soap, sub_name="binding") class PortAddress(Soap11Base): location = XmlAttribute(Unicode) class ServicePort(Wsdl11Base): name = XmlAttribute(Unicode) binding = XmlAttribute(Unicode) address = PortAddress.customize(sub_ns=xml_ns.soap) class Service(Wsdl11Base): port = ServicePort name = XmlAttribute(Unicode) class Wsdl11(Wsdl11Base): _type_info = [ ('types', Types), ('message', Message.customize(max_occurs="unbounded")), ('service', Service), ('portType', PortType), ('binding', Binding), ] spyne-2.11.0/spyne/interface/wsdl/wsdl11.py0000644000175000001440000005443012345433230020445 0ustar plqusers00000000000000 # # 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 # """The ``spyne.interface.wsdl.wsdl11`` module contains an implementation of a subset of the Wsdl 1.1 document standard and its helper methods. """ import logging logger = logging.getLogger(__name__) import re REGEX_WSDL = re.compile('[.?]wsdl$') import spyne.const.xml_ns from lxml import etree from spyne.interface.xml_schema import XmlSchema _ns_plink = spyne.const.xml_ns.plink _ns_xsd = spyne.const.xml_ns.xsd _ns_wsa = spyne.const.xml_ns.wsa _ns_wsdl = spyne.const.xml_ns.wsdl _ns_soap = spyne.const.xml_ns.soap _pref_wsa = spyne.const.xml_ns.const_prefmap[_ns_wsa] _in_header_msg_suffix = 'InHeaderMsg' _out_header_msg_suffix = 'OutHeaderMsg' def check_method_port(service, method): if len(service.__port_types__) != 0 and method.port_type is None: raise ValueError(""" A port must be declared in the RPC decorator if the service class declares a list of ports Method: %r """ % method.name) if (not method.port_type is None) and len(service.__port_types__) == 0: raise ValueError(""" The rpc decorator has declared a port while the service class has not. Remove the port declaration from the rpc decorator or add a list of ports to the service class """) try: if (not method.port_type is None): index = service.__port_types__.index(method.port_type) except ValueError as e: raise ValueError(""" The port specified in the rpc decorator does not match any of the ports defined by the service class """) # FIXME: I don't think this is working. def _add_callbacks(service, root, types, service_name, url): ns_tns = service.get_tns() pref_tns = 'tns' cb_port_type = None # add necessary async headers # WS-Addressing -> RelatesTo ReplyTo MessageID # callback porttype if service._has_callbacks(): wsa_schema = etree.SubElement(types, "{%s}schema" % _ns_xsd) wsa_schema.set("targetNamespace", '%sCallback' % ns_tns) wsa_schema.set("elementFormDefault", "qualified") import_ = etree.SubElement(wsa_schema, "{%s}import" % _ns_xsd) import_.set("namespace", _ns_wsa) import_.set("schemaLocation", _ns_wsa) relt_message = etree.SubElement(root, '{%s}message' % _ns_wsdl) relt_message.set('name', 'RelatesToHeader') relt_part = etree.SubElement(relt_message, '{%s}part' % _ns_wsdl) relt_part.set('name', 'RelatesTo') relt_part.set('element', '%s:RelatesTo' % _pref_wsa) reply_message = etree.SubElement(root, '{%s}message' % _ns_wsdl) reply_message.set('name', 'ReplyToHeader') reply_part = etree.SubElement(reply_message, '{%s}part' % _ns_wsdl) reply_part.set('name', 'ReplyTo') reply_part.set('element', '%s:ReplyTo' % _pref_wsa) id_header = etree.SubElement(root, '{%s}message' % _ns_wsdl) id_header.set('name', 'MessageIDHeader') id_part = etree.SubElement(id_header, '{%s}part' % _ns_wsdl) id_part.set('name', 'MessageID') id_part.set('element', '%s:MessageID' % _pref_wsa) # make portTypes cb_port_type = etree.SubElement(root, '{%s}portType' % _ns_wsdl) cb_port_type.set('name', '%sCallback' % service_name) cb_service_name = '%sCallback' % service_name cb_service = etree.SubElement(root, '{%s}service' % _ns_wsdl) cb_service.set('name', cb_service_name) cb_wsdl_port = etree.SubElement(cb_service, '{%s}port' % _ns_wsdl) cb_wsdl_port.set('name', cb_service_name) cb_wsdl_port.set('binding', '%s:%s' % (pref_tns, cb_service_name)) cb_address = etree.SubElement(cb_wsdl_port, '{%s}address' % _ns_soap) cb_address.set('location', url) return cb_port_type class Wsdl11(XmlSchema): """The implementation of the Wsdl 1.1 interface definition document standard which is avaible here: http://www.w3.org/TR/wsdl :param app: The parent application. :param _with_partnerlink: Include the partnerLink tag in the wsdl. Supported events: * document_built: Called right after the document is built. The handler gets the ``Wsdl11`` instance as the only argument. Also called by XmlSchema class. * wsdl_document_built: Called right after the document is built. The handler gets the ``Wsdl11`` instance as the only argument. Only called from this class. """ #:param import_base_namespaces: Include imports for base namespaces like # xsd, xsi, wsdl, etc. def __init__(self, interface=None, _with_partnerlink=False): super(Wsdl11, self).__init__(interface) self._with_plink = _with_partnerlink self.port_type_dict = {} self.service_elt_dict = {} self.root_elt = None self.service_elt = None self.__wsdl = None self.validation_schema = None def _get_binding_name(self, port_type_name): return port_type_name # subclasses override to control port names. def _get_or_create_port_type(self, pt_name): """Creates a wsdl:portType element.""" pt = None if not pt_name in self.port_type_dict: pt = etree.SubElement(self.root_elt, '{%s}portType' % _ns_wsdl) pt.set('name', pt_name) self.port_type_dict[pt_name] = pt else: pt = self.port_type_dict[pt_name] return pt def _get_or_create_service_node(self, service_name): """Builds a wsdl:service element.""" ser = None if not service_name in self.service_elt_dict: ser = etree.SubElement(self.root_elt, '{%s}service' % _ns_wsdl) ser.set('name', service_name) self.service_elt_dict[service_name] = ser else: ser = self.service_elt_dict[service_name] return ser def get_interface_document(self): return self.__wsdl def build_interface_document(self, url): """Build the wsdl for the application.""" self.build_schema_nodes() self.url = REGEX_WSDL.sub('', url) service_name = self.interface.get_name() # create wsdl root node self.root_elt = root = etree.Element("{%s}definitions" % _ns_wsdl, nsmap=self.interface.nsmap) root.set('targetNamespace', self.interface.tns) root.set('name', service_name) # create types node types = etree.SubElement(root, "{%s}types" % _ns_wsdl) for s in self.schema_dict.values(): types.append(s) messages = set() for s in self.interface.services: self.add_messages_for_methods(s, root, messages) if self._with_plink: plink = etree.SubElement(root, '{%s}partnerLinkType' % _ns_plink) plink.set('name', service_name) self.__add_partner_link(service_name, plink) # create service nodes in advance. they're to be filled in subsequent # add_port_type calls. for s in self.interface.services: if not s.is_auxiliary(): self._get_or_create_service_node(self._get_applied_service_name(s)) # create portType nodes for s in self.interface.services: if not s.is_auxiliary(): self.add_port_type(s, root, service_name, types, self.url) cb_binding = None for s in self.interface.services: if not s.is_auxiliary(): cb_binding = self.add_bindings_for_methods(s, root, service_name, cb_binding) if self.interface.app.transport is None: raise Exception("You must set the 'transport' property of the " "parent 'Application' instance") self.event_manager.fire_event('document_built', self) self.event_manager.fire_event('wsdl_document_built', self) self.__wsdl = etree.tostring(root, xml_declaration=True, encoding="UTF-8") def __add_partner_link(self, service_name, plink): """Add the partnerLinkType node to the wsdl.""" ns_tns = self.interface.tns pref_tns = self.interface.get_namespace_prefix(ns_tns) role = etree.SubElement(plink, '{%s}role' % _ns_plink) role.set('name', service_name) plink_port_type = etree.SubElement(role, '{%s}portType' % _ns_plink) plink_port_type.set('name', '%s:%s' % (pref_tns, service_name)) if self._has_callbacks(): role = etree.SubElement(plink, '{%s}role' % _ns_plink) role.set('name', '%sCallback' % service_name) plink_port_type = etree.SubElement(role, '{%s}portType' % _ns_plink) plink_port_type.set('name', '%s:%sCallback' % (pref_tns, service_name)) def _add_port_to_service(self, service, port_name, binding_name): """ Builds a wsdl:port for a service and binding""" pref_tns = self.interface.get_namespace_prefix(self.interface.tns) wsdl_port = etree.SubElement(service, '{%s}port' % _ns_wsdl) wsdl_port.set('name', port_name) wsdl_port.set('binding', '%s:%s' % (pref_tns, binding_name)) addr = etree.SubElement(wsdl_port, '{%s}address' % _ns_soap) addr.set('location', self.url) def _has_callbacks(self): for s in self.interface.services: if s._has_callbacks(): return True return False def _get_applied_service_name(self, service): if service.get_service_name() is None: # This is the default behavior. i.e. no service interface is # defined in the service heading if len(self.interface.services) == 1: retval = self.get_name() else: retval = service.get_service_class_name() else: retval = service.get_service_name() return retval def add_port_type(self, service, root, service_name, types, url): # FIXME: I don't think this call is working. cb_port_type = _add_callbacks(service, root, types, service_name, url) applied_service_name = self._get_applied_service_name(service) port_binding_names = [] port_type_list = service.get_port_types() if len(port_type_list) > 0: for port_type_name in port_type_list: port_type = self._get_or_create_port_type(port_type_name) port_type.set('name', port_type_name) binding_name = self._get_binding_name(port_type_name) port_binding_names.append((port_type_name, binding_name)) else: port_type = self._get_or_create_port_type(service_name) port_type.set('name', service_name) binding_name = self._get_binding_name(service_name) port_binding_names.append((service_name, binding_name)) for method in service.public_methods.values(): check_method_port(service, method) if method.is_callback: operation = etree.SubElement(cb_port_type, '{%s}operation' % _ns_wsdl) else: operation = etree.SubElement(port_type, '{%s}operation' % _ns_wsdl) operation.set('name', method.operation_name) if method.doc is not None: documentation = etree.SubElement(operation, '{%s}documentation' % _ns_wsdl) documentation.text = method.doc operation.set('parameterOrder', method.in_message.get_element_name()) op_input = etree.SubElement(operation, '{%s}input' % _ns_wsdl) op_input.set('name', method.in_message.get_element_name()) op_input.set('message', method.in_message.get_element_name_ns(self.interface)) if (not method.is_callback) and (not method.is_async): op_output = etree.SubElement(operation, '{%s}output' % _ns_wsdl) op_output.set('name', method.out_message.get_element_name()) op_output.set('message', method.out_message.get_element_name_ns( self.interface)) if not (method.faults is None): for f in method.faults: fault = etree.SubElement(operation, '{%s}fault' % _ns_wsdl) fault.set('name', f.get_type_name()) fault.set('message', '%s:%s' % ( f.get_namespace_prefix(self.interface), f.get_type_name())) ser = self.service_elt_dict[applied_service_name] for port_name, binding_name in port_binding_names: self._add_port_to_service(ser, port_name, binding_name) def _add_message_for_object(self, root, messages, obj, message_name): if obj is not None and not (message_name in messages): messages.add(message_name) message = etree.SubElement(root, '{%s}message' % _ns_wsdl) message.set('name', message_name) if isinstance(obj, (list, tuple)): objs = obj else: objs = (obj,) for obj in objs: part = etree.SubElement(message, '{%s}part' % _ns_wsdl) part.set('name', obj.get_element_name()) part.set('element', obj.get_element_name_ns(self.interface)) def add_messages_for_methods(self, service, root, messages): for method in service.public_methods.values(): self._add_message_for_object(root, messages, method.in_message, method.in_message.get_element_name()) self._add_message_for_object(root, messages, method.out_message, method.out_message.get_element_name()) if method.in_header is not None: if len(method.in_header) > 1: in_header_message_name = ''.join((method.name, _in_header_msg_suffix)) else: in_header_message_name = method.in_header[0].get_type_name() self._add_message_for_object(root, messages, method.in_header, in_header_message_name) if method.out_header is not None: if len(method.out_header) > 1: out_header_message_name = ''.join((method.name, _out_header_msg_suffix)) else: out_header_message_name = method.out_header[0].get_type_name() self._add_message_for_object(root, messages, method.out_header, out_header_message_name) for fault in method.faults: self._add_message_for_object(root, messages, fault, fault.get_type_name()) def add_bindings_for_methods(self, service, root, service_name, cb_binding): pref_tns = self.interface.get_namespace_prefix(service.get_tns()) def inner(method, binding): operation = etree.Element('{%s}operation' % _ns_wsdl) operation.set('name', method.operation_name) soap_operation = etree.SubElement(operation, '{%s}operation' % _ns_soap) soap_operation.set('soapAction', method.operation_name) soap_operation.set('style', 'document') # get input input = etree.SubElement(operation, '{%s}input' % _ns_wsdl) input.set('name', method.in_message.get_element_name()) soap_body = etree.SubElement(input, '{%s}body' % _ns_soap) soap_body.set('use', 'literal') # get input soap header in_header = method.in_header if in_header is None: in_header = service.__in_header__ if not (in_header is None): if isinstance(in_header, (list, tuple)): in_headers = in_header else: in_headers = (in_header,) if len(in_headers) > 1: in_header_message_name = ''.join((method.name, _in_header_msg_suffix)) else: in_header_message_name = in_headers[0].get_type_name() for header in in_headers: soap_header = etree.SubElement(input, '{%s}header' % _ns_soap) soap_header.set('use', 'literal') soap_header.set('message', '%s:%s' % ( header.get_namespace_prefix(self.interface), in_header_message_name)) soap_header.set('part', header.get_type_name()) if not (method.is_async or method.is_callback): output = etree.SubElement(operation, '{%s}output' % _ns_wsdl) output.set('name', method.out_message.get_element_name()) soap_body = etree.SubElement(output, '{%s}body' % _ns_soap) soap_body.set('use', 'literal') # get output soap header out_header = method.out_header if out_header is None: out_header = service.__out_header__ if not (out_header is None): if isinstance(out_header, (list, tuple)): out_headers = out_header else: out_headers = (out_header,) if len(out_headers) > 1: out_header_message_name = ''.join((method.name, _out_header_msg_suffix)) else: out_header_message_name = out_headers[0].get_type_name() for header in out_headers: soap_header = etree.SubElement(output, '{%s}header' % _ns_soap) soap_header.set('use', 'literal') soap_header.set('message', '%s:%s' % ( header.get_namespace_prefix(self.interface), out_header_message_name)) soap_header.set('part', header.get_type_name()) if not (method.faults is None): for f in method.faults: wsdl_fault = etree.SubElement(operation, '{%s}fault' % _ns_wsdl) wsdl_fault.set('name', f.get_type_name()) soap_fault = etree.SubElement(wsdl_fault, '{%s}fault' % _ns_soap) soap_fault.set('name', f.get_type_name()) soap_fault.set('use', 'literal') if method.is_callback: relates_to = etree.SubElement(input, '{%s}header' % _ns_soap) relates_to.set('message', '%s:RelatesToHeader' % pref_tns) relates_to.set('part', 'RelatesTo') relates_to.set('use', 'literal') cb_binding.append(operation) else: if method.is_async: rt_header = etree.SubElement(input, '{%s}header' % _ns_soap) rt_header.set('message', '%s:ReplyToHeader' % pref_tns) rt_header.set('part', 'ReplyTo') rt_header.set('use', 'literal') mid_header = etree.SubElement(input, '{%s}header'% _ns_soap) mid_header.set('message', '%s:MessageIDHeader' % pref_tns) mid_header.set('part', 'MessageID') mid_header.set('use', 'literal') binding.append(operation) port_type_list = service.get_port_types() if len(port_type_list) > 0: for port_type_name in port_type_list: # create binding nodes binding = etree.SubElement(root, '{%s}binding' % _ns_wsdl) binding.set('name', port_type_name) binding.set('type', '%s:%s'% (pref_tns, port_type_name)) transport = etree.SubElement(binding, '{%s}binding' % _ns_soap) transport.set('style', 'document') for m in service.public_methods.values(): if m.port_type == port_type_name: inner(m, binding) else: # here is the default port. if cb_binding is None: cb_binding = etree.SubElement(root, '{%s}binding' % _ns_wsdl) cb_binding.set('name', service_name) cb_binding.set('type', '%s:%s'% (pref_tns, service_name)) transport = etree.SubElement(cb_binding, '{%s}binding' % _ns_soap) transport.set('style', 'document') transport.set('transport', self.interface.app.transport) for m in service.public_methods.values(): inner(m, cb_binding) return cb_binding spyne-2.11.0/spyne/interface/xml_schema/0000755000175000001440000000000012352131452020120 5ustar plqusers00000000000000spyne-2.11.0/spyne/interface/xml_schema/__init__.py0000644000175000001440000000205512342627562022246 0ustar plqusers00000000000000 # # 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 # """The spyne.interface.xml_schema package contains an implementation of a subset of the Xml Schema 1.0 standard. Volunteers are needed to see whether the brand new Xml Schema 1.1 standard is worth the trouble, and patch as necessary. """ from spyne.interface.xml_schema._base import XmlSchema spyne-2.11.0/spyne/interface/xml_schema/_base.py0000644000175000001440000002445012345433230021551 0ustar plqusers00000000000000 # # 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('spyne.interface.xml_schema') import shutil import tempfile import spyne.const.xml_ns from lxml import etree from itertools import chain from spyne.util.cdict import cdict from spyne.util.odict import odict from spyne.util.toposort import toposort2 from spyne.model import SimpleModel, ByteArray, ComplexModelBase, Fault, \ Decimal, DateTime, Date, Time, Unicode from spyne.model.enum import EnumBase from spyne.interface import InterfaceDocumentBase from spyne.interface.xml_schema.model import byte_array_add from spyne.interface.xml_schema.model import simple_add from spyne.interface.xml_schema.model import complex_add from spyne.interface.xml_schema.model import fault_add from spyne.interface.xml_schema.model import enum_add from spyne.interface.xml_schema.model import simple_get_restriction_tag from spyne.interface.xml_schema.model import unicode_get_restriction_tag from spyne.interface.xml_schema.model import Tget_range_restriction_tag _add_handlers = cdict({ object: lambda interface, cls, tags: None, ByteArray: byte_array_add, SimpleModel: simple_add, ComplexModelBase: complex_add, Fault: fault_add, EnumBase: enum_add, }) _get_restriction_tag_handlers = cdict({ object: lambda self, cls: None, SimpleModel: simple_get_restriction_tag, Unicode: unicode_get_restriction_tag, Decimal: Tget_range_restriction_tag(Decimal), DateTime: Tget_range_restriction_tag(DateTime), Time: Tget_range_restriction_tag(Time), Date: Tget_range_restriction_tag(Date), }) _ns_xsd = spyne.const.xml_ns.xsd _ns_wsa = spyne.const.xml_ns.wsa _ns_wsdl = spyne.const.xml_ns.wsdl _ns_soap = spyne.const.xml_ns.soap _pref_wsa = spyne.const.xml_ns.const_prefmap[_ns_wsa] class SchemaInfo(object): def __init__(self): self.elements = odict() self.types = odict() class XmlSchema(InterfaceDocumentBase): """The implementation of a subset of the Xml Schema 1.0 object definition document standard. The standard is available in three parts as follows: http://www.w3.org/TR/xmlschema-0/ http://www.w3.org/TR/xmlschema-1/ http://www.w3.org/TR/xmlschema-2/ :param interface: A :class:`spyne.interface.InterfaceBase` instance. Supported events: * document_built: Called right after the document is built. The handler gets the ``XmlSchema`` instance as the only argument. * xml_document_built: Called right after the document is built. The handler gets the ``XmlSchema`` instance as the only argument. Only called from this class. """ def __init__(self, interface): super(XmlSchema, self).__init__(interface) self.schema_dict = {} self.validation_schema = None pref = self.interface.prefmap[self.interface.app.tns] self.namespaces = odict({pref: SchemaInfo()}) self.complex_types = set() def add(self, cls, tags): if not (cls in tags): tags.add(cls) handler = _add_handlers[cls] handler(self, cls, tags) def get_restriction_tag(self, cls): handler = _get_restriction_tag_handlers[cls] return handler(self, cls) def build_schema_nodes(self, with_schema_location=False): self.schema_dict = {} for cls in chain.from_iterable(toposort2(self.interface.deps)): self.add(cls, set()) for pref in self.namespaces: schema = self.get_schema_node(pref) # append import tags for namespace in self.interface.imports[self.interface.nsmap[pref]]: import_ = etree.SubElement(schema, "{%s}import" % _ns_xsd) import_.set("namespace", namespace) import_pref = self.interface.get_namespace_prefix(namespace) if with_schema_location and \ self.namespaces.get(import_pref, False): import_.set('schemaLocation', "%s.xsd" % import_pref) sl = spyne.const.xml_ns.schema_location.get(namespace, None) if not (sl is None): import_.set('schemaLocation', sl) # append simpleType and complexType tags for node in self.namespaces[pref].types.values(): schema.append(node) # append element tags for node in self.namespaces[pref].elements.values(): schema.append(node) self.add_missing_elements_for_methods() self.event_manager.fire_event('document_built', self) self.event_manager.fire_event('xml_document_built', self) def add_missing_elements_for_methods(self): def missing_methods(): for service in self.interface.services: for method in service.public_methods.values(): if method.aux is None: yield method pref_tns = self.interface.prefmap[self.interface.tns] elements = self.get_schema_info(pref_tns).elements schema_root = self.schema_dict[pref_tns] for method in missing_methods(): name = method.in_message.Attributes.sub_name if name is None: name = method.in_message.get_type_name() if not name in elements: element = etree.Element('{%s}element' % _ns_xsd) element.set('name', name) element.set('type', method.in_message.get_type_name_ns( self.interface)) elements[name] = element schema_root.append(element) if method.out_message is not None: name = method.out_message.Attributes.sub_name if name is None: name = method.out_message.get_type_name() if not name in elements: element = etree.Element('{%s}element' % _ns_xsd) element.set('name', name) element.set('type', method.out_message \ .get_type_name_ns(self.interface)) elements[name] = element schema_root.append(element) def build_validation_schema(self): """Build application schema specifically for xml validation purposes.""" self.build_schema_nodes(with_schema_location=True) pref_tns = self.interface.get_namespace_prefix(self.interface.tns) tmp_dir_name = tempfile.mkdtemp(prefix='spyne') logger.debug("generating schema for targetNamespace=%r, prefix: " "%r in dir %r" % (self.interface.tns, pref_tns, tmp_dir_name)) try: # serialize nodes to files for k, v in self.schema_dict.items(): file_name = '%s/%s.xsd' % (tmp_dir_name, k) with open(file_name, 'wb') as f: etree.ElementTree(v).write(f, pretty_print=True) logger.debug("writing %r for ns %s" % (file_name, self.interface.nsmap[k])) with open('%s/%s.xsd' % (tmp_dir_name, pref_tns), 'r') as f: try: self.validation_schema = etree.XMLSchema(etree.parse(f)) except Exception: f.seek(0) logger.error("This is a Spyne error. Please seek support " "with a minimal test case that reproduces " "this error.") raise shutil.rmtree(tmp_dir_name) logger.debug("Schema built. Removed %r" % tmp_dir_name) except Exception as e: logger.exception(e) logger.error("The schema files are left at: %r" % tmp_dir_name) raise def get_schema_node(self, pref): """Return schema node for the given namespace prefix.""" if not (pref in self.schema_dict): schema = etree.Element("{%s}schema" % _ns_xsd, nsmap=self.interface.nsmap) schema.set("targetNamespace", self.interface.nsmap[pref]) schema.set("elementFormDefault", "qualified") self.schema_dict[pref] = schema else: schema = self.schema_dict[pref] return schema def get_interface_document(self): return self.schema_dict def build_interface_document(self): self.build_schema_nodes() def add_element(self, cls, node): pref = cls.get_element_name_ns(self.interface).split(":")[0] schema_info = self.get_schema_info(pref) name = cls.Attributes.sub_name or cls.get_type_name() schema_info.elements[name] = node def add_simple_type(self, cls, node): tn = cls.get_type_name() pref = cls.get_namespace_prefix(self.interface) schema_info = self.get_schema_info(pref) schema_info.types[tn] = node def add_complex_type(self, cls, node): tn = cls.get_type_name() pref = cls.get_namespace_prefix(self.interface) schema_info = self.get_schema_info(pref) schema_info.types[tn] = node def get_schema_info(self, prefix): """Returns the SchemaInfo object for the corresponding namespace. It creates it if it doesn't exist. The SchemaInfo object holds the simple and complex type definitions for a given namespace.""" if prefix in self.namespaces: schema = self.namespaces[prefix] else: schema = self.namespaces[prefix] = SchemaInfo() return schema spyne-2.11.0/spyne/interface/xml_schema/defn.py0000644000175000001440000001411312345433230021407 0ustar plqusers00000000000000 # # 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 # from spyne.util.six import add_metaclass from spyne.const import xml_ns from spyne.model.primitive import Boolean, AnyHtml from spyne.model.primitive import Unicode from spyne.model.primitive import UnsignedInteger from spyne.model.complex import XmlAttribute from spyne.model.complex import ComplexModelBase from spyne.model.complex import ComplexModelMeta @add_metaclass(ComplexModelMeta) class SchemaBase(ComplexModelBase): __namespace__ = xml_ns.xsd class Import(SchemaBase): namespace = XmlAttribute(Unicode) class Element(SchemaBase): name = XmlAttribute(Unicode) type = XmlAttribute(Unicode) ref = XmlAttribute(Unicode) # it can be "unbounded", so it should be of type Unicode max_occurs = XmlAttribute(Unicode(default="1", sub_name="maxOccurs")) # Also Unicode for consistency with max_occurs min_occurs = XmlAttribute(Unicode(default="1", sub_name="minOccurs")) nillable = XmlAttribute(Boolean(default=False)) default = XmlAttribute(Unicode) class IntegerAttribute(SchemaBase): value = XmlAttribute(UnsignedInteger) class StringAttribute(SchemaBase): value = XmlAttribute(Unicode) class SimpleType(SchemaBase): _type_info = [ ('name', XmlAttribute(Unicode)), ] class Attribute(SchemaBase): use = XmlAttribute(Unicode) ref = XmlAttribute(Unicode) name = XmlAttribute(Unicode) type = XmlAttribute(Unicode) default = XmlAttribute(Unicode) simple_type = SimpleType.customize(sub_name='simpleType') class Restriction(SchemaBase): _type_info = [ ('base', XmlAttribute(Unicode)), ('max_length', IntegerAttribute.customize(sub_name="maxLength")), ('min_length', IntegerAttribute.customize(sub_name="minLength")), ('pattern', StringAttribute), ('enumeration', StringAttribute.customize(max_occurs="unbounded")), ('attributes', Attribute.customize(max_occurs="unbounded", sub_name="attribute")), ] SimpleType.append_field('restriction', Restriction) class Choice(SchemaBase): elements = Element.customize(max_occurs="unbounded", sub_name="element") class Sequence(SchemaBase): elements = Element.customize(max_occurs="unbounded", sub_name="element") choices = Choice.customize(max_occurs="unbounded", sub_name="choice") class Extension(SchemaBase): base = XmlAttribute(Unicode) attributes = Attribute.customize(max_occurs="unbounded", sub_name="attribute") class SimpleContent(SchemaBase): extension = Extension restriction = Restriction class ComplexType(SchemaBase): name = XmlAttribute(Unicode) sequence = Sequence simple_content = SimpleContent.customize(sub_name="simpleContent") attributes = Attribute.customize(max_occurs="unbounded", sub_name="attribute") choice = Choice class Include(SchemaBase): schema_location = XmlAttribute(Unicode(sub_name="schemaLocation")) class XmlSchema10(SchemaBase): _type_info = [ ('target_namespace', XmlAttribute(Unicode(sub_name="targetNamespace"))), ('element_form_default', XmlAttribute(Unicode( sub_name="elementFormDefault"))), ('imports', Import.customize(max_occurs="unbounded", sub_name="import")), ('includes', Include.customize(max_occurs="unbounded", sub_name="include")), ('elements', Element.customize(max_occurs="unbounded", sub_name="element")), ('simple_types', SimpleType.customize(max_occurs="unbounded", sub_name="simpleType")), ('complex_types', ComplexType.customize(max_occurs="unbounded", sub_name="complexType")), ('attributes', Attribute.customize(max_occurs="unbounded", sub_name="attribute")), ] from itertools import chain from inspect import isclass from spyne.model import ModelBase from spyne.model import primitive from spyne.model import binary from spyne.model.fault import Fault TYPE_MAP = dict([ ("{%s}%s" % (cls.get_namespace(), cls.get_type_name()), cls) for cls in chain( [v for v in vars(primitive).values() if getattr(v, '__type_name__', None) is not None], [ binary.ByteArray(encoding='base64'), binary.ByteArray(encoding='hex'), ], [ primitive.Point(2), primitive.Point(3), primitive.Line(2), primitive.Line(3), primitive.Polygon(2), primitive.Polygon(3), primitive.MultiPoint(2), primitive.MultiPoint(3), primitive.MultiLine(2), primitive.MultiLine(3), primitive.MultiPolygon(2), primitive.MultiPolygon(3), ] ) if isclass(cls) and issubclass(cls, ModelBase) and not issubclass(cls, (Fault, AnyHtml)) and not cls in (ModelBase,) ]) if __name__ == '__main__': from pprint import pprint pprint(TYPE_MAP) spyne-2.11.0/spyne/interface/xml_schema/genpy.py0000644000175000001440000001002612345433230021614 0ustar plqusers00000000000000 # # 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 # """ A barely functional Spyne class serializer. If you're using this as part of anything serious, you're insane. TODO: - Customizations are not serialized. """ import logging logger = logging.getLogger(__name__) from collections import defaultdict from itertools import chain from spyne.model import SimpleModel from spyne.model.complex import XmlModifier from spyne.model.complex import ComplexModelBase def gen_fn_from_tns(tns): return tns \ .replace('http://', '') \ .replace('https://', '') \ .replace('/', '') \ .replace('.', '_') \ .replace(':', '_') \ .replace('#', '') \ .replace('-', '_') class CodeGenerator(object): def __init__(self, fn_tns_mapper=gen_fn_from_tns): self.imports = set() self.classes = set() self.pending = defaultdict(list) self.simples = set() self.fn_tns_mapper = fn_tns_mapper def gen_modifier(self, t): return '%s(%s)' % (t.__name__, self.gen_dispatch(t.type)) def gen_simple(self, t): return t.__name__ def gen_complex(self, t): retval = [] retval.append(""" class %s(_ComplexBase): _type_info = [""" % (t.get_type_name())) for k,v in t._type_info.items(): if not issubclass(v, ComplexModelBase) or \ v.get_namespace() != self.tns or \ v in self.classes or \ getattr(v, '__orig__', None) in self.classes: retval.append(" ('%s', %s)," % (k, self.gen_dispatch(v))) else: self.pending[v.get_type_name()].append((k, t.get_type_name())) retval.append(" ]") self.classes.add(t) for k,orig_t in self.pending[t.get_type_name()]: retval.append('%s._type_info["%s"] = %s' % (orig_t, k, t.get_type_name())) return retval def gen_dispatch(self, t): if issubclass(t, XmlModifier): return self.gen_modifier(t) if issubclass(t, SimpleModel): return self.gen_simple(t) if t.get_namespace() == self.tns: return t.get_type_name() i = self.fn_tns_mapper(t.get_namespace()) self.imports.add(i) return "%s.%s" % (i, t.get_type_name()) def genpy(self, tns, s): self.tns = tns retval = [u"""# encoding: utf8 # Automatically generated by Spyne. Modify at your own risk. from spyne.model import * """, "", # imports """ class _ComplexBase(ComplexModelBase): __namespace__ = '%s' __metaclass__ = ComplexModelMeta""" % tns ] for n, t in s.types.items(): if issubclass(t, ComplexModelBase): retval.extend(self.gen_complex(t)) else: retval.append('%s = %s' % (n, self.gen_dispatch(t))) self.simples.add(n) for i in self.imports: retval.insert(1, "import %s" % i) retval.append("") retval.append("") retval.append('__all__ = [') for c in sorted(chain([c.get_type_name() for c in self.classes], self.simples)): retval.append(" '%s'," % c) retval.append(']') retval.append("") return '\n'.join(retval) spyne-2.11.0/spyne/interface/xml_schema/model.py0000644000175000001440000003564012352126507021607 0ustar plqusers00000000000000 # # 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 # """The ``spyne.interface.xml_schema.model`` module contains type-specific logic for schema generation.""" import logging logger = logging.getLogger(__name__) from decimal import Decimal as D from collections import deque, defaultdict from lxml import etree from spyne.model import ModelBase, XmlAttribute, AnyXml, Unicode, XmlData,\ Decimal, Integer from spyne.const.xml_ns import xsd as _ns_xsd from spyne.util import memoize from spyne.util.cdict import cdict from spyne.util.etreeconv import dict_to_etree from spyne.util.six import string_types from spyne.protocol.xml import XmlDocument _prot = XmlDocument() ATTR_NAMES = cdict({ ModelBase: set(['values']), Decimal: set(['pattern', 'gt', 'ge', 'lt', 'le', 'values', 'total_digits', 'fraction_digits']), Integer: set(['pattern', 'gt', 'ge', 'lt', 'le', 'values', 'total_digits']), Unicode: set(['values', 'min_len', 'max_len', 'pattern']), }) def xml_attribute_add(cls, name, element, document): element.set('name', name) element.set('type', cls.type.get_type_name_ns(document.interface)) if cls._use is not None: element.set('use', cls._use) d = cls.type.Attributes.default if d is not None: element.set('default', _prot.to_string(cls.type, d)) def _check_extension_attrs(cls): extends = cls.__extends__ eattrs = extends.Attributes cattrs = cls.Attributes ckeys = set([k for k in vars(cls.Attributes) if not k.startswith('_')]) ekeys = set([k for k in vars(extends.Attributes) if not k.startswith('_')]) diff = set() for k in (ckeys | ekeys): if getattr(eattrs, k, None) != getattr(cattrs, k, None): diff.add(k) attr_names = ATTR_NAMES[cls] retval = None while extends is not None: retval = extends if len(diff & attr_names) > 0: return extends extends = extends.__extends__ return retval # noinspection PyDefaultArgument def simple_get_restriction_tag(document, cls): extends = _check_extension_attrs(cls) if extends is None: return simple_type = etree.Element('{%s}simpleType' % _ns_xsd) simple_type.set('name', cls.get_type_name()) document.add_simple_type(cls, simple_type) restriction = etree.SubElement(simple_type, '{%s}restriction' % _ns_xsd) restriction.set('base', extends.get_type_name_ns(document.interface)) for v in cls.Attributes.values: enumeration = etree.SubElement(restriction, '{%s}enumeration' % _ns_xsd) enumeration.set('value', str(v)) return restriction def simple_add(document, cls, tags): if not cls.is_default(cls): document.get_restriction_tag(cls) def byte_array_add(document, cls, tags): simple_add(document, cls, tags) def complex_add(document, cls, tags): complex_type = etree.Element("{%s}complexType" % _ns_xsd) complex_type.set('name', cls.get_type_name()) if cls.Annotations.doc != '' or cls.Annotations.appinfo != None or \ cls.Annotations.__use_parent_doc__: annotation = etree.SubElement(complex_type, "{%s}annotation" % _ns_xsd) if cls.Annotations.doc != '' or cls.Annotations.__use_parent_doc__: doc = etree.SubElement(annotation, "{%s}documentation" % _ns_xsd) if cls.Annotations.__use_parent_doc__: doc.text = getattr(cls, '__doc__') else: doc.text = cls.Annotations.doc _ai = cls.Annotations.appinfo; if _ai != None: appinfo = etree.SubElement(annotation, "{%s}appinfo" % _ns_xsd) if isinstance(_ai, dict): dict_to_etree(_ai, appinfo) elif isinstance(_ai, str) or isinstance(_ai, unicode): appinfo.text = _ai elif isinstance(_ai, etree._Element): appinfo.append(_ai) else: from spyne.util.xml import get_object_as_xml appinfo.append(get_object_as_xml(_ai)) sequence_parent = complex_type extends = getattr(cls, '__extends__', None) type_info = cls._type_info if extends is not None: if (extends.get_type_name() == cls.get_type_name() and extends.get_namespace() == cls.get_namespace()): raise Exception("%r can't extend %r because they are both '{%s}%s'" % (cls, extends, cls.get_namespace(), cls.get_type_name())) if extends.Attributes.exc_interface: # If the parent class is private, it won't be in the schema, so we # need to act as if its attributes are part of cls as well. type_info = cls.get_simple_type_info(cls) else: complex_content = etree.SubElement(complex_type, "{%s}complexContent" % _ns_xsd) extension = etree.SubElement(complex_content, "{%s}extension" % _ns_xsd) extension.set('base', extends.get_type_name_ns(document.interface)) sequence_parent = extension xtba_key, xtba_type = cls.Attributes._xml_tag_body_as if xtba_key is not None: _sc = etree.SubElement(sequence_parent, '{%s}simpleContent' % _ns_xsd) xtba_ext = etree.SubElement(_sc, '{%s}extension' % _ns_xsd) xtba_ext.attrib['base'] = xtba_type.type.get_type_name_ns( document.interface) sequence = etree.Element('{%s}sequence' % _ns_xsd) deferred = deque() choice_tags = defaultdict(lambda: etree.Element('{%s}choice' % _ns_xsd)) for k, v in type_info.items(): assert isinstance(k, string_types) assert issubclass(v, ModelBase) a = v.Attributes if a.exc_interface: continue if issubclass(v, XmlData): continue if issubclass(v, XmlAttribute): deferred.append((k,v)) continue document.add(v, tags) name = a.sub_name if name is None: name = k #ns = a.sub_ns #if ns is not None: # name = "{%s}%s" % (ns, name) type_name_ns = v.get_type_name_ns(document.interface) if v.__extends__ is not None and v.__orig__ is not None and \ _check_extension_attrs(v) is None: type_name_ns = v.__orig__.get_type_name_ns(document.interface) member = etree.Element(a.schema_tag) if a.schema_tag == '{%s}element' % _ns_xsd: member.set('name', name) member.set('type', type_name_ns) elif a.schema_tag == '{%s}any' % _ns_xsd and issubclass(v, AnyXml): if a.namespace is not None: member.set('namespace', a.namespace) if a.process_contents is not None: member.set('processContents', a.process_contents) else: raise ValueError("Unhandled schema_tag / type combination. %r %r" % (v, a.schema_tag)) if a.min_occurs != 1: # 1 is the xml schema default member.set('minOccurs', str(a.min_occurs)) if a.max_occurs != 1: # 1 is the xml schema default val = a.max_occurs if val in (D('inf'), float('inf')): val = 'unbounded' else: val = str(val) member.set('maxOccurs', val) if a.default is not None: member.set('default', _prot.to_string(v, a.default)) if bool(a.nillable) != False: # False is the xml schema default member.set('nillable', 'true') if v.Annotations.doc != '': # Doesn't support multi-language documentation annotation = etree.SubElement(member, "{%s}annotation" % _ns_xsd) doc = etree.SubElement(annotation, "{%s}documentation" % _ns_xsd) doc.text = v.Annotations.doc if a.xml_choice_group is None: sequence.append(member) else: choice_tags[a.xml_choice_group].append(member) sequence.extend(choice_tags.values()) if len(sequence) > 0: sequence_parent.append(sequence) _ext_elements = dict() for k,v in deferred: ao = v.attribute_of if ao is None: attribute = etree.Element('{%s}attribute' % _ns_xsd) xml_attribute_add(v, k, attribute, document) if xtba_key is None: complex_type.append(attribute) else: xtba_ext.append(attribute) continue elts = complex_type.xpath("//xsd:element[@name='%s']" % ao, namespaces={'xsd': _ns_xsd}) if len(elts) == 0: raise ValueError("Element %r not found for XmlAttribute %r." % (ao, k)) elif len(elts) > 1: raise Exception("Xpath returned more than one element %r " "for %r. Not sure what's going on here." % (elts, ao)) else: elt = elts[0] _ext = _ext_elements.get(ao, None) if _ext is None: _ct = etree.SubElement(elt, '{%s}complexType' % _ns_xsd) _sc = etree.SubElement(_ct, '{%s}simpleContent' % _ns_xsd) _ext = etree.SubElement(_sc, '{%s}extension' % _ns_xsd) _ext_elements[ao] = _ext _ext.attrib['base'] = elt.attrib['type'] del elt.attrib['type'] attribute = etree.SubElement(_ext, '{%s}attribute' % _ns_xsd) xml_attribute_add(v, k, attribute, document) document.add_complex_type(cls, complex_type) # simple node complex_type_name = cls.Attributes.sub_name or cls.get_type_name() element = etree.Element('{%s}element' % _ns_xsd) element.set('name', complex_type_name) element.set('type', cls.get_type_name_ns(document.interface)) document.add_element(cls, element) def enum_add(document, cls, tags): simple_type = etree.Element('{%s}simpleType' % _ns_xsd) simple_type.set('name', cls.get_type_name()) restriction = etree.SubElement(simple_type, '{%s}restriction' % _ns_xsd) restriction.set('base', '%s:string' % document.interface.get_namespace_prefix(_ns_xsd)) for v in cls.__values__: enumeration = etree.SubElement(restriction, '{%s}enumeration' % _ns_xsd) enumeration.set('value', v) document.add_simple_type(cls, simple_type) fault_add = complex_add def unicode_get_restriction_tag(document, cls): restriction = simple_get_restriction_tag(document, cls) if restriction is None: return # length if cls.Attributes.min_len == cls.Attributes.max_len: length = etree.SubElement(restriction, '{%s}length' % _ns_xsd) length.set('value', str(cls.Attributes.min_len)) else: if cls.Attributes.min_len != Unicode.Attributes.min_len: min_l = etree.SubElement(restriction, '{%s}minLength' % _ns_xsd) min_l.set('value', str(cls.Attributes.min_len)) if cls.Attributes.max_len != Unicode.Attributes.max_len: max_l = etree.SubElement(restriction, '{%s}maxLength' % _ns_xsd) max_l.set('value', str(cls.Attributes.max_len)) # pattern if cls.Attributes.pattern != Unicode.Attributes.pattern: pattern = etree.SubElement(restriction, '{%s}pattern' % _ns_xsd) pattern.set('value', cls.Attributes.pattern) return restriction @memoize def Tget_range_restriction_tag(T): """The get_range_restriction template function. Takes a primitive, returns a function that generates range restriction tags. """ from spyne.model.primitive import Decimal from spyne.model.primitive import Integer if issubclass(T, Decimal): def _get_float_restrictions(prot, restriction, cls): if cls.Attributes.fraction_digits != T.Attributes.fraction_digits: elt = etree.SubElement(restriction, '{%s}fractionDigits' % _ns_xsd) elt.set('value', prot.to_string(cls, cls.Attributes.fraction_digits)) def _get_integer_restrictions(prot, restriction, cls): if cls.Attributes.total_digits != T.Attributes.total_digits: elt = etree.SubElement(restriction, '{%s}totalDigits' % _ns_xsd) elt.set('value', prot.to_string(cls, cls.Attributes.total_digits)) if issubclass(T, Integer): def _get_additional_restrictions(prot, restriction, cls): _get_integer_restrictions(prot, restriction, cls) else: def _get_additional_restrictions(prot, restriction, cls): _get_integer_restrictions(prot, restriction, cls) _get_float_restrictions(prot, restriction, cls) else: def _get_additional_restrictions(prot, restriction, cls): pass def _get_range_restriction_tag(document, cls): prot = document.interface.app.in_protocol restriction = simple_get_restriction_tag(document, cls) if restriction is None: return if cls.Attributes.gt != T.Attributes.gt: elt = etree.SubElement(restriction, '{%s}minExclusive' % _ns_xsd) elt.set('value', prot.to_string(cls, cls.Attributes.gt)) if cls.Attributes.ge != T.Attributes.ge: elt = etree.SubElement(restriction, '{%s}minInclusive' % _ns_xsd) elt.set('value', prot.to_string(cls, cls.Attributes.ge)) if cls.Attributes.lt != T.Attributes.lt: elt = etree.SubElement(restriction, '{%s}maxExclusive' % _ns_xsd) elt.set('value', prot.to_string(cls, cls.Attributes.lt)) if cls.Attributes.le != T.Attributes.le: elt = etree.SubElement(restriction, '{%s}maxInclusive' % _ns_xsd) elt.set('value', prot.to_string(cls, cls.Attributes.le)) if cls.Attributes.pattern != T.Attributes.pattern: elt = etree.SubElement(restriction, '{%s}pattern' % _ns_xsd) elt.set('value', cls.Attributes.pattern) _get_additional_restrictions(prot, restriction, cls) return restriction return _get_range_restriction_tag spyne-2.11.0/spyne/interface/xml_schema/parser.py0000644000175000001440000004531512345433230021777 0ustar plqusers00000000000000 # # 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__) import os from spyne.util import six from itertools import chain from pprint import pformat from copy import copy from os.path import dirname from os.path import abspath from os.path import join from lxml import etree from spyne.util import memoize from spyne.util.odict import odict from spyne.model import Null from spyne.model import XmlData from spyne.model import XmlAttribute from spyne.model import Array from spyne.model import ComplexModelBase from spyne.model import ComplexModelMeta from spyne.protocol.xml import XmlDocument from spyne.interface.xml_schema.defn import TYPE_MAP from spyne.interface.xml_schema.defn import SchemaBase from spyne.interface.xml_schema.defn import XmlSchema10 from spyne.util.color import R, G, B, M, Y PARSER = etree.XMLParser(remove_comments=True) _prot = XmlDocument() class _Schema(object): def __init__(self): self.types = {} self.elements = {} self.imports = set() # FIXME: Needs to emit delayed assignment of recursive structures instead of # lousy ellipses. @memoize def Thier_repr(with_ns=False): """Template for ``hier_repr``, a ``repr`` variant that shows spyne ``ComplexModel``s in a hierarchical format. :param with_ns: either bool or a callable that returns the class name as string """ if with_ns is False: def get_class_name(c): return c.get_type_name() elif with_ns is True or with_ns is 1: def get_class_name(c): return "{%s}%s" % (c.get_namespace(), c.get_type_name()) else: def get_class_name(c): return with_ns(c.get_namespace(), c.get_type_name()) def hier_repr(inst, i0=0, I=' ', tags=None): if tags is None: tags = set() cls = inst.__class__ if not hasattr(cls, '_type_info'): return repr(inst) clsid = "%s" % (get_class_name(cls)) if id(inst) in tags: return clsid tags.add(id(inst)) i1 = i0 + 1 i2 = i1 + 1 retval = [] retval.append(clsid) retval.append('(') xtba_key, xtba_type = cls.Attributes._xml_tag_body_as if xtba_key is not None: value = getattr(inst, xtba_key, None) retval.append("%s,\n" % hier_repr(value, i1, I, tags)) else: retval.append('\n') for k,v in inst.get_flat_type_info(cls).items(): value = getattr(inst, k, None) if (issubclass(v, Array) or v.Attributes.max_occurs > 1) and \ value is not None: retval.append("%s%s=[\n" % (I*i1, k)) for subval in value: retval.append("%s%s,\n" % (I*i2, hier_repr(subval,i2, I, tags))) retval.append('%s],\n' % (I*i1)) elif issubclass(v, XmlData): pass else: retval.append("%s%s=%s,\n" % (I*i1, k, hier_repr(value, i1, I, tags))) retval.append('%s)' % (I*i0)) return ''.join(retval) return hier_repr SchemaBase.__repr__ = Thier_repr() class XmlSchemaParser(object): def __init__(self, files, base_dir=None, repr_=Thier_repr(with_ns=False)): self.retval = {} self.indent = 0 self.files = files self.base_dir = base_dir self.repr = repr_ if self.base_dir is None: self.base_dir = os.getcwd() self.parent = None self.children = None self.prefmap = None self.tns = None self.pending_elements = None self.pending_types = None def clone(self, indent=0, base_dir=None): retval = copy(self) if retval.parent is None: retval.parent = self if self.children is None: self.children = [retval] else: self.children.append(retval) else: retval.parent.children.append(retval) retval.indent = self.indent + indent if base_dir is not None: retval.base_dir = base_dir return retval def debug0(self, s, *args, **kwargs): logger.debug("%s%s" % (" " * self.indent, s), *args, **kwargs) def debug1(self, s, *args, **kwargs): logger.debug("%s%s" % (" " * (self.indent + 1), s), *args, **kwargs) def debug2(self, s, *args, **kwargs): logger.debug("%s%s" % (" " * (self.indent + 2), s), *args, **kwargs) def parse_schema_file(self, file_name): elt = etree.fromstring(open(file_name).read(), parser=PARSER) return self.parse_schema(elt) def process_includes(self, include): file_name = include.schema_location if file_name is None: return self.debug1("including %s %s", self.base_dir, file_name) file_name = abspath(join(self.base_dir, file_name)) data = open(file_name).read() elt = etree.fromstring(data, parser=PARSER) self.nsmap.update(elt.nsmap) self.prefmap = dict([(v,k) for k,v in self.nsmap.items()]) sub_schema = _prot.from_element(XmlSchema10, elt) if sub_schema.includes: for inc in sub_schema.includes: base_dir = dirname(file_name) child_ctx = self.clone(base_dir=base_dir) self.process_includes(inc) self.nsmap.update(child_ctx.nsmap) self.prefmap = dict([(v,k) for k,v in self.nsmap.items()]) for attr in ('imports', 'simple_types', 'complex_types', 'elements'): sub = getattr(sub_schema, attr) if sub is None: sub = [] own = getattr(self.schema, attr) if own is None: own = [] own.extend(sub) setattr(self.schema, attr, own) def process_simple_type(self, s, name=None): """Returns the simple Spyne type. Doesn't do any 'pending' processing.""" if name is None: name = s.name if s.restriction is None: self.debug1("skipping simple type: %s", name) return if s.restriction.base is None: self.debug1("skipping simple type: %s", name) return base = self.get_type(s.restriction.base) if base is None: raise ValueError(s) kwargs = {} restriction = s.restriction if restriction.enumeration: kwargs['values'] = [e.value for e in restriction.enumeration] if restriction.max_length: if restriction.max_length.value: kwargs['max_len'] = int(restriction.max_length.value) if restriction.min_length: if restriction.min_length.value: kwargs['min_len'] = int(restriction.min_length.value) if restriction.pattern: if restriction.pattern.value: kwargs['pattern'] = restriction.pattern.value self.debug1("adding simple type: %s", name) retval = base.customize(**kwargs) retval.__type_name__ = name retval.__namespace__ = self.tns if retval.__orig__ is None: retval.__orig__ = base if retval.__extends__ is None: retval.__extends__ = base assert not retval.get_type_name() is retval.Empty return retval def process_schema_element(self, e): if e.name is None: return self.debug1("adding element: %s", e.name) t = self.get_type(e.type) key = e.name if t: if key in self.pending_elements: del self.pending_elements[key] self.retval[self.tns].elements[e.name] = e else: self.pending_elements[key] = e def process_attribute(self, a): if a.ref is not None: t = self.get_type(a.ref) return t.type.get_type_name(), t if a.type is not None: t = self.get_type(a.type) elif a.simple_type is not None: t = self.process_simple_type(a.simple_type, a.name) else: raise Exception("dunno attr") if t is None: raise ValueError(a, 'not found') kwargs = {} if a.default is not None: kwargs['default'] = _prot.from_string(t, a.default) if len(kwargs) > 0: t = t.customize(**kwargs) self.debug2("t = t.customize(**%r)" % kwargs) return (a.name, XmlAttribute(t)) def process_complex_type(self, c): def process_type(tn, name, wrapper=lambda x: x, element=None, attribute=None): t = self.get_type(tn) key = (c.name, name) if t is None: self.pending_types[key] = c self.debug2("not found: %r(%s)", key, tn) return if key in self.pending_types: del self.pending_types[key] assert name is not None, (key, e) kwargs = {} if element is not None: if e.min_occurs != "0": # spyne default kwargs['min_occurs'] = int(e.min_occurs) if e.max_occurs == "unbounded": kwargs['max_occurs'] = e.max_occurs elif e.max_occurs != "1": kwargs['max_occurs'] = int(e.max_occurs) if e.nillable != True: # spyne default kwargs['nillable'] = e.nillable if e.default is not None: kwargs['default'] = _prot.from_string(t, e.default) if len(kwargs) > 0: t = t.customize(**kwargs) if attribute is not None: if attribute.default is not None: kwargs['default'] = _prot.from_string(t, a.default) if len(kwargs) > 0: t = t.customize(**kwargs) ti.append( (name, wrapper(t)) ) self.debug2(" found: %r(%s), c: %r", key, tn, kwargs) def process_element(e): if e.ref is not None: tn = e.ref name = e.ref.split(":", 1)[-1] elif e.name is not None: tn = e.type name = e.name else: raise Exception("dunno") process_type(tn, name, element=e) class L(list): def append(self, a): k, v = a assert isinstance(k, six.string_types), k super(L, self).append(a) ti = L() base = ComplexModelBase if c.name in self.retval[self.tns].types: self.debug1("modifying existing %r", c.name) else: self.debug1("adding complex type: %s", c.name) if c.sequence is not None: if c.sequence.elements is not None: for e in c.sequence.elements: process_element(e) if c.sequence.choices is not None: for ch in c.sequence.choices: if ch.elements is not None: for e in ch.elements: process_element(e) if c.choice is not None: if c.choice.elements is not None: for e in c.choice.elements: process_element(e) if c.attributes is not None: for a in c.attributes: if a.name is None: continue if a.type is None: continue process_type(a.type, a.name, XmlAttribute, attribute=a) if c.simple_content is not None: ext = c.simple_content.extension base_name = None if ext is not None: base_name = ext.base b = self.get_type(ext.base) if ext.attributes is not None: for a in ext.attributes: ti.append(self.process_attribute(a)) restr = c.simple_content.restriction if restr is not None: base_name = restr.base b = self.get_type(restr.base) if restr.attributes is not None: for a in restr.attributes: ti.append(self.process_attribute(a)) if issubclass(b, ComplexModelBase): base = b else: process_type(base_name, "_data", XmlData) if c.name in self.retval[self.tns].types: self.retval[self.tns].types[c.name]._type_info.update(ti) else: cls_dict = odict({ '__type_name__': c.name, '__namespace__': self.tns, '_type_info': ti, }) if self.repr is not None: cls_dict['__repr__'] = self.repr r = ComplexModelMeta(str(c.name), (base,), cls_dict) self.retval[self.tns].types[c.name] = r def get_type(self, tn): if tn is None: return Null if tn.startswith("{"): ns, qn = tn[1:].split('}',1) elif ":" in tn: ns, qn = tn.split(":",1) ns = self.nsmap[ns] else: if None in self.nsmap: ns, qn = self.nsmap[None], tn else: ns, qn = self.tns, tn ti = self.retval.get(ns) if ti is not None: t = ti.types.get(qn) if t: return t e = ti.elements.get(qn) if e: if ":" in e.type: return self.get_type(e.type) else: retval = self.get_type("{%s}%s" % (ns, e.type)) if retval is None and None in self.nsmap: retval = self.get_type("{%s}%s" % (self.nsmap[None], e.type)) return retval return TYPE_MAP.get("{%s}%s" % (ns, qn)) def process_pending(self): # process pending self.debug0("6 %s processing pending complex_types", B(self.tns)) for (c_name, e_name), _v in self.pending_types.items(): self.process_complex_type(_v) self.debug0("7 %s processing pending elements", Y(self.tns)) for _k, _v in self.pending_elements.items(): self.process_schema_element(_v) def print_pending(self, fail=False): if len(self.pending_elements) > 0 or len(self.pending_types) > 0: if fail: logging.basicConfig(level=logging.DEBUG) self.debug0("%" * 50) self.debug0(self.tns) self.debug0("") self.debug0("elements") self.debug0(pformat(self.pending_elements)) self.debug0("") self.debug0("types") self.debug0(pformat(self.pending_types)) self.debug0("%" * 50) if fail: raise Exception("there are still unresolved elements") def parse_schema(self, elt): self.nsmap = nsmap = elt.nsmap self.prefmap = prefmap = dict([(v,k) for k,v in self.nsmap.items()]) self.schema = schema = _prot.from_element(self, XmlSchema10, elt) self.pending_types = {} self.pending_elements = {} self.tns = tns = schema.target_namespace if self.tns is None: self.tns = tns = '__no_ns__' if tns in self.retval: return self.retval[tns] = _Schema() self.debug0("1 %s processing includes", M(tns)) if schema.includes: for include in schema.includes: self.process_includes(include) if schema.elements: schema.elements = odict([(e.name, e) for e in schema.elements]) if schema.complex_types: schema.complex_types = odict([(c.name, c) for c in schema.complex_types]) if schema.simple_types: schema.simple_types = odict([(s.name, s) for s in schema.simple_types]) if schema.attributes: schema.attributes = odict([(a.name, a) for a in schema.attributes]) self.debug0("2 %s processing imports", R(tns)) if schema.imports: for imp in schema.imports: if not imp.namespace in self.retval: self.debug1("%s importing %s", tns, imp.namespace) file_name = self.files[imp.namespace] self.clone(2, dirname(file_name)).parse_schema_file(file_name) self.retval[tns].imports.add(imp.namespace) self.debug0("3 %s processing attributes", G(tns)) if schema.attributes: for s in schema.attributes.values(): n, t = self.process_attribute(s) self.retval[self.tns].types[n] = t self.debug0("4 %s processing simple_types", G(tns)) if schema.simple_types: for s in schema.simple_types.values(): st = self.process_simple_type(s) self.retval[self.tns].types[s.name] = st self.debug0("5 %s processing complex_types", B(tns)) if schema.complex_types: for c in schema.complex_types.values(): self.process_complex_type(c) self.debug0("6 %s processing elements", Y(tns)) if schema.elements: for e in schema.elements.values(): self.process_schema_element(e) self.process_pending() if self.parent is None: # for the top-most schema if self.children is not None: # if it uses or # This is needed for schemas with circular imports for c in chain([self], self.children): c.print_pending() self.debug0('') # FIXME: This has no guarantee of working yet covers all the # schema files found in the wild so far. for c in chain([self], self.children): c.process_pending() for c in chain([self], self.children): c.process_pending() self.debug0('') for c in chain([self], self.children): c.print_pending(fail=True) return self.retval spyne-2.11.0/spyne/interface/__init__.py0000644000175000001440000000241512342627562020126 0ustar plqusers00000000000000 # # 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 # """The ``spyne.interface`` package contains implementations of various interface definition document standards along with the :class:`spyne.interface.Interface` class which holds all the information needed to generate those documents. """ from spyne.interface._base import Interface from spyne.interface._base import InterfaceDocumentBase from spyne.interface._base import AllYourInterfaceDocuments try: from spyne.interface.wsdl.wsdl11 import Wsdl11 HAS_WSDL = True except ImportError: HAS_WSDL = False spyne-2.11.0/spyne/interface/_base.py0000644000175000001440000004232712345433230017434 0ustar plqusers00000000000000 # # 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__) from collections import deque, defaultdict import spyne.interface from spyne import EventManager, MethodDescriptor from spyne.const import xml_ns as namespace from spyne.model import ModelBase from spyne.model import Array, Iterable from spyne.model import ComplexModelBase from spyne.model.complex import XmlModifier def _generate_method_id(cls, descriptor): return '.'.join([ cls.__module__, cls.__name__, descriptor.name, ]) class Interface(object): """The ``Interface`` class holds all information needed to build an interface document. :param app: A :class:`spyne.application.Application` instance. """ def __init__(self, app=None, import_base_namespaces=False): self.__ns_counter = 0 self.__app = None self.url = None self.classes = {} self.imports = {} self.service_method_map = {} self.method_id_map = {} self.nsmap = {} self.prefmap = {} self.member_methods = deque() self.import_base_namespaces = import_base_namespaces self.app = app def set_app(self, value): assert self.__app is None, "One interface instance can belong to only " \ "one application instance." self.__app = value self.reset_interface() self.populate_interface() def get_app(self): return self.__app app = property(get_app, set_app) @property def services(self): if self.__app: return self.__app.services return [] def reset_interface(self): self.classes = {} self.imports = {self.get_tns(): set()} self.service_method_map = {} self.method_id_map = {} self.nsmap = dict(namespace.const_nsmap) self.prefmap = dict(namespace.const_prefmap) self.member_methods = deque() self.nsmap['tns'] = self.get_tns() self.prefmap[self.get_tns()] = 'tns' self.deps = defaultdict(set) def has_class(self, cls): """Returns true if the given class is already included in the interface object somewhere.""" ns = cls.get_namespace() tn = cls.get_type_name() c = self.classes.get('{%s}%s' % (ns, tn)) if c is None: return False if issubclass(c, ComplexModelBase) and \ issubclass(cls, ComplexModelBase): o1 = getattr(cls, '__orig__', None) or cls o2 = getattr(c, '__orig__', None) or c if o1 is o2: return True # So that "Array"s and "Iterable"s don't conflict. if set((o1, o2)) == set((Array, Iterable)): return True raise ValueError("classes %r and %r have conflicting names." % (cls, c)) return True def get_class(self, key): """Returns the class definition that corresponds to the given key. Keys are in '{namespace}class_name' form, a.k.a. XML QName format. Not meant to be overridden. """ return self.classes[key] def get_class_instance(self, key): """Returns the default class instance that corresponds to the given key. Keys are in '{namespace}class_name' form, a.k.a. XML QName format. Classes should not enforce arguments to the constructor. Not meant to be overridden. """ return self.classes[key]() def get_name(self): """Returns service name that is seen in the name attribute of the definitions tag. Not meant to be overridden. """ if self.app: return self.app.name def get_tns(self): """Returns default namespace that is seen in the targetNamespace attribute of the definitions tag. Not meant to be overridden. """ if self.app: return self.app.tns def add_method(self, method): """Generator method that adds the given method descriptor to the interface. Also extracts and yields all the types found in there. :param method: A :class:`MethodDescriptor` instance :returns: Sequence of :class:`spyne.model.ModelBase` subclasses. """ if not (method.in_header is None): if not isinstance(method.in_header, (list, tuple)): method.in_header = (method.in_header,) for in_header in method.in_header: in_header.resolve_namespace(in_header, self.get_tns()) if method.aux is None: yield in_header if in_header.get_namespace() != self.get_tns(): self.imports[self.get_tns()].add(in_header.get_namespace()) if not (method.out_header is None): if not isinstance(method.out_header, (list, tuple)): method.out_header = (method.out_header,) for out_header in method.out_header: out_header.resolve_namespace(out_header, self.get_tns()) if method.aux is None: yield out_header if out_header.get_namespace() != self.get_tns(): self.imports[self.get_tns()].add( out_header.get_namespace()) if method.faults is None: method.faults = [] elif not (isinstance(method.faults, (list, tuple))): method.faults = (method.faults,) for fault in method.faults: fault.__namespace__ = self.get_tns() fault.resolve_namespace(fault, self.get_tns()) if method.aux is None: yield fault method.in_message.resolve_namespace(method.in_message, self.get_tns()) if method.aux is None: yield method.in_message method.out_message.resolve_namespace(method.out_message, self.get_tns()) if method.aux is None: yield method.out_message for p in method.patterns: p.endpoint = method def process_method(self, s, method): assert isinstance(method, MethodDescriptor) method_key = '{%s}%s' % (self.app.tns, method.name) if issubclass(s, ComplexModelBase) and method.in_message_name_override: method_key = '{%s}%s.%s' % (self.app.tns, s.get_type_name(), method.name) logger.debug('\tadding method %r to match %r tag.' % (method.name, method_key)) key = _generate_method_id(s, method) if key in self.method_id_map: c = self.method_id_map[key].parent_class if c.__orig__ is None: assert c is s.__orig__, "%r.%s conflicts with %r.%s" % \ (c, key, s.__orig__, key) elif s.__orig__ is None: assert c.__orig__ is s, "%r.%s conflicts with %r.%s" % \ (c.__orig__, key, s, key) else: assert c.__orig__ is s.__orig__, "%r.%s conflicts with %r.%s" % \ (c.__orig__, key, s.__orig__, key) return self.method_id_map[key] = method val = self.service_method_map.get(method_key, None) if val is None: val = self.service_method_map[method_key] = [] if len(val) == 0: val.append(method) elif method.aux is not None: val.append(method) elif val[0].aux is not None: val.insert(method, 0) else: om = val[0] os = om.service_class if os is None: os = om.parent_class raise ValueError("\nThe message %r defined in both '%s.%s'" " and '%s.%s'" % (method.name, s.__module__, s.__name__, os.__module__, os.__name__)) def populate_interface(self, types=None): """Harvests the information stored in individual classes' _type_info dictionaries. It starts from function definitions and includes only the used objects. """ # populate types for s in self.services: logger.debug("populating '%s.%s (%s)' types..." % (s.__module__, s.__name__, s.get_service_key())) for method in s.public_methods.values(): if method.in_header is None: method.in_header = s.__in_header__ if method.out_header is None: method.out_header = s.__out_header__ if method.aux is None: method.aux = s.__aux__ if method.aux is not None: method.aux.methods.append(_generate_method_id(s, method)) for cls in self.add_method(method): self.add_class(cls) # populate call routes for service methods for s in self.services: s.__tns__ = self.get_tns() logger.debug("populating '%s.%s' methods..." % (s.__module__, s.__name__)) for method in s.public_methods.values(): self.process_method(s, method) # populate call routes for member methods for cls, descriptor in self.member_methods: self.process_method(cls, descriptor) logger.debug("From this point on, you're not supposed to make any " "changes to the class and method structure of the exposed " "services." ) tns = property(get_tns) def get_namespace_prefix(self, ns): """Returns the namespace prefix for the given namespace. Creates a new one automatically if it doesn't exist. Not meant to be overridden. """ if not (isinstance(ns, str) or isinstance(ns, unicode)): raise TypeError(ns) if not (ns in self.prefmap): pref = "s%d" % self.__ns_counter while pref in self.nsmap: self.__ns_counter += 1 pref = "s%d" % self.__ns_counter self.prefmap[ns] = pref self.nsmap[pref] = ns self.__ns_counter += 1 else: pref = self.prefmap[ns] return pref def add_class(self, cls, add_parent=True): if self.has_class(cls): return ns = cls.get_namespace() tn = cls.get_type_name() assert ns is not None, ('either assign a namespace to the class or call' ' cls.resolve_namespace(cls, "some_default_ns") on it.') if not (ns in self.imports): self.imports[ns] = set() class_key = '{%s}%s' % (ns, tn) logger.debug('\tadding class %r for %r' % (repr(cls), class_key)) assert class_key not in self.classes, ("Somehow, you're trying to " "overwrite %r by %r for class key %r." % (self.classes[class_key], cls, class_key)) assert not (cls.get_type_name() is cls.Empty) self.deps[cls] self.classes[class_key] = cls if ns == self.get_tns(): self.classes[tn] = cls # add parent class extends = getattr(cls, '__extends__', None) if add_parent and extends is not None: assert issubclass(extends, ModelBase) self.deps[cls].add(extends) self.add_class(extends) parent_ns = extends.get_namespace() if parent_ns != ns and not parent_ns in self.imports[ns] and \ self.is_valid_import(parent_ns): self.imports[ns].add(parent_ns) logger.debug("\timporting %r to %r because %r extends %r" % ( parent_ns, ns, cls.get_type_name(), extends.get_type_name())) # add fields if issubclass(cls, ComplexModelBase): for k, v in cls._type_info.items(): if v is None: continue self.deps[cls].add(v) logger.debug("\tadding %s.%s = %r", cls.get_type_name(), k, v) if v.get_namespace() is None: v.resolve_namespace(v, ns) self.add_class(v) if v.get_namespace() is None and cls.get_namespace() is not None: v.resolve_namespace(v, cls.get_namespace()) child_ns = v.get_namespace() if child_ns != ns and not child_ns in self.imports[ns] and \ self.is_valid_import(child_ns): self.imports[ns].add(child_ns) logger.debug("\timporting %r to %r for %s.%s(%r)", child_ns, ns, cls.get_type_name(), k, v) if issubclass(v, XmlModifier): self.add_class(v.type) child_ns = v.type.get_namespace() if child_ns != ns and not child_ns in self.imports[ns] and \ self.is_valid_import(child_ns): self.imports[ns].add(child_ns) logger.debug("\timporting %r to %r for %s.%s(%r)", child_ns, ns, v.get_type_name(), k, v.type) if cls.Attributes.methods is not None: logger.debug("\tpopulating member methods for '%s.%s'...", cls.get_namespace(), cls.get_type_name()) for method_key, descriptor in cls.Attributes.methods.items(): assert hasattr(cls, method_key) self.member_methods.append((cls, descriptor)) for c in self.add_method(descriptor): self.add_class(c) if cls.Attributes._subclasses is not None: logger.debug("\tadding subclasses of '%s.%s'...", cls.get_namespace(), cls.get_type_name()) for c in cls.Attributes._subclasses: c.resolve_namespace(c, ns) child_ns = c.get_namespace() if child_ns == ns: if not self.has_class(c): self.add_class(c, add_parent=False) self.deps[c].add(cls) else: logger.debug("\tnot adding %r to %r because it would " "cause circular imports because %r extends %r and " "they don't have the same namespace" % (child_ns, ns, c.get_type_name(), cls.get_type_name())) def is_valid_import(self, ns): """This will return False for base namespaces unless told otherwise.""" if ns is None: raise ValueError(ns) return self.import_base_namespaces or not (ns in namespace.const_prefmap) class AllYourInterfaceDocuments(object): # AreBelongToUs def __init__(self, interface, wsdl11=None): self.wsdl11 = wsdl11 if self.wsdl11 is None and spyne.interface.HAS_WSDL: from spyne.interface.wsdl import Wsdl11 self.wsdl11 = Wsdl11(interface) class InterfaceDocumentBase(object): """Base class for all interface document implementations. :param interface: A :class:`spyne.interface.InterfaceBase` instance. """ def __init__(self, interface): self.interface = interface self.event_manager = EventManager(self) def build_interface_document(self): """This function is supposed to be called just once, as late as possible into the process start. It builds the interface document and caches it somewhere. The overriding function should never call the overridden function as this may result in the same event firing more than once. """ raise NotImplementedError('Extend and override.') def get_interface_document(self): """This function is called by server transports that try to satisfy the request for the interface document. This should just return a previously cached interface document. """ raise NotImplementedError('Extend and override.') spyne-2.11.0/spyne/model/0000755000175000001440000000000012352131452015140 5ustar plqusers00000000000000spyne-2.11.0/spyne/model/__init__.py0000644000175000001440000001027412345433230017256 0ustar plqusers00000000000000 # # 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 # """The ``spyne.model`` package contains data types that Spyne is able to distinguish. These are just type markers, they are not of much use without protocols. """ from spyne.model._base import ModelBase from spyne.model._base import PushBase from spyne.model._base import Null from spyne.model._base import SimpleModel # store_as values from spyne.model._base import xml from spyne.model._base import json from spyne.model._base import table from spyne.model._base import msgpack # Boolean from spyne.model.primitive import Boolean # Any* types from spyne.model.primitive import AnyXml from spyne.model.primitive import AnyDict from spyne.model.primitive import AnyHtml # Unicode children from spyne.model.primitive import Unicode from spyne.model.primitive import String from spyne.model.primitive import AnyUri from spyne.model.primitive import ImageUri from spyne.model.primitive import Uuid from spyne.model.primitive import NormalizedString from spyne.model.primitive import Token from spyne.model.primitive import Name from spyne.model.primitive import NCName from spyne.model.primitive import ID from spyne.model.primitive import Language from spyne.model.primitive import ID from spyne.model.primitive import Point from spyne.model.primitive import Line from spyne.model.primitive import LineString from spyne.model.primitive import Polygon from spyne.model.primitive import MultiPoint from spyne.model.primitive import MultiLine from spyne.model.primitive import MultiLineString from spyne.model.primitive import MultiPolygon # Date/Time types from spyne.model.primitive import Date from spyne.model.primitive import DateTime from spyne.model.primitive import Duration from spyne.model.primitive import Time # Numbers from spyne.model.primitive import Decimal from spyne.model.primitive import Double from spyne.model.primitive import Float from spyne.model.primitive import Integer8 from spyne.model.primitive import Byte from spyne.model.primitive import Integer16 from spyne.model.primitive import Short from spyne.model.primitive import Integer32 from spyne.model.primitive import Int from spyne.model.primitive import Integer64 from spyne.model.primitive import Long from spyne.model.primitive import Integer from spyne.model.primitive import UnsignedInteger8 from spyne.model.primitive import UnsignedByte from spyne.model.primitive import UnsignedInteger16 from spyne.model.primitive import UnsignedShort from spyne.model.primitive import UnsignedInteger32 from spyne.model.primitive import UnsignedInt from spyne.model.primitive import UnsignedInteger64 from spyne.model.primitive import UnsignedLong from spyne.model.primitive import NonNegativeInteger # Xml Schema calls it so from spyne.model.primitive import UnsignedInteger # Classes from spyne.model.complex import ComplexModelMeta from spyne.model.complex import ComplexModelBase from spyne.model.complex import ComplexModel from spyne.model.complex import TTableModelBase from spyne.model.complex import TTableModel # Iterables from spyne.model.complex import Array from spyne.model.complex import Iterable from spyne.model.complex import PushBase # Modifiers from spyne.model.complex import Mandatory from spyne.model.complex import XmlAttribute from spyne.model.complex import XmlData # Markers from spyne.model.complex import SelfReference # Binary from spyne.model.binary import File from spyne.model.binary import ByteArray # Enum from spyne.model.enum import Enum # Fault from spyne.model.fault import Fault spyne-2.11.0/spyne/model/_base.py0000644000175000001440000005200712345433230016570 0ustar plqusers00000000000000 # # 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 # """This module contains the ModelBase class and other building blocks for defining models. """ import re import spyne.const.xml_ns from decimal import Decimal from spyne.util import Break from spyne.util.odict import odict from spyne.util.six import add_metaclass from spyne.const.xml_ns import DEFAULT_NS # All this code to get rid of a one letter quirk: nillable vs nullable. class AttributesMeta(type(object)): NULLABLE_DEFAULT = True def __new__(cls, cls_name, cls_bases, cls_dict): # Mapper args should not be inherited. if not 'sqla_mapper_args' in cls_dict: cls_dict['sqla_mapper_args'] = None return super(AttributesMeta, cls).__new__(cls, cls_name, cls_bases, cls_dict) def __init__(self, cls_name, cls_bases, cls_dict): nullable = cls_dict.get('nullable', None) nillable = cls_dict.get('nillable', None) if nullable is not None: assert nillable is None or nullable == nillable self._nullable = nullable elif nillable is not None: assert nullable is None or nullable == nillable self._nullable = nillable if getattr(self, '_nullable', None) is None: self._nullable = None super(AttributesMeta, self).__init__(cls_name, cls_bases, cls_dict) def get_nullable(self): return (self._nullable if self._nullable is not None else self.NULLABLE_DEFAULT) def set_nullable(self, what): self._nullable = what nullable = property(get_nullable, set_nullable) def get_nillable(self): return self.nullable def set_nillable(self, what): self.nullable = what nillable = property(get_nillable, set_nillable) class ModelBase(object): """The base class for type markers. It defines the model interface for the interface generators to use and also manages class customizations that are mainly used for defining constraints on input values. """ __orig__ = None """This holds the original class the class .customize()d from. Ie if this is None, the class is not a customize()d one.""" __extends__ = None """This holds the original class the class inherited or .customize()d from. This is different from __orig__ because it's only set when ``cls.is_default(cls) == False``""" __namespace__ = None """The public namespace of this class. Use ``get_namespace()`` instead of accessing it directly.""" __type_name__ = None """The public type name of the class. Use ``get_type_name()`` instead of accessing it directly.""" # These are not the xml schema defaults. The xml schema defaults are # considered in XmlSchema's add() method. the defaults here are to reflect # what people seem to want most. # # Please note that min_occurs and max_occurs must be validated in the # ComplexModelBase deserializer. @add_metaclass(AttributesMeta) class Attributes(object): """The class that holds the constraints for the given type.""" _wrapper = False # when skip_wrappers=True is passed to a protocol, these objects # are skipped. just for internal use. default = None """The default value if the input is None""" default_factory = None """The default value if the input is None""" nillable = None """Set this to false to reject null values. Synonyms with ``nullable``. True by default. The default value can be changed by setting ``AttributesMeta.NULLABLE_DEFAULT``.""" min_occurs = 0 """Set this to 1 to make this object mandatory. Can be set to any positive integer. Note that an object can still be null or empty, even if it's there.""" max_occurs = 1 """Can be set to any strictly positive integer. Values greater than 1 will imply an iterable of objects as native python type. Can be set to ``decimal.Decimal("inf")`` for arbitrary number of arguments.""" schema_tag = '{%s}element' % spyne.const.xml_ns.xsd """The tag used to add a primitives as child to a complex type in the xml schema.""" translations = None """A dict that contains locale codes as keys and translations of field names to that language as values. """ sub_ns = None """An Xml-specific attribute that specifies which namespace should be used for field names in classes. """ sub_name = None """This specifies which string should be used as field name when this type is seriazed under a ComplexModel. """ sqla_column_args = None """A dict that will be passed to SQLAlchemy's ``Column`` constructor as ``**kwargs``. """ exc_mapper = False """If true, this field will be excluded from the table mapper of the parent class. """ exc_table = False """If true, this field will be excluded from the table of the parent class. """ exc_interface = False """If true, this field will be excluded from the interface document.""" logged = True """If false, this object will be ignored in ``log_repr``, mostly used for logging purposes.""" unique = None """If True, this object will be set as unique in the database schema with default indexing options. If the value is a string, it will be used as the indexing method to create the unique index. See sqlalchemy documentation on how to create multi-column unique constraints. """ db_type = None """When not None, it overrides Spyne's own mapping from Spyne types to SQLAlchemy types. It's a standard SQLAlchemy type marker, e.g. ``sqlalchemy.Integer``. """ table_name = None """Database table name.""" xml_choice_group = None """When not None, shares the same tag with fields with the same xml_choice_group value. """ index = None """Can be ``True``, a string, or a tuple of two strings. * If True, this object will be set as indexed in the database schema with default options. * If the value is a string, the value will denote the indexing method used by the database. See: http://www.postgresql.org/docs/9.2/static/indexes-types.html * If the vale is a tuple of two strings, the first value will denote the index name and the second value will denote the indexing method as above. """ read_only= False """If True, the attribute won't be initialized from outside values.""" prot_attrs = None """to be implemented.""" #"""Customize child attributes for protocols. It's a dict of dicts. #The key is either a ProtocolBase subclass or a ProtocolBase instance. #Instances override classes.""" empty_is_none = False """When the incoming object is empty (e.g. '' for strings) treat it as None. No effect (yet) for outgoing values.""" order = None """An integer that's passed to ``_type_info.insert()`` as first argument when not None. ``.append()`` is used otherwise.""" class Annotations(object): """The class that holds the annotations for the given type.""" __use_parent_doc__ = False """If set to True Annotations will use __doc__ from parent, This is a convenience option""" doc = "" """The public documentation for the given type.""" appinfo = None """Any object that carries app-specific info.""" class Empty(object): pass _force_own_namespace = None @staticmethod def is_default(cls): return True @classmethod def get_namespace_prefix(cls, interface): """Returns the namespace prefix for the given interface. The get_namespace_prefix of the interface class generates a prefix if none is defined. """ ns = cls.get_namespace() retval = interface.get_namespace_prefix(ns) return retval @classmethod def get_namespace(cls): """Returns the namespace of the class. Defaults to the python module name.""" return cls.__namespace__ # TODO: rename to "resolve_identifier" @staticmethod def resolve_namespace(cls, default_ns, tags=None): """This call finalizes the namespace assignment. The default namespace is not available until the application calls populate_interface method of the interface generator. """ if tags is None: tags = set() elif cls in tags: return False tags.add(cls) if cls.__namespace__ is spyne.const.xml_ns.DEFAULT_NS: cls.__namespace__ = default_ns if (cls.__namespace__ in spyne.const.xml_ns.const_prefmap and not cls.is_default(cls)): cls.__namespace__ = default_ns if cls.__namespace__ is None: ret = [] for f in cls.__module__.split('.'): if f.startswith('_'): break else: ret.append(f) cls.__namespace__ = '.'.join(ret) if cls.__namespace__ is None or len(cls.__namespace__) == 0: cls.__namespace__ = default_ns if cls.__namespace__ is None or len(cls.__namespace__) == 0: raise ValueError("You need to explicitly set %r.__namespace__" % cls) if getattr(cls, '__extends__', None) != None: cls.__extends__.resolve_namespace(cls.__extends__, default_ns, tags) return True @classmethod def get_type_name(cls): """Returns the class name unless the __type_name__ attribute is defined. """ retval = cls.__type_name__ if retval is None: retval = cls.__name__ return retval @classmethod def get_type_name_ns(cls, interface): """Returns the type name with a namespace prefix, separated by a column. """ if cls.get_namespace() != None: return "%s:%s" % (cls.get_namespace_prefix(interface), cls.get_type_name()) @classmethod def get_element_name(cls): return cls.Attributes.sub_name or cls.get_type_name() @classmethod def get_element_name_ns(cls, interface): ns = cls.Attributes.sub_ns or cls.get_namespace() if ns is DEFAULT_NS: ns = interface.get_tns() if ns is not None: pref = interface.get_namespace_prefix(ns) return "%s:%s" % (pref, cls.get_element_name()) @classmethod def to_string(cls, value): """Returns str(value). This should be overridden if this is not enough. """ return str(value) @classmethod def customize(cls, **kwargs): """Duplicates cls and overwrites the values in ``cls.Attributes`` with ``**kwargs`` and returns the new class.""" cls_name, cls_bases, cls_dict = cls._s_customize(cls, **kwargs) return type(cls_name, cls_bases, cls_dict) @staticmethod def _s_customize(cls, **kwargs): """This function duplicates and customizes the class it belongs to. The original class remains unchanged. Not meant to be overridden. """ cls_dict = odict({'__module__': cls.__module__}) if getattr(cls, '__orig__', None) is None: cls_dict['__orig__'] = cls class Attributes(cls.Attributes): pass if cls.Attributes.translations is None: Attributes.translations = {} if cls.Attributes.sqla_column_args is None: Attributes.sqla_column_args = (), {} cls_dict['Attributes'] = Attributes # as nillable is a property, it gets reset everytime a new class is # defined. So we need to reinitialize it explicitly. Attributes.nillable = cls.Attributes.nillable class Annotations(cls.Annotations): pass cls_dict['Annotations'] = Annotations for k, v in kwargs.items(): if k.startswith('_'): continue elif k in ("doc", "appinfo"): setattr(Annotations, k, v) elif k in ('primary_key', 'pk'): Attributes.sqla_column_args[-1]['primary_key'] = v elif k in ('foreign_key', 'fk'): from sqlalchemy.schema import ForeignKey t, d = Attributes.sqla_column_args fkt = (ForeignKey(v),) Attributes.sqla_column_args = (t + fkt, d) elif k in ('autoincrement', 'onupdate', 'server_default'): Attributes.sqla_column_args[-1][k] = v elif k == 'max_occurs' and v in ('unbounded', 'inf', float('inf')): setattr(Attributes, k, Decimal('inf')) else: setattr(Attributes, k, v) return (cls.__name__, (cls,), cls_dict) @staticmethod def validate_string(cls, value): """Override this method to do your own input validation on the input string. This is called before converting the incoming string to the native python value.""" return (cls.Attributes.nillable or value is not None) @staticmethod def validate_native(cls, value): """Override this method to do your own input validation on the native value. This is called after converting the incoming string to the native python value.""" return (cls.Attributes.nullable or value is not None) class Null(ModelBase): pass class SimpleModelAttributesMeta(AttributesMeta): def __init__(self, cls_name, cls_bases, cls_dict): super(SimpleModelAttributesMeta, self).__init__(cls_name, cls_bases, cls_dict) if getattr(self, '_pattern', None) is None: self._pattern = None def get_pattern(self): return self._pattern def set_pattern(self, pattern): self._pattern = pattern if pattern is not None: self._pattern_re = re.compile(pattern) pattern = property(get_pattern, set_pattern) class SimpleModel(ModelBase): """The base class for primitives.""" __namespace__ = "http://www.w3.org/2001/XMLSchema" @add_metaclass(SimpleModelAttributesMeta) class Attributes(ModelBase.Attributes): """The class that holds the constraints for the given type.""" values = set() """The set of possible values for this type.""" _pattern_re = None def __new__(cls, **kwargs): """Overriden so that any attempt to instantiate a primitive will return a customized class instead of an instance. See spyne.model.base.ModelBase for more information. """ return cls.customize(**kwargs) @classmethod def customize(cls, **kwargs): """Duplicates cls and overwrites the values in ``cls.Attributes`` with ``**kwargs`` and returns the new class.""" cls_name, cls_bases, cls_dict = cls._s_customize(cls, **kwargs) retval = type(cls_name, cls_bases, cls_dict) if not retval.is_default(retval): retval.__extends__ = cls retval.__type_name__ = kwargs.get("type_name", ModelBase.Empty) retval.resolve_namespace(retval, kwargs.get('__namespace__')) return retval @staticmethod def is_default(cls): return (cls.Attributes.values == SimpleModel.Attributes.values) @staticmethod def validate_native(cls, value): return ( ModelBase.validate_native(cls, value) and (cls.Attributes.values is None or len(cls.Attributes.values) == 0 or ( (value is None and cls.Attributes.nillable) or (value is not None and value in cls.Attributes.values) )) ) class PushBase(object): def __init__(self, callback=None, errback=None): self._cb = callback self._eb = errback self.length = 0 self.ctx = None self.app = None self.response = None self.gen = None self._cb_finish = None self._eb_finish = None def _init(self, ctx, response, gen, _cb_finish, _eb_finish): self.length = 0 self.ctx = ctx self.app = ctx.app self.response = response self.gen = gen self._cb_finish = _cb_finish self._eb_finish = _eb_finish def init(self, ctx, response, gen, _cb_finish, _eb_finish): self._init(ctx, response, gen, _cb_finish, _eb_finish) if self._cb is not None: return self._cb(self) def __len__(self): return self.length def append(self, inst): self.gen.send(inst) self.length += 1 def close(self): try: self.gen.throw(Break()) except (Break, StopIteration, GeneratorExit): pass self._cb_finish() class xml: """Compound option object for xml serialization. It's meant to be passed to :func:`ComplexModelBase.Attributes.store_as`. :param root_tag: Root tag of the xml element that contains the field values. :param no_ns: When true, the xml document is stripped from namespace information. This is generally a stupid thing to do. Use with caution. """ def __init__(self, root_tag=None, no_ns=False): self.root_tag = root_tag self.no_ns = no_ns class table: """Compound option object for for storing the class instance as in row in a table in a relational database. It's meant to be passed to :func:`ComplexModelBase.Attributes.store_as`. :param multi: When False, configures a one-to-many relationship where the child table has a foreign key to the parent. When not ``False``, configures a many-to-many relationship by creating an intermediate relation table that has foreign keys to both parent and child classes and generates a table name automatically. When ``True``, the table name is generated automatically. Otherwise, it should be a string, as the value is used as the name of the intermediate table. :param left: Name of the left join column. :param right: Name of the right join column. :param backref: See http://docs.sqlalchemy.org/en/rel_0_9/orm/relationships.html#sqlalchemy.orm.relationship.params.backref :param cascade: See http://docs.sqlalchemy.org/en/rel_0_9/orm/relationships.html#sqlalchemy.orm.relationship.params.cascade :param lazy: See http://docs.sqlalchemy.org/en/rel_0_9/orm/relationships.html#sqlalchemy.orm.relationship.params.lazy :param back_populates: See http://docs.sqlalchemy.org/en/rel_0_9/orm/relationships.html#sqlalchemy.orm.relationship.params.back_populates """ def __init__(self, multi=False, left=None, right=None, backref=None, id_backref=None, cascade=False, lazy='select', back_populates=None): self.multi = multi self.left = left self.right = right self.backref = backref self.id_backref = id_backref self.cascade = cascade self.lazy = lazy self.back_populates = back_populates class json: """Compound option object for json serialization. It's meant to be passed to :func:`ComplexModelBase.Attributes.store_as`. Make sure you don't mix this with the json package when importing. """ def __init__(self, ignore_wrappers=True, complex_as=dict): if ignore_wrappers != True: raise NotImplementedError("ignore_wrappers != True") self.ignore_wrappers = ignore_wrappers self.complex_as = complex_as class msgpack: """Compound option object for msgpack serialization. It's meant to be passed to :func:`ComplexModelBase.Attributes.store_as`. Make sure you don't mix this with the msgpack package when importing. """ def __init__(self): pass def apply_pssm(val, pssm_map): if val is not None: val_c = pssm_map.get(val, None) if val_c is None: assert isinstance(val, tuple(pssm_map.values())), \ "'store_as' should be one of: %r or an instance of %r not %r" \ % (tuple(pssm_map.keys()), tuple(pssm_map.values()), val) return val return val_c() spyne-2.11.0/spyne/model/binary.py0000644000175000001440000002552012352126507017007 0ustar plqusers00000000000000 # # 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 # """The ``spyne.model.binary`` package contains binary type markers.""" import os import base64 import tempfile from base64 import b64encode from base64 import b64decode from base64 import urlsafe_b64encode from base64 import urlsafe_b64decode from binascii import hexlify from binascii import unhexlify from os.path import abspath, isdir from spyne.util.six import StringIO from spyne.error import ValidationError from spyne.util import _bytes_join from spyne.model import ModelBase, ComplexModel, Unicode from spyne.model import SimpleModel from spyne.util import six class BINARY_ENCODING_HEX: pass class BINARY_ENCODING_BASE64: pass class BINARY_ENCODING_USE_DEFAULT: pass class BINARY_ENCODING_URLSAFE_BASE64: pass class ByteArray(SimpleModel): """Canonical container for arbitrary data. Every protocol has a different way of encapsulating this type. E.g. xml-based protocols encode this as base64, while HttpRpc just hands it over. Its native python format is a sequence of ``str`` objects for Python 2.x and a sequence of ``bytes`` objects for Python 3.x. """ __type_name__ = 'base64Binary' __namespace__ = "http://www.w3.org/2001/XMLSchema" class Attributes(SimpleModel.Attributes): encoding = BINARY_ENCODING_USE_DEFAULT """The binary encoding to use when the protocol does not enforce an encoding for binary data. One of (None, 'base64', 'hex') """ def __new__(cls, **kwargs): tn = None if 'encoding' in kwargs: v = kwargs['encoding'] if v is None: kwargs['encoding'] = BINARY_ENCODING_USE_DEFAULT elif v in ('base64', 'base64Binary', BINARY_ENCODING_BASE64): # This string is defined in the Xml Schema Standard tn = 'base64Binary' kwargs['encoding'] = BINARY_ENCODING_BASE64 elif v in ('urlsafe_base64', BINARY_ENCODING_URLSAFE_BASE64): # the Xml Schema Standard does not define urlsafe base64 # FIXME: produce a regexp that validates urlsafe base64 strings tn = 'string' kwargs['encoding'] = BINARY_ENCODING_URLSAFE_BASE64 elif v in ('hex', 'hexBinary', BINARY_ENCODING_HEX): # This string is defined in the Xml Schema Standard tn = 'hexBinary' kwargs['encoding'] = BINARY_ENCODING_HEX else: raise ValueError("'encoding' must be one of: %r" % \ (tuple(ByteArray._encoding.handlers.values()),)) retval = cls.customize(**kwargs) if tn is not None: retval.__type_name__ = tn return retval @staticmethod def is_default(cls): return True @classmethod def to_base64(cls, value): return b64encode(_bytes_join(value)) @classmethod def from_base64(cls, value): try: return [b64decode(_bytes_join(value))] except TypeError as e: raise ValidationError(value) @classmethod def to_urlsafe_base64(cls, value): return urlsafe_b64encode(_bytes_join(value)) @classmethod def from_urlsafe_base64(cls, value): #FIXME: Find out why we need to do this. if isinstance(value, six.text_type): value = value.encode('utf8') return [urlsafe_b64decode(_bytes_join(value))] @classmethod def to_hex(cls, value): return hexlify(_bytes_join(value)) @classmethod def from_hex(cls, value): return [unhexlify(_bytes_join(value))] binary_encoding_handlers = { None: ''.join, BINARY_ENCODING_HEX: ByteArray.to_hex, BINARY_ENCODING_BASE64: ByteArray.to_base64, BINARY_ENCODING_URLSAFE_BASE64: ByteArray.to_urlsafe_base64, } binary_decoding_handlers = { None: lambda x: [x], BINARY_ENCODING_HEX: ByteArray.from_hex, BINARY_ENCODING_BASE64: ByteArray.from_base64, BINARY_ENCODING_URLSAFE_BASE64: ByteArray.from_urlsafe_base64, } class HybridFileStore(object): def __init__(self, store_path, db_format='json'): """Hybrid Sql/Filesystem store. :param store_path: The path where the file contents are stored. This is converted to an absolute path if it's not already one. :param db_format: The format (and the relevant column type) used to store file metadata. Currently only 'json' is implemented. """ self.store = abspath(store_path) self.db_format = db_format if not isdir(self.store): os.makedirs(self.store) assert isdir(self.store) class File(SimpleModel): """A compact way of dealing with incoming files for protocols with a standard way of encoding file metadata along with binary data. (E.g. Http) """ __type_name__ = 'base64Binary' __namespace__ = "http://www.w3.org/2001/XMLSchema" class Attributes(SimpleModel.Attributes): encoding = BINARY_ENCODING_USE_DEFAULT """The binary encoding to use when the protocol does not enforce an encoding for binary data. One of (None, 'base64', 'hex') """ class Value(ComplexModel): """The class for values marked as ``File``. :param name: Original name of the file :param path: Current path to the file. :param type: The mime type of the file's contents. :param data: Optional sequence of ``str`` or ``bytes`` instances that contain the file's data. :param handle: :class:`file` object that contains the file's data. It is ignored unless the ``path`` argument is ``None``. """ _type_info = [ ('name', Unicode(encoding='utf8')), ('type', Unicode), ('data', ByteArray(logged=False)), ] def __init__(self, name=None, path=None, type='application/octet-stream', data=None, handle=None, move=False): self.name = name if self.name is not None: if not os.path.basename(self.name) == self.name: raise ValidationError(self.name, "File name %r should not contain any '/' char") self.path = path self.type = type self.data = data self.handle = handle self.move = move self.abspath = None if self.path is not None: self.abspath = abspath(self.path) def rollover(self): """This method normalizes the file object by making ``path``, ``name`` and ``handle`` properties consistent. It writes incoming data to the file object and points the ``data`` iterable to the contents of this file. """ iter(self.data) if self.path is None: handle, self.path = tempfile.mkstemp() f = os.fdopen(handle, 'wb') else: assert os.path.isabs(self.path) f = open(self.path, 'wb') if self.name is None: self.name = os.path.basename(self.path) for data in self.data: f.write(data) f.close() self.data = None @classmethod def to_base64(cls, value): if value is None: raise StopIteration() assert value.path, "You need to write data to persistent storage first " \ "if you want to read it back." f = open(value.path, 'rb') # base64 encodes every 3 bytes to 4 base64 characters data = f.read(0x4001) # so this needs to be a multiple of 3 while len(data) > 0: yield base64.b64encode(data) data = f.read(0x4001) f.close() @classmethod def from_base64(cls, value): if value is None: return None return File.Value(data=[base64.b64decode(value)]) def __repr__(self): return "File(name=%r, path=%r, type=%r, data=%r)" % \ (self.name, self.path, self.type, self.data) @classmethod def store_as(cls, what): return cls.customize(store_as=what) # **DEPRECATED!** Use ByteArray or File instead. class Attachment(ModelBase): __type_name__ = 'base64Binary' __namespace__ = "http://www.w3.org/2001/XMLSchema" def __init__(self, data=None, file_name=None): self.data = data self.file_name = file_name def save_to_file(self): """This method writes the data to the specified file. This method assumes that the file_name is the full path to the file to be written. This method also assumes that self.data is the base64 decoded data, and will do no additional transformations on it, simply write it to disk. """ if not self.data: raise Exception("No data to write") if not self.file_name: raise Exception("No file_name specified") f = open(self.file_name, 'wb') f.write(self.data) f.close() def load_from_file(self): """This method loads the data from the specified file, and does no encoding/decoding of the data """ if not self.file_name: raise Exception("No file_name specified") f = open(self.file_name, 'rb') self.data = f.read() f.close() @classmethod def to_base64(cls, value): if value is None: return None ostream = StringIO() if not (value.data is None): istream = StringIO(value.data) elif not (value.file_name is None): istream = open(value.file_name, 'rb') else: raise ValueError("Neither data nor a file_name has been specified") base64.encode(istream, ostream) ostream.seek(0) return ostream.read() @classmethod def from_base64(cls, value): if value is None: return None istream = StringIO(value) ostream = StringIO() base64.decode(istream, ostream) ostream.seek(0) return Attachment(data=ostream.read()) spyne-2.11.0/spyne/model/complex.py0000644000175000001440000012122612352126507017172 0ustar plqusers00000000000000 # # 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 # """The ``spyne.model.complex`` module contains :class:`spyne.model.complex.ComplexBase` class and its helper objects and subclasses. These are mainly container classes for other simple or complex objects -- they don't carry any data by themselves. """ import logging logger = logging.getLogger(__name__) import decimal from weakref import WeakKeyDictionary from collections import deque from inspect import isclass from spyne import BODY_STYLE_BARE, BODY_STYLE_WRAPPED, BODY_STYLE_EMPTY from spyne import const from spyne.const import xml_ns from spyne.model import Point from spyne.model import Unicode from spyne.model import PushBase from spyne.model import ModelBase from spyne.model import json, xml, msgpack, table from spyne.model._base import apply_pssm from spyne.model.primitive import NATIVE_MAP from spyne.util import six from spyne.util import memoize from spyne.util import memoize_id from spyne.util import sanitize_args from spyne.util.meta import Prepareable from spyne.util.odict import odict from spyne.util.six import add_metaclass, with_metaclass, string_types PSSM_VALUES = {'json': json, 'xml': xml, 'msgpack': msgpack, 'table': table} def _get_flat_type_info(cls, retval): parent = getattr(cls, '__extends__', None) if parent != None: _get_flat_type_info(parent, retval) retval.update(cls._type_info) return retval class TypeInfo(odict): def __init__(self, *args, **kwargs): super(TypeInfo, self).__init__(*args, **kwargs) self.attributes = {} def __setitem__(self, key, val): assert isinstance(key, string_types) super(TypeInfo, self).__setitem__(key, val) class _SimpleTypeInfoElement(object): __slots__ = ['path', 'parent', 'type', 'is_array'] def __init__(self, path, parent, type_, is_array): self.path = path self.parent = parent self.type = type_ self.is_array = is_array def __repr__(self): return "SimpleTypeInfoElement(path=%r, parent=%r, type=%r, is_array=%r)" \ % (self.path, self.parent, self.type, self.is_array) class XmlModifier(ModelBase): def __new__(cls, type, ns=None): retval = cls.customize() retval.type = type retval.Attributes = type.Attributes retval._ns = ns if type.__type_name__ is ModelBase.Empty: retval.__type_name__ = ModelBase.Empty return retval @staticmethod def resolve_namespace(cls, default_ns, tags=None): cls.type.resolve_namespace(cls.type, default_ns, tags) cls.__namespace__ = cls._ns if cls.__namespace__ is None: cls.__namespace__ = cls.type.get_namespace() if cls.__namespace__ in xml_ns.const_prefmap: cls.__namespace__ = default_ns class XmlData(XmlModifier): """Items which are marshalled as data of the parent element.""" @classmethod def marshall(cls, prot, name, value, parent_elt): if value is not None: if len(parent_elt) == 0: parent_elt.text = prot.to_string(cls.type, value) else: parent_elt[-1].tail = prot.to_string(cls.type, value) @classmethod def get_type_name(cls): return cls.type.get_type_name() @classmethod def get_type_name_ns(cls, interface): return cls.type.get_type_name_ns(interface) @classmethod def get_namespace(cls): return cls.type.get_namespace() @classmethod def get_element_name(cls): return cls.type.get_element_name() @classmethod def get_element_name_ns(cls, interface): return cls.type.get_element_name_ns(interface) class XmlAttribute(XmlModifier): """Items which are marshalled as attributes of the parent element. If ``attribute_of`` is passed, it's marshalled as the attribute of the element with given name. """ def __new__(cls, type_, use=None, ns=None, attribute_of=None): retval = super(XmlAttribute, cls).__new__(cls, type_, ns) retval._use = use if retval.type.Attributes.min_occurs > 0 and retval._use is None: retval._use = 'required' retval.attribute_of = attribute_of return retval class XmlAttributeRef(XmlAttribute): """Reference to an Xml attribute.""" def __init__(self, ref, use=None): self._ref = ref self._use = use def describe(self, name, element, app): element.set('ref', self._ref) if self._use: element.set('use', self._use) class SelfReference(object): """Use this as a placeholder type in classes that contain themselves. See :func:`spyne.test.model.test_complex.TestComplexModel.test_self_reference`. """ def __init__(self): raise NotImplementedError() def _get_spyne_type(cls_name, k, v): try: v = NATIVE_MAP.get(v, v) except TypeError: return try: subc = issubclass(v, ModelBase) or issubclass(v, SelfReference) except: subc = False if subc: if issubclass(v, Array) and len(v._type_info) != 1: raise Exception("Invalid Array definition in %s.%s."% (cls_name, k)) elif issubclass(v, Point) and v.Attributes.dim is None: raise Exception("Please specify the number of dimensions") return v def _join_args(x, y): if x is None: return y if y is None: return x xa, xk = sanitize_args(x) ya, yk = sanitize_args(y) xk = dict(xk) xk.update(yk) return xa + ya, xk def _gen_attrs(cls_bases, cls_dict): attrs = cls_dict.get('Attributes', None) if attrs is None: for b in cls_bases: if hasattr(b, 'Attributes'): class Attributes(b.Attributes): pass attrs = cls_dict['Attributes'] = Attributes break else: raise Exception("No ModelBase subclass in bases? Huh?") return attrs def _get_type_info(cls, cls_name, cls_bases, cls_dict, attrs): base_type_info = TypeInfo() mixin = {} extends = cls_dict.get('__extends__', None) if extends is None: for b in cls_bases: base_types = getattr(b, "_type_info", None) if base_types is not None: if getattr(b, '__mixin__', False) == True: mixin = b.get_flat_type_info(b) else: if not (extends in (None, b)): raise Exception("Spyne objects do not support multiple " "inheritance. Use mixins if you need to reuse " "fields from multiple classes.") try: if len(base_types) > 0 and issubclass(b, ModelBase): extends = cls_dict["__extends__"] = b except Exception as e: logger.exception(e) logger.error(repr(extends)) raise if not ('_type_info' in cls_dict): cls_dict['_type_info'] = _type_info = TypeInfo() _type_info.update(base_type_info) class_fields = [] for k, v in cls_dict.items(): if not k.startswith('_'): v = _get_spyne_type(cls_name, k, v) if v is not None: class_fields.append((k, v)) _type_info.update(class_fields) else: _type_info = cls_dict['_type_info'] if not isinstance(_type_info, TypeInfo): _type_info = cls_dict['_type_info'] = TypeInfo(_type_info) _type_info.update(mixin) return _type_info class _MethodsDict(dict): def __init__(self, *args, **kwargs): super(_MethodsDict, self).__init__(*args, **kwargs) self._processed = False def sanitize(self, cls): # sanitize is called on every customization, so we make sure it's run # only once in this class' lifetime. if self._processed: return self._processed = True for d in self.values(): d.parent_class = cls if d.in_message_name_override: d.in_message.__type_name__ = '%s.%s' % \ (cls.get_type_name(), d.in_message.get_type_name()) if d.body_style is BODY_STYLE_WRAPPED or d.out_message_name_override: d.out_message.__type_name__ = '%s.%s' % \ (cls.get_type_name(), d.out_message.get_type_name()) if d.body_style in (BODY_STYLE_BARE, BODY_STYLE_EMPTY): # The method only needs the primary key(s) and shouldn't # complain when other mandatory fields are missing. d.in_message = cls.novalidate_freq() d.body_style = BODY_STYLE_BARE else: d.in_message.insert_field(0, 'self', cls.novalidate_freq()) d.body_style = BODY_STYLE_WRAPPED for k, v in d.in_message._type_info.items(): # SelfReference is replaced by descriptor.in_message itself. # However, in the context of mrpc, SelfReference means # parent class. here, we do that substitution. It's a safe # hack but a hack nevertheless. if v is d.in_message: d.in_message._type_info[k] = cls # Same as above, for the output type. for k, v in d.out_message._type_info.items(): if v is d.out_message: d.out_message._type_info[k] = cls def _gen_methods(cls_dict): methods = _MethodsDict() for k, v in cls_dict.items(): if not k.startswith('_') and hasattr(v, '_is_rpc'): descriptor = v(_default_function_name=k) cls_dict[k] = descriptor.function methods[k] = descriptor return methods def _get_ordered_attributes(cls_name, cls_dict, attrs): if not isinstance(cls_dict, odict): # FIXME: Maybe add a warning here? return cls_dict SUPPORTED_ORDERS = ('random', 'declared') if (attrs.declare_order is not None and not attrs.declare_order in SUPPORTED_ORDERS): msg = "The declare_order attribute value %r is invalid in %s" raise Exception(msg % (attrs.declare_order, cls_name)) declare_order = attrs.declare_order or const.DEFAULT_DECLARE_ORDER if declare_order is None or declare_order == 'random': # support old behaviour cls_dict = dict(cls_dict) return cls_dict def _sanitize_sqlalchemy_parameters(cls_dict, attrs): table_name = cls_dict.get('__tablename__', None) if attrs.table_name is None: attrs.table_name = table_name _cls_table = cls_dict.get('__table__', None) if attrs.sqla_table is None: attrs.sqla_table = _cls_table metadata = cls_dict.get('__metadata__', None) if attrs.sqla_metadata is None: attrs.sqla_metadata = metadata margs = cls_dict.get('__mapper_args__', None) attrs.sqla_mapper_args = _join_args(attrs.sqla_mapper_args, margs) targs = cls_dict.get('__table_args__', None) attrs.sqla_table_args = _join_args(attrs.sqla_table_args, targs) def _sanitize_type_info(cls_name, _type_info, _type_info_alt): # make sure _type_info contents are sane for k, v in _type_info.items(): if not isinstance(k, six.string_types): raise ValueError("Invalid class key", k) if not isclass(v): raise ValueError(v) if issubclass(v, SelfReference): continue elif not issubclass(v, ModelBase): v = _get_spyne_type(cls_name, k, v) if v is None: raise ValueError( (cls_name, k, v) ) _type_info[k] = v elif issubclass(v, Array) and len(v._type_info) != 1: raise Exception("Invalid Array definition in %s.%s." % (cls_name, k)) sub_ns = v.Attributes.sub_ns sub_name = v.Attributes.sub_name if sub_ns is None and sub_name is None: pass elif sub_ns is not None and sub_name is not None: key = "{%s}%s" % (sub_ns, sub_name) if key in _type_info: raise Exception("%r is already defined: %r" % (key, _type_info[key])) _type_info_alt[key] = v, k elif sub_ns is None: key = sub_name if sub_ns in _type_info: raise Exception("%r is already defined: %r" % (key, _type_info[key])) _type_info_alt[key] = v, k elif sub_name is None: key = "{%s}%s" % (sub_ns, k) if key in _type_info: raise Exception("%r is already defined: %r" % (key, _type_info[key])) _type_info_alt[key] = v, k class ComplexModelMeta(with_metaclass(Prepareable, type(ModelBase))): """This metaclass sets ``_type_info``, ``__type_name__`` and ``__extends__`` which are going to be used for (de)serialization and schema generation. """ def __new__(cls, cls_name, cls_bases, cls_dict): """This function initializes the class and registers attributes for serialization. """ attrs = _gen_attrs(cls_bases, cls_dict) cls_dict = _get_ordered_attributes(cls_name, cls_dict, attrs) type_name = cls_dict.get("__type_name__", None) if type_name is None: cls_dict["__type_name__"] = cls_name _type_info = _get_type_info(cls, cls_name, cls_bases, cls_dict, attrs) methods = _gen_methods(cls_dict) if len(methods) > 0: attrs.methods = methods # used for sub_name and sub_ns _type_info_alt = cls_dict['_type_info_alt'] = TypeInfo() for b in cls_bases: if hasattr(b, '_type_info_alt'): _type_info_alt.update(b._type_info_alt) _sanitize_type_info(cls_name, _type_info, _type_info_alt) _sanitize_sqlalchemy_parameters(cls_dict, attrs) return super(ComplexModelMeta, cls).__new__(cls, cls_name, cls_bases, cls_dict) def __init__(self, cls_name, cls_bases, cls_dict): type_info = self._type_info extends = self.__extends__ if extends is not None and self.__orig__ is None: eattr = extends.Attributes; if eattr._subclasses is None: eattr._subclasses = [] eattr._subclasses.append(self) if self.Attributes._subclasses is eattr._subclasses: self.Attributes._subclasses = None for k,v in type_info.items(): if issubclass(v, SelfReference): type_info[k] = self elif issubclass(v, XmlData): self.Attributes._xml_tag_body_as = k, v elif issubclass(v, XmlAttribute): a_of = v.attribute_of if a_of is not None: type_info.attributes[k] = type_info[a_of] elif issubclass(v, Array): v2, = v._type_info.values() while issubclass(v2, Array): v = v2 v2, = v2._type_info.values() if issubclass(v2, SelfReference): v._set_serializer(self) # FIXME: Implement this better new_type_info = [] for k, v in self._type_info.items(): if v.Attributes.order == None: new_type_info.append(k) for k, v in self._type_info.items(): if v.Attributes.order is not None: new_type_info.insert(v.Attributes.order, k) assert len(self._type_info) == len(new_type_info) self._type_info.keys()[:] = new_type_info tn = self.Attributes.table_name meta = self.Attributes.sqla_metadata t = self.Attributes.sqla_table methods = self.Attributes.methods if methods is not None: assert isinstance(methods, _MethodsDict) methods.sanitize(self) # For spyne objects reflecting an existing db table if tn is None: if t is not None: self.Attributes.sqla_metadata = t.metadata from spyne.util.sqlalchemy import gen_spyne_info gen_spyne_info(self) # For spyne objects being converted to a sqlalchemy table elif meta is not None and (tn is not None or t is not None) and \ len(self._type_info) > 0: from spyne.util.sqlalchemy import gen_sqla_info gen_sqla_info(self, cls_bases) super(ComplexModelMeta, self).__init__(cls_name, cls_bases, cls_dict) # # We record the order fields are defined into ordered dict, so we can # declare them in the same order in the WSDL. # # For Python 3 __prepare__ works out of the box, see PEP 3115. # But we use `Preparable` metaclass for both Python 2 and Python 3 to # support six.add_metaclass decorator # @classmethod def __prepare__(mcs, name, bases, **kwds): return odict() # FIXME: what an ugly hack. def _fill_empty_type_name(v, parent_ns, parent_tn, k): v.__namespace__ = parent_ns tn = "%s_%s%s" % (parent_tn, k, const.TYPE_SUFFIX) if issubclass(v, Array): child_v, = v._type_info.values() child_v.__type_name__ = tn v._type_info = TypeInfo({tn: child_v}) v.__type_name__ = '%s%s%s'% (const.ARRAY_PREFIX, tn, const.ARRAY_SUFFIX) extends = child_v.__extends__ while extends is not None and extends.get_type_name() is v.Empty: _fill_empty_type_name(extends, parent_ns, parent_tn, k + const.PARENT_SUFFIX) extends = extends.__extends__ elif issubclass(v, XmlModifier): child_v = v.type child_v.__type_name__ = tn v._type_info = TypeInfo({tn: child_v}) v.__type_name__ = '%s%s%s'% (const.ARRAY_PREFIX, tn, const.ARRAY_SUFFIX) extends = child_v.__extends__ while extends is not None and extends.get_type_name() is v.Empty: _fill_empty_type_name(extends, parent_ns, parent_tn, k + const.PARENT_SUFFIX) extends = extends.__extends__ else: v.__type_name__ = "%s_%s%s" % (parent_tn, k, const.TYPE_SUFFIX) extends = v.__extends__ while extends is not None and extends.__type_name__ is ModelBase.Empty: _fill_empty_type_name(v.__extends__, v.get_namespace(), v.get_type_name(), k + const.PARENT_SUFFIX) extends = extends.__extends__ _is_array = lambda v: issubclass(v, Array) or (v.Attributes.min_occurs > 1) class ComplexModelBase(ModelBase): """If you want to make a better class type, this is what you should inherit from. """ __mixin__ = False class Attributes(ModelBase.Attributes): """ComplexModel-specific attributes""" store_as = None """Method for serializing to persistent storage. One of %r. It makes sense to specify this only when this object is a child of another ComplexModel sublass.""" % (PSSM_VALUES,) sqla_metadata = None """None or :class:`sqlalchemy.MetaData` instance.""" sqla_table_args = None """A dict that will be passed to :class:`sqlalchemy.schema.Table` constructor as ``**kwargs``. """ sqla_mapper_args = None """A dict that will be passed to :func:`sqlalchemy.orm.mapper` constructor as. ``**kwargs``. """ sqla_table = None """The sqlalchemy table object""" sqla_mapper = None """The sqlalchemy mapper object""" validate_freq = True """When ``False``, soft validation ignores missing mandatory attributes. """ child_attrs = None """Customize child attributes in one go. It's a dict of dicts. This is ignored unless used via explicit customization.""" declare_order = None """The order fields of the :class:``ComplexModel`` are to be declared in the SOAP WSDL. If this is left as None or explicitly set to ``'random'`` declares then the fields appear in whatever order the Python's hash map implementation seems fit in the WSDL. This randomised order can change every time the program is run. This is what Spyne <2.11 did if you didn't set _type_info as an explicit sequence (e.g. using a list, odict, etc.). It means that clients who are manually complied or generated from the WSDL will likely need to be recompiled every time it changes. The string ``name`` means the field names are alphabetically sorted in the WSDL declaration. The string ``declared`` means in the order the field type was declared in Python 2, and the order the field was declared in Python 3. In order to get declared field order in Python 2, the :class:`spyne.util.meta.Preparable` class inspects the frame stack in order to locate the class definition, re-parses it to get declaration order from the AST and uses that information to order elements. It's a horrible hack that we tested to work with CPython 2.6 through 3.3 and PyPy. It breaks in Nuitka as Nuitka does away with code objects. Other platforms were not tested. It's not recommended to use set this to ``'declared'`` in Python 2 unless you're sure you fully understand the consequences. """ parent_variant = None """FIXME: document me yo.""" methods = None """FIXME: document me yo.""" _variants = None _xml_tag_body_as = None, None _delayed_child_attrs = None _subclasses = None def __init__(self, *args, **kwargs): cls = self.__class__ fti = cls.get_flat_type_info(cls) xtba_key, xtba_type = cls.Attributes._xml_tag_body_as if xtba_key is not None and len(args) == 1: self._safe_set(xtba_key, args[0], xtba_type) elif len(args) > 0: raise TypeError("Positional argument is only for ComplexModels " "with XmlData field. You must use keyword " "arguments in any other case.") for k,v in fti.items(): if k in kwargs: self._safe_set(k, kwargs[k], v) elif not k in self.__dict__: attr = v.Attributes def_val = attr.default def_fac = attr.default_factory if def_fac is not None: # should not check for read-only for default values setattr(self, k, def_fac()) elif def_val is not None: # should not check for read-only for default values setattr(self, k, def_val) # sqlalchemy objects do their own init. elif '_sa_class_manager' in cls.__dict__: # except the attributes that sqlalchemy doesn't know about if v.Attributes.exc_table: setattr(self, k, None) elif issubclass(v, ComplexModelBase) and \ v.Attributes.store_as is None: setattr(self, k, None) else: setattr(self, k, None) def __len__(self): return len(self._type_info) def __getitem__(self, i): if isinstance(i, slice): retval = [] for key in self._type_info.keys()[i]: retval.append(getattr(self, key, None)) else: retval = getattr(self, self._type_info.keys()[i], None) return retval def __repr__(self): return "%s(%s)" % (self.get_type_name(), ', '.join( ['%s=%r' % (k, self.__dict__.get(k)) for k in self.__class__.get_flat_type_info(self.__class__) if self.__dict__.get(k, None) is not None])) def _safe_set(self, key, value, t): if t.Attributes.read_only: pass else: setattr(self, key, value) def as_dict(self): return dict(( (k, getattr(self, k)) for k in self.get_flat_type_info(self) )) @classmethod def get_serialization_instance(cls, value): """Returns the native object corresponding to the serialized form passed in the ``value`` argument. :param value: This argument can be: * A list or tuple of native types aligned with cls._type_info. * A dict of native types. * The native type itself. If the value type is not a ``list``, ``tuple`` or ``dict``, the value is returned untouched. """ # if the instance is a list, convert it to a cls instance. # this is only useful when deserializing method arguments for a client # request which is the only time when the member order is not arbitrary # (as the members are declared and passed around as sequences of # arguments, unlike dictionaries in a regular class definition). if isinstance(value, list) or isinstance(value, tuple): assert len(value) <= len(cls._type_info) cls_orig = cls if cls.__orig__ is not None: cls_orig = cls.__orig__ inst = cls_orig() keys = cls._type_info.keys() for i in range(len(value)): setattr(inst, keys[i], value[i]) elif isinstance(value, dict): inst = cls() for k in cls._type_info: setattr(inst, k, value.get(k, None)) else: inst = value return inst @classmethod def get_deserialization_instance(cls): """Get an empty native type so that the deserialization logic can set its attributes. """ if cls.__orig__ is None: return cls() else: return cls.__orig__() @staticmethod @memoize def get_flat_type_info(cls): """Returns a _type_info dict that includes members from all base classes. It's called a "flat" dict because it flattens all members from the inheritance hierarchy into one dict. """ return _get_flat_type_info(cls, TypeInfo()) @classmethod def get_orig(cls): return cls.__orig__ or cls @staticmethod @memoize def get_simple_type_info(cls, hier_delim="."): """Returns a _type_info dict that includes members from all base classes and whose types are only primitives. It will prefix field names in non-top-level complex objects with field name of its parent. For example, given hier_delim='_'; the following hierarchy: :: {'some_object': [{'some_string': ['abc']}]} would be transformed to: :: {'some_object_some_string': ['abc']} :param hier_delim: String that will be used as delimiter between field names. Default is ``'_'``. """ fti = cls.get_flat_type_info(cls) retval = TypeInfo() tags = set() queue = deque([(k, v, (k,), (_is_array(v),), cls) for k,v in fti.items()]) tags.add(cls) while len(queue) > 0: k, v, prefix, is_array, parent = queue.popleft() if issubclass(v, Array) and v.Attributes.max_occurs == 1: v, = v._type_info.values() if issubclass(v, ComplexModelBase): if not (v in tags): tags.add(v) queue.extend([ (k2, v2, prefix + (k2,), is_array + (v.Attributes.max_occurs > 1,), v) for k2, v2 in v.get_flat_type_info(v).items()]) else: key = hier_delim.join(prefix) value = retval.get(key, None) if value is not None: raise ValueError("%r.%s conflicts with %r" % (cls, k, value.path)) retval[key] = _SimpleTypeInfoElement(path=tuple(prefix), parent=parent, type_=v, is_array=tuple(is_array)) return retval @staticmethod def resolve_namespace(cls, default_ns, tags=None): if tags is None: tags = set() elif cls in tags: return False if not ModelBase.resolve_namespace(cls, default_ns, tags): return False for k, v in cls._type_info.items(): if v is None: continue if v.__type_name__ is ModelBase.Empty: _fill_empty_type_name(v, cls.get_namespace(), cls.get_type_name(), k) v.resolve_namespace(v, default_ns, tags) if cls._force_own_namespace is not None: for c in cls._force_own_namespace: c.__namespace__ = cls.get_namespace() ComplexModel.resolve_namespace(c, cls.get_namespace(), tags) assert not (cls.__namespace__ is ModelBase.Empty) assert not (cls.__type_name__ is ModelBase.Empty) return True @staticmethod def produce(namespace, type_name, members): """Lets you create a class programmatically.""" return ComplexModelMeta(type_name, (ComplexModel,), odict({ '__namespace__': namespace, '__type_name__': type_name, '_type_info': TypeInfo(members), })) @classmethod def customize(cls, **kwargs): """Duplicates cls and overwrites the values in ``cls.Attributes`` with ``**kwargs`` and returns the new class.""" store_as = apply_pssm(kwargs.get('store_as', None), PSSM_VALUES) if store_as is not None: kwargs['store_as'] = store_as cls_name, cls_bases, cls_dict = cls._s_customize(cls, **kwargs) cls_dict['__module__'] = cls.__module__ retval = type(cls_name, cls_bases, cls_dict) retval._type_info = TypeInfo(cls._type_info) retval.__type_name__ = cls.__type_name__ retval.__namespace__ = cls.__namespace__ retval.Attributes.parent_variant = cls dca = retval.Attributes._delayed_child_attrs if retval.Attributes._delayed_child_attrs is None: retval.Attributes._delayed_child_attrs = {} else: retval.Attributes._delayed_child_attrs = dict(dca.items()) child_attrs = kwargs.get('child_attrs', None) if child_attrs is not None: ti = retval._type_info for k, v in child_attrs.items(): if k in ti: ti[k] = ti[k].customize(**v) else: retval.Attributes._delayed_child_attrs[k] = v tn = kwargs.get("type_name", None) if tn is not None: retval.__type_name__ = tn ns = kwargs.get("namespace", None) if ns is not None: retval.__namespace__ = ns if not cls is ComplexModel: cls._process_variants(retval) return retval @classmethod def _process_variants(cls, retval): orig = getattr(retval, '__orig__', None) if orig is not None: retval.__extends__ = getattr(orig, '__extends__', None) if orig.Attributes._variants is None: orig.Attributes._variants = WeakKeyDictionary() orig.Attributes._variants[retval] = True # _variants is only for the root class. retval.Attributes._variants = None @classmethod def append_field(cls, field_name, field_type): assert isinstance(field_name, string_types) dca = cls.Attributes._delayed_child_attrs if dca is not None: d_cust = dca.get(field_name, None) if d_cust is not None: field_type = field_type.customize(**d_cust) cls._type_info[field_name] = field_type if cls.Attributes._variants is not None: for c in cls.Attributes._variants: c.append_field(field_name, field_type) ComplexModelBase.get_flat_type_info.memo.clear() ComplexModelBase.get_simple_type_info.memo.clear() @classmethod def insert_field(cls, index, field_name, field_type): assert isinstance(index, int) assert isinstance(field_name, string_types) dca = cls.Attributes._delayed_child_attrs if dca is not None: if field_name in dca: d_cust = dca.pop(field_name) field_type = field_type.customize(**d_cust) cls._type_info.insert(index, (field_name, field_type)) if cls.Attributes._variants is not None: for c in cls.Attributes._variants: c.insert_field(index, field_name, field_type) ComplexModelBase.get_flat_type_info.memo.clear() ComplexModelBase.get_simple_type_info.memo.clear() @classmethod def store_as(cls, what): return cls.customize(store_as=what) @classmethod def novalidate_freq(cls): return cls.customize(validate_freq=False) @classmethod def init_from(cls, other): retval = cls() for k in cls._type_info: setattr(retval, k, getattr(other, k, None)) return retval @classmethod def __respawn__(cls, ctx=None): if ctx is not None and ctx.in_object is not None and \ len(ctx.in_object) > 0: return ctx.in_object[0] @add_metaclass(ComplexModelMeta) class ComplexModel(ComplexModelBase): """The general complexType factory. The __call__ method of this class will return instances, contrary to primivites where the same call will result in customized duplicates of the original class definition. Those who'd like to customize the class should use the customize method. (see :class:``spyne.model.ModelBase``). """ @add_metaclass(ComplexModelMeta) class Array(ComplexModelBase): """This class generates a ComplexModel child that has one attribute that has the same name as the serialized class. It's contained in a Python list. """ class Attributes(ComplexModelBase.Attributes): _wrapper = True def __new__(cls, serializer, member_name=None, **kwargs): retval = cls.customize(**kwargs) _serializer = _get_spyne_type(cls.__name__, '__serializer__', serializer) if _serializer is None: raise ValueError("serializer=%r is not a valid spyne type" % serializer) if issubclass(_serializer, SelfReference): # hack to make sure the array passes ComplexModel sanity checks # that are there to prevent empty arrays. retval._type_info = {'_bogus': _serializer} else: retval._set_serializer(_serializer, member_name) tn = kwargs.get("type_name", None) if tn is not None: retval.__type_name__ = tn return retval @classmethod def customize(cls, **kwargs): serializer_attrs = kwargs.get('serializer_attrs', None) if serializer_attrs is None: return super(Array, cls).customize(**kwargs) del kwargs['serializer_attrs'] serializer, = cls._type_info.values() return cls(serializer.customize(**serializer_attrs)).customize(**kwargs) @classmethod def _set_serializer(cls, serializer, member_name=None): if serializer.get_type_name() is ModelBase.Empty: # A customized class member_name = "OhNoes" # mark array type name as "to be resolved later". cls.__type_name__ = ModelBase.Empty else: if member_name is None: member_name = serializer.get_type_name() cls.__type_name__ = '%s%s%s' % (const.ARRAY_PREFIX, serializer.get_type_name(), const.ARRAY_SUFFIX) # hack to default to unbounded arrays when the user didn't specify # max_occurs. if serializer.Attributes.max_occurs == 1: serializer = serializer.customize(max_occurs=decimal.Decimal('inf')) assert isinstance(member_name, string_types), member_name cls._type_info = {member_name: serializer} # the array belongs to its child's namespace, it doesn't have its own # namespace. @staticmethod def resolve_namespace(cls, default_ns, tags=None): (serializer,) = cls._type_info.values() serializer.resolve_namespace(serializer, default_ns, tags) if cls.__namespace__ is None: cls.__namespace__ = serializer.get_namespace() if cls.__namespace__ in xml_ns.const_prefmap: cls.__namespace__ = default_ns return ComplexModel.resolve_namespace(cls, default_ns, tags) @classmethod def get_serialization_instance(cls, value): inst = ComplexModel.__new__(Array) (member_name,) = cls._type_info.keys() setattr(inst, member_name, value) return inst @classmethod def get_deserialization_instance(cls): return [] class Iterable(Array): """This class generates a ``ComplexModel`` child that has one attribute that has the same name as the serialized class. It's contained in a Python iterable. The distinction with the ``Array`` is made in the protocol implementation, this is just a marker. Whenever you return a generator instead of a list, you should use this type as this suggests the intermediate machinery to NEVER actually try to iterate over the value. An ``Array`` could be iterated over for e.g. logging purposes. """ class Attributes(Array.Attributes): logged = False class Push(PushBase): pass @memoize def TTableModelBase(): from spyne.util.sqlalchemy import add_column class TableModelBase(ComplexModelBase): @classmethod def append_field(cls, field_name, field_type): super(TableModelBase, cls).append_field(field_name, field_type) # There could have been changes to field_type in ComplexModel so we # should not use field_type directly from above add_column(cls, field_name, cls._type_info[field_name]) @classmethod def insert_field(cls, index, field_name, field_type): super(TableModelBase, cls).insert_field(index, field_name, field_type) # There could have been changes to field_type in ComplexModel so we # should not use field_type directly from above add_column(cls, field_name, cls._type_info[field_name]) return TableModelBase # this has docstring repeated in the documentation at reference/model/complex.rst @memoize_id def TTableModel(metadata=None): """A TableModel template that generates a new TableModel class for each call. If metadata is not supplied, a new one is instantiated. """ from sqlalchemy import MetaData @add_metaclass(ComplexModelMeta) class TableModel(TTableModelBase()): class Attributes(ComplexModelBase.Attributes): sqla_metadata = metadata or MetaData() return TableModel def Mandatory(cls, **_kwargs): """Customizes the given type to be a mandatory one. Has special cases for :class:`spyne.model.primitive.Unicode` and :class:`spyne.model.complex.Array`\. """ kwargs = dict(min_occurs=1, nillable=False) if cls.get_type_name() is not cls.Empty: kwargs['type_name'] = '%s%s%s' % (const.MANDATORY_PREFIX, cls.get_type_name(), const.MANDATORY_SUFFIX) kwargs.update(_kwargs) if issubclass(cls, Unicode): kwargs.update(dict(min_len=1)) elif issubclass(cls, Array): (k,v), = cls._type_info.items() if v.Attributes.min_occurs == 0: cls._type_info[k] = Mandatory(v) return cls.customize(**kwargs) spyne-2.11.0/spyne/model/enum.py0000644000175000001440000000672612345433230016472 0ustar plqusers00000000000000 # # 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 # from spyne.model import SimpleModel # adapted from: http://code.activestate.com/recipes/413486/ class EnumBase(SimpleModel): __namespace__ = None @staticmethod def resolve_namespace(cls, default_ns, tags=None): if cls.__namespace__ is None: cls.__namespace__ = default_ns return True @staticmethod def validate_string(cls, value): return ( SimpleModel.validate_string(cls, value) and value in cls.__values__ ) def Enum(*values, **kwargs): """The enum type that can only return ``True`` when compared to types of own type. Here's how it's supposed to work: >>> from spyne.model.enum import Enum >>> SomeEnum = Enum("SomeValue", "SomeOtherValue", type_name="SomeEnum") >>> SomeEnum.SomeValue == SomeEnum.SomeOtherValue False >>> SomeEnum.SomeValue == SomeEnum.SomeValue True >>> SomeEnum.SomeValue is SomeEnum.SomeValue True >>> SomeEnum.SomeValue == 0 False >>> SomeEnum2 = Enum("SomeValue", "SomeOtherValue", type_name="SomeEnum") >>> SomeEnum2.SomeValue == SomeEnum.SomeValue False In the above example, ``SomeEnum`` can be used as a regular Spyne model. """ type_name = kwargs.get('type_name', None) docstr = kwargs.get('doc', '') if type_name is None: raise Exception("Please specify 'type_name' as a keyword argument") assert len(values) > 0, "Empty enums are meaningless" maximum = len(values) # to make __invert__ work class EnumValue(object): __slots__ = ('__value') def __init__(self, value): self.__value = value def __hash__(self): return hash(self.__value) def __cmp__(self, other): if isinstance(self, type(other)): return cmp(self.__value, other.__value) else: return cmp(id(self), id(other)) def __invert__(self): return values[maximum - self.__value] def __nonzero__(self): return bool(self.__value) def __bool__(self): return bool(self.__value) def __repr__(self): return str(values[self.__value]) class EnumType(EnumBase): __doc__ = docstr __type_name__ = type_name __values__ = values def __iter__(self): return iter(values) def __len__(self): return len(values) def __getitem__(self, i): return values[i] def __repr__(self): return 'Enum' + str(enumerate(values)) def __str__(self): return 'enum ' + str(values) for i, v in enumerate(values): setattr(EnumType, v, EnumValue(i)) return EnumType spyne-2.11.0/spyne/model/fault.py0000644000175000001440000000661012345433230016631 0ustar plqusers00000000000000 # # 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 spyne.const from spyne.util.six import add_metaclass from spyne.model.complex import ComplexModelMeta from spyne.model.complex import ComplexModelBase @add_metaclass(ComplexModelMeta) class Fault(ComplexModelBase, Exception): """Use this class as a base for all public exceptions. The Fault object adheres to the `SOAP 1.1 Fault definition `_, which has three main attributes: :param faultcode: It's a dot-delimited string whose first fragment is either 'Client' or 'Server'. Just like HTTP 4xx and 5xx codes, 'Client' indicates that something was wrong with the input, and 'Server' indicates something went wrong during the processing of an otherwise legitimate request. Protocol implementors should heed the values in ``faultcode`` to set proper return codes in the protocol level when necessary. E.g. HttpRpc protocol will return a HTTP 404 error when a :class:`spyne.error.ResourceNotFound` is raised, and a general HTTP 400 when the ``faultcode`` starts with ``'Client.'`` or is ``'Client'``. Soap would return Http 500 for any kind of exception, and denote the nature of the exception in the Soap response body. (because that's what the standard says... Yes, soap is famous for a reason :)) :param faultstring: It's the human-readable explanation of the exception. :param detail: Additional information dict. """ __type_name__ = "Fault" def __init__(self, faultcode='Server', faultstring="", faultactor="", detail=None): self.faultcode = faultcode self.faultstring = faultstring or self.get_type_name() self.faultactor = faultactor self.detail = detail def __len__(self): return 1 def __str__(self): return repr(self) def __repr__(self): return "Fault(%s: %r)" % (self.faultcode, self.faultstring) @staticmethod def to_dict(cls, value): if issubclass(cls, Fault): retval = { "faultcode": value.faultcode, "faultstring": value.faultstring, } if value.detail is not None: retval["detail"] = value.detail return retval else: return { "faultcode": str(cls), "faultstring": cls.__class__.__name__, "detail": str(value), } @classmethod def to_string_iterable(cls, value): return [value.faultcode, '\n\n', value.faultstring] spyne-2.11.0/spyne/model/primitive.py0000644000175000001440000011203312345433230017523 0ustar plqusers00000000000000 # # 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 # """ The ``spyne.model.primitive`` package contains types with values that fit in a single field. See :mod:`spyne.protocol._model` for {to,from}_string implementations. """ from __future__ import absolute_import import sys import re import math import uuid import decimal import datetime import platform import spyne from spyne.const import xml_ns from spyne.model import SimpleModel from spyne.util import memoize from spyne.model._base import apply_pssm, msgpack, xml, json string_encoding = 'utf8' FLOAT_PATTERN = r'-?[0-9]+\.?[0-9]*(e-?[0-9]+)?' DATE_PATTERN = r'(?P\d{4})-(?P\d{2})-(?P\d{2})' TIME_PATTERN = r'(?P
\d{2}):(?P\d{2}):(?P\d{2})(?P\.\d+)?' OFFSET_PATTERN = r'(?P[+-]\d{2}):(?P\d{2})' DATETIME_PATTERN = DATE_PATTERN + '[T ]' + TIME_PATTERN UUID_PATTERN = "%(x)s{8}-%(x)s{4}-%(x)s{4}-%(x)s{4}-%(x)s{12}" % \ {'x': '[a-fA-F0-9]'} # # FIXME: Supports e.g. # MULTIPOINT (10 40, 40 30, 20 20, 30 10) # # but not: # MULTIPOINT ((10 40), (40 30), (20 20), (30 10)) # _rinse_and_repeat = r'\s*\(%s\s*(,\s*%s)*\)\s*' def _get_one_point_pattern(dim): return ' +'.join([FLOAT_PATTERN] * dim) def _get_point_pattern(dim): return r'POINT\s*\(%s\)' % _get_one_point_pattern(dim) def _get_one_multipoint_pattern(dim): one_point = _get_one_point_pattern(dim) return _rinse_and_repeat % (one_point, one_point) def _get_multipoint_pattern(dim): return r'MULTIPOINT\s*%s' % _get_one_multipoint_pattern(dim) def _get_one_line_pattern(dim): one_point = _get_one_point_pattern(dim) return _rinse_and_repeat % (one_point, one_point) def _get_linestring_pattern(dim): return r'LINESTRING\s*%s' % _get_one_line_pattern(dim) def _get_one_multilinestring_pattern(dim): one_line = _get_one_line_pattern(dim) return _rinse_and_repeat % (one_line, one_line) def _get_multilinestring_pattern(dim): return r'MULTILINESTRING\s*%s' % _get_one_multilinestring_pattern(dim) def _get_one_polygon_pattern(dim): one_line = _get_one_line_pattern(dim) return _rinse_and_repeat % (one_line, one_line) def _get_polygon_pattern(dim): return r'POLYGON\s*%s' % _get_one_polygon_pattern(dim) def _get_one_multipolygon_pattern(dim): one_line = _get_one_polygon_pattern(dim) return _rinse_and_repeat % (one_line, one_line) def _get_multipolygon_pattern(dim): return r'MULTIPOLYGON\s*%s' % _get_one_multipolygon_pattern(dim) _date_re = re.compile(DATE_PATTERN) _time_re = re.compile(TIME_PATTERN) _duration_re = re.compile( r'(?P-?)' r'P' r'(?:(?P\d+)Y)?' r'(?:(?P\d+)M)?' r'(?:(?P\d+)D)?' r'(?:T(?:(?P\d+)H)?' r'(?:(?P\d+)M)?' r'(?:(?P\d+(.\d+)?)S)?)?' ) _ns_xs = xml_ns.xsd _ns_xsi = xml_ns.xsi class AnyXml(SimpleModel): """An xml node that can contain any number of sub nodes. It's represented by an ElementTree object.""" __type_name__ = 'anyType' class Attributes(SimpleModel.Attributes): namespace = None """Xml-Schema specific namespace attribute""" process_contents = None """Xml-Schema specific processContents attribute""" # EXPERIMENTAL class AnyHtml(SimpleModel): __type_name__ = 'string' class AnyDict(SimpleModel): """A dict instance that can contain other dicts, iterables or primitive types. Its serialization is protocol-dependent. """ __type_name__ = 'anyType' class Attributes(SimpleModel.Attributes): store_as = None """Method for serializing to persistent storage. One of 'xml', 'json' or 'msgpack'. It makes sense to specify this only when this object is child of a `ComplexModel` sublass.""" @classmethod def customize(cls, **kwargs): """Duplicates cls and overwrites the values in ``cls.Attributes`` with ``**kwargs`` and returns the new class.""" store_as = apply_pssm(kwargs.get('store_as', None), {'json': json, 'xml': xml, 'msgpack': msgpack}) if store_as is not None: kwargs['store_as'] = store_as return super(AnyDict, cls).customize(**kwargs) class Unicode(SimpleModel): """The type to represent human-readable data. Its native format is `unicode` or `str` with given encoding. """ __type_name__ = 'string' class Attributes(SimpleModel.Attributes): """Customizable attributes of the :class:`spyne.model.primitive.Unicode` type.""" min_len = 0 """Minimum length of string. Can be set to any positive integer""" max_len = decimal.Decimal('inf') """Maximum length of string. Can be set to ``decimal.Decimal('inf')`` to accept strings of arbitrary length. You may also need to adjust :const:`spyne.server.wsgi.MAX_CONTENT_LENGTH`.""" pattern = None """A regular expression that matches the whole string. See here for more info: http://www.regular-expressions.info/xml.html""" encoding = None """The encoding of `str` objects this class may have to deal with.""" unicode_errors = 'strict' """The argument to the ``unicode`` builtin; one of 'strict', 'replace' or 'ignore'.""" format = None """A regular python string formatting string. See here: http://docs.python.org/library/stdtypes.html#string-formatting""" def __new__(cls, *args, **kwargs): assert len(args) <= 1 if len(args) == 1: kwargs['max_len'] = args[0] retval = SimpleModel.__new__(cls, ** kwargs) return retval @staticmethod def is_default(cls): return ( SimpleModel.is_default(cls) and cls.Attributes.min_len == Unicode.Attributes.min_len and cls.Attributes.max_len == Unicode.Attributes.max_len and cls.Attributes.pattern == Unicode.Attributes.pattern ) @staticmethod def validate_string(cls, value): return ( SimpleModel.validate_string(cls, value) and (value is None or ( cls.Attributes.min_len <= len(value) <= cls.Attributes.max_len and _re_match_with_span(cls.Attributes, value) ))) def _re_match_with_span(attr, value): if attr.pattern is None: return True m = attr._pattern_re.match(value) return (m is not None) and (m.span() == (0, len(value))) class String(Unicode): pass if sys.version > '3': String = Unicode class AnyUri(Unicode): """A special kind of String type designed to hold an uri.""" __type_name__ = 'anyURI' class Attributes(String.Attributes): text = None """The text shown in link. This is an object-wide constant.""" class Value(object): """A special object that is just a better way of carrying the information carried with a link. :param href: The uri string. :param text: The text data that goes with the link. This is a ``str`` or a ``unicode`` instance. :param content: The structured data that goes with the link. This is an `lxml.etree.Element` instance. """ def __init__(self, href, text=None, content=None): self.href = href self.text = text self.content = content class ImageUri(AnyUri): """A special kind of String that holds the uri of an image.""" class Decimal(SimpleModel): """The primitive that corresponds to the native python Decimal. This is also the base class for denoting numbers. Note that it is your responsibility to make sure that the scale and precision constraints set in this type is consistent with the values in the context of the decimal package. See the :func:`decimal.getcontext` documentation for more information. """ __type_name__ = 'decimal' class Attributes(SimpleModel.Attributes): """Customizable attributes of the :class:`spyne.model.primitive.Decimal` type.""" gt = decimal.Decimal('-inf') # minExclusive """The value should be greater than this number.""" ge = decimal.Decimal('-inf') # minInclusive """The value should be greater than or equal to this number.""" lt = decimal.Decimal('inf') # maxExclusive """The value should be lower than this number.""" le = decimal.Decimal('inf') # maxInclusive """The value should be lower than or equal to this number.""" max_str_len = 1024 """The maximum length of string to be attempted to convert to number.""" format = None """A regular python string formatting string. See here: http://docs.python.org/library/stdtypes.html#string-formatting""" str_format = None """A regular python string formatting string used by invoking its ``format()`` function. See here: http://docs.python.org/2/library/string.html#format-string-syntax""" pattern = None """A regular expression that matches the whole field. See here for more info: http://www.regular-expressions.info/xml.html""" total_digits = decimal.Decimal('inf') """Maximum number of digits.""" fraction_digits = decimal.Decimal('inf') """Maximum number of digits after the decimal separator.""" min_bound = None """Hardware limit that determines the lowest value this type can store.""" max_bound = None """Hardware limit that determines the highest value this type can store.""" def __new__(cls, *args, **kwargs): assert len(args) <= 2 if len(args) >= 1 and args[0] is not None: kwargs['total_digits'] = args[0] kwargs['fraction_digits'] = 0 if len(args) == 2 and args[1] is not None: kwargs['fraction_digits'] = args[1] assert args[0] > 0, "'total_digits' must be positive." assert args[1] <= args[0], "'total_digits' must be greater than" \ " or equal to 'fraction_digits'." \ " %r ! <= %r" % (args[1], args[0]) # + 1 for decimal separator # + 1 for negative sign msl = kwargs.get('max_str_len', None) if msl is None: kwargs['max_str_len'] = (cls.Attributes.total_digits + cls.Attributes.fraction_digits + 2) else: kwargs['max_str_len'] = msl retval = SimpleModel.__new__(cls, ** kwargs) return retval @staticmethod def is_default(cls): return ( SimpleModel.is_default(cls) and cls.Attributes.gt == Decimal.Attributes.gt and cls.Attributes.ge == Decimal.Attributes.ge and cls.Attributes.lt == Decimal.Attributes.lt and cls.Attributes.le == Decimal.Attributes.le and cls.Attributes.total_digits == Decimal.Attributes.total_digits and cls.Attributes.fraction_digits == Decimal.Attributes.fraction_digits ) @staticmethod def validate_string(cls, value): return SimpleModel.validate_string(cls, value) and ( value is None or (len(value) <= cls.Attributes.max_str_len) ) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) and ( value is None or ( value > cls.Attributes.gt and value >= cls.Attributes.ge and value < cls.Attributes.lt and value <= cls.Attributes.le )) class Double(Decimal): """This is serialized as the python ``float``. So this type comes with its gotchas. Unless you really know what you're doing, you should use a :class:`Decimal` with a pre-defined number of integer and decimal digits. """ __type_name__ = 'double' if platform.python_version_tuple()[:2] == ('2','6'): class Attributes(Decimal.Attributes): """Customizable attributes of the :class:`spyne.model.primitive.Double` type. This class is only here for Python 2.6: See this bug report for more info: http://bugs.python.org/issue2531 """ gt = float('-inf') # minExclusive """The value should be greater than this number.""" ge = float('-inf') # minInclusive """The value should be greater than or equal to this number.""" lt = float('inf') # maxExclusive """The value should be lower than this number.""" le = float('inf') # maxInclusive """The value should be lower than or equal to this number.""" @staticmethod def is_default(cls): return ( SimpleModel.is_default(cls) and cls.Attributes.gt == Double.Attributes.gt and cls.Attributes.ge == Double.Attributes.ge and cls.Attributes.lt == Double.Attributes.lt and cls.Attributes.le == Double.Attributes.le ) class Float(Double): """Synonym for Double (as far as python side of things are concerned). It's here for compatibility reasons.""" __type_name__ = 'float' class Integer(Decimal): """The arbitrary-size signed integer.""" __type_name__ = 'integer' @staticmethod def validate_native(cls, value): return ( Decimal.validate_native(cls, value) and (value is None or int(value) == value) ) class UnsignedInteger(Integer): """The arbitrary-size unsigned integer, also known as nonNegativeInteger.""" __type_name__ = 'nonNegativeInteger' @staticmethod def validate_native(cls, value): return ( Integer.validate_native(cls, value) and (value is None or value >= 0) ) NonNegativeInteger = UnsignedInteger """The arbitrary-size unsigned integer, alias for UnsignedInteger.""" class PositiveInteger(NonNegativeInteger): """The arbitrary-size positive integer (natural number).""" __type_name__ = 'positiveInteger' @staticmethod def validate_native(cls, value): return (Integer.validate_native(cls, value) and (value is None or value > 0)) @memoize def TBoundedInteger(num_bits, type_name): _min_b = -(0x8<<(num_bits-4)) # 0x8 is 4 bits. _max_b = (0x8<<(num_bits-4)) - 1 # -1? c'est la vie class _BoundedInteger(Integer): __type_name__ = type_name class Attributes(Integer.Attributes): max_str_len = math.ceil(math.log(2**num_bits, 10)) min_bound = _min_b max_bound = _max_b @staticmethod def validate_native(cls, value): return ( Integer.validate_native(cls, value) and (value is None or (_min_b <= value <= _max_b)) ) return _BoundedInteger @memoize def TBoundedUnsignedInteger(num_bits, type_name): _min_b = 0 _max_b = 2 ** num_bits - 1 # -1? c'est la vie ;) class _BoundedUnsignedInteger(UnsignedInteger): __type_name__ = type_name class Attributes(UnsignedInteger.Attributes): max_str_len = math.ceil(math.log(2**num_bits, 10)) min_bound = _min_b max_bound = _max_b @staticmethod def validate_native(cls, value): return ( UnsignedInteger.validate_native(cls, value) and (value is None or (_min_b <= value < _max_b)) ) return _BoundedUnsignedInteger Integer64 = TBoundedInteger(64, 'long') """The 64-bit signed integer, also known as ``long``.""" Long = Integer64 """The 64-bit signed integer, alias for :class:`Integer64`.""" Integer32 = TBoundedInteger(32, 'int') """The 64-bit signed integer, also known as ``int``.""" Int = Integer32 """The 32-bit signed integer, alias for :class:`Integer32`.""" Integer16 = TBoundedInteger(16, 'short') """The 16-bit signed integer, also known as ``short``.""" Short = Integer16 """The 16-bit signed integer, alias for :class:`Integer16`.""" Integer8 = TBoundedInteger(8, 'byte') """The 8-bit signed integer, also known as ``byte``.""" Byte = Integer8 """The 8-bit signed integer, alias for :class:`Integer8`.""" UnsignedInteger64 = TBoundedUnsignedInteger(64, 'unsignedLong') """The 64-bit unsigned integer, also known as ``unsignedLong``.""" UnsignedLong = UnsignedInteger64 """The 64-bit unsigned integer, alias for :class:`UnsignedInteger64`.""" UnsignedInteger32 = TBoundedUnsignedInteger(32, 'unsignedInt') """The 64-bit unsigned integer, also known as ``unsignedInt``.""" UnsignedInt = UnsignedInteger32 """The 32-bit unsigned integer, alias for :class:`UnsignedInteger32`.""" UnsignedInteger16 = TBoundedUnsignedInteger(16, 'unsignedShort') """The 16-bit unsigned integer, also known as ``unsignedShort``.""" UnsignedShort = UnsignedInteger16 """The 16-bit unsigned integer, alias for :class:`UnsignedInteger16`.""" UnsignedInteger8 = TBoundedUnsignedInteger(8, 'unsignedByte') """The 8-bit unsigned integer, also known as ``unsignedByte``.""" UnsignedByte = UnsignedInteger8 """The 8-bit unsigned integer, alias for :class:`UnsignedInteger8`.""" class Time(SimpleModel): """Just that, Time. No time zone support. Native type is :class:`datetime.time`. """ __type_name__ = 'time' class Attributes(SimpleModel.Attributes): """Customizable attributes of the :class:`spyne.model.primitive.Time` type.""" gt = datetime.time(0, 0, 0, 0) # minExclusive """The time should be greater than this time.""" ge = datetime.time(0, 0, 0, 0) # minInclusive """The time should be greater than or equal to this time.""" lt = datetime.time(23, 59, 59, 999999) # maxExclusive """The time should be lower than this time.""" le = datetime.time(23, 59, 59, 999999) # maxInclusive """The time should be lower than or equal to this time.""" pattern = None """A regular expression that matches the whole time. See here for more info: http://www.regular-expressions.info/xml.html""" @staticmethod def is_default(cls): return ( SimpleModel.is_default(cls) and cls.Attributes.gt == Time.Attributes.gt and cls.Attributes.ge == Time.Attributes.ge and cls.Attributes.lt == Time.Attributes.lt and cls.Attributes.le == Time.Attributes.le and cls.Attributes.pattern == Time.Attributes.pattern ) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) and ( value is None or ( value > cls.Attributes.gt and value >= cls.Attributes.ge and value < cls.Attributes.lt and value <= cls.Attributes.le )) class DateTime(SimpleModel): """A compact way to represent dates and times together. Supports time zones. Working with timezones is a bit quirky -- Spyne works very hard to have all datetimes with time zones internally and only strips them when explicitly requested with ``timezone=False``\. See :attr:`DateTime.Attributes.as_timezone` for more information. Native type is :class:`datetime.datetime`. """ __type_name__ = 'dateTime' _local_re = re.compile(DATETIME_PATTERN) _utc_re = re.compile(DATETIME_PATTERN + 'Z') _offset_re = re.compile(DATETIME_PATTERN + OFFSET_PATTERN) class Attributes(SimpleModel.Attributes): """Customizable attributes of the :class:`spyne.model.primitive.DateTime` type.""" gt = datetime.datetime(datetime.MINYEAR, 1, 1, 0, 0, 0, 0, spyne.LOCAL_TZ) # minExclusive """The datetime should be greater than this datetime. It must always have a timezone.""" ge = datetime.datetime(datetime.MINYEAR, 1, 1, 0, 0, 0, 0, spyne.LOCAL_TZ) # minInclusive """The datetime should be greater than or equal to this datetime. It must always have a timezone.""" lt = datetime.datetime(datetime.MAXYEAR, 12, 31, 23, 59, 59, 999999, spyne.LOCAL_TZ) # maxExclusive """The datetime should be lower than this datetime. It must always have a timezone.""" le = datetime.datetime(datetime.MAXYEAR, 12, 31, 23, 59, 59, 999999, spyne.LOCAL_TZ) # maxInclusive """The datetime should be lower than or equal to this datetime. It must always have a timezone.""" pattern = None """A regular expression that matches the whole datetime. See here for more info: http://www.regular-expressions.info/xml.html""" format = None """DateTime format fed to the ``strftime`` function. See: http://docs.python.org/library/datetime.html?highlight=strftime#strftime-strptime-behavior Ignored by protocols like SOAP which have their own ideas about how DateTime objects should be serialized.""" string_format = None """A regular python string formatting string. %s will contain the date string. See here for more info: http://docs.python.org/library/stdtypes.html#string-formatting""" as_timezone = None """When not None, converts: - Outgoing values to the given time zone (by calling ``.astimezone()``). - Incoming values without tzinfo to the given time zone by calling ``.replace(tzinfo=)`` and values with tzinfo to the given timezone by calling ``.astimezone()``. Either None or a return value of pytz.timezone() When this is None and a datetime with tzinfo=None comes in, it's converted to spyne.LOCAL_TZ which defaults to ``pytz.utc``. You can use `tzlocal `_ to set it to local time right after ``import spyne``. """ timezone = True """If False, time zone info is stripped before serialization. Also makes sqlalchemy schema generator emit 'timestamp without timezone'.""" serialize_as = None """One of (None, 'sec', 'sec_float', 'msec', 'msec_float', 'usec')""" @staticmethod def is_default(cls): return ( SimpleModel.is_default(cls) and cls.Attributes.gt == DateTime.Attributes.gt and cls.Attributes.ge == DateTime.Attributes.ge and cls.Attributes.lt == DateTime.Attributes.lt and cls.Attributes.le == DateTime.Attributes.le and cls.Attributes.pattern == DateTime.Attributes.pattern ) @staticmethod def validate_native(cls, value): if isinstance(value, datetime.datetime) and value.tzinfo is None: value = value.replace(tzinfo=spyne.LOCAL_TZ) return SimpleModel.validate_native(cls, value) and ( value is None or ( value > cls.Attributes.gt and value >= cls.Attributes.ge and value < cls.Attributes.lt and value <= cls.Attributes.le )) class Date(DateTime): """Just that, Date. No time zone support. Native type is :class:`datetime.date`. """ __type_name__ = 'date' _offset_re = re.compile(DATE_PATTERN + '(' + OFFSET_PATTERN + '|Z)') class Attributes(DateTime.Attributes): """Customizable attributes of the :class:`spyne.model.primitive.Date` type.""" gt = datetime.date(1, 1, 1) # minExclusive """The date should be greater than this date.""" ge = datetime.date(1, 1, 1) # minInclusive """The date should be greater than or equal to this date.""" lt = datetime.date(datetime.MAXYEAR, 12, 31) # maxExclusive """The date should be lower than this date.""" le = datetime.date(datetime.MAXYEAR, 12, 31) # maxInclusive """The date should be lower than or equal to this date.""" format = '%Y-%m-%d' """DateTime format fed to the ``strftime`` function. See: http://docs.python.org/library/datetime.html?highlight=strftime#strftime-strptime-behavior Ignored by protocols like SOAP which have their own ideas about how Date objects should be serialized.""" pattern = None """A regular expression that matches the whole date. See here for more info: http://www.regular-expressions.info/xml.html""" @staticmethod def is_default(cls): return ( SimpleModel.is_default(cls) and cls.Attributes.gt == Date.Attributes.gt and cls.Attributes.ge == Date.Attributes.ge and cls.Attributes.lt == Date.Attributes.lt and cls.Attributes.le == Date.Attributes.le and cls.Attributes.pattern == Date.Attributes.pattern ) # this object tries to follow ISO 8601 standard. class Duration(SimpleModel): """Native type is :class:`datetime.timedelta`.""" __type_name__ = 'duration' class Boolean(SimpleModel): """Life is simple here. Just true or false.""" __type_name__ = 'boolean' def _uuid_validate_string(cls, value): return ( SimpleModel.validate_string(cls, value) and (value is None or ( cls.Attributes.min_len <= len(value) <= cls.Attributes.max_len and _re_match_with_span(cls.Attributes, value) ))) def _Tuuid_validate(key): from uuid import UUID def _uvalid(cls, v): try: UUID(**{key:v}) except ValueError: return False return True return _uvalid _uuid_validate = { None: _uuid_validate_string, 'hex': _Tuuid_validate('hex'), 'urn': _Tuuid_validate('urn'), 'bytes': _Tuuid_validate('bytes'), 'bytes_le': _Tuuid_validate('bytes_le'), 'fields': _Tuuid_validate('fields'), 'int': _Tuuid_validate('int'), } class Uuid(Unicode(pattern=UUID_PATTERN)): """Unicode subclass for Universially-Unique Identifiers.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'uuid' class Attributes(Unicode(pattern=UUID_PATTERN).Attributes): serialize_as = None @staticmethod def validate_string(cls, value): return _uuid_validate[cls.Attributes.serialize_as](cls, value) class NormalizedString(Unicode): __type_name__ = 'normalizedString' __extends__ = Unicode class Attributes(Unicode.Attributes): white_space = "replace" class Token(NormalizedString): __type_name__ = 'token' class Attributes(Unicode.Attributes): white_space = "collapse" class Name(Token): __type_name__ = 'Name' class Attributes(Unicode.Attributes): # Original: '[\i-[:]][\c-[:]]*' # See: http://www.regular-expressions.info/xmlcharclass.html pattern = '[[_:A-Za-z]-[:]][[-._:A-Za-z0-9]-[:]]*' class NCName(Name): __type_name__ = 'NCName' class ID(NCName): __type_name__ = 'ID' class Language(Token): __type_name__ = 'language' class Attributes(Unicode.Attributes): pattern = '[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*' class Point(Unicode): """A point type whose native format is a WKT string. You can use :func:`shapely.wkt.loads` to get a proper point type. It's a subclass of the :class:`Unicode` type, so regular Unicode constraints apply. The only additional parameter is the number of dimensions. :param dim: Number of dimensons. """ __type_name__ = None class Attributes(Unicode.Attributes): dim = None @staticmethod def Value(x, y, prec=15): return ('POINT(%%3.%(prec)sf %%3.%(prec)sf)' % {'prec': prec}) % (x,y) def __new__(cls, dim=None, **kwargs): assert dim in (None,2,3) if dim is not None: kwargs['dim'] = dim kwargs['pattern'] = _get_point_pattern(dim) kwargs['type_name'] = 'point%dd' % dim retval = SimpleModel.__new__(cls, **kwargs) retval.__namespace__ = 'http://spyne.io/schema' retval.__extends__ = Unicode retval.__orig__ = Unicode return retval class Line(Unicode): """A line type whose native format is a WKT string. You can use :func:`shapely.wkt.loads` to get a proper line type. It's a subclass of the :class:`Unicode` type, so regular Unicode constraints apply. The only additional parameter is the number of dimensions. :param dim: Number of dimensons. """ __type_name__ = None class Attributes(Unicode.Attributes): dim = None def __new__(cls, dim=None, **kwargs): assert dim in (None,2,3) if dim is not None: kwargs['dim'] = dim kwargs['pattern'] = _get_linestring_pattern(dim) kwargs['type_name'] = 'line%dd' % dim retval = SimpleModel.__new__(cls, **kwargs) retval.__namespace__ = 'http://spyne.io/schema' retval.__extends__ = Unicode retval.__orig__ = Unicode return retval LineString = Line class Polygon(Unicode): """A polygon type whose native format is a WKT string. You can use :func:`shapely.wkt.loads` to get a proper polygon type. It's a subclass of the :class:`Unicode` type, so regular Unicode constraints apply. The only additional parameter is the number of dimensions. :param dim: Number of dimensons. """ __type_name__ = None class Attributes(Unicode.Attributes): dim = None def __new__(cls, dim=None, **kwargs): assert dim in (None,2,3) if dim is not None: kwargs['dim'] = dim kwargs['pattern'] = _get_polygon_pattern(dim) kwargs['type_name'] = 'polygon%dd' % dim retval = SimpleModel.__new__(cls, **kwargs) retval.__namespace__ = 'http://spyne.io/schema' retval.__extends__ = Unicode retval.__orig__ = Unicode return retval class MultiPoint(Unicode): """A MultiPoint type whose native format is a WKT string. You can use :func:`shapely.wkt.loads` to get a proper MultiPoint type. It's a subclass of the :class:`Unicode` type, so regular Unicode constraints apply. The only additional parameter is the number of dimensions. :param dim: Number of dimensons. """ __type_name__ = None class Attributes(Unicode.Attributes): dim = None def __new__(cls, dim=None, **kwargs): assert dim in (None,2,3) if dim is not None: kwargs['dim'] = dim kwargs['pattern'] = _get_multipoint_pattern(dim) kwargs['type_name'] = 'multiPoint%dd' % dim retval = SimpleModel.__new__(cls, **kwargs) retval.__namespace__ = 'http://spyne.io/schema' retval.__extends__ = Unicode retval.__orig__ = Unicode return retval class MultiLine(Unicode): """A MultiLine type whose native format is a WKT string. You can use :func:`shapely.wkt.loads` to get a proper MultiLine type. It's a subclass of the :class:`Unicode` type, so regular Unicode constraints apply. The only additional parameter is the number of dimensions. :param dim: Number of dimensons. """ __type_name__ = None class Attributes(Unicode.Attributes): dim = None def __new__(cls, dim=None, **kwargs): assert dim in (None,2,3) if dim is not None: kwargs['dim'] = dim kwargs['pattern'] = _get_multilinestring_pattern(dim) kwargs['type_name'] = 'multiLine%dd' % dim retval = SimpleModel.__new__(cls, **kwargs) retval.__namespace__ = 'http://spyne.io/schema' retval.__extends__ = Unicode retval.__orig__ = Unicode return retval MultiLineString = MultiLine class MultiPolygon(Unicode): """A MultiPolygon type whose native format is a WKT string. You can use :func:`shapely.wkt.loads` to get a proper MultiPolygon type. It's a subclass of the :class:`Unicode` type, so regular Unicode constraints apply. The only additional parameter is the number of dimensions. :param dim: Number of dimensons. """ __type_name__ = None class Attributes(Unicode.Attributes): dim = None def __new__(cls, dim=None, **kwargs): assert dim in (None,2,3) if dim is not None: kwargs['dim'] = dim kwargs['pattern'] = _get_multipolygon_pattern(dim) kwargs['type_name'] = 'multipolygon%dd' % dim retval = SimpleModel.__new__(cls, **kwargs) retval.__namespace__ = 'http://spyne.io/schema' retval.__extends__ = Unicode retval.__orig__ = Unicode return retval # This class is DEPRECATED. Use the spyne.model.Mandatory like this: # >>> from spyne.model import Mandatory as M, Unicode # >>> MandatoryEmail = M(Unicode(pattern='[^@]+@[^@]+')) class Mandatory: Unicode = Unicode(type_name="MandatoryString", min_occurs=1, nillable=False, min_len=1) String = String(type_name="MandatoryString", min_occurs=1, nillable=False, min_len=1) AnyXml = AnyXml(type_name="MandatoryXml", min_occurs=1, nillable=False) AnyDict = AnyDict(type_name="MandatoryDict", min_occurs=1, nillable=False) AnyUri = AnyUri(type_name="MandatoryUri", min_occurs=1, nillable=False, min_len=1) ImageUri = ImageUri(type_name="MandatoryImageUri", min_occurs=1, nillable=False, min_len=1) Boolean = Boolean(type_name="MandatoryBoolean", min_occurs=1, nillable=False) Date = Date(type_name="MandatoryDate", min_occurs=1, nillable=False) Time = Time(type_name="MandatoryTime", min_occurs=1, nillable=False) DateTime = DateTime(type_name="MandatoryDateTime", min_occurs=1, nillable=False) Duration = Duration(type_name="MandatoryDuration", min_occurs=1, nillable=False) Decimal = Decimal(type_name="MandatoryDecimal", min_occurs=1, nillable=False) Double = Double(type_name="MandatoryDouble", min_occurs=1, nillable=False) Float = Float(type_name="MandatoryFloat", min_occurs=1, nillable=False) Integer = Integer(type_name="MandatoryInteger", min_occurs=1, nillable=False) Integer64 = Integer64(type_name="MandatoryLong", min_occurs=1, nillable=False) Integer32 = Integer32(type_name="MandatoryInt", min_occurs=1, nillable=False) Integer16 = Integer16(type_name="MandatoryShort", min_occurs=1, nillable=False) Integer8 = Integer8(type_name="MandatoryByte", min_occurs=1, nillable=False) Long = Integer64 Int = Integer32 Short = Integer16 Byte = Integer8 UnsignedInteger = UnsignedInteger(type_name="MandatoryUnsignedInteger", min_occurs=1, nillable=False) UnsignedInteger64 = UnsignedInteger64(type_name="MandatoryUnsignedLong", min_occurs=1, nillable=False) UnsignedInteger32 = UnsignedInteger32(type_name="MandatoryUnsignedInt", min_occurs=1, nillable=False) UnsignedInteger16 = UnsignedInteger16(type_name="MandatoryUnsignedShort", min_occurs=1, nillable=False) UnsignedInteger8 = UnsignedInteger8(type_name="MandatoryUnsignedByte", min_occurs=1, nillable=False) UnsignedLong = UnsignedInteger64 UnsignedInt = UnsignedInteger32 UnsignedShort = UnsignedInteger16 UnsignedByte = UnsignedInteger8 Uuid = Uuid(type_name="MandatoryUuid", min_len=1, min_occurs=1, nillable=False) Point = Point(type_name="Point", min_len=1, min_occurs=1, nillable=False) Line = Line(type_name="LineString", min_len=1, min_occurs=1, nillable=False) LineString = Line Polygon = Polygon(type_name="Polygon", min_len=1, min_occurs=1, nillable=False) MultiPoint = MultiPoint(type_name="MandatoryMultiPoint", min_len=1, min_occurs=1, nillable=False) MultiLine = MultiLine(type_name="MandatoryMultiLineString", min_len=1, min_occurs=1, nillable=False) MultiLineString = MultiLine MultiPolygon = MultiPolygon(type_name="MandatoryMultiPolygon", min_len=1, min_occurs=1, nillable=False) NATIVE_MAP = { float: Double, bool: Boolean, datetime.datetime: DateTime, datetime.time: Time, datetime.date: Date, datetime.timedelta: Duration, decimal.Decimal: Decimal, uuid.UUID: Uuid, } from spyne.util import six if six.PY3: NATIVE_MAP.update({ str: Unicode, int: Integer, }) else: NATIVE_MAP.update({ str: String, unicode: Unicode, long: Integer, }) if isinstance (0x80000000, long): # 32-bit architecture NATIVE_MAP[int] = Integer32 else: # not 32-bit (so most probably 64-bit) architecture NATIVE_MAP[int] = Integer64 assert Mandatory.Long == Mandatory.Integer64 spyne-2.11.0/spyne/model/table.py0000644000175000001440000001662312345433230016612 0ustar plqusers00000000000000 # # 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 # """This module is DEPRECATED. Create your own TableModel using :func:`spyne.model.complex.TTableModel` Here's an example way of using the :class:`spyne.model.table.TableModel`: :: class User(TableModel, DeclarativeBase): __namespace__ = 'spyne.examples.user_manager' __tablename__ = 'spyne_user' user_id = Column(sqlalchemy.Integer, primary_key=True) user_name = Column(sqlalchemy.String(256)) first_name = Column(sqlalchemy.String(256)) last_name = Column(sqlalchemy.String(256)) Defined this way, SQLAlchemy objects are regular Spyne objects that can be used anywhere the regular Spyne types go. The definition for the `User` object is quite similar to vanilla SQLAlchemy declarative syntax, save for two elements: #. The object also bases on :class:`spyne.model.table.TableModel`, which bridges SQLAlchemy and Spyne types. #. It has a namespace declaration, which is just so the service looks good on wsdl. The SQLAlchemy integration is far from perfect at the moment: * SQL constraints are not reflected to the interface document. * It's not possible to define additional constraints for the Spyne schema. * Object attributes defined by mechanisms other than Column and limited uses of `relationship` (no string arguments) are not supported. If you need any of the above features, you need to separate the Spyne and SQLAlchemy object definitions. Spyne makes it easy (to an extent) with the following syntax: :: class AlternativeUser(TableModel, DeclarativeBase): __namespace__ = 'spyne.examples.user_manager' __table__ = User.__table__ Here, The AlternativeUser object is automatically populated using columns from the table definition. """ import logging logger = logging.getLogger(__name__) import sqlalchemy from spyne.util.six import add_metaclass from sqlalchemy import Column from sqlalchemy.orm import RelationshipProperty from sqlalchemy.ext.declarative import DeclarativeMeta from sqlalchemy.dialects.postgresql import UUID from spyne.model import primitive from spyne.model import binary from spyne.model import complex from spyne.model.complex import Array from spyne.model.complex import TypeInfo from spyne.model.complex import ComplexModelBase from spyne.model.complex import ComplexModelMeta _type_map = { sqlalchemy.Text: primitive.String, sqlalchemy.String: primitive.String, sqlalchemy.Unicode: primitive.String, sqlalchemy.UnicodeText: primitive.String, sqlalchemy.Float: primitive.Float, sqlalchemy.Numeric: primitive.Decimal, sqlalchemy.BigInteger: primitive.Integer, sqlalchemy.Integer: primitive.Integer, sqlalchemy.SmallInteger: primitive.Integer, sqlalchemy.Binary: binary.ByteArray, sqlalchemy.LargeBinary: binary.ByteArray, sqlalchemy.Boolean: primitive.Boolean, sqlalchemy.DateTime: primitive.DateTime, sqlalchemy.Date: primitive.Date, sqlalchemy.Time: primitive.Time, sqlalchemy.orm.relation: complex.Array, UUID: primitive.String(pattern="%(x)s{8}-" "%(x)s{4}-" "%(x)s{4}-" "%(x)s{4}-" "%(x)s{12}" % {'x': '[a-fA-F0-9]'}, name='uuid') } def _process_item(v): """This function maps sqlalchemy types to spyne types.""" rpc_type = None if isinstance(v, Column): if isinstance(v.type, sqlalchemy.Enum): if v.type.convert_unicode: rpc_type = primitive.Unicode(values=v.type.enums) else: rpc_type = primitive.String(values=v.type.enums) elif v.type in _type_map: rpc_type = _type_map[v.type] elif type(v.type) in _type_map: rpc_type = _type_map[type(v.type)] else: raise Exception("soap_type was not found. maybe _type_map needs a " "new entry. %r" % v) elif isinstance(v, RelationshipProperty): v.enable_typechecks = False # FIXME: Distinguish between *ToMany and *ToOne relationship. # rpc_type = v.argument rpc_type = Array(v.argument) return rpc_type def _is_interesting(k, v): if k.startswith('__'): return False if isinstance(v, Column): return True if isinstance(v, RelationshipProperty): if getattr(v.argument, '_type_info', None) is None: logger.warning("the argument to relationship should be a reference " "to the real column, not a string.") return False else: return True class TableModelMeta(DeclarativeMeta, ComplexModelMeta): """This class uses the information in class definition dictionary to build the _type_info dictionary that spyne relies on. It otherwise leaves SQLAlchemy and its information alone. """ def __new__(cls, cls_name, cls_bases, cls_dict): if cls_dict.get("__type_name__", None) is None: cls_dict["__type_name__"] = cls_name if cls_dict.get("_type_info", None) is None: cls_dict["_type_info"] = _type_info = TypeInfo() def check_mixin_inheritance(bases): for b in bases: check_mixin_inheritance(b.__bases__) for k, v in vars(b).items(): if _is_interesting(k, v): _type_info[k] = _process_item(v) check_mixin_inheritance(cls_bases) def check_same_table_inheritance(bases): for b in bases: check_same_table_inheritance(b.__bases__) table = getattr(b, '__table__', None) if not (table is None): for c in table.c: _type_info[c.name] = _process_item(c) check_same_table_inheritance(cls_bases) # include from table table = cls_dict.get('__table__', None) if not (table is None): for c in table.c: _type_info[c.name] = _process_item(c) # own attributes for k, v in cls_dict.items(): if _is_interesting(k, v): _type_info[k] = _process_item(v) return super(TableModelMeta, cls).__new__(cls, cls_name, cls_bases, cls_dict) @add_metaclass(TableModelMeta) class TableModel(ComplexModelBase): """The main base class for complex types shared by both SQLAlchemy and spyne. Classes that inherit from this class should also inherit from an sqlalchemy.declarative base class. See the :ref:`manual-sqlalchemy` section for more info. """ _decl_class_registry = {} spyne-2.11.0/spyne/protocol/0000755000175000001440000000000012352131452015701 5ustar plqusers00000000000000spyne-2.11.0/spyne/protocol/cloth/0000755000175000001440000000000012352131452017012 5ustar plqusers00000000000000spyne-2.11.0/spyne/protocol/cloth/__init__.py0000644000175000001440000000202112345433230021117 0ustar plqusers00000000000000# encoding: utf8 # # 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 # """The ``spyne.protocol.cloth`` package contains an EXPERIMENTAL protocol for clothing otherwise boring data. """ from spyne.protocol.cloth._base import XmlCloth # huge hack to have the last line of microformat.py execute import spyne.protocol.html spyne-2.11.0/spyne/protocol/cloth/_base.py0000644000175000001440000001447612345433230020452 0ustar plqusers00000000000000# encoding: utf8 # # 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 # from __future__ import print_function import logging logger = logging.getLogger(__name__) from inspect import isgenerator from lxml import etree from lxml.etree import LxmlSyntaxError from lxml.builder import E from spyne import BODY_STYLE_WRAPPED from spyne.util import Break, coroutine from spyne.protocol.cloth.to_parent import ToParentMixin from spyne.protocol.cloth.to_cloth import ToClothMixin from spyne.util.six import StringIO, string_types class XmlCloth(ToParentMixin, ToClothMixin): mime_type = 'text/xml' HtmlMicroFormat = None def __init__(self, app=None, mime_type=None, ignore_uncap=False, ignore_wrappers=False, cloth=None, attr_name='spyne_id', root_attr_name='spyne', cloth_parser=None): super(XmlCloth, self).__init__(app=app, mime_type=mime_type, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers) self._init_cloth(cloth, attr_name, root_attr_name, cloth_parser) @staticmethod def translate(cls, locale, default): """ :param cls: class :param locale: locale string :param default: default string if no translation found :returns: translated string """ if locale is None: locale = 'en_US' if cls.Attributes.translations is not None: return cls.Attributes.translations.get(locale, default) return default def serialize(self, ctx, message): """Uses ``ctx.out_object``, ``ctx.out_header`` or ``ctx.out_error`` to set ``ctx.out_body_doc``, ``ctx.out_header_doc`` and ``ctx.out_document`` as an ``lxml.etree._Element instance``. Not meant to be overridden. """ assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_serialize', ctx) if ctx.out_stream is None: ctx.out_stream = StringIO() print(ctx.out_stream, id(ctx.out_stream)) if ctx.out_error is not None: # All errors at this point must be Fault subclasses. inst = ctx.out_error cls = inst.__class__ name = cls.get_type_name() ctx.out_document = E.div() with etree.xmlfile(ctx.out_stream) as xf: # as XmlDocument is not push-ready yet, this is what we do. # this is a huge hack, bear with me. retval = XmlCloth.HtmlMicroFormat() \ .to_parent(ctx, cls, inst, xf, name) else: assert message is self.RESPONSE result_message_class = ctx.descriptor.out_message name = result_message_class.get_type_name() if ctx.descriptor.body_style == BODY_STYLE_WRAPPED: if self.ignore_wrappers: result_message = ctx.out_object[0] while result_message_class.Attributes._wrapper and \ len(result_message_class._type_info) == 1: result_message_class, = \ result_message_class._type_info.values() else: result_message = result_message_class() for i, attr_name in enumerate( result_message_class._type_info.keys()): setattr(result_message, attr_name, ctx.out_object[i]) else: result_message, = ctx.out_object retval = self.incgen(ctx, result_message_class, result_message, name) self.event_manager.fire_event('after_serialize', ctx) return retval def create_out_string(self, ctx, charset=None): """Sets an iterable of string fragments to ctx.out_string""" if isinstance(ctx.out_stream, StringIO): ctx.out_string = [ctx.out_stream.getvalue()] @coroutine def incgen(self, ctx, cls, inst, name): if name is None: name = cls.get_type_name() try: with etree.xmlfile(ctx.out_stream) as xf: ret = self.subserialize(ctx, cls, inst, xf, name) if isgenerator(ret): # Poor man's yield from try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass except LxmlSyntaxError as e: if e.msg == 'no content written': pass else: raise def subserialize(self, ctx, cls, inst, parent, name=None, **kwargs): if name is None: name = cls.get_type_name() if self._root_cloth is not None: print("to root cloth") return self.to_root_cloth(ctx, cls, inst, self._root_cloth, parent, name, **kwargs) if self._cloth is not None: print("to parent cloth") return self.to_parent_cloth(ctx, cls, inst, self._cloth, parent, name, **kwargs) print("to parent") return self.to_parent(ctx, cls, inst, parent, name, **kwargs) def decompose_incoming_envelope(self, ctx, message): raise NotImplementedError("This is an output-only protocol.") spyne-2.11.0/spyne/protocol/cloth/to_cloth.py0000644000175000001440000003241012352126507021204 0ustar plqusers00000000000000# encoding: utf8 # # 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 # from __future__ import print_function import logging logger = logging.getLogger(__name__) from lxml import html, etree from copy import deepcopy from inspect import isgenerator from collections import deque from spyne.util import Break, coroutine from spyne.util.six import string_types from spyne.model import Array, AnyXml, AnyHtml, ModelBase, ComplexModelBase, \ PushBase from spyne.protocol import ProtocolBase from spyne.util.cdict import cdict _prevsibs = lambda elt: list(elt.itersiblings(preceding=True)) _revancestors = lambda elt: list(reversed(list(elt.iterancestors()))) class ToClothMixin(ProtocolBase): def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, ignore_wrappers=False): super(ToClothMixin, self).__init__(app=app, validator=validator, mime_type=mime_type, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers) self.rendering_handlers = cdict({ ModelBase: self.model_base_to_cloth, AnyXml: self.element_to_cloth, AnyHtml: self.element_to_cloth, ComplexModelBase: self.complex_to_cloth, }) def _init_cloth(self, cloth, attr_name, root_attr_name, cloth_parser): """Called from XmlCloth.__init__ in order to not break the dunder init signature consistency""" self.attr_name = attr_name self.root_attr_name = root_attr_name self._mrpc_cloth = self._root_cloth = None self._cloth = cloth if isinstance(self._cloth, string_types): if cloth_parser is None: cloth_parser = etree.XMLParser(remove_comments=True) self._cloth = html.parse(cloth, parser=cloth_parser) self._cloth = self._cloth.getroot() if self._cloth is not None: q = "//*[@%s]" % self.root_attr_name elts = self._cloth.xpath(q) if len(elts) > 0: self._root_cloth = elts[0] q = "//*[@%s]" % self.attr_name elts = self._cloth.xpath(q) if len(elts) == 0: self._cloth = None if self._cloth is None and self._root_cloth is None: raise Exception("Invalid cloth: It does not contain any " "element with '%s' or '%s' attribute defined." % (self.root_attr_name, self.attr_name)) if self._cloth is not None: self._mrpc_cloth = self._pop_elt(self._cloth, 'mrpc_entry') def _get_elts(self, elt, tag_id=None): if tag_id is None: return elt.xpath('//*[@%s]' % self.attr_name) return elt.xpath('//*[@%s="%s"]' % (self.attr_name, tag_id)) def _get_outmost_elts(self, tmpl, tag_id=None): ids = set() # we assume xpath() returns elements in top to bottom (or outside to # inside) order. for elt in self._get_elts(tmpl, tag_id): if elt is tmpl: continue if len(set((id(e) for e in elt.iterancestors())) & ids): continue if not id(elt) in ids: ids.add(id(elt)) yield elt def _pop_elt(self, elt, what): query = '//*[@%s="%s"]' % (self.attr_name, what) retval = elt.xpath(query) if len(retval) > 1: raise ValueError("more than one element found for query %r" % query) elif len(retval) == 1: retval = retval[0] retval.iterancestors().next().remove(retval) return retval def _get_clean_elt(self, elt, what): query = '//*[@%s="%s"]' % (self.attr_name, what) retval = elt.xpath(query) if len(retval) > 1: raise ValueError("more than one element found for query %r" % query) elif len(retval) == 1: retval = retval[0] del retval.attrib[self.attr_name] return retval def _get_elts_by_id(self, elt, what): print("id=%r" % what, "got", end='') retval = elt.xpath('//*[@id="%s"]' % what) print(retval) return retval @staticmethod def _methods(cls, inst): while cls.Attributes._wrapper and len(cls._type_info) > 0: cls, = cls._type_info.values() if cls.Attributes.methods is not None: for k, v in cls.Attributes.methods.items(): is_shown = True if v.when is not None: is_shown = v.when(inst) if is_shown: yield k, v def _actions_to_cloth(self, ctx, cls, inst, template): if self._mrpc_cloth is None: logger.warning("missing 'mrpc_template'") return for elt in self._get_elts(template, "mrpc"): for k, v in self._methods(cls, inst): href = v.in_message.get_type_name() text = v.translate(ctx.locale, v.in_message.get_type_name()) mrpc_template = deepcopy(self._mrpc_cloth) anchor = self._get_clean_elt(mrpc_template, 'mrpc_link') anchor.attrib['href'] = href text_elt = self._get_clean_elt(mrpc_template, 'mrpc_text') if text_elt is not None: text_elt.text = text else: anchor.text = text elt.append(mrpc_template) def complex_to_cloth(self, ctx, cls, inst, cloth, parent, name=None): for elt in self._get_elts(cloth, "mrpc"): self._actions_to_cloth(ctx, cls, inst, elt) fti = cls.get_flat_type_info(cls) for i, elt in enumerate(self._get_outmost_elts(cloth)): k = elt.attrib[self.attr_name] v = fti.get(k, None) if v is None: logger.warning("elt id %r not in %r", k, cls) continue # if cls is an array, inst should already be a sequence type # (eg list), so there's no point in doing a getattr -- we will # unwrap it and serialize it in the next round of to_cloth call. if issubclass(cls, Array): val = inst else: val = getattr(inst, k, None) self.to_cloth(ctx, v, val, elt, parent, name=k) @coroutine def array_to_cloth(self, ctx, cls, inst, cloth, parent, name=None, **kwargs): if isinstance(inst, PushBase): while True: sv = (yield) ret = self.to_cloth(ctx, cls, sv, cloth, parent, from_arr=True, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass else: for sv in inst: ret = self.to_cloth(ctx, cls, sv, cloth, parent, from_arr=True, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass def to_cloth(self, ctx, cls, inst, cloth, parent, name=None, from_arr=False, **kwargs): if cloth is None: return self.to_parent(ctx, cls, inst, parent, name, **kwargs) #if issubclass(inst.__class__, cls.__orig__ or cls): # cls = inst.__class__ if inst is None: inst = cls.Attributes.default if not from_arr and cls.Attributes.max_occurs > 1: return self.array_to_cloth(ctx, cls, inst, cloth, parent, name=name) self._enter_cloth(ctx, cloth, parent) subprot = getattr(cls.Attributes, 'prot', None) if subprot is not None and not (subprot is self): return subprot.subserialize(ctx, cls, inst, parent, name, **kwargs) retval = None if inst is None: if cls.Attributes.min_occurs > 0: parent.write(cloth) else: handler = self.rendering_handlers[cls] retval = handler(ctx, cls, inst, cloth, parent, name=name) return retval def model_base_to_cloth(self, ctx, cls, inst, cloth, parent, name): print(cls, inst) parent.write(self.to_string(cls, inst)) def element_to_cloth(self, ctx, cls, inst, cloth, parent, name): print(cls, inst) parent.write(inst) def _enter_cloth(self, ctx, cloth, parent): if cloth is self._cloth: print("entering", cloth.tag, "return same") return print("entering", cloth.tag) assert len(list(cloth.iterancestors())) > 0 stack = ctx.protocol.stack tags = ctx.protocol.tags # exit from prev cloth write to the first common ancestor anc = _revancestors(cloth) last_elt = None while anc[:len(stack)] != list([s for s, sc in stack]): elt, elt_ctx = ctx.protocol.stack.pop() elt_ctx.__exit__(None, None, None) last_elt = elt print("\texit ", elt.tag, "norm") for sibl in elt.itersiblings(): if sibl in anc: break print("\twrite", sibl.tag, "exit sibl") parent.write(sibl) deps = deque() sibls = _prevsibs(cloth) try: sibls = sibls[sibls.index(last_elt):] except ValueError: pass for sibl in sibls: if id(sibl) in tags: break deps.appendleft((False, sibl)) for elt in cloth.iterancestors(): if elt in list([s for s, sc in stack]): break deps.appendleft((True, elt)) for sibl in _prevsibs(elt): if id(sibl) in tags: break deps.appendleft((False, sibl)) # write parents with parent siblings print("\tdeps:") for p, tag in deps: print("\t\t", ("parent" if p else "sibling"), tag) for new, elt in deps: open_elts = [id(e) for e, e_ctx in stack] if id(elt) in open_elts: print("\tskip ", elt) else: if new: curtag = parent.element(elt.tag, elt.attrib) curtag.__enter__() print("\tenter", elt.tag, "norm") stack.append((elt, curtag)) else: parent.write(elt) print("\twrite", elt.tag, "norm") tags.add(id(elt)) # write the element itself attrib = dict([(k2, v2) for k2, v2 in cloth.attrib.items() if not (k2 in (self.attr_name, self.root_attr_name))]) curtag = parent.element(cloth.tag, attrib) curtag.__enter__() stack.append((cloth, curtag)) print("entering", cloth.tag, 'ok') def _close_cloth(self, ctx, parent): for elt, elt_ctx in reversed(ctx.protocol.stack): print("exit ", elt.tag, "close") elt_ctx.__exit__(None, None, None) for sibl in elt.itersiblings(): print("write", sibl.tag, "close sibl") parent.write(sibl) def to_parent_cloth(self, ctx, cls, inst, cloth, parent, name, from_arr=False, **kwargs): ctx.protocol.stack = deque() ctx.protocol.tags = set() self.to_cloth(ctx, cls, inst, cloth, parent) self._close_cloth(ctx, parent) @coroutine def to_root_cloth(self, ctx, cls, inst, cloth, parent, name=None): ctx.protocol.stack = deque() ctx.protocol.tags = set() self._enter_cloth(ctx, cloth, parent) ret = self.to_parent(ctx, cls, inst, parent, name) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except (Break, StopIteration, GeneratorExit): self._close_cloth(ctx, parent) spyne-2.11.0/spyne/protocol/cloth/to_parent.py0000644000175000001440000003327112345433230021366 0ustar plqusers00000000000000# encoding: utf8 # # 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 # from __future__ import print_function import logging logger = logging.getLogger(__name__) from inspect import isgenerator from lxml import etree, html from lxml.builder import E from spyne.const.xml_ns import xsi as NS_XSI, soap_env as NS_SOAP_ENV from spyne.model import PushBase, ComplexModelBase, AnyXml, Fault, AnyDict, \ AnyHtml, ModelBase, ByteArray, XmlData from spyne.model.enum import EnumBase from spyne.protocol import ProtocolBase from spyne.protocol.xml import SchemaValidationError from spyne.util import coroutine, Break from spyne.util.cdict import cdict from spyne.util.etreeconv import dict_to_etree # FIXME: Serialize xml attributes!!! from spyne.util.six import string_types class ToParentMixin(ProtocolBase): def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, ignore_wrappers=False): super(ToParentMixin, self).__init__(app=app, validator=validator, mime_type=mime_type, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers) self.serialization_handlers = cdict({ AnyXml: self.xml_to_parent, Fault: self.fault_to_parent, AnyDict: self.dict_to_parent, AnyHtml: self.html_to_parent, EnumBase: self.enum_to_parent, ModelBase: self.base_to_parent, ByteArray: self.byte_array_to_parent, ComplexModelBase: self.complex_to_parent, SchemaValidationError: self.schema_validation_error_to_parent, }) def to_parent(self, ctx, cls, inst, parent, name, **kwargs): #if issubclass(inst.__class__, cls.__orig__ or cls): # cls = inst.__class__ subprot = getattr(cls.Attributes, 'prot', None) if subprot is not None and not (subprot is self): return subprot.subserialize(ctx, cls, inst, parent, name, **kwargs) if inst is None: inst = cls.Attributes.default if inst is None: return self.null_to_parent(ctx, cls, inst, parent, name, **kwargs) if self.ignore_wrappers and issubclass(cls, ComplexModelBase): cls, inst = self.strip_wrappers(cls, inst) from_arr = kwargs.get('from_arr', False) if not from_arr and cls.Attributes.max_occurs > 1: return self.array_to_parent(ctx, cls, inst, parent, name, **kwargs) try: handler = self.serialization_handlers[cls] except KeyError: logger.error("%r is missing handler for %r", self, cls) raise return handler(ctx, cls, inst, parent, name, **kwargs) def model_base_to_parent(self, ctx, cls, inst, parent, name, **kwargs): parent.write(E(name, self.to_string(cls, inst))) @coroutine def complex_model_to_parent(self, ctx, cls, inst, parent, name, **kwargs): with parent.element(name): ret = self._get_members(ctx, cls, inst, parent, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass @coroutine def array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): name = cls.get_type_name() if isinstance(inst, PushBase): while True: sv = (yield) print(sv) ret = self.to_parent(ctx, cls, sv, parent, name, from_arr=True, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass else: for sv in inst: ret = self.to_parent(ctx, cls, sv, parent, name, from_arr=True, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass @coroutine def _get_members(self, ctx, cls, inst, parent, **kwargs): for k, v in cls.get_flat_type_info(cls).items(): print("_get_members", k, v) try: subvalue = getattr(inst, k, None) except: # to guard against e.g. SqlAlchemy throwing NoSuchColumnError subvalue = None sub_name = v.Attributes.sub_name if sub_name is None: sub_name = k # Don't include empty values for non-nillable optional attributes. if subvalue is not None or v.Attributes.min_occurs > 0: ret = self.to_parent(ctx, v, subvalue, parent, sub_name, **kwargs) if ret is not None: try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass def not_supported(self, cls, *args, **kwargs): if not self.ignore_uncap: raise NotImplementedError("Serializing %r not supported!" % cls) def anyhtml_to_parent(self, ctx, cls, inst, parent, name, **kwargs): parent.write(inst) def anyuri_to_parent(self, ctx, cls, inst, parent, name, **kwargs): assert name is not None href = getattr(inst, 'href', None) if href is None: # this is not a AnyUri.Value instance. href = inst text = getattr(cls.Attributes, 'text', name) content = None else: text = getattr(inst, 'text', None) if text is None: text = getattr(cls.Attributes, 'text', name) content = getattr(inst, 'content', None) if text is None: text = name retval = E.a(text) if href is not None: retval.attrib['href'] = href if content is not None: retval.append(content) parent.write(retval) def imageuri_to_parent(self, ctx, cls, inst, parent, name, **kwargs): # with ImageUri, content is ignored. href = getattr(inst, 'href', None) if href is None: # this is not a AnyUri.Value instance. href = inst text = getattr(cls.Attributes, 'text', None) else: text = getattr(inst, 'text', None) if text is None: text = getattr(cls.Attributes, 'text', None) retval = E.img(src=href) if text is not None: retval.attrib['alt'] = text parent.write(retval) def byte_array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): parent.write(E(name, self.to_string(cls, inst, self.default_binary_encoding))) def base_to_parent(self, ctx, cls, inst, parent, name, **kwargs): parent.write(E(name, self.to_string(cls, inst))) def null_to_parent(self, ctx, cls, inst, parent, name, **kwargs): parent.write(E(name, **{'{%s}nil' % NS_XSI: 'true'})) @coroutine def _write_members(self, ctx, cls, inst, parent): parent_cls = getattr(cls, '__extends__', None) if not (parent_cls is None): ret = self._write_members(ctx, parent_cls, inst, parent) if ret is not None: try: while True: sv2 = (yield) ret.send(sv2) except Break: try: ret.throw(Break()) except StopIteration: pass for k, v in cls._type_info.items(): try: # to guard against e.g. SqlAlchemy throwing NoSuchColumnError subvalue = getattr(inst, k, None) except: subvalue = None # This is a tight loop, so enable this only when necessary. # logger.debug("get %r(%r) from %r: %r" % (k, v, inst, subvalue)) if issubclass(v, XmlData): if subvalue is not None: parent.write(self.to_string(k.type, subvalue)) continue sub_ns = v.Attributes.sub_ns if sub_ns is None: sub_ns = cls.get_namespace() sub_name = v.Attributes.sub_name if sub_name is None: sub_name = k name = "{%s}%s" % (sub_ns, sub_name) if subvalue is not None or v.Attributes.min_occurs > 0: ret = self.to_parent(ctx, v, subvalue, parent, name) if ret is not None: try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass @coroutine def complex_to_parent(self, ctx, cls, inst, parent, name): inst = cls.get_serialization_instance(inst) # TODO: Put xml attributes as well in the below element() call. with parent.element(name): ret = self._write_members(ctx, cls, inst, parent) if ret is not None: try: while True: sv2 = (yield) # may throw Break ret.send(sv2) except Break: try: ret.throw(Break()) except StopIteration: pass @coroutine def fault_to_parent(self, ctx, cls, inst, parent, name): PREF_SOAP_ENV = ctx.app.interface.prefmap[NS_SOAP_ENV] tag_name = "{%s}Fault" % NS_SOAP_ENV with parent.element(tag_name): parent.write( E("faultcode", '%s:%s' % (PREF_SOAP_ENV, inst.faultcode)), E("faultstring", inst.faultstring), E("faultactor", inst.faultactor), ) if isinstance(etree._Element): parent.write(E.detail(inst.detail)) # add other nonstandard fault subelements with get_members_etree ret = self._write_members(ctx, cls, inst, parent) if ret is not None: try: while True: sv2 = (yield) # may throw Break ret.send(sv2) except Break: try: ret.throw(Break()) except StopIteration: pass @coroutine def schema_validation_error_to_parent(self, ctx, cls, inst, parent, ns): PREF_SOAP_ENV = ctx.app.interface.prefmap[NS_SOAP_ENV] tag_name = "{%s}Fault" % NS_SOAP_ENV with parent.element(tag_name): parent.write( E("faultcode", '%s:%s' % (PREF_SOAP_ENV, inst.faultcode)), # HACK: Does anyone know a better way of injecting raw xml entities? E("faultstring", html.fromstring(inst.faultstring).text), E("faultactor", inst.faultactor), ) if isinstance(etree._Element): parent.write(E.detail(inst.detail)) # add other nonstandard fault subelements with get_members_etree ret = self._write_members(ctx, cls, inst, parent) if ret is not None: try: while True: sv2 = (yield) # may throw Break ret.send(sv2) except Break: try: ret.throw(Break()) except StopIteration: pass def enum_to_parent(self, ctx, cls, inst, parent, ns, name='retval'): self.base_to_parent(ctx, cls, str(inst), parent, ns, name) def xml_to_parent(self, ctx, cls, inst, parent, ns, name): if isinstance(inst, string_types): inst = etree.fromstring(inst) parent.write(inst) def html_to_parent(self, ctx, cls, inst, parent, ns, name): if isinstance(inst, str) or isinstance(inst, unicode): inst = html.fromstring(inst) parent.write(inst) def dict_to_parent(self, ctx, cls, inst, parent, ns, name): elt = E(name) dict_to_etree(inst, elt) parent.write(elt) spyne-2.11.0/spyne/protocol/html/0000755000175000001440000000000012352131452016645 5ustar plqusers00000000000000spyne-2.11.0/spyne/protocol/html/__init__.py0000644000175000001440000000266712345433230020772 0ustar plqusers00000000000000# encoding: utf8 # # 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 # """ This package contains EXPERIMENTAL html output protocols. The API here is in constant change. You have been warned. """ from spyne.protocol.html._base import HtmlBase from spyne.protocol.html._base import E from spyne.protocol.html._base import NSMAP from spyne.protocol.html.table import HtmlColumnTable from spyne.protocol.html.table import HtmlRowTable from spyne.protocol.html.microformat import HtmlMicroFormat # FIXME: REMOVE ME def translate(cls, locale, default): retval = None if cls.Attributes.translations is not None: retval = cls.Attributes.translations.get(locale, None) if retval is None: return default return retval spyne-2.11.0/spyne/protocol/html/_base.py0000644000175000001440000000265212346140504020276 0ustar plqusers00000000000000# encoding: utf8 # # 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 # from lxml.builder import ElementMaker from spyne.protocol.cloth import XmlCloth NS_HTML = "http://www.w3.org/1999/xhtml" NSMAP = {None: NS_HTML} E = ElementMaker(namespace=NS_HTML) class HtmlBase(XmlCloth): mime_type = 'application/xhtml+xml' def __init__(self, *args, **kwargs): super(HtmlBase, self).__init__(*args, **kwargs) if self._cloth is not None: if not self._cloth.tag.endswith('html'): self._cloth = E.html(self._cloth) if self._cloth.tag != '{%s}html' % NS_HTML: for elt in self._cloth.xpath("//*"): elt.tag = "{%s}%s" % (NS_HTML, elt.tag) spyne-2.11.0/spyne/protocol/html/microformat.py0000644000175000001440000001505212345433230021545 0ustar plqusers00000000000000# encoding: utf8 # # 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 # from inspect import isgenerator from lxml.html.builder import E from spyne.model import Array, AnyHtml from spyne.model import ComplexModelBase from spyne.model import ByteArray from spyne.model import ModelBase from spyne.model import PushBase from spyne.model import ImageUri from spyne.model import AnyUri from spyne.model.binary import Attachment from spyne.protocol.html import HtmlBase from spyne.util import coroutine, Break from spyne.util.cdict import cdict class HtmlMicroFormat(HtmlBase): def __init__(self, app=None, ignore_uncap=False, ignore_wrappers=False, cloth=None, attr_name='spyne_id', root_attr_name='spyne', cloth_parser=None, root_tag='div', child_tag='div', field_name_attr='class', field_name_tag=None, field_name_class='field_name'): """Protocol that returns the response object according to the "html microformat" specification. See https://en.wikipedia.org/wiki/Microformats for more info. The simple flavour is like the XmlDocument protocol, but returns data in
or tags. :param app: A spyne.application.Application instance. :param validator: The validator to use. Ignored. :param root_tag: The type of the root tag that encapsulates the return data. :param child_tag: The type of the tag that encapsulates the fields of the returned object. :param field_name_attr: The name of the attribute that will contain the field names of the complex object children. """ super(HtmlMicroFormat, self).__init__(app=app, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers, cloth=cloth, attr_name=attr_name, root_attr_name=root_attr_name, cloth_parser=cloth_parser) assert root_tag in ('div', 'span') assert child_tag in ('div', 'span') assert field_name_attr in ('class', 'id') assert field_name_tag in (None, 'span', 'div') self.root_tag = root_tag self.child_tag = child_tag self.field_name_attr = field_name_attr self.field_name_tag = field_name_tag if field_name_tag is not None: self.field_name_tag = E(field_name_tag) self._field_name_class = field_name_class self.serialization_handlers = cdict({ ModelBase: self.model_base_to_parent, AnyUri: self.anyuri_to_parent, AnyHtml: self.anyhtml_to_parent, ImageUri: self.imageuri_to_parent, ByteArray: self.not_supported, Attachment: self.not_supported, ComplexModelBase: self.complex_model_to_parent, Array: self.array_to_parent, }) def model_base_to_parent(self, ctx, cls, inst, parent, name, **kwargs): retval = E(self.child_tag, **{self.field_name_attr: name}) data_str = self.to_string(cls, inst) if self.field_name_tag is not None: field_name = cls.Attributes.translations.get( name) field_name_tag = self.field_name_tag(field_name, **{'class':self._field_name_class}) field_name_tag.tail = data_str retval.append(field_name_tag) else: retval.text = data_str parent.write(retval) @coroutine def complex_model_to_parent(self, ctx, cls, inst, parent, name, **kwargs): attrs = {self.field_name_attr: name} with parent.element(self.root_tag, attrs): ret = self._get_members(ctx, cls, inst, parent, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass @coroutine def array_to_parent(self, ctx, cls, inst, parent, name, from_arr=False, **kwargs): attrs = {self.field_name_attr: name} if issubclass(cls, Array): cls, = cls._type_info.values() name = cls.get_type_name() with parent.element(self.root_tag, attrs): if isinstance(inst, PushBase): while True: sv = (yield) ret = self.to_parent(ctx, cls, sv, parent, name, from_arr=True, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass else: for sv in inst: ret = self.to_parent(ctx, cls, sv, parent, name, from_arr=True, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass def null_to_parent(self, ctx, cls, inst, parent, name, **kwargs): return [ E(self.child_tag, **{self.field_name_attr: name}) ] # yuck. from spyne.protocol.cloth import XmlCloth XmlCloth.HtmlMicroFormat = HtmlMicroFormat spyne-2.11.0/spyne/protocol/html/table.py0000644000175000001440000004251112345433230020312 0ustar plqusers00000000000000# encoding: utf8 # # 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 # from inspect import isgenerator from spyne.model import ModelBase, AnyHtml from spyne.model import ByteArray from spyne.model import ComplexModelBase from spyne.model import Array from spyne.model import AnyUri from spyne.model import ImageUri from spyne.model.binary import Attachment from spyne.protocol.html import HtmlBase from spyne.protocol.html import NSMAP from lxml.html.builder import E from spyne.util import coroutine, Break from spyne.util.cdict import cdict def HtmlTable(app=None, ignore_uncap=False, ignore_wrappers=True, produce_header=True, table_name_attr='class', field_name_attr='class', border=0, fields_as='columns', row_class=None, cell_class=None, header_cell_class=None): """Protocol that returns the response object as a html table. The simple flavour is like the HtmlMicroFormatprotocol, but returns data as a html table using the tag. :param app: A spyne.application.Application instance. :param produce_header: Boolean value to determine whether to show field names in the beginning of the table or not. Defaults to True. Set to False to skip headers. :param table_name_attr: The name of the attribute that will contain the response name of the complex object in the table tag. Set to None to disable. :param field_name_attr: The name of the attribute that will contain the field names of the complex object children for every table cell. Set to None to disable. :param fields_as: One of 'columns', 'rows'. :param row_class: value that goes inside the :param cell_class: value that goes inside the
:param header_cell_class: value that goes inside the "Fields as rows" returns one record per table in a table with two columns. "Fields as columns" returns one record per table row in a table that has as many columns as field names, just like a regular spreadsheet. """ if fields_as == 'columns': return HtmlColumnTable(app, ignore_uncap, ignore_wrappers, produce_header, table_name_attr, field_name_attr, border, row_class, cell_class, header_cell_class) elif fields_as == 'rows': return HtmlRowTable(app, ignore_uncap, ignore_wrappers, produce_header, table_name_attr, field_name_attr, border, row_class, cell_class, header_cell_class) else: raise ValueError(fields_as) class HtmlTableBase(HtmlBase): def __init__(self, app=None, ignore_uncap=False, ignore_wrappers=True, cloth=None, attr_name='spyne_id', root_attr_name='spyne', cloth_parser=None, produce_header=True, table_name_attr='class', field_name_attr='class', border=0, row_class=None, cell_class=None, header_cell_class=None): super(HtmlTableBase, self).__init__(app=app, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers, cloth=cloth, attr_name=attr_name, root_attr_name=root_attr_name, cloth_parser=cloth_parser) self.produce_header = produce_header self.table_name_attr = table_name_attr self.field_name_attr = field_name_attr self.border = border self.row_class = row_class self.cell_class = cell_class self.header_cell_class = header_cell_class if self.cell_class is not None and field_name_attr == 'class': raise Exception("Either 'cell_class' should be None or " "field_name_attr should be != 'class'") if self.header_cell_class is not None and field_name_attr == 'class': raise Exception("Either 'header_cell_class' should be None or " "field_name_attr should be != 'class'") def model_base_to_parent(self, ctx, cls, inst, parent, name, **kwargs): parent.write(self.to_string(cls, inst)) def null_to_parent(self, ctx, cls, inst, parent, name, **kwargs): pass class HtmlColumnTable(HtmlTableBase): def __init__(self, *args, **kwargs): super(HtmlColumnTable, self).__init__(*args, **kwargs) self.serialization_handlers = cdict({ ModelBase: self.model_base_to_parent, AnyUri: self.anyuri_to_parent, AnyHtml: self.anyhtml_to_parent, ImageUri: self.imageuri_to_parent, ByteArray: self.not_supported, Attachment: self.not_supported, ComplexModelBase: self.complex_model_to_parent, Array: self.array_to_parent, }) def model_base_to_parent(self, ctx, cls, inst, parent, name, from_arr=False, **kwargs): if from_arr: td_attrs = {} #if self.field_name_attr: # td_attrs[self.field_name_attr] = name parent.write(E.tr( E.td( self.to_string(cls, inst), **td_attrs ) )) else: parent.write(self.to_string(cls, inst)) @coroutine def _gen_row(self, ctx, cls, inst, parent, name, **kwargs): with parent.element('tr'): for k, v in cls.get_flat_type_info(cls).items(): # FIXME: To be fixed to work with prot_attrs and renamed to exc if getattr(v.Attributes, 'exc_html', False) == True: continue if getattr(v.Attributes, 'read', True) == False: continue try: sub_value = getattr(inst, k, None) except: # to guard against e.g. SQLAlchemy throwing NoSuchColumnError sub_value = None sub_name = v.Attributes.sub_name if sub_name is None: sub_name = k td_attrs = {} if self.field_name_attr is not None: td_attrs[self.field_name_attr] = sub_name with parent.element('td', td_attrs): ret = self.to_parent(ctx, v, sub_value, parent, sub_name, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass self.extend_data_row(ctx, cls, inst, parent, name, **kwargs) def _gen_header(self, ctx, cls, name, parent): with parent.element('thead'): with parent.element('tr'): th_attrs = {} if self.field_name_attr is not None: th_attrs[self.field_name_attr] = name # fti is none when the type inside Array is not a ComplexModel. if issubclass(cls, ComplexModelBase): fti = cls.get_flat_type_info(cls) if self.field_name_attr is None: for k, v in fti.items(): if getattr(v.Attributes, 'exc_html', None): continue header_name = self.translate(v, ctx.locale, k) parent.write(E.th(header_name, **th_attrs)) else: for k, v in fti.items(): if getattr(v.Attributes, 'exc_html', None): continue th_attrs[self.field_name_attr] = k header_name = self.translate(v, ctx.locale, k) parent.write(E.th(header_name, **th_attrs)) else: if self.field_name_attr is not None: th_attrs[self.field_name_attr] = name header_name = self.translate(cls, ctx.locale, name) parent.write(E.th(header_name, **th_attrs)) self.extend_header_row(ctx, cls, name, parent) @coroutine def _gen_table(self, ctx, cls, inst, parent, name, gen_rows, **kwargs): attrs = {} if self.table_name_attr is not None: attrs[self.table_name_attr] = cls.get_type_name() with parent.element('table', attrs, nsmap=NSMAP): if self.produce_header: self._gen_header(ctx, cls, name, parent) with parent.element('tbody'): ret = gen_rows(ctx, cls, inst, parent, name, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass ret = self.extend_table(ctx, cls, parent, name, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass def complex_model_to_parent(self, ctx, cls, inst, parent, name, from_arr=False, **kwargs): # If this is direct child of an array, table is already set up in the # array_to_parent. if from_arr: return self._gen_row(ctx, cls, inst, parent, name, **kwargs) else: return self._gen_table(ctx, cls, inst, parent, name, self._gen_row, **kwargs) def array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): return self._gen_table(ctx, cls, inst, parent, name, super(HtmlColumnTable, self).array_to_parent, **kwargs) def extend_table(self, ctx, cls, parent, name, **kwargs): pass def extend_data_row(self, ctx, cls, inst, parent, name, **kwargs): pass def extend_header_row(self, ctx, cls, parent, name, **kwargs): pass class HtmlRowTable(HtmlTableBase): def __init__(self, *args, **kwargs): super(HtmlRowTable, self).__init__(*args, **kwargs) self.serialization_handlers = cdict({ ModelBase: self.model_base_to_parent, AnyUri: self.anyuri_to_parent, ImageUri: self.imageuri_to_parent, ByteArray: self.not_supported, Attachment: self.not_supported, ComplexModelBase: self.complex_model_to_parent, Array: self.array_to_parent, }) def model_base_to_parent(self, ctx, cls, inst, parent, name, from_arr=False, **kwargs): if from_arr: td_attrs = {} if False and self.field_name_attr: td_attrs[self.field_name_attr] = name parent.write(E.tr(E.td(self.to_string(cls, inst), **td_attrs))) else: parent.write(self.to_string(cls, inst)) @coroutine def complex_model_to_parent(self, ctx, cls, inst, parent, name, from_arr=False, **kwargs): attrs = {} if self.table_name_attr is not None: attrs[self.table_name_attr] = cls.get_type_name() with parent.element('table', attrs, nsmap=NSMAP): with parent.element('tbody'): for k, v in cls.get_flat_type_info(cls).items(): try: sub_value = getattr(inst, k, None) except: # to guard against e.g. SQLAlchemy throwing NoSuchColumnError sub_value = None sub_name = v.Attributes.sub_name if sub_name is None: sub_name = k if sub_value is None and cls.Attributes.min_occurs == 0: self.null_to_parent(ctx, cls, sub_value, parent, sub_name, **kwargs) continue tr_attrs = {} if self.row_class is not None: tr_attrs['class'] = self.row_class with parent.element('tr', tr_attrs): th_attrs = {} if self.header_cell_class is not None: th_attrs['class'] = self.header_cell_class if self.field_name_attr is not None: th_attrs[self.field_name_attr] = sub_name if self.produce_header: parent.write(E.th( self.translate(v, ctx.locale, sub_name), **th_attrs )) td_attrs = {} if self.cell_class is not None: td_attrs['class'] = self.cell_class if self.field_name_attr is not None: td_attrs[self.field_name_attr] = sub_name with parent.element('td', td_attrs): ret = self.to_parent(ctx, v, sub_value, parent, sub_name, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass @coroutine def array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): with parent.element('div', nsmap=NSMAP): if issubclass(cls, ComplexModelBase): ret = super(HtmlRowTable, self).array_to_parent( ctx, cls, inst, parent, name, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass else: table_attrs = {} if self.table_name_attr: table_attrs = {self.table_name_attr: name} with parent.element('table', table_attrs, nsmap=NSMAP): tr_attrs = {} if self.row_class is not None: tr_attrs['class'] = self.row_class with parent.element('tr', tr_attrs): if self.produce_header: parent.write(E.th(self.translate(cls, ctx.locale, cls.get_type_name()))) td_attrs = {} if self.cell_class is not None: td_attrs['class'] = self.cell_class with parent.element('td', td_attrs): with parent.element('table'): ret = super(HtmlRowTable, self) \ .array_to_parent(ctx, cls, inst, parent, name, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass spyne-2.11.0/spyne/protocol/html/template.py0000644000175000001440000000624512345433230021042 0ustar plqusers00000000000000# encoding: utf8 # # 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 # # # This module contains DEPRECATED code. It can disappear at any moment now. # import logging logger = logging.getLogger(__name__) from lxml import html from spyne.util import six class HtmlPage(object): """An EXPERIMENTAL protocol-ish that parses and generates a template for a html file. >>> open('temp.html', 'w').write('
') >>> t = HtmlPage('temp.html') >>> t.some_div = "some_text" >>> from lxml import html >>> print html.tostring(t.html)
some_text
""" reserved = ('html', 'file_name') def __init__(self, file_name): self.__frozen = False self.__file_name = file_name self.__html = html.fromstring(open(file_name, 'r').read()) self.__ids = {} for elt in self.__html.xpath('//*[@id]'): key = elt.attrib['id'] if key in self.__ids: raise ValueError("Don't use duplicate values in id attributes " "of the tags in template documents. " "id=%r appears more than once." % key) if key in HtmlPage.reserved: raise ValueError("id attribute values %r are reserved." % HtmlPage.reserved) self.__ids[key] = elt s = "%r -> %r" % (key, elt) logger.debug(s) self.__frozen = True @property def file_name(self): return self.__file_name @property def html(self): return self.__html def __getattr__(self, key): try: return object.__getattr__(self, key) except AttributeError: try: return self.__ids[key] except KeyError: raise AttributeError(key) def __setattr__(self, key, value): if key.endswith('__frozen') or not self.__frozen: object.__setattr__(self, key, value) else: elt = self.__ids.get(key, None) if elt is None: raise AttributeError(key) # set it in. if isinstance(value, six.string_types): elt.text = value else: elt.addnext(value) parent = elt.getparent() parent.remove(elt) self.__ids[key] = value spyne-2.11.0/spyne/protocol/soap/0000755000175000001440000000000012352131452016643 5ustar plqusers00000000000000spyne-2.11.0/spyne/protocol/soap/__init__.py0000644000175000001440000000215612342627562020773 0ustar plqusers00000000000000 # # 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 # """The ``spyne.protocol.soap`` package contains an implementation of a subset of the Soap 1.1 standard and awaits for volunteers for implementing the brand new Soap 1.2 standard. Patches are welcome. """ from spyne.protocol.soap.soap11 import Soap11 from spyne.protocol.soap.soap11 import _from_soap from spyne.protocol.soap.soap11 import _parse_xml_string spyne-2.11.0/spyne/protocol/soap/mime.py0000644000175000001440000002341612345433230020153 0ustar plqusers00000000000000 # # 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 # """The ``spyne.protocol.soap.mime`` module contains additional logic for using optimized encodings for binary when encapsulating Soap 1.1 messages in Http. The functionality in this code is not well tested and is reported NOT TO WORK. Patches are welcome. """ import logging logger = logging.getLogger(__name__) from lxml import etree from base64 import b64encode try: from urllib import unquote except ImportError: # Python 3 from urllib.parse import unquote # import email data format related stuff from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication from email.encoders import encode_7or8bit from email import message_from_string from spyne.model.binary import Attachment from spyne.model.binary import ByteArray import spyne.const.xml_ns _ns_xop = spyne.const.xml_ns.xop _ns_soap_env = spyne.const.xml_ns.soap_env def _join_attachment(href_id, envelope, payload, prefix=True): '''Places the data from an attachment back into a SOAP message, replacing its xop:Include element or href. Returns a tuple of length 2 with the new message and the number of replacements made :param id: content-id or content-location of attachment :param prefix: Set this to true if id is content-id or false if it is content-location. It prefixes a "cid:" to the href value. :param envelope: soap envelope string to be operated on :param payload: attachment data ''' def replacing(parent, node, payload, numreplaces): if node.tag == '{%s}Include' % _ns_xop: attrib = node.attrib.get('href') if not attrib is None: if unquote(attrib) == href_id: parent.remove(node) parent.text = payload numreplaces += 1 else: for child in node: numreplaces = replacing(node, child, payload, numreplaces) return numreplaces # grab the XML element of the message in the SOAP body soaptree = etree.fromstring(envelope) soapbody = soaptree.find("{%s}Body" % _ns_soap_env) message = None for child in list(soapbody): if child.tag != "{%s}Fault" % _ns_soap_env: message = child break numreplaces = 0 idprefix = '' if prefix == True: idprefix = "cid:" href_id = "%s%s" % (idprefix, href_id, ) # Make replacement. for param in message: # Look for Include subelement. for sub in param: numreplaces = replacing(param, sub, payload, numreplaces) if numreplaces < 1: attrib = param.attrib.get('href') if not attrib is None: if unquote(attrib) == href_id: del(param.attrib['href']) param.text = payload numreplaces += 1 return (etree.tostring(soaptree), numreplaces) def collapse_swa(content_type, envelope): ''' Translates an SwA multipart/related message into an application/soap+xml message. Returns the 'appication/soap+xml' version of the given HTTP body. References: SwA http://www.w3.org/TR/SOAP-attachments XOP http://www.w3.org/TR/xop10/ MTOM http://www.w3.org/TR/soap12-mtom/ http://www.w3.org/Submission/soap11mtom10/ :param content_type: value of the Content-Type header field, parsed by cgi.parse_header() function :param envelope: body of the HTTP message, a soap envelope ''' # convert multipart messages back to pure SOAP mime_type = content_type[0] if 'multipart/related' not in mime_type: return envelope charset = content_type[1].get('charset', None) if charset is None: charset='ascii' # parse the body into an email.Message object msg_string = [ "MIME-Version: 1.0", "Content-Type: %s; charset=%s" % (mime_type, charset), "", ] msg_string.extend(envelope) msg = message_from_string('\r\n'.join(msg_string)) # our message soapmsg = None root = msg.get_param('start') # walk through sections, reconstructing pure SOAP for part in msg.walk(): # skip the multipart container section if part.get_content_maintype() == 'multipart': continue # detect main soap section if (part.get('Content-ID') and part.get('Content-ID') == root) or \ (root == None and part == msg.get_payload()[0]): soapmsg = part.get_payload() continue # binary packages cte = part.get("Content-Transfer-Encoding") payload = None if cte != 'base64': payload = b64encode(part.get_payload()) else: payload = part.get_payload() cid = part.get("Content-ID").strip("<>") cloc = part.get("Content-Location") numreplaces = None # Check for Content-ID and make replacement if cid: soapmsg, numreplaces = _join_attachment(cid, soapmsg, payload) # Check for Content-Location and make replacement if cloc and not cid and not numreplaces: soapmsg, numreplaces = _join_attachment(cloc, soapmsg, payload, False) return [soapmsg] def apply_mtom(headers, envelope, params, paramvals): '''Apply MTOM to a SOAP envelope, separating attachments into a MIME multipart message. Returns a tuple of length 2 with dictionary of headers and string of body that can be sent with HTTPConnection References: XOP http://www.w3.org/TR/xop10/ MTOM http://www.w3.org/TR/soap12-mtom/ http://www.w3.org/Submission/soap11mtom10/ :param headers Headers dictionary of the SOAP message that would originally be sent. :param envelope Iterable containing SOAP envelope string that would have originally been sent. :param params params attribute from the Message object used for the SOAP :param paramvals values of the params, passed to Message.to_parent ''' # grab the XML element of the message in the SOAP body envelope = ''.join(envelope) soaptree = etree.fromstring(envelope) soapbody = soaptree.find("{%s}Body" % _ns_soap_env) message = None for child in list(soapbody): if child.tag == ("{%s}Fault" % _ns_soap_env): return (headers, envelope) else: message = child break # Get additional parameters from original Content-Type ctarray = [] for n, v in headers.items(): if n.lower() == 'content-type': ctarray = v.split(';') break roottype = ctarray[0].strip() rootparams = {} for ctparam in ctarray[1:]: n, v = ctparam.strip().split('=') rootparams[n] = v.strip("\"'") # Set up initial MIME parts. mtompkg = MIMEMultipart('related', boundary='?//<><>spyne_MIME_boundary<>') rootpkg = MIMEApplication(envelope, 'xop+xml', encode_7or8bit) # Set up multipart headers. del(mtompkg['mime-version']) mtompkg.set_param('start-info', roottype) mtompkg.set_param('start', '') if 'SOAPAction' in headers: mtompkg.add_header('SOAPAction', headers.get('SOAPAction')) # Set up root SOAP part headers. del(rootpkg['mime-version']) rootpkg.add_header('Content-ID', '') for n, v in rootparams.items(): rootpkg.set_param(n, v) rootpkg.set_param('type', roottype) mtompkg.attach(rootpkg) # Extract attachments from SOAP envelope. for i in range(len(params)): name, typ = params[i] if typ in (ByteArray, Attachment): id = "spyneAttachment_%s" % (len(mtompkg.get_payload()), ) param = message[i] param.text = "" incl = etree.SubElement(param, "{%s}Include" % _ns_xop) incl.attrib["href"] = "cid:%s" % id if paramvals[i].fileName and not paramvals[i].data: paramvals[i].load_from_file() if type == Attachment: data = paramvals[i].data else: data = ''.join(paramvals[i]) attachment = None attachment = MIMEApplication(data, _encoder=encode_7or8bit) del(attachment['mime-version']) attachment.add_header('Content-ID', '<%s>' % (id, )) mtompkg.attach(attachment) # Update SOAP envelope. rootpkg.set_payload(etree.tostring(soaptree)) # extract body string from MIMEMultipart message bound = '--%s' % (mtompkg.get_boundary(), ) marray = mtompkg.as_string().split(bound) mtombody = bound mtombody += bound.join(marray[1:]) # set Content-Length mtompkg.add_header("Content-Length", str(len(mtombody))) # extract dictionary of headers from MIMEMultipart message mtomheaders = {} for name, value in mtompkg.items(): mtomheaders[name] = value if len(mtompkg.get_payload()) <= 1: return (headers, envelope) return (mtomheaders, [mtombody]) from spyne.model.binary import Attachment spyne-2.11.0/spyne/protocol/soap/soap11.py0000644000175000001440000003321512345433230020326 0ustar plqusers00000000000000 # # 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 # """The ``spyne.protoco.soap.soap11`` module contains the implementation of a subset of the Soap 1.1 standard. Except the binary optimizations (MtoM, attachments, etc) that mostly **do not work**, this protocol is production quality. One must specifically enable the debug output for the Xml protocol to see the actual document exchange. That's because the xml formatting code is run only when explicitly enabled due to performance reasons. :: logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) Initially released in soaplib-0.8.0. Logs valid documents to %r and invalid documents to %r. """ % (__name__, __name__ + ".invalid") import logging logger = logging.getLogger(__name__) logger_invalid = logging.getLogger(__name__ + ".invalid") import cgi import spyne.const.xml_ns as ns from lxml import etree from lxml.etree import XMLSyntaxError from lxml.etree import XMLParser from spyne import BODY_STYLE_WRAPPED from spyne.const.xml_ns import DEFAULT_NS from spyne.const.http import HTTP_405 from spyne.const.http import HTTP_500 from spyne.error import RequestNotAllowed from spyne.model.fault import Fault from spyne.model.primitive import Date from spyne.model.primitive import Time from spyne.model.primitive import DateTime from spyne.protocol.xml import XmlDocument from spyne.protocol.soap.mime import collapse_swa def _from_soap(in_envelope_xml, xmlids=None): """Parses the xml string into the header and payload. """ if xmlids: resolve_hrefs(in_envelope_xml, xmlids) if in_envelope_xml.tag != '{%s}Envelope' % ns.soap_env: raise Fault('Client.SoapError', 'No {%s}Envelope element was found!' % ns.soap_env) header_envelope = in_envelope_xml.xpath('e:Header', namespaces={'e': ns.soap_env}) body_envelope = in_envelope_xml.xpath('e:Body', namespaces={'e': ns.soap_env}) if len(header_envelope) == 0 and len(body_envelope) == 0: raise Fault('Client.SoapError', 'Soap envelope is empty!') header = None if len(header_envelope) > 0: header = header_envelope[0].getchildren() body = None if len(body_envelope) > 0 and len(body_envelope[0]) > 0: body = body_envelope[0][0] return header, body def _parse_xml_string(xml_string, parser, charset=None): string = b''.join(xml_string) if charset: string = string.decode(charset) try: try: root, xmlids = etree.XMLID(string, parser) except ValueError as e: logger.debug('ValueError: Deserializing from unicode strings with ' 'encoding declaration is not supported by lxml.') root, xmlids = etree.XMLID(string.encode(charset), parser) except XMLSyntaxError as e: logger_invalid.error(string) raise Fault('Client.XMLSyntaxError', str(e)) return root, xmlids # see http://www.w3.org/TR/2000/NOTE-SOAP-20000508/ # section 5.2.1 for an example of how the id and href attributes are used. def resolve_hrefs(element, xmlids): for e in element: if e.get('id'): continue # don't need to resolve this element elif e.get('href'): resolved_element = xmlids[e.get('href').replace('#', '')] if resolved_element is None: continue resolve_hrefs(resolved_element, xmlids) # copies the attributes [e.set(k, v) for k, v in resolved_element.items()] # copies the children [e.append(child) for child in resolved_element.getchildren()] # copies the text e.text = resolved_element.text else: resolve_hrefs(e, xmlids) return element class Soap11(XmlDocument): """The base implementation of a subset of the Soap 1.1 standard. The document is available here: http://www.w3.org/TR/soap11/ :param app: The owner application instance. :param validator: One of (None, 'soft', 'lxml', 'schema', ProtocolBase.SOFT_VALIDATION, XmlDocument.SCHEMA_VALIDATION). Both ``'lxml'`` and ``'schema'`` values are equivalent to ``XmlDocument.SCHEMA_VALIDATION``. :param xml_declaration: Whether to add xml_declaration to the responses Default is 'True'. :param cleanup_namespaces: Whether to add clean up namespace declarations in the response document. Default is 'True'. :param encoding: The suggested string encoding for the returned xml documents. The transport can override this. :param pretty_print: When ``True``, returns the document in a pretty-printed format. """ mime_type = 'text/xml; charset=utf-8' type = set(XmlDocument.type) type.update(('soap', 'soap11')) def __init__(self, app=None, validator=None, xml_declaration=True, cleanup_namespaces=True, encoding='UTF-8', pretty_print=False): super(Soap11, self).__init__(app, validator, xml_declaration, cleanup_namespaces, encoding, pretty_print) # SOAP requires DateTime strings to be in iso format. The following # lines make sure custom datetime formatting via DateTime(format="...") # string is bypassed. self._to_string_handlers[Time] = lambda cls, value: value.isoformat() self._to_string_handlers[DateTime] = lambda cls, value: value.isoformat() self._from_string_handlers[Date] = self.date_from_string_iso self._from_string_handlers[DateTime] = self.datetime_from_string_iso def create_in_document(self, ctx, charset=None): if ctx.transport.type == 'wsgi': # according to the soap via http standard, soap requests must only # work with proper POST requests. content_type = ctx.transport.req_env.get("CONTENT_TYPE") http_verb = ctx.transport.req_env['REQUEST_METHOD'].upper() if content_type is None or http_verb != "POST": ctx.transport.resp_code = HTTP_405 raise RequestNotAllowed( "You must issue a POST request with the Content-Type " "header properly set.") content_type = cgi.parse_header(content_type) collapse_swa(content_type, ctx.in_string) ctx.in_document = _parse_xml_string(ctx.in_string, XMLParser(**self.parser_kwargs), charset) def decompose_incoming_envelope(self, ctx, message=XmlDocument.REQUEST): envelope_xml, xmlids = ctx.in_document header_document, body_document = _from_soap(envelope_xml, xmlids) ctx.in_document = envelope_xml if body_document.tag == '{%s}Fault' % ns.soap_env: ctx.in_body_doc = body_document else: ctx.in_header_doc = header_document ctx.in_body_doc = body_document ctx.method_request_string = ctx.in_body_doc.tag self.validate_body(ctx, message) def deserialize(self, ctx, message): """Takes a MethodContext instance and a string containing ONE soap message. Returns the corresponding native python object Not meant to be overridden. """ assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_deserialize', ctx) if ctx.in_body_doc.tag == "{%s}Fault" % ns.soap_env: ctx.in_object = None ctx.in_error = self.from_element(ctx, Fault, ctx.in_body_doc) else: if message is self.REQUEST: header_class = ctx.descriptor.in_header body_class = ctx.descriptor.in_message elif message is self.RESPONSE: header_class = ctx.descriptor.out_header body_class = ctx.descriptor.out_message # decode header objects if (ctx.in_header_doc is not None and header_class is not None): headers = [None] * len(header_class) for i, (header_doc, head_class) in enumerate( zip(ctx.in_header_doc, header_class)): if i < len(header_class): headers[i] = self.from_element(ctx, head_class, header_doc) if len(headers) == 1: ctx.in_header = headers[0] else: ctx.in_header = headers # decode method arguments if ctx.in_body_doc is None: ctx.in_object = [None] * len(body_class._type_info) else: ctx.in_object = self.from_element(ctx, body_class, ctx.in_body_doc) self.event_manager.fire_event('after_deserialize', ctx) def serialize(self, ctx, message): """Uses ctx.out_object, ctx.out_header or ctx.out_error to set ctx.out_body_doc, ctx.out_header_doc and ctx.out_document as an lxml.etree._Element instance. Not meant to be overridden. """ assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_serialize', ctx) # construct the soap response, and serialize it nsmap = self.app.interface.nsmap ctx.out_document = etree.Element('{%s}Envelope' % ns.soap_env, nsmap=nsmap) if ctx.out_error is not None: # FIXME: There's no way to alter soap response headers for the user. ctx.out_body_doc = out_body_doc = etree.SubElement(ctx.out_document, '{%s}Body' % ns.soap_env, nsmap=nsmap) self.to_parent(ctx, ctx.out_error.__class__, ctx.out_error, out_body_doc, self.app.interface.get_tns()) else: if message is self.REQUEST: header_message_class = ctx.descriptor.in_header body_message_class = ctx.descriptor.in_message elif message is self.RESPONSE: header_message_class = ctx.descriptor.out_header body_message_class = ctx.descriptor.out_message # body ctx.out_body_doc = out_body_doc = etree.Element( '{%s}Body' % ns.soap_env) # assign raw result to its wrapper, result_message if ctx.descriptor.body_style is BODY_STYLE_WRAPPED: out_type_info = body_message_class._type_info out_object = body_message_class() keys = iter(out_type_info) values = iter(ctx.out_object) while True: try: k = next(keys) except StopIteration: break try: v = next(values) except StopIteration: v = None setattr(out_object, k, v) self.to_parent(ctx, body_message_class, out_object, out_body_doc, body_message_class.get_namespace()) else: out_object = ctx.out_object[0] sub_ns = body_message_class.Attributes.sub_ns if sub_ns is None: sub_ns = body_message_class.get_namespace() if sub_ns is DEFAULT_NS: sub_ns = self.app.interface.get_tns() sub_name = body_message_class.Attributes.sub_name if sub_name is None: sub_name = body_message_class.get_type_name() self.to_parent(ctx, body_message_class, out_object, out_body_doc, sub_ns, sub_name) # header if ctx.out_header is not None and header_message_class is not None: ctx.out_header_doc = soap_header_elt = etree.SubElement( ctx.out_document, '{%s}Header' % ns.soap_env) if isinstance(ctx.out_header, (list, tuple)): out_headers = ctx.out_header else: out_headers = (ctx.out_header,) for header_class, out_header in zip(header_message_class, out_headers): self.to_parent(ctx, header_class, out_header, soap_header_elt, header_class.get_namespace(), header_class.get_type_name(), ) ctx.out_document.append(ctx.out_body_doc) if self.cleanup_namespaces: etree.cleanup_namespaces(ctx.out_document) self.event_manager.fire_event('after_serialize', ctx) def fault_to_http_response_code(self, fault): return HTTP_500 spyne-2.11.0/spyne/protocol/__init__.py0000644000175000001440000000174512342627562020034 0ustar plqusers00000000000000 # # 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 # """The ``spyne.protocol`` package contains the :class:`spyne.protocol.ProtocolBase`` abstract base class. Every protocol implementation is a subclass of ``ProtocolBase``. """ from spyne.protocol._base import ProtocolBase spyne-2.11.0/spyne/protocol/_base.py0000644000175000001440000007565012345433230017342 0ustar plqusers00000000000000 # # 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__) import pytz import uuid import errno from os.path import isabs, join from collections import deque from datetime import timedelta, time, datetime, date from math import modf from decimal import Decimal as D, InvalidOperation from pytz import FixedOffset from mmap import mmap, ACCESS_READ from time import strptime, mktime try: from lxml import etree from lxml import html except ImportError: etree = None html = None from spyne import EventManager from spyne.const.http import HTTP_400 from spyne.const.http import HTTP_401 from spyne.const.http import HTTP_404 from spyne.const.http import HTTP_405 from spyne.const.http import HTTP_413 from spyne.const.http import HTTP_500 from spyne.error import Fault, InternalError from spyne.error import ResourceNotFoundError from spyne.error import RequestTooLongError from spyne.error import RequestNotAllowed from spyne.error import InvalidCredentialsError from spyne.error import ValidationError from spyne.model.binary import binary_encoding_handlers from spyne.model.binary import binary_decoding_handlers from spyne.model.binary import BINARY_ENCODING_USE_DEFAULT from spyne.model.primitive import _time_re from spyne.model.primitive import _duration_re from spyne.model import ModelBase, XmlAttribute, Array from spyne.model import SimpleModel from spyne.model import Null from spyne.model import ByteArray from spyne.model import File from spyne.model import ComplexModelBase from spyne.model import AnyXml from spyne.model import AnyHtml from spyne.model import Unicode from spyne.model import String from spyne.model import Decimal from spyne.model import Double from spyne.model import Integer from spyne.model import Time from spyne.model import DateTime from spyne.model import Uuid from spyne.model import Date from spyne.model import Duration from spyne.model import Boolean from spyne.model.binary import Attachment # DEPRECATED from spyne.util import DefaultAttrDict, memoize_id, six from spyne.util.cdict import cdict class ProtocolBase(object): """This is the abstract base class for all protocol implementations. Child classes can implement only the required subset of the public methods. An output protocol must implement :func:`serialize` and :func:`create_out_string`. An input protocol must implement :func:`create_in_document`, :func:`decompose_incoming_envelope` and :func:`deserialize`. The ProtocolBase class supports the following events: * ``before_deserialize``: Called before the deserialization operation is attempted. * ``after_deserialize``: Called after the deserialization operation is finished. * ``before_serialize``: Called before after the serialization operation is attempted. * ``after_serialize``: Called after the serialization operation is finished. The arguments the constructor takes are as follows: :param app: The application this protocol belongs to. :param validator: The type of validation this protocol should do on incoming data. :param mime_type: The mime_type this protocol should set for transports that support this. This is a quick way to override the mime_type by default instead of subclassing the releavant protocol implementation. :param ignore_uncap: Silently ignore cases when the protocol is not capable of serializing return values instead of raising a TypeError. """ mime_type = 'application/octet-stream' SOFT_VALIDATION = type("Soft", (object,), {}) REQUEST = type("Request", (object,), {}) RESPONSE = type("Response", (object,), {}) type = set() """Set that contains keywords about a protocol.""" default_binary_encoding = None def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, ignore_wrappers=False): self.__app = None self.set_app(app) self.validator = None self.set_validator(validator) self.event_manager = EventManager(self) self.ignore_uncap = ignore_uncap self.ignore_wrappers = ignore_wrappers self.message = None if mime_type is not None: self.mime_type = mime_type self._to_string_handlers = cdict({ ModelBase: self.model_base_to_string, File: self.file_to_string, Time: self.time_to_string, Uuid: self.uuid_to_string, Null: self.null_to_string, Double: self.double_to_string, AnyXml: self.any_xml_to_string, Unicode: self.unicode_to_string, Boolean: self.boolean_to_string, Decimal: self.decimal_to_string, Integer: self.integer_to_string, AnyHtml: self.any_html_to_string, DateTime: self.datetime_to_string, Duration: self.duration_to_string, ByteArray: self.byte_array_to_string, Attachment: self.attachment_to_string, XmlAttribute: self.xmlattribute_to_string, ComplexModelBase: self.complex_model_base_to_string, }) self._to_string_iterable_handlers = cdict({ File: self.file_to_string_iterable, ByteArray: self.byte_array_to_string_iterable, ModelBase: self.model_base_to_string_iterable, SimpleModel: self.simple_model_to_string_iterable, ComplexModelBase: self.complex_model_to_string_iterable, }) self._from_string_handlers = cdict({ Null: self.null_from_string, Time: self.time_from_string, Date: self.date_from_string, Uuid: self.uuid_from_string, File: self.file_from_string, Double: self.double_from_string, String: self.string_from_string, AnyXml: self.any_xml_from_string, Boolean: self.boolean_from_string, Integer: self.integer_from_string, Unicode: self.unicode_from_string, Decimal: self.decimal_from_string, AnyHtml: self.any_html_from_string, DateTime: self.datetime_from_string, Duration: self.duration_from_string, ByteArray: self.byte_array_from_string, ModelBase: self.model_base_from_string, Attachment: self.attachment_from_string, XmlAttribute: self.xmlattribute_from_string, ComplexModelBase: self.complex_model_base_from_string }) self._datetime_dsmap = { None: self._datetime_from_string, 'sec': self._datetime_from_sec, 'sec_float': self._datetime_from_sec_float, 'msec': self._datetime_from_msec, 'msec_float': self._datetime_from_msec_float, 'usec': self._datetime_from_usec, } def _datetime_from_sec(self, cls, value): return datetime.fromtimestamp(value) def _datetime_from_sec_float(self, cls, value): return datetime.fromtimestamp(value) def _datetime_from_msec(self, cls, value): return datetime.fromtimestamp(value // 1000) def _datetime_from_msec_float(self, cls, value): return datetime.fromtimestamp(value / 1000) def _datetime_from_usec(self, cls, value): return datetime.fromtimestamp(value / 1e6) @property def app(self): return self.__app @staticmethod def strip_wrappers(cls, inst): ti = getattr(cls, '_type_info', {}) while len(ti) == 1 and cls.Attributes._wrapper: # Wrappers are auto-generated objects that have exactly one # child type. key, = ti.keys() if not issubclass(cls, Array): inst = getattr(inst, key, None) cls, = ti.values() ti = getattr(cls, '_type_info', {}) return cls, inst def set_app(self, value): assert self.__app is None, "One protocol instance should belong to one " \ "application instance. It currently belongs " \ "to: %r" % self.__app self.__app = value def create_in_document(self, ctx, in_string_encoding=None): """Uses ``ctx.in_string`` to set ``ctx.in_document``.""" def decompose_incoming_envelope(self, ctx, message): """Sets the ``ctx.method_request_string``, ``ctx.in_body_doc``, ``ctx.in_header_doc`` and ``ctx.service`` properties of the ctx object, if applicable. """ def deserialize(self, ctx, message): """Takes a MethodContext instance and a string containing ONE document instance in the ``ctx.in_string`` attribute. Returns the corresponding native python object in the ctx.in_object attribute. """ def serialize(self, ctx, message): """Serializes ``ctx.out_object``. If ctx.out_stream is not None, ``ctx.out_document`` and ``ctx.out_string`` are skipped and the response is written directly to ``ctx.out_stream``. :param ctx: :class:`MethodContext` instance. :param message: One of ``(ProtocolBase.REQUEST, ProtocolBase.RESPONSE)``. """ def create_out_string(self, ctx, out_string_encoding=None): """Uses ctx.out_document to set ctx.out_string""" def validate_document(self, payload): """Method to be overriden to perform any sort of custom input validation on the parsed input document. """ def generate_method_contexts(self, ctx): """Generates MethodContext instances for every callable assigned to the given method handle. The first element in the returned list is always the primary method context whereas the rest are all auxiliary method contexts. """ call_handles = self.get_call_handles(ctx) if len(call_handles) == 0: raise ResourceNotFoundError(ctx.method_request_string) retval = [] for d in call_handles: assert d is not None c = ctx.copy() c.descriptor = d retval.append(c) return retval def get_call_handles(self, ctx): """Method to be overriden to perform any sort of custom method mapping using any data in the method context. Returns a list of contexts. Can return multiple contexts if a method_request_string matches more than one function. (This is called the fanout mode.) """ name = ctx.method_request_string if not name.startswith("{"): name = '{%s}%s' % (self.app.interface.get_tns(), name) call_handles = self.app.interface.service_method_map.get(name, []) return call_handles def fault_to_http_response_code(self, fault): """Special function to convert native Python exceptions to Http response codes. """ if isinstance(fault, RequestTooLongError): return HTTP_413 if isinstance(fault, ResourceNotFoundError): return HTTP_404 if isinstance(fault, RequestNotAllowed): return HTTP_405 if isinstance(fault, InvalidCredentialsError): return HTTP_401 if isinstance(fault, Fault) and (fault.faultcode.startswith('Client.') or fault.faultcode == 'Client'): return HTTP_400 return HTTP_500 def set_validator(self, validator): """You must override this function if you want your protocol to support validation.""" assert validator is None self.validator = None def from_string(self, class_, string, *args, **kwargs): if string is None: return None if string == '' and class_.Attributes.empty_is_none: return None handler = self._from_string_handlers[class_] return handler(class_, string, *args, **kwargs) def to_string(self, class_, value, *args, **kwargs): if value is None: return None handler = self._to_string_handlers[class_] return handler(class_, value, *args, **kwargs) def to_string_iterable(self, class_, value): if value is None: return [] handler = self._to_string_iterable_handlers[class_] return handler(class_, value) @memoize_id def get_cls_attrs(self, cls): attr = DefaultAttrDict([(k, getattr(cls.Attributes, k)) for k in dir(cls.Attributes) if not k.startswith('__')]) if cls.Attributes.prot_attrs: attr.update(cls.Attributes.prot_attrs.get(self.__class__, {})) attr.update(cls.Attributes.prot_attrs.get(self, {})) return attr def null_to_string(self, cls, value): return "" def null_from_string(self, cls, value): return None def any_xml_to_string(self, cls, value): return etree.tostring(value) def any_xml_from_string(self, cls, string): try: return etree.fromstring(string) except etree.XMLSyntaxError as e: raise ValidationError(string, "%%r: %r" % e) def any_html_to_string(self, cls, value): return html.tostring(value) def any_html_from_string(self, cls, string): return html.fromstring(string) def uuid_to_string(self, cls, value): return _uuid_serialize[cls.Attributes.serialize_as](value) def uuid_from_string(self, cls, string): return _uuid_deserialize[cls.Attributes.serialize_as](string) def unicode_to_string(self, cls, value): retval = value if cls.Attributes.encoding is not None and \ isinstance(value, six.text_type): retval = value.encode(cls.Attributes.encoding) if cls.Attributes.format is None: return retval else: return cls.Attributes.format % retval def unicode_from_string(self, cls, value): retval = value if isinstance(value, six.binary_type): if cls.Attributes.encoding is None: retval = six.text_type(value, errors=cls.Attributes.unicode_errors) else: retval = six.text_type(value, cls.Attributes.encoding, errors=cls.Attributes.unicode_errors) return retval def string_from_string(self, cls, value): retval = value if isinstance(value, six.text_type): if cls.Attributes.encoding is None: raise Exception("You need to define an encoding to convert the " "incoming unicode values to.") else: retval = value.encode(cls.Attributes.encoding) return retval def decimal_to_string(self, cls, value): D(value) if cls.Attributes.str_format is not None: return cls.Attributes.str_format.format(value) elif cls.Attributes.format is not None: return cls.Attributes.format % value else: return str(value) def decimal_from_string(self, cls, string): if cls.Attributes.max_str_len is not None and len(string) > \ cls.Attributes.max_str_len: raise ValidationError(string, "Decimal %%r longer than %d characters" % cls.Attributes.max_str_len) try: return D(string) except InvalidOperation as e: raise ValidationError(string, "%%r: %r" % e) def double_to_string(self, cls, value): float(value) # sanity check if cls.Attributes.format is None: return repr(value) else: return cls.Attributes.format % value def double_from_string(self, cls, string): try: return float(string) except (TypeError, ValueError) as e: raise ValidationError(string, "%%r: %r" % e) def integer_to_string(self, cls, value): int(value) # sanity check if cls.Attributes.format is None: return str(value) else: return cls.Attributes.format % value def integer_from_string(self, cls, string): if cls.Attributes.max_str_len is not None and len(string) > \ cls.Attributes.max_str_len: raise ValidationError(string, "Integer %%r longer than %d characters" % cls.Attributes.max_str_len) try: return int(string) except ValueError: raise ValidationError(string, "Could not cast %r to integer") def time_to_string(self, cls, value): """Returns ISO formatted dates.""" return value.isoformat() def time_from_string(self, cls, string): """Expects ISO formatted times.""" match = _time_re.match(string) if match is None: raise ValidationError(string, "%%r does not match regex %r " % _time_re.pattern) fields = match.groupdict(0) microsec = fields.get('sec_frac') if microsec is None or microsec == 0: microsec = 0 else: microsec = int(round(float(microsec) * 1e6)) return time(int(fields['hr']), int(fields['min']), int(fields['sec']), microsec) def datetime_to_string(self, cls, val): return _datetime_smap[cls.Attributes.serialize_as](cls, val) def date_from_string_iso(self, cls, string): """This is used by protocols like SOAP who need ISO8601-formatted dates no matter what. """ try: return date(*(strptime(string, '%Y-%m-%d')[0:3])) except ValueError: match = cls._offset_re.match(string) if match: return date(int(match.group('year')), int(match.group('month')), int(match.group('day'))) else: raise ValidationError(string) def model_base_from_string(self, cls, value): return cls.from_string(value) def datetime_from_string_iso(self, cls, string): astz = cls.Attributes.as_timezone match = cls._utc_re.match(string) if match: tz = pytz.utc retval = _parse_datetime_iso_match(match, tz=tz) if astz is not None: retval = retval.astimezone(astz) return retval if match is None: match = cls._offset_re.match(string) if match: tz_hr, tz_min = [int(match.group(x)) for x in ("tz_hr", "tz_min")] tz = FixedOffset(tz_hr * 60 + tz_min, {}) retval = _parse_datetime_iso_match(match, tz=tz) if astz is not None: retval = retval.astimezone(astz) return retval if match is None: match = cls._local_re.match(string) if match: retval = _parse_datetime_iso_match(match) if astz: retval = retval.replace(tzinfo=astz) return retval raise ValidationError(string) def datetime_from_string(self, cls, string): return self._datetime_dsmap[cls.Attributes.serialize_as](cls, string) def date_from_string(self, cls, string): try: d = datetime.strptime(string, cls.Attributes.format) return date(d.year, d.month, d.day) except ValueError as e: match = cls._offset_re.match(string) if match: return date(int(match.group('year')), int(match.group('month')), int(match.group('day'))) else: raise ValidationError(string, "%%r: %s" % repr(e).replace("%", "%%")) def duration_to_string(self, cls, value): if value.days < 0: value = -value negative = True else: negative = False tot_sec = _total_seconds(value) seconds = value.seconds % 60 minutes = value.seconds / 60 hours = minutes / 60 minutes %= 60 seconds = float(seconds) useconds = value.microseconds retval = deque() if negative: retval.append("-P") else: retval.append("P") if value.days != 0: retval.extend([ "%iD" % value.days, ]) if tot_sec != 0 and tot_sec % 86400 == 0 and useconds == 0: return ''.join(retval) retval.append('T') if hours > 0: retval.append("%iH" % hours) if minutes > 0: retval.append("%iM" % minutes) if seconds > 0 or useconds > 0: retval.append("%i" % seconds) if useconds > 0: retval.append(".%i" % useconds) retval.append("S") if len(retval) == 2: retval.append('0S') return ''.join(retval) def duration_from_string(self, cls, string): duration = _duration_re.match(string).groupdict(0) if duration is None: raise ValidationError("time data '%s' does not match regex '%s'" % (string, _duration_re.pattern)) days = int(duration['days']) days += int(duration['months']) * 30 days += int(duration['years']) * 365 hours = int(duration['hours']) minutes = int(duration['minutes']) seconds = float(duration['seconds']) f, i = modf(seconds) seconds = i microseconds = int(1e6 * f) delta = timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds, microseconds=microseconds) if duration['sign'] == "-": delta *= -1 return delta def boolean_to_string(self, cls, value): return str(bool(value)).lower() def boolean_from_string(self, cls, string): return (string.lower() in ['true', '1']) def byte_array_from_string(self, cls, value, suggested_encoding=None): encoding = cls.Attributes.encoding if encoding is BINARY_ENCODING_USE_DEFAULT: encoding = suggested_encoding return binary_decoding_handlers[encoding](value) def byte_array_to_string(self, cls, value, suggested_encoding=None): encoding = cls.Attributes.encoding if encoding is BINARY_ENCODING_USE_DEFAULT: encoding = suggested_encoding return binary_encoding_handlers[encoding](value) def byte_array_to_string_iterable(self, cls, value): return value def file_from_string(self, cls, value, suggested_encoding=None): encoding = cls.Attributes.encoding if encoding is BINARY_ENCODING_USE_DEFAULT: encoding = suggested_encoding return File.Value(data=binary_decoding_handlers[encoding](value)) def file_to_string(self, cls, value, suggested_encoding=None): """ :param cls: A :class:`spyne.model.File` subclass :param value: Either a sequence of byte chunks or a :class:`spyne.model.File.Value` instance. """ encoding = cls.Attributes.encoding if encoding is BINARY_ENCODING_USE_DEFAULT: encoding = suggested_encoding if isinstance(value, File.Value): if value.data is not None: return binary_encoding_handlers[encoding](value.data) if value.handle is not None: assert isinstance(value.handle, file) fileno = value.handle.fileno() data = mmap(fileno, 0, access=ACCESS_READ) return binary_encoding_handlers[encoding](data) else: return binary_encoding_handlers[encoding](value) def file_to_string_iterable(self, cls, value): if value.data is None: if value.handle is None: assert value.path is not None, "You need to write data to " \ "persistent storage first if you want to read it back." try: path = value.path if not isabs(value.path): path = join(value.store, value.path) f = open(path, 'rb') except IOError as e: if e.errno == errno.ENOENT: raise ResourceNotFoundError(value.path) else: raise InternalError("Error accessing requested file") else: f = value.handle f.seek(0) return _file_to_iter(f) else: if isinstance(value.data, (list,tuple)) and \ isinstance(value.data[0], mmap): return _file_to_iter(value.data[0]) else: return iter(value.data) def simple_model_to_string_iterable(self, cls, value): retval = self.to_string(cls, value) if retval is None: return ('',) return (retval,) def complex_model_to_string_iterable(self, cls, value): if self.ignore_uncap: return tuple() raise TypeError("HttpRpc protocol can only serialize primitives.") def attachment_to_string(self, cls, value): if not (value.data is None): # the data has already been loaded, just encode # and return the element data = value.data elif not (value.file_name is None): # the data hasn't been loaded, but a file has been # specified data = open(value.file_name, 'rb').read() else: raise ValueError("Neither data nor a file_name has been specified") return data def attachment_from_string(self, cls, value): return Attachment(data=value) def complex_model_base_to_string(self, cls, value): raise TypeError("Only primitives can be serialized to string.") def complex_model_base_from_string(self, cls, string): raise TypeError("Only primitives can be deserialized from string.") def xmlattribute_to_string(self, cls, string): return self.to_string(cls.type, string) def xmlattribute_from_string(self, cls, value): return self.from_string(cls.type, value) def model_base_to_string_iterable(self, cls, value): return cls.to_string_iterable(value) def model_base_to_string(self, cls, value): return cls.to_string(value) def _datetime_from_string(self, cls, string): attrs = cls.Attributes format = attrs.format if format is None: retval = self.datetime_from_string_iso(cls, string) else: astz = cls.Attributes.as_timezone retval = datetime.strptime(string, format) if astz: retval = retval.astimezone(cls.Attributes.as_time_zone) return retval _uuid_serialize = { None: str, 'hex': lambda u:u.hex, 'urn': lambda u:u.urn, 'bytes': lambda u:u.bytes, 'bytes_le': lambda u:u.bytes_le, 'fields': lambda u:u.fields, 'int': lambda u:u.int, } if six.PY3: long = int _uuid_deserialize = { None: lambda s: uuid.UUID(s), 'hex': lambda s: uuid.UUID(hex=s), 'urn': lambda s: uuid.UUID(hex=s), 'bytes': lambda s: uuid.UUID(bytes=s), 'bytes_le': lambda s: uuid.UUID(bytes_le=s), 'fields': lambda s: uuid.UUID(fields=s), 'int': lambda s: _uuid_deserialize[('int', type(s))](s), ('int', int): lambda s: uuid.UUID(int=s), ('int', long): lambda s: uuid.UUID(int=s), ('int', str): lambda s: uuid.UUID(int=int(s)), } if hasattr(timedelta, 'total_seconds'): def _total_seconds(td): return td.total_seconds() else: def _total_seconds(td): return (td.microseconds + (td.seconds + td.days * 24 * 3600) *1e6) / 1e6 def _parse_datetime_iso_match(date_match, tz=None): fields = date_match.groupdict() year = int(fields.get('year')) month = int(fields.get('month')) day = int(fields.get('day')) hour = int(fields.get('hr')) min = int(fields.get('min')) sec = int(fields.get('sec')) usec = fields.get("sec_frac") if usec is None: usec = 0 else: # we only get the most significant 6 digits because that's what # datetime can handle. usec = int(round(float(usec) * 1e6)) return datetime(year, month, day, hour, min, sec, usec, tz) def _datetime_to_string(cls, value): if cls.Attributes.as_timezone is not None and value.tzinfo is not None: value = value.astimezone(cls.Attributes.as_timezone) if not cls.Attributes.timezone: value = value.replace(tzinfo=None) format = cls.Attributes.format if format is None: ret_str = value.isoformat() else: ret_str = value.strftime(format) string_format = cls.Attributes.string_format if string_format is None: return ret_str else: return string_format % ret_str _dt_sec = lambda cls, val: \ int(mktime(val.timetuple())) _dt_sec_float = lambda cls, val: \ mktime(val.timetuple()) + (val.microsecond / 1e6) _dt_msec = lambda cls, val: \ int(mktime(val.timetuple())) * 1000 + (val.microsecond // 1000) _dt_msec_float = lambda cls, val: \ mktime(val.timetuple()) * 1000 + (val.microsecond / 1000.0) _dt_usec = lambda cls, val: \ int(mktime(val.timetuple())) * 1000000 + val.microsecond _datetime_smap = { None: _datetime_to_string, 'sec': _dt_sec, 'secs': _dt_sec, 'second': _dt_sec, 'seconds': _dt_sec, 'sec_float': _dt_sec_float, 'secs_float': _dt_sec_float, 'second_float': _dt_sec_float, 'seconds_float': _dt_sec_float, 'msec': _dt_msec, 'msecs': _dt_msec, 'msecond': _dt_msec, 'mseconds': _dt_msec, 'millisecond': _dt_msec, 'milliseconds': _dt_msec, 'msec_float': _dt_msec_float, 'msecs_float': _dt_msec_float, 'msecond_float': _dt_msec_float, 'mseconds_float': _dt_msec_float, 'millisecond_float': _dt_msec_float, 'milliseconds_float': _dt_msec_float, 'usec': _dt_usec, 'usecs': _dt_usec, 'usecond': _dt_usec, 'useconds': _dt_usec, 'microsecond': _dt_usec, 'microseconds': _dt_usec, } def _file_to_iter(f): data = f.read(65536) while len(data) > 0: yield data data = f.read(65536) f.close() spyne-2.11.0/spyne/protocol/csv.py0000644000175000001440000000647712345433230017065 0ustar plqusers00000000000000 # # 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 # """The ``spyne.protocol.csv`` package contains the Csv output protocol. This protocol is here merely for illustration purposes. While it is in a somewhat working state, it is not that easy to use. Expect a revamp in the coming versions. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) import csv from spyne.util import six from spyne.protocol.dictdoc import HierDictDocument if six.PY2: from StringIO import StringIO else: from io import StringIO def _complex_to_csv(prot, ctx): cls, = ctx.descriptor.out_message._type_info.values() queue = StringIO() serializer, = cls._type_info.values() type_info = getattr(serializer, '_type_info', {serializer.get_type_name(): serializer}) keys = sorted(type_info.keys()) if ctx.out_object is None: writer = csv.writer(queue, dialect=csv.excel) writer.writerow(['Error in generating the document']) if ctx.out_error is not None: for r in ctx.out_error.to_string_iterable(ctx.out_error): writer.writerow([r]) yield queue.getvalue() queue.truncate(0) elif ctx.out_error is None: writer = csv.DictWriter(queue, dialect=csv.excel, fieldnames=keys) writer.writerow(dict(((k,k) for k in keys))) yield queue.getvalue() queue.truncate(0) if ctx.out_object[0] is not None: for v in ctx.out_object[0]: d = prot._to_value(serializer, v) for k in d: if isinstance(d[k], unicode): d[k] = d[k].encode('utf8') writer.writerow(d) yval = queue.getvalue() yield yval queue.truncate(0) class Csv(HierDictDocument): mime_type = 'text/csv' type = set(HierDictDocument.type) type.add('csv') def create_in_document(self, ctx): raise NotImplementedError() def serialize(self, ctx, message): assert message in (self.RESPONSE, ) if ctx.out_object is None: ctx.out_object = [] assert len(ctx.descriptor.out_message._type_info) == 1, """CSV Serializer supports functions with exactly one return type: %r""" % ctx.descriptor.out_message._type_info def create_out_string(self, ctx): ctx.out_string = _complex_to_csv(self, ctx) if 'http' in ctx.transport.type: ctx.transport.resp_headers['Content-Disposition'] = ( 'attachment; filename=%s.csv;' % ctx.descriptor.name) spyne-2.11.0/spyne/protocol/dictdoc.py0000644000175000001440000006735712345433230017707 0ustar plqusers00000000000000 # # 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 # """The ``spyne.protocol.dictdoc`` module contains an abstract protocol that deals with hierarchical and flat dicts as {in,out}_documents. This module is EXPERIMENTAL. You may not recognize the code here next time you look at it. Flattening ========== Plain HTTP does not support hierarchical key-value stores. Spyne makes plain HTTP fake hierarchical dicts with two small hacks. Let's look at the following object hierarchy: :: class Inner(ComplexModel): c = Integer d = Array(Integer) class Outer(ComplexModel): a = Integer b = Inner For example, the ``Outer(a=1, b=Inner(c=2))`` object would correspond to the following hierarchichal dict representation: :: {'a': 1, 'b': { 'c': 2 }} Here's what we do to deserialize the above object structure from a flat dict: 1. Object hierarchies are flattened. e.g. the flat representation of the above dict is: ``{'a': 1, 'b.c': 2}``. 2. Arrays of objects are sent using variables with array indexes in square brackets. So the request with the following query object: :: {'a': 1, 'b.d[0]': 1, 'b.d[1]': 2}} ... corresponds to: :: {'a': 1, 'b': { 'd': [1,2] }} If we had: :: class Inner(ComplexModel): c = Integer class Outer(ComplexModel): a = Integer b = Array(SomeObject) Or the following object: :: {'a': 1, 'b[0].c': 1, 'b[1].c': 2}} ... would correspond to: :: {'a': 1, 'b': [{ 'c': 1}, {'c': 2}]} ... which would deserialize as: :: Outer(a=1, b=[Inner(c=1), Inner(c=2)]) These hacks are both slower to process and bulkier on wire, so use class hierarchies with HTTP only when performance is not that much of a concern. Cookies ======= Cookie headers are parsed and fields within HTTP requests are assigned to fields in the ``in_header`` class, if defined. It's also possible to get the ``Cookie`` header intact by defining an ``in_header`` object with a field named ``Cookie`` (case sensitive). As an example, let's assume the following HTTP request: :: GET / HTTP/1.0 Cookie: v1=4;v2=8 (...) The keys ``v1`` and ``v2`` are passed to the instance of the ``in_header`` class if it has fields named ``v1`` or ``v2``\. Wrappers ======== Wrapper objects are an artifact of the Xml world, which don't really make sense in other protocols. Let's look at the following object: :: v = Permission(application='app', feature='f1'), Here's how it would be serialized to XML: :: app f1 With ``ignore_wrappers=True`` (which is the default) This gets serialized to dict as follows: :: { "application": "app", "feature": "f1" } When ``ignore_wrappers=False``, the same value/type combination would result in the following dict: :: {"Permission": { { "application": "app", "feature": "f1" } }, This could come in handy in case you don't know what type to expect. """ import logging logger = logging.getLogger(__name__) import re RE_HTTP_ARRAY_INDEX = re.compile("\\[([0-9]+)\\]") from collections import deque from collections import defaultdict from spyne.util import six from spyne.error import ValidationError from spyne.error import ResourceNotFoundError from spyne.model import ByteArray from spyne.model import String from spyne.model import File from spyne.model import Fault from spyne.model import ComplexModelBase from spyne.model import Array from spyne.model import SimpleModel from spyne.model import AnyDict from spyne.model import AnyXml from spyne.model import AnyHtml from spyne.model import Uuid from spyne.model import DateTime from spyne.model import Date from spyne.model import Time from spyne.model import Duration from spyne.model import Unicode from spyne.protocol import ProtocolBase def _check_freq_dict(cls, d, fti=None): if fti is None: fti = cls.get_flat_type_info(cls) for k, v in fti.items(): val = d[k] min_o, max_o = v.Attributes.min_occurs, v.Attributes.max_occurs if issubclass(v, Array) and v.Attributes.max_occurs == 1: v, = v._type_info.values() min_o, max_o = v.Attributes.min_occurs, v.Attributes.max_occurs if val < min_o: raise ValidationError("%r.%s" % (cls, k), '%%s member must occur at least %d times.' % min_o) elif val > max_o: raise ValidationError("%r.%s" % (cls, k), '%%s member must occur at most %d times.' % max_o) def _s2cmi(m, nidx): """ Sparse to contigous mapping inserter. >>> m1={3:0, 4:1, 7:2} >>> _s2cmi(m1, 5); m1 1 {3: 0, 4: 1, 5: 2, 7: 3} >>> _s2cmi(m1, 0); m1 0 {0: 0, 3: 1, 4: 2, 5: 3, 7: 4} >>> _s2cmi(m1, 8); m1 4 {0: 0, 3: 1, 4: 2, 5: 3, 7: 4, 8: 5} """ nv = -1 for i, v in m.items(): if i >= nidx: m[i] += 1 elif v > nv: nv = v m[nidx] = nv + 1 return nv + 1 class DictDocument(ProtocolBase): """An abstract protocol that can use hierarchical or flat dicts as input and output documents. Implement ``serialize()``, ``deserialize()``, ``create_in_document()`` and ``create_out_string()`` to use this. """ # flags to be used in tests _decimal_as_string = False _huge_numbers_as_string = False def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, ignore_wrappers=True, complex_as=dict, ordered=False): super(DictDocument, self).__init__(app, validator, mime_type, ignore_uncap, ignore_wrappers) self.complex_as = complex_as self.ordered = ordered if ordered: raise NotImplementedError('ordered == False') self.stringified_types = (DateTime, Date, Time, Uuid, Duration, AnyXml, AnyHtml) def set_validator(self, validator): """Sets the validator for the protocol. :param validator: one of ('soft', None) """ if validator == 'soft' or validator is self.SOFT_VALIDATION: self.validator = self.SOFT_VALIDATION elif validator is None: self.validator = None else: raise ValueError(validator) def decompose_incoming_envelope(self, ctx, message): """Sets ``ctx.in_body_doc``, ``ctx.in_header_doc`` and ``ctx.method_request_string`` using ``ctx.in_document``. """ assert message in (ProtocolBase.REQUEST, ProtocolBase.RESPONSE) # set ctx.in_header ctx.transport.in_header_doc = None # use an rpc protocol if you want headers. doc = ctx.in_document ctx.in_header_doc = None ctx.in_body_doc = doc if len(doc) == 0: raise Fault("Client", "Empty request") logger.debug('\theader : %r' % (ctx.in_header_doc)) logger.debug('\tbody : %r' % (ctx.in_body_doc)) if not isinstance(doc, dict) or len(doc) != 1: raise ValidationError("Need a dictionary with exactly one key " "as method name.") mrs, = doc.keys() ctx.method_request_string = '{%s}%s' % (self.app.interface.get_tns(), mrs) def deserialize(self, ctx, message): raise NotImplementedError() def serialize(self, ctx, message): raise NotImplementedError() def create_in_document(self, ctx, in_string_encoding=None): raise NotImplementedError() def create_out_string(self, ctx, out_string_encoding='utf8'): raise NotImplementedError() class SimpleDictDocument(DictDocument): """This protocol contains logic for protocols that serialize and deserialize flat dictionaries. The only example as of now is Http. """ def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, ignore_wrappers=True, complex_as=dict, ordered=False, hier_delim='.', strict_arrays=False): super(SimpleDictDocument, self).__init__(app=app, validator=validator, mime_type=mime_type, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers, complex_as=complex_as, ordered=ordered) self.hier_delim = hier_delim self.strict_arrays = strict_arrays def simple_dict_to_object(self, doc, inst_class, validator=None, req_enc=None): """Converts a flat dict to a native python object. See :func:`spyne.model.complex.ComplexModelBase.get_flat_type_info`. """ if issubclass(inst_class, AnyDict): return doc assert issubclass(inst_class, ComplexModelBase), "Patches are welcome" # this is for validating cls.Attributes.{min,max}_occurs frequencies = defaultdict(lambda: defaultdict(int)) if validator is self.SOFT_VALIDATION: _fill(inst_class, frequencies) retval = inst_class.get_deserialization_instance() simple_type_info = inst_class.get_simple_type_info(inst_class, hier_delim=self.hier_delim) idxmap = defaultdict(dict) for orig_k, v in sorted(doc.items(), key=lambda k: k[0]): k = RE_HTTP_ARRAY_INDEX.sub("", orig_k) member = simple_type_info.get(k, None) if member is None: logger.debug("discarding field %r" % k) continue # extract native values from the list of strings in the flat dict # entries. value = [] for v2 in v: # some wsgi implementations pass unicode strings, some pass str # strings. we get unicode here when we can and should. if v2 is not None and req_enc is not None \ and not issubclass(member.type, String) \ and issubclass(member.type, Unicode) \ and not isinstance(v2, unicode): v2 = v2.decode(req_enc) try: if (validator is self.SOFT_VALIDATION and not member.type.validate_string(member.type, v2)): raise ValidationError((orig_k, v2)) except TypeError: raise ValidationError((orig_k, v2)) if issubclass(member.type, File): if isinstance(v2, File.Value): native_v2 = v2 else: native_v2 = self.from_string(member.type, v2, self.default_binary_encoding) elif issubclass(member.type, ByteArray): native_v2 = self.from_string(member.type, v2, self.default_binary_encoding) else: try: native_v2 = self.from_string(member.type, v2) except ValidationError as e: raise ValidationError(str(e), "Validation failed for %r.%r: %%r" % (inst_class, k)) if (validator is self.SOFT_VALIDATION and not member.type.validate_native(member.type, native_v2)): raise ValidationError((orig_k, v2)) value.append(native_v2) # assign the native value to the relevant class in the nested object # structure. cinst = retval ctype_info = inst_class.get_flat_type_info(inst_class) idx, nidx = 0, 0 pkey = member.path[0] cfreq_key = inst_class, idx indexes = deque(RE_HTTP_ARRAY_INDEX.findall(orig_k)) for pkey in member.path[:-1]: nidx = 0 ncls, ninst = ctype_info[pkey], getattr(cinst, pkey, None) if issubclass(ncls, Array): ncls, = ncls._type_info.values() mo = ncls.Attributes.max_occurs if mo > 1: if len(indexes) == 0: nidx = 0 else: nidx = int(indexes.popleft()) if ninst is None: ninst = [] cinst._safe_set(pkey, ninst, ncls) if self.strict_arrays: if len(ninst) == 0: newval = ncls.get_deserialization_instance() ninst.append(newval) frequencies[cfreq_key][pkey] += 1 if nidx > len(ninst): raise ValidationError(orig_k, "%%r Invalid array index %d." % idx) if nidx == len(ninst): ninst.append(ncls.get_deserialization_instance()) frequencies[cfreq_key][pkey] += 1 cinst = ninst[nidx] else: _m = idxmap[id(ninst)] cidx = _m.get(nidx, None) if cidx is None: cidx = _s2cmi(_m, nidx) newval = ncls.get_deserialization_instance() ninst.insert(cidx, newval) frequencies[cfreq_key][pkey] += 1 cinst = ninst[cidx] assert cinst is not None, ninst else: if ninst is None: ninst = ncls.get_deserialization_instance() cinst._safe_set(pkey, ninst, ncls) frequencies[cfreq_key][pkey] += 1 cinst = ninst cfreq_key = cfreq_key + (ncls, nidx) idx = nidx ctype_info = ncls.get_flat_type_info(ncls) frequencies[cfreq_key][member.path[-1]] += len(value) if member.type.Attributes.max_occurs > 1: _v = getattr(cinst, member.path[-1], None) if _v is None: cinst._safe_set(member.path[-1], value, member.type) else: _v.extend(value) logger.debug("\tset array %r(%r) = %r" % (member.path, pkey, value)) else: cinst._safe_set(member.path[-1], value[0], member.type) logger.debug("\tset default %r(%r) = %r" % (member.path, pkey, value)) if validator is self.SOFT_VALIDATION: for k, d in frequencies.items(): for path_cls in k[:-1:2]: if not path_cls.Attributes.validate_freq: break else: _check_freq_dict(path_cls, d) return retval def object_to_simple_dict(self, inst_cls, value, retval=None, prefix=None, parent=None, subvalue_eater=lambda prot,v,t:v, tags=None): """Converts a native python object to a flat dict. See :func:`spyne.model.complex.ComplexModelBase.get_flat_type_info`. """ if retval is None: retval = {} if prefix is None: prefix = [] if value is None and inst_cls.Attributes.min_occurs == 0: return retval if tags is None: tags = set([id(value)]) else: if id(value) in tags: return retval if issubclass(inst_cls, ComplexModelBase): fti = inst_cls.get_flat_type_info(inst_cls) for k, v in fti.items(): new_prefix = list(prefix) new_prefix.append(k) subvalue = getattr(value, k, None) if (issubclass(v, Array) or v.Attributes.max_occurs > 1) and \ subvalue is not None: if issubclass(v, Array): subtype, = v._type_info.values() else: subtype = v if issubclass(subtype, SimpleModel): key = self.hier_delim.join(new_prefix) l = [] for ssv in subvalue: l.append(subvalue_eater(self, ssv, subtype)) retval[key] = l else: last_prefix = new_prefix[-1] for i, ssv in enumerate(subvalue): new_prefix[-1] = '%s[%d]' % (last_prefix, i) self.object_to_simple_dict(subtype, ssv, retval, new_prefix, parent=inst_cls, subvalue_eater=subvalue_eater, tags=tags) else: self.object_to_simple_dict(v, subvalue, retval, new_prefix, parent=inst_cls, subvalue_eater=subvalue_eater, tags=tags) else: key = self.hier_delim.join(prefix) if key in retval: raise ValueError("%r.%s conflicts with previous value %r" % (inst_cls, key, retval[key])) retval[key] = subvalue_eater(self, value, inst_cls) return retval class HierDictDocument(DictDocument): """This protocol contains logic for protocols that serialize and deserialize hierarchical dictionaries. Examples include: Json, MessagePack and Yaml. Implement ``create_in_document()`` and ``create_out_string()`` to use this. """ def deserialize(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_deserialize', ctx) if ctx.descriptor is None: raise ResourceNotFoundError(ctx.method_request_string) # instantiate the result message if message is self.REQUEST: body_class = ctx.descriptor.in_message elif message is self.RESPONSE: body_class = ctx.descriptor.out_message if body_class: # assign raw result to its wrapper, result_message result_class = ctx.descriptor.in_message value = ctx.in_body_doc.get(result_class.get_type_name(), None) result_message = self._doc_to_object(result_class, value, self.validator) ctx.in_object = result_message else: ctx.in_object = [] self.event_manager.fire_event('after_deserialize', ctx) def serialize(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_serialize', ctx) if ctx.out_error is not None: ctx.out_document = [Fault.to_dict(ctx.out_error.__class__, ctx.out_error)] else: # get the result message if message is self.REQUEST: out_type = ctx.descriptor.in_message elif message is self.RESPONSE: out_type = ctx.descriptor.out_message if out_type is None: return out_type_info = out_type.get_flat_type_info(out_type) # instantiate the result message out_instance = out_type() # assign raw result to its wrapper, result_message for i, (k, v) in enumerate(out_type_info.items()): attr_name = k out_instance._safe_set(attr_name, ctx.out_object[i], v) ctx.out_document = self._object_to_doc(out_type, out_instance), self.event_manager.fire_event('after_serialize', ctx) def validate(self, key, class_, value): # validate raw input if issubclass(class_, Unicode) and not isinstance(value, six.string_types): raise ValidationError((key, value)) def _from_dict_value(self, key, class_, value, validator): if validator is self.SOFT_VALIDATION: self.validate(key, class_, value) if issubclass(class_, AnyDict): return value # get native type if issubclass(class_, File) and isinstance(value, self.complex_as): retval = self._doc_to_object(File.Value, value, validator) elif issubclass(class_, ComplexModelBase): retval = self._doc_to_object(class_, value, validator) else: if value == '' and class_.Attributes.empty_is_none: value = None if (validator is self.SOFT_VALIDATION and isinstance(value, six.string_types) and not class_.validate_string(class_, value)): raise ValidationError((key, value)) if issubclass(class_, (ByteArray, File)): retval = self.from_string(class_, value, self.default_binary_encoding) else: retval = self.from_string(class_, value) # validate native type if validator is self.SOFT_VALIDATION and \ not class_.validate_native(class_, retval): raise ValidationError((key, retval)) return retval def _doc_to_object(self, class_, doc, validator=None): if doc is None: return [] if issubclass(class_, Array): retval = [ ] (serializer,) = class_._type_info.values() for i,child in enumerate(doc): retval.append(self._from_dict_value(i, serializer, child, validator)) return retval inst = class_.get_deserialization_instance() # get all class attributes, including the ones coming from parent classes. flat_type_info = class_.get_flat_type_info(class_) # this is for validating class_.Attributes.{min,max}_occurs frequencies = defaultdict(int) try: items = doc.items() except AttributeError: # Input is not a dict, so we assume it's a sequence that we can pair # with the incoming sequence with field names. items = zip(flat_type_info.keys(), doc) # parse input to set incoming data to related attributes. for k, v in items: member = flat_type_info.get(k, None) if member is None: continue mo = member.Attributes.max_occurs if mo > 1: value = getattr(inst, k, None) if value is None: value = [] for a in v: value.append(self._from_dict_value(k, member, a, validator)) else: value = self._from_dict_value(k, member, v, validator) inst._safe_set(k, value, member) frequencies[k] += 1 if validator is self.SOFT_VALIDATION and class_.Attributes.validate_freq: _check_freq_dict(class_, frequencies, flat_type_info) return inst def _object_to_doc(self, cls, value): retval = None if self.ignore_wrappers: ti = getattr(cls, '_type_info', {}) while cls.Attributes._wrapper and len(ti) == 1: # Wrappers are auto-generated objects that have exactly one # child type. key, = ti.keys() if not issubclass(cls, Array): value = getattr(value, key, None) cls, = ti.values() ti = getattr(cls, '_type_info', {}) # transform the results into a dict: if cls.Attributes.max_occurs > 1: if value is not None: retval = [self._to_value(cls, inst) for inst in value] else: retval = self._to_value(cls, value) return retval def _get_member_pairs(self, class_, inst): parent_cls = getattr(class_, '__extends__', None) if parent_cls is not None: for r in self._get_member_pairs(parent_cls, inst): yield r for k, v in class_.get_flat_type_info(class_).items(): if getattr(v.Attributes, 'exc_dict', None): continue try: sub_value = getattr(inst, k, None) # to guard against e.g. sqlalchemy throwing NoSuchColumnError except Exception as e: logger.error("Error getting %r: %r" %(k,e)) sub_value = None if sub_value is None: sub_value = v.Attributes.default val = self._object_to_doc(v, sub_value) min_o = v.Attributes.min_occurs if val is not None or min_o > 0 or self.complex_as is list: sub_name = v.Attributes.sub_name if sub_name is None: sub_name = k yield (sub_name, val) def _to_value(self, cls, value): if issubclass(cls, AnyDict): return value if issubclass(cls, Array): st, = cls._type_info.values() return self._object_to_doc(st, value) if issubclass(cls, ComplexModelBase): return self._complex_to_doc(cls, value) if issubclass(cls, File) and isinstance(value, File.Value): retval = self._complex_to_doc(File.Value, value) if self.complex_as is dict and not self.ignore_wrappers: retval = iter(retval.values()).next() return retval if issubclass(cls, (ByteArray, File)): return self.to_string(cls, value, self.default_binary_encoding) return self.to_string(cls, value) def _complex_to_doc(self, cls, value): if self.complex_as is list: return list(self._complex_to_list(cls, value)) else: return self._complex_to_dict(cls, value) def _complex_to_dict(self, class_, inst): inst = class_.get_serialization_instance(inst) d = self.complex_as(self._get_member_pairs(class_, inst)) if self.ignore_wrappers: return d else: return {class_.get_type_name(): d} def _complex_to_list(self, class_, inst): inst = class_.get_serialization_instance(inst) for k,v in self._get_member_pairs(class_, inst): yield v def _fill(inst_class, frequencies): """This function initializes the frequencies dict with null values. If this is not done, it won't be possible to catch missing elements when validating the incoming document. """ ctype_info = inst_class.get_flat_type_info(inst_class) cfreq_key = inst_class, 0 for k,v in ctype_info.items(): if v.Attributes.min_occurs > 0: frequencies[cfreq_key][k] = 0 spyne-2.11.0/spyne/protocol/http.py0000644000175000001440000003163012345433230017236 0ustar plqusers00000000000000 # # 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 # """The ``spyne.protocol.http`` module contains the HttpRpc protocol implementation. """ import logging logger = logging.getLogger(__name__) import re import pytz import tempfile from spyne.util.six import string_types, BytesIO, PY3 if PY3: from http.cookies import SimpleCookie else: from Cookie import SimpleCookie from spyne import BODY_STYLE_WRAPPED, MethodDescriptor from spyne.error import ResourceNotFoundError from spyne.model.binary import BINARY_ENCODING_URLSAFE_BASE64, File from spyne.model.primitive import DateTime from spyne.protocol.dictdoc import SimpleDictDocument try: from io import StringIO except ImportError: # Python 2 try: from cStringIO import StringIO except ImportError: from StringIO import StringIO TEMPORARY_DIR = None STREAM_READ_BLOCK_SIZE = 0x4000 SWAP_DATA_TO_FILE_THRESHOLD = 512 * 1024 def get_stream_factory(dir=None, delete=True): def stream_factory(total_content_length, filename, content_type, content_length=None): if total_content_length >= SWAP_DATA_TO_FILE_THRESHOLD or \ delete == False: if delete == False: # You need python >= 2.6 for this. retval = tempfile.NamedTemporaryFile('wb+', dir=dir, delete=delete) else: retval = tempfile.NamedTemporaryFile('wb+', dir=dir) else: retval = BytesIO() return retval return stream_factory _weekday = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] _month = ['w00t', "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] def _header_to_string(prot, val, cls): if issubclass(cls, DateTime): if val.tzinfo is not None: val = val.astimezone(pytz.utc) else: val = val.replace(tzinfo=pytz.utc) return "%s, %02d %s %04d %02d:%02d:%02d GMT" % ( _weekday[val.weekday()], val.day, _month[val.month], val.year, val.hour, val.minute, val.second) else: return prot.to_string(cls, val) class HttpRpc(SimpleDictDocument): """The so-called HttpRpc protocol implementation. It only works with Http (wsgi and twisted) transports. :param app: An :class:'spyne.application.Application` instance. :param validator: Validation method to use. One of (None, 'soft') :param mime_type: Default mime type to set. Default is 'application/octet-stream' :param tmp_dir: Temporary directory to store partial file uploads. Default is to use the OS default. :param tmp_delete_on_close: The ``delete`` argument to the :class:`tempfile.NamedTemporaryFile`. See: http://docs.python.org/2/library/tempfile.html#tempfile.NamedTemporaryFile. :param ignore_uncap: As HttpRpc can't serialize complex models, it throws a server exception when the return type of the user function is Complex. Passing ``True`` to this argument prevents that by ignoring the return value. """ mime_type = 'text/plain' default_binary_encoding = BINARY_ENCODING_URLSAFE_BASE64 type = set(SimpleDictDocument.type) type.add('http') def __init__(self, app=None, validator=None, mime_type=None, tmp_dir=None, tmp_delete_on_close=True, ignore_uncap=False, parse_cookie=True, hier_delim=".", strict_arrays=False): super(HttpRpc, self).__init__(app, validator, mime_type, ignore_uncap=ignore_uncap, hier_delim=hier_delim, strict_arrays=strict_arrays) self.tmp_dir = tmp_dir self.tmp_delete_on_close = tmp_delete_on_close self.parse_cookie = parse_cookie def get_tmp_delete_on_close(self): return self.__tmp_delete_on_close def set_tmp_delete_on_close(self, val): self.__tmp_delete_on_close = val self.stream_factory = get_stream_factory(self.tmp_dir, self.__tmp_delete_on_close) tmp_delete_on_close = property(get_tmp_delete_on_close, set_tmp_delete_on_close) def set_validator(self, validator): if validator == 'soft' or validator is self.SOFT_VALIDATION: self.validator = self.SOFT_VALIDATION elif validator is None: self.validator = None else: raise ValueError(validator) def create_in_document(self, ctx, in_string_encoding=None): assert ctx.transport.type.endswith('http'), \ ("This protocol only works with an http transport, not %r, (in %r)" % (ctx.transport.type, ctx.transport)) ctx.in_document = ctx.transport.req ctx.transport.request_encoding = in_string_encoding def decompose_incoming_envelope(self, ctx, message): assert message == SimpleDictDocument.REQUEST ctx.transport.itself.decompose_incoming_envelope(self, ctx, message) if self.parse_cookie: cookies = ctx.in_header_doc.get('cookie', None) if cookies is None: cookies = ctx.in_header_doc.get('Cookie', None) if cookies is not None: for cookie_string in cookies: cookie = SimpleCookie() cookie.load(cookie_string) for k,v in cookie.items(): l = ctx.in_header_doc.get(k, []) l.append(v.coded_value) ctx.in_header_doc[k] = l logger.debug('\theader : %r' % (ctx.in_header_doc)) logger.debug('\tbody : %r' % (ctx.in_body_doc)) def deserialize(self, ctx, message): assert message in (self.REQUEST,) self.event_manager.fire_event('before_deserialize', ctx) if ctx.descriptor is None: raise ResourceNotFoundError(ctx.method_request_string) req_enc = getattr(ctx.transport, 'request_encoding', None) if ctx.descriptor.in_header is not None: # HttpRpc supports only one header class in_header_class = ctx.descriptor.in_header[0] ctx.in_header = self.simple_dict_to_object(ctx.in_header_doc, in_header_class, self.validator, req_enc=req_enc) if ctx.descriptor.in_message is not None: ctx.in_object = self.simple_dict_to_object(ctx.in_body_doc, ctx.descriptor.in_message, self.validator, req_enc=req_enc) self.event_manager.fire_event('after_deserialize', ctx) def serialize(self, ctx, message): assert message in (self.RESPONSE,) if ctx.out_document is not None: return if ctx.out_error is None: result_class = ctx.descriptor.out_message header_class = ctx.descriptor.out_header if header_class is not None: # HttpRpc supports only one header class header_class = header_class[0] # assign raw result to its wrapper, result_message if ctx.out_object is None or len(ctx.out_object) < 1: ctx.out_document = [''] else: out_class = None out_object = None if ctx.descriptor.body_style is BODY_STYLE_WRAPPED: fti = result_class.get_flat_type_info(result_class) if len(fti) > 1 and not self.ignore_uncap: raise TypeError("HttpRpc protocol can only " "serialize functions with a single return type.") if len(fti) == 1: out_class, = fti.values() out_object, = ctx.out_object else: out_class = result_class out_object, = ctx.out_object if out_class is not None: ctx.out_document = self.to_string_iterable(out_class, out_object) if issubclass(out_class, File) and not \ isinstance(out_object, (list, tuple, string_types)) \ and out_object.type is not None: ctx.transport.set_mime_type(str(out_object.type)) # header if ctx.out_header is not None: out_header = ctx.out_header if isinstance(ctx.out_header, (list, tuple)): out_header = ctx.out_header[0] ctx.out_header_doc = self.object_to_simple_dict(header_class, out_header, subvalue_eater=_header_to_string) else: ctx.transport.mime_type = 'text/plain' ctx.out_document = ctx.out_error.to_string_iterable(ctx.out_error) self.event_manager.fire_event('serialize', ctx) def create_out_string(self, ctx, out_string_encoding='utf8'): if ctx.out_string is not None: return ctx.out_string = ctx.out_document _fragment_pattern_re = re.compile('<([A-Za-z0-9_]+)>') _full_pattern_re = re.compile('{([A-Za-z0-9_]+)}') class HttpPattern(object): """Experimental. Stay away. :param address: Address pattern :param verb: HTTP Verb pattern :param host: HTTP "Host:" header pattern """ @staticmethod def _compile_url_pattern(pattern): """where <> placeholders don't contain slashes.""" if pattern is None: return None pattern = _fragment_pattern_re.sub(r'(?P<\1>[^/]*)', pattern) pattern = _full_pattern_re.sub(r'(?P<\1>[^/]*)', pattern) return re.compile(pattern) @staticmethod def _compile_host_pattern(pattern): """where <> placeholders don't contain dots.""" if pattern is None: return None pattern = _fragment_pattern_re.sub(r'(?P<\1>[^\.]*)', pattern) pattern = _full_pattern_re.sub(r'(?P<\1>.*)', pattern) return re.compile(pattern) @staticmethod def _compile_verb_pattern(pattern): """where <> placeholders are same as {} ones.""" if pattern is None: return None pattern = _fragment_pattern_re.sub(r'(?P<\1>.*)', pattern) pattern = _full_pattern_re.sub(r'(?P<\1>.*)', pattern) return re.compile(pattern) def __init__(self, address=None, verb=None, host=None, endpoint=None): self.address = address self.host = host self.verb = verb self.endpoint = endpoint if self.endpoint is not None: assert isinstance(self.endpoint, MethodDescriptor) def hello(self, descriptor): if self.address is None: self.address = descriptor.name @property def address(self): return self.__address @address.setter def address(self, what): self.__address = what self.address_re = self._compile_url_pattern(what) @property def host(self): return self.__host @host.setter def host(self, what): self.__host = what self.host_re = self._compile_host_pattern(what) @property def verb(self): return self.__verb @verb.setter def verb(self, what): self.__verb = what self.verb_re = self._compile_verb_pattern(what) def as_werkzeug_rule(self): from werkzeug.routing import Rule from spyne.util.invregexp import invregexp methods = None if self.verb is not None: methods = invregexp(self.verb) host = self.host if host is None: host = '<__ignored>' # for some reason, this is necessary when # host_matching is enabled. return Rule(self.address, host=host, endpoint=self.endpoint.name, methods=methods) def __repr__(self): return "HttpPattern(address=%r, host=%r, verb=%r, endpoint=%r" % ( self.address, self.host, self.verb, self.endpoint.name) spyne-2.11.0/spyne/protocol/json.py0000644000175000001440000003073212345433230017232 0ustar plqusers00000000000000 # # 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 # """The ``spyne.protocol.json`` package contains the Json-related protocols. Currently, only :class:`spyne.protocol.json.JsonDocument` is supported. Initially released in 2.8.0-rc. This module is EXPERIMENTAL. You may not recognize the code here next time you look at it. Missing Types ============= The JSON standard does not define every type that Spyne supports. These include Date/Time types as well as arbitrary-length integers and arbitrary-precision decimals. Integers are parsed to ``int``\s or ``long``\s seamlessly but ``Decimal``\s are only parsed correctly when they come off as strings. While it's possible to e.g. (de)serialize floats to ``Decimal``\s by adding hooks to ``parse_float`` [#]_ (and convert later as necessary), such customizations apply to the whole incoming document which pretty much messes up ``AnyDict`` serialization and deserialization. It also wasn't possible to work with ``object_pairs_hook`` as Spyne's parsing is always "from outside to inside" whereas ``object_pairs_hook`` is passed ``dict``\s basically in any order "from inside to outside". .. [#] http://docs.python.org/2/library/json.html#json.loads """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) from spyne.util import six from itertools import chain try: import simplejson as json from simplejson.decoder import JSONDecodeError except ImportError: import json JSONDecodeError = ValueError from spyne.error import ValidationError from spyne.error import ResourceNotFoundError from spyne.model.binary import BINARY_ENCODING_BASE64 from spyne.model.primitive import Date from spyne.model.primitive import Time from spyne.model.primitive import DateTime from spyne.model.primitive import Double from spyne.model.primitive import Integer from spyne.model.primitive import Boolean from spyne.model.fault import Fault from spyne.protocol.dictdoc import HierDictDocument class JsonEncoder(json.JSONEncoder): def default(self, o): try: return super(JsonEncoder, self).default(o) except TypeError as e: # if json can't serialize it, it's possibly a generator. If not, # additional hacks are welcome :) if logger.level == logging.DEBUG: logger.exception(e) return list(o) class JsonDocument(HierDictDocument): """An implementation of the json protocol that uses simplejson package when available, json package otherwise. :param ignore_wrappers: Does not serialize wrapper objects. :param complex_as: One of (list, dict). When list, the complex objects are serialized to a list of values instead of a dict of key/value pairs. """ mime_type = 'application/json' type = set(HierDictDocument.type) type.add('json') default_binary_encoding = BINARY_ENCODING_BASE64 # flags used just for tests _decimal_as_string = True def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, # DictDocument specific ignore_wrappers=True, complex_as=dict, ordered=False, default_string_encoding=None, **kwargs): super(JsonDocument, self).__init__(app, validator, mime_type, ignore_uncap, ignore_wrappers, complex_as, ordered) # this is needed when we're overriding a regular instance attribute # with a property. self.__message = HierDictDocument.__getattribute__(self, 'message') self._from_string_handlers[Double] = self._ret self._from_string_handlers[Boolean] = self._ret self._from_string_handlers[Integer] = self._ret self._to_string_handlers[Double] = self._ret self._to_string_handlers[Boolean] = self._ret self._to_string_handlers[Integer] = self._ret self.default_string_encoding = default_string_encoding self.kwargs = kwargs def _ret(self, cls, value): return value def validate(self, key, cls, val): super(JsonDocument, self).validate(key, cls, val) if issubclass(cls, (DateTime, Date, Time)) and not ( isinstance(val, six.string_types) and cls.validate_string(cls, val)): raise ValidationError(key, val) @property def message(self): return self.__message @message.setter def message(self, val): if val is self.RESPONSE and not ('cls' in self.kwargs): self.kwargs['cls'] = JsonEncoder self.__message = val def create_in_document(self, ctx, in_string_encoding=None): """Sets ``ctx.in_document`` using ``ctx.in_string``.""" try: in_string = ''.join(ctx.in_string) if not isinstance(in_string, six.text_type): if in_string_encoding is None: in_string_encoding = self.default_string_encoding if in_string_encoding is not None: in_string = in_string.decode(in_string_encoding) ctx.in_document = json.loads(in_string, **self.kwargs) except JSONDecodeError as e: raise Fault('Client.JsonDecodeError', repr(e)) def create_out_string(self, ctx, out_string_encoding='utf8'): """Sets ``ctx.out_string`` using ``ctx.out_document``.""" ctx.out_string = (json.dumps(o, **self.kwargs) for o in ctx.out_document) class JsonP(JsonDocument): """The JsonP protocol puts the reponse document inside a designated javascript function call. The input protocol is identical to the JsonDocument protocol. :param callback_name: The name of the function call that will wrapp all response documents. For other arguents, see :class:`spyne.protocol.json.JsonDocument`. """ type = set(HierDictDocument.type) type.add('jsonp') def __init__(self, callback_name, *args, **kwargs): super(JsonP, self).__init__(*args, **kwargs) self.callback_name = callback_name def create_out_string(self, ctx): super(JsonP, self).create_out_string(ctx) ctx.out_string = chain( [self.callback_name, '('], ctx.out_string, [');'], ) class _SpyneJsonRpc1(JsonDocument): version = 1 VERSION = 'ver' BODY = 'body' HEAD = 'head' FAULT = 'fault' def decompose_incoming_envelope(self, ctx, message=JsonDocument.REQUEST): indoc = ctx.in_document if not isinstance(indoc, dict): raise ValidationError("Invalid Request") ver = indoc.get(self.VERSION) if ver is None: raise ValidationError("Missing Version") body = indoc.get(self.BODY) err = indoc.get(self.FAULT) if body is None and err is None: raise ValidationError("Missing request") ctx.protocol.error = False if err is not None: ctx.in_body_doc = err ctx.protocol.error = True else: if not isinstance(body, dict): raise ValidationError("Missing request body") if not len(body) == 1: raise ValidationError("Need len(body) == 1") ctx.in_header_doc = indoc.get(self.HEAD) if not isinstance(ctx.in_header_doc, list): ctx.in_header_doc = [ctx.in_header_doc] (ctx.method_request_string,ctx.in_body_doc), = body.items() def deserialize(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_deserialize', ctx) if ctx.descriptor is None: raise ResourceNotFoundError(ctx.method_request_string) if ctx.protocol.error: ctx.in_object = None ctx.in_error = self._doc_to_object(Fault, ctx.in_body_doc) else: if message is self.REQUEST: header_class = ctx.descriptor.in_header body_class = ctx.descriptor.in_message elif message is self.RESPONSE: header_class = ctx.descriptor.out_header body_class = ctx.descriptor.out_message # decode header objects if (ctx.in_header_doc is not None and header_class is not None): headers = [None] * len(header_class) for i, (header_doc, head_class) in enumerate( zip(ctx.in_header_doc, header_class)): if header_doc is not None and i < len(header_doc): headers[i] = self._doc_to_object(head_class, header_doc) if len(headers) == 1: ctx.in_header = headers[0] else: ctx.in_header = headers # decode method arguments if ctx.in_body_doc is None: ctx.in_object = [None] * len(body_class._type_info) else: ctx.in_object = self._doc_to_object(body_class, ctx.in_body_doc) self.event_manager.fire_event('after_deserialize', ctx) def serialize(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_serialize', ctx) # construct the soap response, and serialize it nsmap = self.app.interface.nsmap ctx.out_document = { "ver": self.version, } if ctx.out_error is not None: ctx.out_document[self.FAULT] = Fault.to_dict(Fault, ctx.out_error) else: if message is self.REQUEST: header_message_class = ctx.descriptor.in_header body_message_class = ctx.descriptor.in_message elif message is self.RESPONSE: header_message_class = ctx.descriptor.out_header body_message_class = ctx.descriptor.out_message # assign raw result to its wrapper, result_message out_type_info = body_message_class._type_info out_object = body_message_class() keys = iter(out_type_info) values = iter(ctx.out_object) while True: try: k = next(keys) except StopIteration: break try: v = next(values) except StopIteration: v = None setattr(out_object, k, v) ctx.out_document[self.BODY] = ctx.out_body_doc = \ self._object_to_doc(body_message_class, out_object) # header if ctx.out_header is not None and header_message_class is not None: if isinstance(ctx.out_header, (list, tuple)): out_headers = ctx.out_header else: out_headers = (ctx.out_header,) ctx.out_header_doc = out_header_doc = [] for header_class, out_header in zip(header_message_class, out_headers): out_header_doc.append(self._object_to_doc(header_class, out_header)) if len(out_header_doc) > 1: ctx.out_document[self.HEAD] = out_header_doc else: ctx.out_document[self.HEAD] = out_header_doc[0] self.event_manager.fire_event('after_serialize', ctx) _json_rpc_flavours = { 'spyne': _SpyneJsonRpc1 } def JsonRpc(flavour, *args, **kwargs): assert flavour in _json_rpc_flavours, "Unknown JsonRpc flavour. " \ "Accepted ones are: %r" % tuple(_json_rpc_flavours) return _json_rpc_flavours[flavour](*args, **kwargs) spyne-2.11.0/spyne/protocol/msgpack.py0000644000175000001440000002117212345433230017704 0ustar plqusers00000000000000 # # 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 # """The ``spyne.protocol.msgpack`` module contains implementations for protocols that use MessagePack as serializer. Initially released in 2.8.0-rc. This module is EXPERIMENTAL. You may not recognize the code here next time you look at it. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) from spyne.util import six import msgpack from spyne.model.fault import Fault from spyne.protocol.dictdoc import HierDictDocument from spyne.model.primitive import Double from spyne.model.primitive import Boolean from spyne.model.primitive import Integer class MessagePackDecodeError(Fault): def __init__(self, data=None): super(MessagePackDecodeError, self).__init__("Client.MessagePackDecodeError", data) class MessagePackDocument(HierDictDocument): """An integration class for the msgpack protocol.""" mime_type = 'application/x-msgpack' type = set(HierDictDocument.type) type.add('msgpack') # flags to be used in tests _decimal_as_string = True _huge_numbers_as_string = True def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, # DictDocument specific ignore_wrappers=True, complex_as=dict, ordered=False): super(MessagePackDocument, self).__init__(app, validator, mime_type, ignore_uncap, ignore_wrappers, complex_as, ordered) self._from_string_handlers[Double] = self._ret self._from_string_handlers[Boolean] = self._ret self._from_string_handlers[Integer] = self.integer_from_string self._to_string_handlers[Double] = self._ret self._to_string_handlers[Boolean] = self._ret self._to_string_handlers[Integer] = self.integer_to_string def _ret(self, cls, value): return value def create_in_document(self, ctx, in_string_encoding=None): """Sets ``ctx.in_document``, using ``ctx.in_string``. :param ctx: The MethodContext object :param in_string_encoding: MessagePack is a binary protocol. So this argument is ignored. """ try: ctx.in_document = msgpack.unpackb(b''.join(ctx.in_string)) except ValueError as e: raise MessagePackDecodeError(''.join(e.args)) if not isinstance(ctx.in_document, dict): logger.debug("reqobj: %r", ctx.in_document) raise MessagePackDecodeError("Request object must be a dictionary") def create_out_string(self, ctx, out_string_encoding='utf8'): ctx.out_string = (msgpack.packb(o) for o in ctx.out_document) def integer_from_string(self, cls, value): if isinstance(value, six.string_types): return super(MessagePackDocument, self).integer_from_string(cls, value) else: return value def integer_to_string(self, cls, value): if -1<<63 <= value < 1<<64: # if it's inside the range msgpack can deal with return value else: return super(MessagePackDocument, self).integer_to_string(cls, value) class MessagePackRpc(MessagePackDocument): """An integration class for the msgpack-rpc protocol.""" mime_type = 'application/x-msgpack' MSGPACK_REQUEST = 0 MSGPACK_RESPONSE = 1 MSGPACK_NOTIFY = 2 def create_in_document(self, ctx, in_string_encoding=None): """Sets ``ctx.in_document``, using ``ctx.in_string``. :param ctx: The MethodContext object :param in_string_encoding: MessagePack is a binary protocol. So this argument is ignored. """ # TODO: Use feed api try: ctx.in_document = msgpack.unpackb(b''.join(ctx.in_string)) except ValueError as e: raise MessagePackDecodeError(''.join(e.args)) try: len(ctx.in_document) except TypeError: raise MessagePackDecodeError("Input must be a sequence.") if not (3 <= len(ctx.in_document) <= 4): raise MessagePackDecodeError("Length of input iterable must be " "either 3 or 4") def decompose_incoming_envelope(self, ctx, message): # FIXME: For example: {0: 0, 1: 0, 2: "some_call", 3: [1,2,3]} will also # work. Is this a problem? # FIXME: Msgid is ignored. Is this a problem? msgparams = [] if len(ctx.in_document) == 3: msgtype, msgid, msgname = ctx.in_document else: msgtype, msgid, msgname, msgparams = ctx.in_document[:4] if msgtype == MessagePackRpc.MSGPACK_REQUEST: assert message == MessagePackRpc.REQUEST elif msgtype == MessagePackRpc.MSGPACK_RESPONSE: assert message == MessagePackRpc.RESPONSE elif msgtype == MessagePackRpc.MSGPACK_NOTIFY: raise NotImplementedError() else: raise MessagePackDecodeError("Unknown message type %r" % msgtype) ctx.method_request_string = '{%s}%s' % (self.app.interface.get_tns(), msgname) ctx.in_header_doc = None # MessagePackRpc does not seem to have Header support ctx.in_body_doc = msgparams logger.debug('\theader : %r' % (ctx.in_header_doc)) logger.debug('\tbody : %r' % (ctx.in_body_doc)) def deserialize(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_deserialize', ctx) if ctx.descriptor is None: raise Fault("Client", "Method %r not found." % ctx.method_request_string) # instantiate the result message if message is self.REQUEST: body_class = ctx.descriptor.in_message elif message is self.RESPONSE: body_class = ctx.descriptor.out_message else: raise Exception("what?") if body_class: ctx.in_object = body_class.get_serialization_instance( ctx.in_body_doc) else: ctx.in_object = [] self.event_manager.fire_event('after_deserialize', ctx) def serialize(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_serialize', ctx) if ctx.out_error is not None: ctx.out_document = [MessagePackRpc.MSGPACK_RESPONSE, 0, Fault.to_dict(ctx.out_error.__class__, ctx.out_error)] else: # get the result message if message is self.REQUEST: out_type = ctx.descriptor.in_message elif message is self.RESPONSE: out_type = ctx.descriptor.out_message else: raise Exception("what?") if out_type is None: return out_type_info = out_type._type_info # instantiate the result message out_instance = out_type() # assign raw result to its wrapper, result_message for i in range(len(out_type_info)): attr_name = out_type_info.keys()[i] setattr(out_instance, attr_name, ctx.out_object[i]) # transform the results into a dict: if out_type.Attributes.max_occurs > 1: ctx.out_document = [[MessagePackRpc.MSGPACK_RESPONSE, 0, None, (self._to_value(out_type, inst) for inst in out_instance)]] else: ctx.out_document = [[MessagePackRpc.MSGPACK_RESPONSE, 0, None, self._to_value(out_type, out_instance)]] self.event_manager.fire_event('after_serialize', ctx) spyne-2.11.0/spyne/protocol/xml.py0000644000175000001440000011214712352126507017066 0ustar plqusers00000000000000 # # 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 # """The ``spyne.protocol.xml`` module contains an xml-based protocol that serializes python objects to xml using Xml Schema conventions. Logs valid documents to ``'%r'`` and invalid documents to ``'%r'``. Use the usual ``logging.getLogger()`` and friends to configure how these get logged. Warning! You can get a lot of crap in the 'invalid' logger. You're not advised to turn it on for a production system. """ % ('spyne.protocol.xml', 'spyne.protocol.xml.invalid') import logging logger = logging.getLogger('spyne.protocol.xml') logger_invalid = logging.getLogger('spyne.protocol.xml.invalid') from inspect import isgenerator from collections import defaultdict from lxml import etree from lxml import html from lxml.builder import E from lxml.etree import XMLSyntaxError from lxml.etree import XMLParser from spyne import BODY_STYLE_WRAPPED from spyne.util import _bytes_join, Break, coroutine from spyne.util.six import text_type, string_types from spyne.util.cdict import cdict from spyne.util.etreeconv import etree_to_dict, dict_to_etree,\ root_dict_to_etree from spyne.error import Fault from spyne.error import ValidationError from spyne.const.ansi_color import LIGHT_GREEN from spyne.const.ansi_color import LIGHT_RED from spyne.const.ansi_color import END_COLOR from spyne.const.xml_ns import xsi as _ns_xsi from spyne.const.xml_ns import soap_env as _ns_soap_env from spyne.const.xml_ns import const_prefmap, DEFAULT_NS _pref_soap_env = const_prefmap[_ns_soap_env] from spyne.model import ModelBase from spyne.model import Array from spyne.model import Iterable from spyne.model import ComplexModelBase from spyne.model import AnyHtml from spyne.model import AnyXml from spyne.model import AnyDict from spyne.model import Unicode from spyne.model import PushBase from spyne.model import File from spyne.model import ByteArray from spyne.model import XmlData from spyne.model import XmlAttribute from spyne.model.binary import Attachment # deprecated from spyne.model.binary import BINARY_ENCODING_BASE64 from spyne.model.enum import EnumBase from spyne.protocol import ProtocolBase NIL_ATTR = {'{%s}nil' % _ns_xsi: 'true'} def _append(parent, child_elt): if hasattr(parent, 'append'): parent.append(child_elt) else: parent.write(child_elt) def _gen_tagname(ns, name): if ns is not None: name = "{%s}%s" % (ns, name) return name class SchemaValidationError(Fault): """Raised when the input stream could not be validated by the Xml Schema.""" def __init__(self, faultstring): super(SchemaValidationError, self) \ .__init__('Client.SchemaValidationError', faultstring) class SubXmlBase(ProtocolBase): def subserialize(self, ctx, cls, inst, parent, ns=None, name=None): return self.to_parent(ctx, cls, inst, parent, name) def to_parent(self, ctx, cls, inst, parent, ns, *args, **kwargs): """Serializes inst to an Element instance and appends it to the 'parent'. :param self: The protocol that will be used to serialize the given value. :param cls: The type of the value that's going to determine how to pack the given value. :param inst: The value to be set for the 'text' element of the newly created SubElement :param parent: The parent Element to which the new child will be appended. :param ns: The target namespace of the new SubElement, used with 'name' to set the tag. :param name: The tag name of the new SubElement, 'retval' by default. """ raise NotImplementedError() class XmlDocument(SubXmlBase): """The Xml input and output protocol, using the information from the Xml Schema generated by Spyne types. See the following material for more (much much more!) information. * http://www.w3.org/TR/xmlschema-0/ * http://www.w3.org/TR/xmlschema-1/ * http://www.w3.org/TR/xmlschema-2/ Receiving Xml from untrusted sources is a dodgy security dance as the Xml attack surface is /huge/. Spyne's ```lxml.etree.XMLParser``` instance has ```resolve_pis```, ```load_dtd```, ```resolve_entities```, ```dtd_validation```, ```huge_tree``` off by default. Having ```resolve_entities``` disabled will prevent the 'lxml' validation for documents with custom xml entities defined in the DTD. See the example in examples/xml/validation_error to play with the settings that work best for you. Please note that enabling ```resolve_entities``` is a security hazard that can lead to disclosure of sensitive information. See https://pypi.python.org/pypi/defusedxml for a pragmatic overview of Xml security in Python world. :param app: The owner application instance. :param validator: One of (None, 'soft', 'lxml', 'schema', ProtocolBase.SOFT_VALIDATION, XmlDocument.SCHEMA_VALIDATION). Both ``'lxml'`` and ``'schema'`` values are equivalent to ``XmlDocument.SCHEMA_VALIDATION``. :param xml_declaration: Whether to add xml_declaration to the responses Default is 'True'. :param cleanup_namespaces: Whether to add clean up namespace declarations in the response document. Default is 'True'. :param encoding: The suggested string encoding for the returned xml documents. The transport can override this. :param pretty_print: When ``True``, returns the document in a pretty-printed format. The following are passed straight to the XMLParser() instance. Docs are plagiarized from the lxml documentation. Please note that some of the defaults are different to make parsing safer by default. :param attribute_defaults: read the DTD (if referenced by the document) and add the default attributes from it. Off by default. :param dtd_validation: validate while parsing (if a DTD was referenced). Off by default. :param load_dtd: load and parse the DTD while parsing (no validation is performed). Off by default. :param no_network: prevent network access when looking up external documents. On by default. :param ns_clean: try to clean up redundant namespace declarations. Off by default. The note that this is for incoming documents. The ```cleanup_namespaces``` parameter is for output documents, which is that's on by default. :param recover: try hard to parse through broken Xml. Off by default. :param remove_blank_text: discard blank text nodes between tags, also known as ignorable whitespace. This is best used together with a DTD or schema (which tells data and noise apart), otherwise a heuristic will be applied. Off by default. :param remove_pis: discard processing instructions. On by default. :param strip_cdata: replace CDATA sections by normal text content. On by default. :param resolve_entities: replace entities by their text value. Off by default. :param huge_tree: disable security restrictions and support very deep trees and very long text content. (only affects libxml2 2.7+) Off by default. :param compact: use compact storage for short text content. On by default. """ SCHEMA_VALIDATION = type("Schema", (object,), {}) mime_type = 'text/xml' default_binary_encoding = BINARY_ENCODING_BASE64 type = set(ProtocolBase.type) type.add('xml') def __init__(self, app=None, validator=None, xml_declaration=True, cleanup_namespaces=True, encoding=None, pretty_print=False, attribute_defaults=False, dtd_validation=False, load_dtd=False, no_network=True, ns_clean=False, recover=False, remove_blank_text=False, remove_pis=True, strip_cdata=True, resolve_entities=False, huge_tree=False, compact=True, ): super(XmlDocument, self).__init__(app, validator) self.xml_declaration = xml_declaration self.cleanup_namespaces = cleanup_namespaces if encoding is None: self.encoding = 'UTF-8' else: self.encoding = encoding self.pretty_print = pretty_print self.serialization_handlers = cdict({ AnyXml: self.xml_to_parent, Fault: self.fault_to_parent, AnyDict: self.dict_to_parent, AnyHtml: self.html_to_parent, EnumBase: self.enum_to_parent, XmlData: self.xmldata_to_parent, ModelBase: self.modelbase_to_parent, ByteArray: self.byte_array_to_parent, Attachment: self.attachment_to_parent, XmlAttribute: self.xmlattribute_to_parent, ComplexModelBase: self.complex_to_parent, SchemaValidationError: self.schema_validation_error_to_parent, }) self.deserialization_handlers = cdict({ AnyXml: self.xml_from_element, Array: self.array_from_element, Fault: self.fault_from_element, AnyDict: self.dict_from_element, EnumBase: self.enum_from_element, ModelBase: self.base_from_element, Unicode: self.unicode_from_element, Iterable: self.iterable_from_element, ByteArray: self.byte_array_from_element, Attachment: self.attachment_from_element, ComplexModelBase: self.complex_from_element, }) self.log_messages = (logger.level == logging.DEBUG) self.parser_kwargs = dict( attribute_defaults=attribute_defaults, dtd_validation=dtd_validation, load_dtd=load_dtd, no_network=no_network, ns_clean=ns_clean, recover=recover, remove_blank_text=remove_blank_text, remove_comments=True, remove_pis=remove_pis, strip_cdata=strip_cdata, resolve_entities=resolve_entities, huge_tree=huge_tree, compact=compact, encoding=encoding, ) def subserialize(self, ctx, cls, inst, parent, ns=None, name=None): return self.to_parent(ctx, cls, inst, parent, name) def set_validator(self, validator): if validator in ('lxml', 'schema') or \ validator is self.SCHEMA_VALIDATION: self.validate_document = self.__validate_lxml self.validator = self.SCHEMA_VALIDATION elif validator == 'soft' or validator is self.SOFT_VALIDATION: self.validator = self.SOFT_VALIDATION elif validator is None: pass else: raise ValueError(validator) self.validation_schema = None def validate_body(self, ctx, message): """Sets ctx.method_request_string and calls :func:`generate_contexts` for validation.""" assert message in (self.REQUEST, self.RESPONSE), message line_header = LIGHT_RED + "Error:" + END_COLOR try: self.validate_document(ctx.in_body_doc) if message is self.REQUEST: line_header = LIGHT_GREEN + "Method request string:" + END_COLOR else: line_header = LIGHT_RED + "Response:" + END_COLOR finally: if self.log_messages: logger.debug("%s %s" % (line_header, ctx.method_request_string)) logger.debug(etree.tostring(ctx.in_document, pretty_print=True)) def set_app(self, value): ProtocolBase.set_app(self, value) self.validation_schema = None if value: from spyne.interface.xml_schema import XmlSchema xml_schema = XmlSchema(value.interface) xml_schema.build_validation_schema() self.validation_schema = xml_schema.validation_schema def __validate_lxml(self, payload): ret = self.validation_schema.validate(payload) logger.debug("Validated ? %r" % ret) if ret == False: error_text = text_type(self.validation_schema.error_log.last_error) raise SchemaValidationError(error_text.encode('ascii', 'xmlcharrefreplace')) def create_in_document(self, ctx, charset=None): """Uses the iterable of string fragments in ``ctx.in_string`` to set ``ctx.in_document``.""" string = _bytes_join(ctx.in_string) try: try: ctx.in_document = etree.fromstring(string, parser=XMLParser(**self.parser_kwargs)) except ValueError: logger.debug('ValueError: Deserializing from unicode strings ' 'with encoding declaration is not supported by ' 'lxml.') ctx.in_document = etree.fromstring(string.decode(charset), self.parser) except XMLSyntaxError as e: logger_invalid.error(string) raise Fault('Client.XMLSyntaxError', str(e)) def decompose_incoming_envelope(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) ctx.in_header_doc = None # If you need header support, you should use Soap ctx.in_body_doc = ctx.in_document ctx.method_request_string = ctx.in_body_doc.tag self.validate_body(ctx, message) def from_element(self, ctx, cls, element): if bool(element.get('{%s}nil' % _ns_xsi)): if self.validator is self.SOFT_VALIDATION and not \ cls.Attributes.nillable: raise ValidationError('') return cls.Attributes.default handler = self.deserialization_handlers[cls] return handler(ctx, cls, element) def to_parent(self, ctx, cls, inst, parent, ns, *args, **kwargs): subprot = getattr(cls.Attributes, 'prot', None) if subprot is not None: return subprot.subserialize(ctx, cls, inst, parent, ns, *args, **kwargs) handler = self.serialization_handlers[cls] if inst is None: inst = cls.Attributes.default if inst is None: return self.null_to_parent(ctx, cls, inst, parent, ns, *args, **kwargs) return handler(ctx, cls, inst, parent, ns, *args, **kwargs) def deserialize(self, ctx, message): """Takes a MethodContext instance and a string containing ONE root xml tag. Returns the corresponding native python object. Not meant to be overridden. """ assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_deserialize', ctx) if ctx.descriptor is None: if ctx.in_error is None: raise Fault("Client", "Method %r not found." % ctx.method_request_string) else: raise ctx.in_error if message is self.REQUEST: body_class = ctx.descriptor.in_message elif message is self.RESPONSE: body_class = ctx.descriptor.out_message # decode method arguments if ctx.in_body_doc is None: ctx.in_object = [None] * len(body_class._type_info) else: ctx.in_object = self.from_element(ctx, body_class, ctx.in_body_doc) if self.log_messages and message is self.REQUEST: line_header = '%sRequest%s' % (LIGHT_GREEN, END_COLOR) logger.debug("%s %s" % (line_header, etree.tostring(ctx.out_document, xml_declaration=self.xml_declaration, pretty_print=True))) self.event_manager.fire_event('after_deserialize', ctx) def serialize(self, ctx, message): """Uses ``ctx.out_object``, ``ctx.out_header`` or ``ctx.out_error`` to set ``ctx.out_body_doc``, ``ctx.out_header_doc`` and ``ctx.out_document`` as an ``lxml.etree._Element instance``. Not meant to be overridden. """ assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_serialize', ctx) if ctx.out_error is not None: tmp_elt = etree.Element('punk') retval = self.to_parent(ctx, ctx.out_error.__class__, ctx.out_error, tmp_elt, self.app.interface.get_tns()) ctx.out_document = tmp_elt[0] else: if message is self.REQUEST: result_message_class = ctx.descriptor.in_message elif message is self.RESPONSE: result_message_class = ctx.descriptor.out_message # assign raw result to its wrapper, result_message if ctx.descriptor.body_style == BODY_STYLE_WRAPPED: result_message = result_message_class() for i, attr_name in enumerate( result_message_class._type_info.keys()): setattr(result_message, attr_name, ctx.out_object[i]) else: result_message = ctx.out_object if ctx.out_stream is None: tmp_elt = etree.Element('punk') retval = self.to_parent(ctx, result_message_class, result_message, tmp_elt, self.app.interface.get_tns()) ctx.out_document = tmp_elt[0] else: retval = self.incgen(ctx, result_message_class, result_message, self.app.interface.get_tns()) if self.cleanup_namespaces and ctx.out_document is not None: etree.cleanup_namespaces(ctx.out_document) self.event_manager.fire_event('after_serialize', ctx) return retval def create_out_string(self, ctx, charset=None): """Sets an iterable of string fragments to ctx.out_string""" if charset is None: charset = self.encoding ctx.out_string = [etree.tostring(ctx.out_document, encoding=charset, pretty_print=self.pretty_print, xml_declaration=self.xml_declaration)] if self.log_messages: logger.debug('%sResponse%s %s' % (LIGHT_RED, END_COLOR, etree.tostring(ctx.out_document, pretty_print=True, encoding='UTF-8'))) @coroutine def incgen(self, ctx, cls, inst, ns, name=None): if name is None: name = cls.get_type_name() with etree.xmlfile(ctx.out_stream) as xf: ret = self.to_parent(ctx, cls, inst, xf, ns, name) if isgenerator(ret): try: while True: y = (yield) # may throw Break ret.send(y) except Break: try: ret.throw(Break()) except StopIteration: pass if hasattr(ctx.out_stream, 'finish'): ctx.out_stream.finish() def byte_array_to_parent(self, ctx, cls, inst, parent, ns, name='retval'): _append(parent, E(_gen_tagname(ns, name), self.to_string(cls, inst, self.default_binary_encoding))) def modelbase_to_parent(self, ctx, cls, inst, parent, ns, name='retval'): _append(parent, E(_gen_tagname(ns, name), self.to_string(cls, inst))) def null_to_parent(self, ctx, cls, inst, parent, ns, name='retval'): if issubclass(cls, XmlAttribute): return elif issubclass(cls, XmlData): parent.attrib.update(NIL_ATTR) else: _append(parent, E(_gen_tagname(ns, name), **NIL_ATTR)) def null_from_element(self, ctx, cls, element): return None def xmldata_to_parent(self, ctx, cls, inst, parent, ns, name): ns = cls._ns if ns is None: ns = cls.Attributes.sub_ns name = _gen_tagname(ns, name) cls.marshall(self, name, inst, parent) def xmlattribute_to_parent(self, ctx, cls, inst, parent, ns, name): ns = cls._ns if ns is None: ns = cls.Attributes.sub_ns name = _gen_tagname(ns, name) if inst is not None: if issubclass(cls.type, (ByteArray, File)): parent.set(name, self.to_string(cls.type, inst, self.default_binary_encoding)) else: parent.set(name, self.to_string(cls.type, inst)) def attachment_to_parent(self, cls, inst, ns, parent, name='retval'): _append(parent, E(_gen_tagname(ns, name), ''.join([b.decode('ascii') for b in cls.to_base64(inst)]))) @coroutine def gen_members_parent(self, ctx, cls, inst, parent, tag_name, subelts): delay = set() if isinstance(parent, etree._Element): elt = etree.SubElement(parent, tag_name) elt.extend(subelts) ret = self._get_members_etree(ctx, cls, inst, elt, delay) if isgenerator(ret): try: while True: y = (yield) # may throw Break ret.send(y) except Break: try: ret.throw(Break()) except StopIteration: pass else: with parent.element(tag_name): for e in subelts: parent.write(e) ret = self._get_members_etree(ctx, cls, inst, parent, delay) if isgenerator(ret): try: while True: y = (yield) ret.send(y) except Break: try: ret.throw(Break()) except StopIteration: pass @coroutine def _get_members_etree(self, ctx, cls, inst, parent, delay): try: parent_cls = getattr(cls, '__extends__', None) if not (parent_cls is None): ret = self._get_members_etree(ctx, parent_cls, inst, parent, delay) if ret is not None: try: while True: sv2 = (yield) # may throw Break ret.send(sv2) except Break: try: ret.throw(Break()) except StopIteration: pass for k, v in cls._type_info.items(): try: subvalue = getattr(inst, k, None) except: # e.g. SqlAlchemy could throw NoSuchColumnError subvalue = None # This is a tight loop, so enable this only when necessary. # logger.debug("get %r(%r) from %r: %r" % (k, v, inst, subvalue)) sub_ns = v.Attributes.sub_ns if sub_ns is None: sub_ns = cls.get_namespace() sub_name = v.Attributes.sub_name if sub_name is None: sub_name = k if issubclass(v, XmlAttribute) and \ v.attribute_of in cls._type_info.keys(): delay.add(k) continue mo = v.Attributes.max_occurs if subvalue is not None and mo > 1: if isinstance(subvalue, PushBase): while True: sv = (yield) ret = self.to_parent(ctx, v, sv, parent, sub_ns, sub_name) if ret is not None: try: while True: sv2 = (yield) # may throw Break ret.send(sv2) except Break: try: ret.throw(Break()) except StopIteration: pass else: for sv in subvalue: ret = self.to_parent(ctx, v, sv, parent, sub_ns, sub_name) if ret is not None: try: while True: sv2 = (yield) # may throw Break ret.send(sv2) except Break: try: ret.throw(Break()) except StopIteration: pass # Don't include empty values for non-nillable optional attributes. elif subvalue is not None or v.Attributes.min_occurs > 0: ret = self.to_parent(ctx, v, subvalue, parent, sub_ns, sub_name) if ret is not None: try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass except Break: pass if isinstance(parent, etree._Element): # attribute_of won't work with async. for k in delay: v = cls._type_info[k] subvalue = getattr(inst, k, None) sub_name = v.Attributes.sub_name if sub_name is None: sub_name = k a_of = v.attribute_of ns = cls.__namespace__ attr_parents = parent.findall("{%s}%s" % (ns, a_of)) if cls._type_info[a_of].Attributes.max_occurs > 1: for subsubvalue, attr_parent in zip(subvalue, attr_parents): self.to_parent(ctx, v, subsubvalue, attr_parent, v.get_namespace(), k) else: for attr_parent in attr_parents: self.to_parent(ctx, v, subvalue, attr_parent, v.get_namespace(), k) def complex_to_parent(self, ctx, cls, inst, parent, ns, name=None): sub_name = cls.Attributes.sub_name if sub_name is not None: name = sub_name if name is None: name = cls.get_type_name() sub_ns = cls.Attributes.sub_ns if not sub_ns in (None, DEFAULT_NS): ns = sub_ns tag_name = _gen_tagname(ns, name) inst = cls.get_serialization_instance(inst) return self.gen_members_parent(ctx, cls, inst, parent, tag_name, []) def fault_to_parent(self, ctx, cls, inst, parent, ns, *args, **kwargs): tag_name = "{%s}Fault" % _ns_soap_env subelts = [ E("faultcode", '%s:%s' % (_pref_soap_env, inst.faultcode)), E("faultstring", inst.faultstring), E("faultactor", inst.faultactor), ] # Accepting raw lxml objects as detail is deprecated. It's also not # documented. It's kept for backwards-compatibility purposes. if isinstance(inst.detail, string_types + (etree._Element,)): _append(subelts, E('detail', inst.detail)) elif isinstance(inst.detail, dict): _append(subelts, E('detail', root_dict_to_etree(inst.detail))) elif inst.detail is None: pass else: raise TypeError('Fault detail Must be dict, got', type(inst.detail)) # add other nonstandard fault subelements with get_members_etree return self.gen_members_parent(ctx, cls, inst, parent, tag_name, subelts) def schema_validation_error_to_parent(self, ctx, cls, inst, parent, ns): tag_name = "{%s}Fault" % _ns_soap_env subelts = [ E("faultcode", '%s:%s' % (_pref_soap_env, inst.faultcode)), # HACK: Does anyone know a better way of injecting raw xml entities? E("faultstring", html.fromstring(inst.faultstring).text), E("faultactor", inst.faultactor), ] if inst.detail != None: _append(subelts, E('detail', inst.detail)) # add other nonstandard fault subelements with get_members_etree return self.gen_members_parent(ctx, cls, inst, parent, tag_name, subelts) def enum_to_parent(self, ctx, cls, inst, parent, ns, name='retval'): self.modelbase_to_parent(ctx, cls, str(inst), parent, ns, name) def xml_to_parent(self, ctx, cls, inst, parent, ns, name): if isinstance(inst, str) or isinstance(inst, unicode): inst = etree.fromstring(inst) _append(parent, E(_gen_tagname(ns, name), inst)) def html_to_parent(self, ctx, cls, inst, parent, ns, name): if isinstance(inst, str) or isinstance(inst, unicode): inst = html.fromstring(inst) _append(parent, E(_gen_tagname(ns, name), inst)) def dict_to_parent(self, ctx, cls, inst, parent, ns, name): elt = E(_gen_tagname(ns, name)) dict_to_etree(inst, elt) _append(parent, elt) def complex_from_element(self, ctx, cls, elt): inst = cls.get_deserialization_instance() flat_type_info = cls.get_flat_type_info(cls) # this is for validating cls.Attributes.{min,max}_occurs frequencies = defaultdict(int) xtba_key, xtba_type = cls.Attributes._xml_tag_body_as if xtba_key is not None: if issubclass(xtba_type.type, (ByteArray, File)): value = self.from_string(xtba_type.type, elt.text, self.default_binary_encoding) else: value = self.from_string(xtba_type.type, elt.text) setattr(inst, xtba_key, value) # parse input to set incoming data to related attributes. for c in elt: key = c.tag.split('}')[-1] frequencies[key] += 1 member = flat_type_info.get(key, None) if member is None: member, key = cls._type_info_alt.get(key, (None, key)) if member is None: member, key = cls._type_info_alt.get(c.tag, (None, key)) if member is None: continue mo = member.Attributes.max_occurs if mo > 1: value = getattr(inst, key, None) if value is None: value = [] value.append(self.from_element(ctx, member, c)) else: value = self.from_element(ctx, member, c) setattr(inst, key, value) for key, value_str in c.attrib.items(): member = flat_type_info.get(key, None) if member is None: member, key = cls._type_info_alt.get(key, (None, key)) if member is None: continue if (not issubclass(member, XmlAttribute)) or \ member.attribute_of == key: continue if mo > 1: value = getattr(inst, key, None) if value is None: value = [] value.append(self.from_string(member.type, value_str)) else: value = self.from_string(member.type, value_str) setattr(inst, key, value) for key, value_str in elt.attrib.items(): member = flat_type_info.get(key, None) if member is None: member, key = cls._type_info_alt.get(key, (None, key)) if member is None: continue if (not issubclass(member, XmlAttribute)) or member.attribute_of == key: continue if issubclass(member.type, (ByteArray, File)): value = self.from_string(member.type, value_str, self.default_binary_encoding) else: value = self.from_string(member.type, value_str) setattr(inst, key, value) if self.validator is self.SOFT_VALIDATION: for key, c in flat_type_info.items(): val = frequencies.get(key, 0) attr = c.Attributes if val < attr.min_occurs or val > attr.max_occurs: raise Fault('Client.ValidationError', '%r member does not ' 'respect frequency constraints.' % key) return inst def array_from_element(self, ctx, cls, element): retval = [ ] (serializer,) = cls._type_info.values() for child in element.getchildren(): retval.append(self.from_element(ctx, serializer, child)) return retval def iterable_from_element(self, ctx, cls, element): (serializer,) = cls._type_info.values() for child in element.getchildren(): yield self.from_element(ctx, serializer, child) def enum_from_element(self, ctx, cls, element): if self.validator is self.SOFT_VALIDATION and not ( cls.validate_string(cls, element.text)): raise ValidationError(element.text) return getattr(cls, element.text) def fault_from_element(self, ctx, cls, element): code = element.find('faultcode').text string = element.find('faultstring').text factor = element.find('faultactor') if factor is not None: factor = factor.text detail = element.find('detail') return cls(faultcode=code, faultstring=string, faultactor=factor, detail=detail) def xml_from_element(self, ctx, cls, element): children = element.getchildren() retval = None if children: retval = element.getchildren()[0] return retval def dict_from_element(self, ctx, cls, element): children = element.getchildren() if children: return etree_to_dict(element) return None def unicode_from_element(self, ctx, cls, element): if self.validator is self.SOFT_VALIDATION and not ( cls.validate_string(cls, element.text)): raise ValidationError(element.text) s = element.text if s is None: s = '' retval = self.from_string(cls, s) if self.validator is self.SOFT_VALIDATION and not ( cls.validate_native(cls, retval)): raise ValidationError(retval) return retval def base_from_element(self, ctx, cls, element): if self.validator is self.SOFT_VALIDATION and not ( cls.validate_string(cls, element.text)): raise ValidationError(element.text) retval = self.from_string(cls, element.text) if self.validator is self.SOFT_VALIDATION and not ( cls.validate_native(cls, retval)): raise ValidationError(retval) return retval def byte_array_from_element(self, ctx, cls, element): if self.validator is self.SOFT_VALIDATION and not ( cls.validate_string(cls, element.text)): raise ValidationError(element.text) retval = self.from_string(cls, element.text, self.default_binary_encoding) if self.validator is self.SOFT_VALIDATION and not ( cls.validate_native(cls, retval)): raise ValidationError(retval) return retval def attachment_from_element(self, ctx, cls, element): return cls.from_base64([element.text]) spyne-2.11.0/spyne/protocol/yaml.py0000644000175000001440000001160412345433230017220 0ustar plqusers00000000000000 # # 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 # """The ``spyne.protocol.yaml`` package contains the Yaml-related protocols. Currently, only :class:`spyne.protocol.yaml.YamlDocument` is supported. Initially released in 2.10.0-rc. This module is EXPERIMENTAL. You may not recognize the code here next time you look at it. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) from spyne.model.binary import BINARY_ENCODING_BASE64 from spyne.model.primitive import Boolean from spyne.model.primitive import Integer from spyne.model.primitive import Double from spyne.model.fault import Fault from spyne.protocol.dictdoc import HierDictDocument import yaml from yaml.parser import ParserError try: from yaml import CLoader as Loader from yaml import CDumper as Dumper from yaml import CSafeLoader as SafeLoader from yaml import CSafeDumper as SafeDumper except ImportError: from yaml import Loader from yaml import Dumper from yaml import SafeLoader from yaml import SafeDumper class YamlDocument(HierDictDocument): """An implementation of the Yaml protocol that uses the PyYaml package. See ProtocolBase ctor docstring for its arguments. Yaml-specific arguments follow: :param safe: Use ``safe_dump`` instead of ``dump`` and ``safe_load`` instead of ``load``. This is not a security feature, search for 'safe_dump' in http://www.pyyaml.org/wiki/PyYAMLDocumentation :param kwargs: See the yaml documentation in ``load, ``safe_load``, ``dump`` or ``safe_dump`` depending on whether you use yaml as an input or output protocol. For the output case, Spyne sets ``default_flow_style=False`` and ``indent=4`` by default. """ mime_type = 'text/yaml' type = set(HierDictDocument.type) type.add('yaml') default_binary_encoding = BINARY_ENCODING_BASE64 # for test classes _decimal_as_string = True def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, # DictDocument specific ignore_wrappers=True, complex_as=dict, ordered=False, # YamlDocument specific safe=True, **kwargs): super(YamlDocument, self).__init__(app, validator, mime_type, ignore_uncap, ignore_wrappers, complex_as, ordered) self._from_string_handlers[Double] = lambda cls, val: val self._from_string_handlers[Boolean] = lambda cls, val: val self._from_string_handlers[Integer] = lambda cls, val: val self._to_string_handlers[Double] = lambda cls, val: val self._to_string_handlers[Boolean] = lambda cls, val: val self._to_string_handlers[Integer] = lambda cls, val: val self.in_kwargs = dict(kwargs) self.out_kwargs = dict(kwargs) self.in_kwargs['Loader'] = Loader self.out_kwargs['Dumper'] = Dumper if safe: self.in_kwargs['Loader'] = SafeLoader self.out_kwargs['Dumper'] = SafeDumper if not 'indent' in self.out_kwargs: self.out_kwargs['indent'] = 4 if not 'default_flow_style' in self.out_kwargs: self.out_kwargs['default_flow_style'] = False def create_in_document(self, ctx, in_string_encoding=None): """Sets ``ctx.in_document`` using ``ctx.in_string``.""" if in_string_encoding is None: in_string_encoding = 'UTF-8' try: ctx.in_document = yaml.load(''.join(ctx.in_string).decode( in_string_encoding), **self.in_kwargs) except ParserError as e: raise Fault('Client.YamlDecodeError', repr(e)) def create_out_string(self, ctx, out_string_encoding='utf8'): """Sets ``ctx.out_string`` using ``ctx.out_document``.""" ctx.out_string = (yaml.dump(o, **self.out_kwargs) for o in ctx.out_document) def _decimal_to_string(): pass def _decimal_from_string(): pass spyne-2.11.0/spyne/server/0000755000175000001440000000000012352131452015346 5ustar plqusers00000000000000spyne-2.11.0/spyne/server/twisted/0000755000175000001440000000000012352131452017031 5ustar plqusers00000000000000spyne-2.11.0/spyne/server/twisted/__init__.py0000644000175000001440000000160412345433230021144 0ustar plqusers00000000000000 # # 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 # from spyne.server.twisted.http import TwistedWebResource from spyne.server.twisted.websocket import TwistedWebSocketResource spyne-2.11.0/spyne/server/twisted/_base.py0000644000175000001440000000407112345433230020457 0ustar plqusers00000000000000 # # 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 # from twisted.internet.defer import Deferred from twisted.internet.interfaces import IPullProducer from twisted.web.iweb import UNKNOWN_LENGTH from zope.interface import implements class Producer(object): implements(IPullProducer) deferred = None def __init__(self, body, consumer): """:param body: an iterable of strings""" # check to see if we can determine the length try: len(body) # iterator? self.length = sum([len(fragment) for fragment in body]) self.body = iter(body) except TypeError: self.length = UNKNOWN_LENGTH self.body = body self.deferred = Deferred() self.consumer = consumer def resumeProducing(self): try: chunk = next(self.body) except StopIteration as e: self.consumer.unregisterProducer() if self.deferred is not None: self.deferred.callback(self.consumer) self.deferred = None return self.consumer.write(chunk) def pauseProducing(self): pass def stopProducing(self): if self.deferred is not None: self.deferred.errback( Exception("Consumer asked us to stop producing")) self.deferred = None spyne-2.11.0/spyne/server/twisted/http.py0000644000175000001440000004512712352126507020400 0ustar plqusers00000000000000 # # 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 # """The ``spyne.server.twisted`` module contains a server transport compatible with the Twisted event loop. It uses the TwistedWebResource object as transport. Also see the twisted examples in the examples directory of the source distribution. If you want to have a hard-coded URL in the wsdl document, this is how to do it: :: resource = TwistedWebResource(...) resource.http_transport.doc.wsdl11.build_interface_document("http://example.com") This is not strictly necessary. If you don't do this, Spyne will get the URL from the first request, build the wsdl on-the-fly and cache it as a string in memory for later requests. However, if you want to make sure you only have this url on the WSDL, this is how to do it. Note that if your client takes the information in wsdl seriously, all requests will go to the designated url above which can make testing a bit difficult. Use in moderation. This module is EXPERIMENTAL. Your mileage may vary. Patches are welcome. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) import re from os import fstat from mmap import mmap from inspect import isgenerator, isclass from collections import namedtuple from twisted.web.server import NOT_DONE_YET, Request from twisted.web.resource import Resource, NoResource, ForbiddenResource from twisted.web import static from twisted.web.static import getTypeAndEncoding from twisted.web.http import CACHED from twisted.python.log import err from twisted.internet.defer import Deferred from spyne.error import InternalError from spyne.auxproc import process_contexts from spyne.const.ansi_color import LIGHT_GREEN from spyne.const.ansi_color import END_COLOR from spyne.const.http import HTTP_404, HTTP_200 from spyne.model import PushBase, File, ComplexModelBase from spyne.model.fault import Fault from spyne.protocol.http import HttpRpc from spyne.server.http import HttpBase from spyne.server.http import HttpMethodContext from spyne.server.http import HttpTransportContext from spyne.server.twisted._base import Producer from spyne.util.six import text_type, string_types from spyne.util.six.moves.urllib.parse import unquote def _render_file(file, request): """ Begin sending the contents of this L{File} (or a subset of the contents, based on the 'range' header) to the given request. """ file.restat(False) if file.type is None: file.type, file.encoding = getTypeAndEncoding(file.basename(), file.contentTypes, file.contentEncodings, file.defaultType) if not file.exists(): return file.childNotFound.render(request) if file.isdir(): return file.redirect(request) request.setHeader('accept-ranges', 'bytes') try: fileForReading = file.openForReading() except IOError, e: import errno if e[0] == errno.EACCES: return ForbiddenResource().render(request) else: raise #if request.setLastModified(file.getmtime()) is CACHED: # return '' producer = file.makeProducer(request, fileForReading) if request.method == 'HEAD': return '' producer.start() # and make sure the connection doesn't get closed return NOT_DONE_YET def _set_response_headers(request, headers): retval = [] for k, v in headers.items(): if isinstance(v, (list, tuple)): request.responseHeaders.setRawHeaders(k, v) else: request.responseHeaders.setRawHeaders(k, [v]) return retval def _reconstruct_url(request): server_name = request.getRequestHostname() server_port = request.getHost().port if (bool(request.isSecure()), server_port) not in [(True, 443), (False, 80)]: server_name = '%s:%d' % (server_name, server_port) if request.isSecure(): url_scheme = 'https' else: url_scheme = 'http' return ''.join([url_scheme, "://", server_name, request.uri]) class TwistedHttpTransportContext(HttpTransportContext): def set_mime_type(self, what): if isinstance(what, text_type): what = what.encode('ascii', errors='replace') super(TwistedHttpTransportContext, self).set_mime_type(what) self.req.setHeader('Content-Type', what) def get_cookie(self, key): return self.req.getCookie(key) class TwistedHttpMethodContext(HttpMethodContext): default_transport_context = TwistedHttpTransportContext class TwistedHttpTransport(HttpBase): def __init__(self, app, chunked=False, max_content_length=2 * 1024 * 1024, block_length=8 * 1024): super(TwistedHttpTransport, self).__init__(app, chunked=chunked, max_content_length=max_content_length, block_length=block_length) def decompose_incoming_envelope(self, prot, ctx, message): """This function is only called by the HttpRpc protocol to have the twisted web's Request object is parsed into ``ctx.in_body_doc`` and ``ctx.in_header_doc``. """ request = ctx.in_document assert isinstance(request, Request) ctx.in_header_doc = dict(request.requestHeaders.getAllRawHeaders()) fi = ctx.transport.file_info if fi is not None and len(request.args) == 1: key, = request.args.keys() if fi.field_name == key and fi.file_name is not None: ctx.in_body_doc = {key: [File.Value(name=fi.file_name, type=fi.file_type, data=request.args[key])]} else: ctx.in_body_doc = request.args else: ctx.in_body_doc = request.args # this is a huge hack because twisted seems to take the slashes in urls # too seriously. postpath = getattr(request, 'realpostpath', None) if postpath is None: postpath = request.path params = self.match_pattern(ctx, request.method, postpath, request.getHeader('Host')) if ctx.method_request_string is None: # no pattern match ctx.method_request_string = '{%s}%s' % (self.app.interface.get_tns(), request.path.split('/')[-1]) logger.debug("%sMethod name: %r%s" % (LIGHT_GREEN, ctx.method_request_string, END_COLOR)) for k, v in params.items(): val = ctx.in_body_doc.get(k, []) val.extend(v) ctx.in_body_doc[k] = val r = {} for k,v in ctx.in_body_doc.items(): l = [] for v2 in v: if isinstance(v2, string_types): l.append(unquote(v2)) else: l.append(v2) r[k] = l ctx.in_body_doc = r # This is consistent with what server.wsgi does. if request.method in ('POST', 'PUT', 'PATCH'): for k, v in ctx.in_body_doc.items(): if v == ['']: ctx.in_body_doc[k] = [None] FIELD_NAME_RE = re.compile(r'name="([^"]+)"') FILE_NAME_RE = re.compile(r'filename="([^"]+)"') _FileInfo = namedtuple("_FileInfo", "field_name file_name file_type " "header_offset") def _get_file_name(instr): """We need this huge hack because twisted doesn't offer a way to get file name from Content-Disposition header. This works only when there's just one file because we want to avoid scanning the whole stream. So this won't get the names of the subsequent files even though that's a perfectly valid request. """ field_name = file_name = file_type = content_idx = None # hack to see if it looks like a multipart request. 5 is arbitrary. if instr[:5] == "-----": first_page = instr[:4096] # 4096 = default page size on linux. # this normally roughly <200 header_idx = first_page.find('\r\n') + 2 content_idx = first_page.find('\r\n\r\n', header_idx) if header_idx > 0 and content_idx > header_idx: headerstr = first_page[header_idx:content_idx] for line in headerstr.split("\r\n"): k, v = line.split(":", 2) if k == "Content-Disposition": for subv in v.split(";"): subv = subv.strip() m = FIELD_NAME_RE.match(subv) if m: field_name = m.group(1) continue m = FILE_NAME_RE.match(subv) if m: file_name = m.group(1) continue if k == "Content-Type": file_type = v.strip() # 4 == len('\r\n\r\n') return _FileInfo(field_name, file_name, file_type, content_idx + 4) def _has_fd(istr): if hasattr(istr, 'fileno'): try: istr.fileno() return True except IOError: return False return False class TwistedWebResource(Resource): """A server transport that exposes the application as a twisted web Resource. """ def __init__(self, app, chunked=False, max_content_length=2 * 1024 * 1024, block_length=8 * 1024, prepath=None): Resource.__init__(self) self.http_transport = TwistedHttpTransport(app, chunked, max_content_length, block_length) self._wsdl = None self.prepath = prepath def getChildWithDefault(self, path, request): # this hack is necessary because twisted takes the slash character in # http requests too seriously. i.e. it insists that a leaf node can only # handle the last path fragment. if self.prepath is None: request.realprepath = '/' + '/'.join(request.prepath) else: if not self.prepath.startswith('/'): request.realprepath = '/' + self.prepath else: request.realprepath = self.prepath request.realpostpath = request.path[len(request.realprepath):] if path in self.children: retval = self.children[path] else: retval = self.getChild(path, request) if isinstance(retval, NoResource): retval = self return retval def render(self, request): if request.method == 'GET' and ( request.uri.endswith('.wsdl') or request.uri.endswith('?wsdl')): return self.__handle_wsdl_request(request) return self.handle_rpc(request) def handle_rpc_error(self, p_ctx, others, error, request): resp_code = p_ctx.transport.resp_code # If user code set its own response code, don't touch it. if resp_code is None: resp_code = p_ctx.out_protocol.fault_to_http_response_code(error) request.setResponseCode(int(resp_code[:3])) _set_response_headers(request, p_ctx.transport.resp_headers) # In case user code set its own out_* attributes before failing. p_ctx.out_document = None p_ctx.out_string = None p_ctx.out_object = error self.http_transport.get_out_string(p_ctx) retval = ''.join(p_ctx.out_string) p_ctx.close() process_contexts(self.http_transport, others, p_ctx, error=error) return retval def handle_rpc(self, request): initial_ctx = TwistedHttpMethodContext(self.http_transport, request, self.http_transport.app.out_protocol.mime_type) if _has_fd(request.content): f = request.content # it's best to avoid empty mappings. if fstat(f.fileno()).st_size == 0: initial_ctx.in_string = [''] else: initial_ctx.in_string = [mmap(f.fileno(), 0)] else: request.content.seek(0) initial_ctx.in_string = [request.content.read()] initial_ctx.transport.file_info = \ _get_file_name(initial_ctx.in_string[0]) contexts = self.http_transport.generate_contexts(initial_ctx) p_ctx, others = contexts[0], contexts[1:] if p_ctx.in_error: return self.handle_rpc_error(p_ctx, others, p_ctx.in_error, request) else: self.http_transport.get_in_object(p_ctx) if p_ctx.in_error: return self.handle_rpc_error(p_ctx, others, p_ctx.in_error, request) self.http_transport.get_out_object(p_ctx) if p_ctx.out_error: return self.handle_rpc_error(p_ctx, others, p_ctx.out_error, request) ret = p_ctx.out_object[0] retval = NOT_DONE_YET if isinstance(ret, Deferred): ret.addCallback(_cb_deferred, request, p_ctx, others, self) ret.addErrback(_eb_deferred, request, p_ctx, others, self) elif isinstance(ret, PushBase): _init_push(ret, request, p_ctx, others, self) else: retval = _cb_deferred(p_ctx.out_object, request, p_ctx, others, self, cb=False) return retval def __handle_wsdl_request(self, request): ctx = TwistedHttpMethodContext(self.http_transport, request, "text/xml; charset=utf-8") url = _reconstruct_url(request) if self.http_transport.doc.wsdl11 is None: return HTTP_404 if self._wsdl is None: self._wsdl = self.http_transport.doc.wsdl11.get_interface_document() ctx.transport.wsdl = self._wsdl _set_response_headers(request, ctx.transport.resp_headers) try: if self._wsdl is None: self.http_transport.doc.wsdl11.build_interface_document(url) ctx.transport.wsdl = self._wsdl = \ self.http_transport.doc.wsdl11.get_interface_document() assert ctx.transport.wsdl is not None self.http_transport.event_manager.fire_event('wsdl', ctx) return ctx.transport.wsdl except Exception as e: ctx.transport.wsdl_error = e self.http_transport.event_manager.fire_event('wsdl_exception', ctx) raise finally: ctx.close() def _cb_request_finished(retval, request, p_ctx): request.finish() p_ctx.close() def _eb_request_finished(retval, request, p_ctx): err(request) p_ctx.close() request.finish() def _init_push(ret, request, p_ctx, others, resource): assert isinstance(ret, PushBase) p_ctx.out_stream = request # fire events p_ctx.app.event_manager.fire_event('method_return_push', p_ctx) if p_ctx.service_class is not None: p_ctx.service_class.event_manager.fire_event('method_return_push', p_ctx) gen = resource.http_transport.get_out_string_push(p_ctx) assert isgenerator(gen), "It looks like this protocol is not " \ "async-compliant yet." def _cb_push_finish(): p_ctx.out_stream.finish() process_contexts(resource.http_transport, others, p_ctx) retval = ret.init(p_ctx, request, gen, _cb_push_finish, None) if isinstance(retval, Deferred): def _eb_push_close(f): ret.close() def _cb_push_close(r): def _eb_inner(f): return f if r is None: ret.close() else: r.addCallback(_cb_push_close).addErrback(_eb_inner) retval.addCallback(_cb_push_close).addErrback(_eb_push_close) else: ret.close() return retval def _cb_deferred(ret, request, p_ctx, others, resource, cb=True): resp_code = p_ctx.transport.resp_code # If user code set its own response code, don't touch it. if resp_code is None: resp_code = HTTP_200 request.setResponseCode(int(resp_code[:3])) _set_response_headers(request, p_ctx.transport.resp_headers) om = p_ctx.descriptor.out_message single_class = None if cb and ((not issubclass(om, ComplexModelBase)) or len(om._type_info) <= 1): p_ctx.out_object = [ret] if len(om._type_info) == 1: single_class, = om._type_info.values() else: p_ctx.out_object = ret retval = NOT_DONE_YET if isinstance(ret, PushBase): retval = _init_push(ret, request, p_ctx, others, resource) elif ((isclass(om) and issubclass(om, File)) or (isclass(single_class) and issubclass(single_class, File))) and \ isinstance(p_ctx.out_protocol, HttpRpc) and \ getattr(ret, 'abspath', None) is not None: file = static.File(ret.abspath, defaultType=str(ret.type) or 'application/octet-stream') retval = _render_file(file, request) if retval != NOT_DONE_YET and cb: request.write(retval) request.finish() else: resource.http_transport.get_out_string(p_ctx) producer = Producer(p_ctx.out_string, request) producer.deferred.addCallback(_cb_request_finished, request, p_ctx) producer.deferred.addErrback(_eb_request_finished, request, p_ctx) request.registerProducer(producer, False) process_contexts(resource.http_transport, others, p_ctx) return retval def _eb_deferred(retval, request, p_ctx, others, resource): p_ctx.out_error = retval.value if not issubclass(retval.type, Fault): retval.printTraceback() p_ctx.out_error = InternalError(retval.value) ret = resource.handle_rpc_error(p_ctx, others, p_ctx.out_error, request) request.write(ret) request.finish() spyne-2.11.0/spyne/server/twisted/msgpack.py0000644000175000001440000001266112345433230021037 0ustar plqusers00000000000000# encoding: utf8 # # 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 # from __future__ import absolute_import import logging logger = logging.getLogger(__name__) import msgpack from twisted.internet.defer import Deferred from twisted.internet.protocol import Protocol, Factory, connectionDone from twisted.python.failure import Failure from twisted.python import log from spyne import EventManager from spyne.auxproc import process_contexts from spyne.error import ValidationError, InternalError from spyne.server.msgpack import MessagePackServerBase from spyne.server.msgpack import OUT_RESPONSE_SERVER_ERROR, \ OUT_RESPONSE_CLIENT_ERROR class TwistedMessagePackProtocolFactory(Factory): def __init__(self, app, base=MessagePackServerBase): self.app = app self.base = base self.event_manager = EventManager(self) def buildProtocol(self, address): return TwistedMessagePackProtocol(self.app, self.base, factory=self) class TwistedMessagePackProtocol(Protocol): def __init__(self, app, base=MessagePackServerBase, max_buffer_size=2*1024*1024, factory=None): self.factory = factory self._buffer = msgpack.Unpacker(max_buffer_size=max_buffer_size) self._transport = base(app) def connectionMade(self): logger.info("%r connection made.", self) if self.factory is not None: self.factory.event_manager.fire_event("connection_made", self) def connectionLost(self, reason=connectionDone): logger.info("%r connection lost yo.", self) if self.factory is not None: self.factory.event_manager.fire_event("connection_lost", self) def dataReceived(self, data): self._buffer.feed(data) for msg in self._buffer: p_ctx = others = None try: self.process_incoming_message(msg) except ValidationError as e: import traceback traceback.print_exc() logger.exception(e) self.handle_error(p_ctx, others, e) def process_incoming_message(self, msg): p_ctx, others = self._transport.produce_contexts(msg) p_ctx.transport.protocol = self self.process_contexts(p_ctx, others) def handle_error(self, p_ctx, others, exc): self._transport.get_out_string(p_ctx) if isinstance(exc, InternalError): error = OUT_RESPONSE_SERVER_ERROR else: error = OUT_RESPONSE_CLIENT_ERROR out_string = msgpack.packb([ error, msgpack.packb(p_ctx.out_document[0].values()), ]) self.transport.write(out_string) p_ctx.close() try: process_contexts(self, others, p_ctx, error=error) except Exception as e: # Report but ignore any exceptions from auxiliary methods. logger.exception(e) def process_contexts(self, p_ctx, others): if p_ctx.in_error: self.handle_error(p_ctx, others, p_ctx.in_error) return self._transport.get_in_object(p_ctx) if p_ctx.in_error: logger.error(p_ctx.in_error) self.handle_error(p_ctx, others, p_ctx.in_error) return self._transport.get_out_object(p_ctx) if p_ctx.out_error: self.handle_error(p_ctx, others, p_ctx.out_error) return ret = p_ctx.out_object[0] if isinstance(ret, Deferred): ret.addCallback(_cb_deferred, self, p_ctx, others) ret.addErrback(_eb_deferred, self, p_ctx, others) ret.addErrback(log.err) return _cb_deferred(p_ctx.out_object, self, p_ctx, others, nowrap=True) def _eb_deferred(retval, prot, p_ctx, others): p_ctx.out_error = retval.value tb = None if isinstance(retval, Failure): tb = retval.getTracebackObject() retval.printTraceback() p_ctx.out_error = InternalError(retval.value) prot.handle_error(p_ctx, others, p_ctx.out_error) prot.transport.write(''.join(p_ctx.out_string)) prot.transport.loseConnection() return Failure(p_ctx.out_error, p_ctx.out_error.__class__, tb) def _cb_deferred(retval, prot, p_ctx, others, nowrap=False): if len(p_ctx.descriptor.out_message._type_info) > 1 or nowrap: p_ctx.out_object = retval else: p_ctx.out_object = [retval] try: prot._transport.get_out_string(p_ctx) prot._transport.pack(p_ctx) out_string = ''.join(p_ctx.out_string) prot.transport.write(out_string) except Exception as e: logger.exception(e) prot.handle_error(p_ctx, others, InternalError(e)) finally: p_ctx.close() process_contexts(prot._transport, others, p_ctx) spyne-2.11.0/spyne/server/twisted/websocket.py0000644000175000001440000001724512345433230021403 0ustar plqusers00000000000000 # # 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 # """The ``spyne.server.twisted`` module contains a server transport compatible with the Twisted event loop. It uses the TwistedWebResource object as transport. Also see the twisted examples in the examples directory of the source distribution. If you want to have a hard-coded URL in the wsdl document, this is how to do it: :: resource = TwistedWebResource(...) resource.http_transport.doc.wsdl11.build_interface_document("http://example.com") This is not strictly necessary -- if you don't do this, Spyne will get the URL from the first request, build the wsdl on-the-fly and cache it as a string in memory for later requests. However, if you want to make sure you only have this url on the WSDL, this is how to do it. Note that if your client takes the information in wsdl seriously, all requests will go to the designated url above which can make testing a bit difficult. Use in moderation. This module is EXPERIMENTAL. Your mileage may vary. Patches are welcome. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) from inspect import isgenerator from twisted.internet.defer import Deferred from twisted.internet.protocol import Factory # FIXME: Switch to: # from twisted.web.websockets import WebSocketsProtocol # from twisted.web.websockets import WebSocketsResource # from twisted.web.websockets import CONTROLS from spyne.util._twisted_ws import WebSocketsProtocol from spyne.util._twisted_ws import WebSocketsResource from spyne.util._twisted_ws import CONTROLS from spyne import MethodContext from spyne import TransportContext from spyne.auxproc import process_contexts from spyne.model import PushBase from spyne.model.complex import ComplexModel from spyne.model.fault import Fault from spyne.server import ServerBase class WebSocketTransportContext(TransportContext): def __init__(self, parent, transport, type, client_handle): TransportContext.__init__(self, parent, transport, type) self.client_handle = client_handle """TwistedWebSocketProtocol instance.""" self.parent = parent """Parent Context""" class WebSocketMethodContext(MethodContext): def __init__(self, transport, client_handle): MethodContext.__init__(self, transport) self.transport = WebSocketTransportContext(self, transport, 'ws', client_handle) class TwistedWebSocketProtocol(WebSocketsProtocol): """A protocol that parses and generates messages in a WebSocket stream.""" def __init__(self, transport, bookkeep=False, _clients=None): self._spyne_transport = transport self._clients = _clients self.__app_id = id(self) if bookkeep: self.connectionMade = self._connectionMade self.connectionLost = self._connectionLost @property def app_id(self): return self.__app_id @app_id.setter def app_id(self, what): entry = self._clients.get(self.__app_id, None) if entry: del self._clients[self.__app_id] self._clients[what] = entry self.__app_id = what def _connectionMade(self): WebSocketsProtocol.connectionMade(self) self._clients[self.app_id] = self def _connectionLost(self, reason): del self._clients[id(self)] def frameReceived(self, opcode, data, fin): tpt = self._spyne_transport initial_ctx = WebSocketMethodContext(tpt, client_handle=self) initial_ctx.in_string = [data] contexts = tpt.generate_contexts(initial_ctx) p_ctx, others = contexts[0], contexts[1:] if p_ctx.in_error: p_ctx.out_object = p_ctx.in_error else: tpt.get_in_object(p_ctx) if p_ctx.in_error: p_ctx.out_object = p_ctx.in_error else: tpt.get_out_object(p_ctx) if p_ctx.out_error: p_ctx.out_object = p_ctx.out_error def _cb_deferred(retval, cb=True): if cb and len(p_ctx.descriptor.out_message._type_info) <= 1: p_ctx.out_object = [retval] else: p_ctx.out_object = retval tpt.get_out_string(p_ctx) self.sendFrame(opcode, ''.join(p_ctx.out_string), fin) p_ctx.close() process_contexts(tpt, others, p_ctx) def _eb_deferred(retval): p_ctx.out_error = retval.value if not issubclass(retval.type, Fault): retval.printTraceback() tpt.get_out_string(p_ctx) self.sendFrame(opcode, ''.join(p_ctx.out_string), fin) p_ctx.close() ret = p_ctx.out_object if isinstance(ret, (list, tuple)): ret = ret[0] if isinstance(ret, Deferred): ret.addCallback(_cb_deferred) ret.addErrback(_eb_deferred) elif isinstance(ret, PushBase): raise NotImplementedError() else: _cb_deferred(p_ctx.out_object, cb=False) class TwistedWebSocketFactory(Factory): def __init__(self, app, bookkeep=False, _clients=None): self.app = app self.transport = ServerBase(app) self.bookkeep = bookkeep self._clients = _clients if _clients is None: self._clients = {} def buildProtocol(self, addr): return TwistedWebSocketProtocol(self.transport, self.bookkeep, self._clients) class _Fake(object): pass def _FakeWrap(cls): class _Ret(ComplexModel): _type_info = {"ugh ": cls} return _Ret class _FakeCtx(object): def __init__(self, obj, cls): self.out_object = obj self.out_error = None self.descriptor = _Fake() self.descriptor.out_message = cls class InvalidRequestError(Exception): pass class TwistedWebSocketResource(WebSocketsResource): def __init__(self, app, bookkeep=False, clients=None): self.app = app self.clients = clients if clients is None: self.clients = {} if bookkeep: self.propagate = self.do_propagate WebSocketsResource.__init__(self, TwistedWebSocketFactory(app, bookkeep, self.clients)) def propagate(self): raise InvalidRequestError("You must enable bookkeeping to have " "message propagation work.") def get_doc(self, obj, cls=None): if cls is None: cls = obj.__class__ op = self.app.out_protocol ctx = _FakeCtx(obj, cls) op.serialize(ctx, op.RESPONSE) op.create_out_string(ctx) return ''.join(ctx.out_string) def do_propagate(self, obj, cls=None): doc = self.get_doc(obj, cls) for c in self.clients.itervalues(): print('sending to', c) c.sendFrame(CONTROLS.TEXT, doc, True) spyne-2.11.0/spyne/server/__init__.py0000644000175000001440000000156512342627562017501 0ustar plqusers00000000000000 # # 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 # """The ``spyne.server`` package contains the server transports.""" from spyne.server._base import ServerBase spyne-2.11.0/spyne/server/_base.py0000644000175000001440000001467312345433230017005 0ustar plqusers00000000000000 # # 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__) from inspect import isgenerator from spyne import EventManager from spyne.auxproc import process_contexts from spyne.interface import AllYourInterfaceDocuments from spyne.model import Fault from spyne.model import PushBase from spyne.protocol import ProtocolBase from spyne.util import Break from spyne.util import coroutine class ServerBase(object): """This class is the abstract base class for all server transport implementations. Unlike the client transports, this class does not define a pure-virtual method that needs to be implemented by all base classes. If there needs to be a call to start the main loop, it's called ``serve_forever()`` by convention. """ transport = None """The transport type, which is a URI string to its definition by convention.""" def __init__(self, app): self.app = app self.app.transport = self.transport self.event_manager = EventManager(self) self.doc = AllYourInterfaceDocuments(app.interface) def generate_contexts(self, ctx, in_string_charset=None): """Calls create_in_document and decompose_incoming_envelope to get method_request string in order to generate contexts. """ try: # sets ctx.in_document self.app.in_protocol.create_in_document(ctx, in_string_charset) # sets ctx.in_body_doc, ctx.in_header_doc and # ctx.method_request_string self.app.in_protocol.decompose_incoming_envelope(ctx, ProtocolBase.REQUEST) # returns a list of contexts. multiple contexts can be returned # when the requested method also has bound auxiliary methods. retval = self.app.in_protocol.generate_method_contexts(ctx) except Fault as e: ctx.in_object = None ctx.in_error = e ctx.out_error = e retval = (ctx,) return retval def get_in_object(self, ctx): """Uses the ``ctx.in_string`` to set ``ctx.in_body_doc``, which in turn is used to set ``ctx.in_object``.""" try: # sets ctx.in_object and ctx.in_header self.app.in_protocol.deserialize(ctx, message=self.app.in_protocol.REQUEST) except Fault as e: logger.exception(e) ctx.in_object = None ctx.in_error = e ctx.out_error = e def get_out_object(self, ctx): """Calls the matched user function by passing it the ``ctx.in_object`` to set ``ctx.out_object``.""" if ctx.in_error is None: # event firing is done in the spyne.application.Application self.app.process_request(ctx) else: raise ctx.in_error def get_out_string_pull(self, ctx): """Uses the ``ctx.out_object`` to set ``ctx.out_document`` and later ``ctx.out_string``.""" # This means the user wanted to override the way Spyne generates the # outgoing byte stream. So we leave it alone. if ctx.out_string is not None: return if ctx.out_document is None: ret = ctx.out_protocol.serialize(ctx, message=ProtocolBase.RESPONSE) if isgenerator(ret): oobj, = ctx.out_object if oobj is None: ret.throw(Break()) else: assert isinstance(oobj, PushBase), \ "%r is not a PushBase instance" % oobj self.run_push(oobj, ctx, [], ret) oobj.close() if ctx.service_class != None: if ctx.out_error is None: ctx.service_class.event_manager.fire_event( 'method_return_document', ctx) else: ctx.service_class.event_manager.fire_event( 'method_exception_document', ctx) ctx.out_protocol.create_out_string(ctx) if ctx.service_class != None: if ctx.out_error is None: ctx.service_class.event_manager.fire_event( 'method_return_string', ctx) else: ctx.service_class.event_manager.fire_event( 'method_exception_string', ctx) if ctx.out_string is None: ctx.out_string = [""] # for backwards compatibility get_out_string = get_out_string_pull @coroutine def get_out_string_push(self, ctx): """Uses the ``ctx.out_object`` to directly set ``ctx.out_string``.""" ret = ctx.out_protocol.serialize(ctx, message=ProtocolBase.RESPONSE) if isgenerator(ret): try: while True: y = (yield) ret.send(y) except Break: try: ret.throw(Break()) except StopIteration: pass def serve_forever(self): """Implement your event loop here, if needed.""" raise NotImplementedError() def run_push(self, ret, p_ctx, others, gen): assert isinstance(ret, PushBase) assert p_ctx.out_stream is not None # fire events p_ctx.app.event_manager.fire_event('method_return_push', p_ctx) if p_ctx.service_class is not None: p_ctx.service_class.event_manager.fire_event('method_return_push', p_ctx) def _cb_push_finish(): process_contexts(self, others, p_ctx) ret.init(p_ctx, p_ctx.out_stream, gen, _cb_push_finish, None) spyne-2.11.0/spyne/server/django.py0000644000175000001440000002774312345433230017200 0ustar plqusers00000000000000# encoding: utf-8 # # 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 # """The ``spyne.server.django`` module contains a Django-compatible Http transport. It's a thin wrapper around :class:`spyne.server.wsgi.WsgiApplication`. This module is EXPERIMENTAL. Tests and patches are welcome. """ from __future__ import absolute_import import logging from functools import update_wrapper from django.conf import settings from django.http import HttpResponse, HttpResponseNotAllowed, Http404 from django.views.decorators.csrf import csrf_exempt try: from django.http import StreamingHttpResponse except ImportError as e: def StreamingHttpResponse(*args, **kwargs): raise e from spyne.application import get_fault_string_from_exception, Application from spyne.auxproc import process_contexts from spyne.interface import AllYourInterfaceDocuments from spyne.model.fault import Fault from spyne.protocol.soap import Soap11 from spyne.protocol.http import HttpRpc from spyne.server.http import HttpBase, HttpMethodContext from spyne.server.wsgi import WsgiApplication logger = logging.getLogger(__name__) class DjangoApplication(WsgiApplication): """You should use this for regular RPC.""" HttpResponseObject = HttpResponse def __call__(self, request): retval = self.HttpResponseObject() def start_response(status, headers): # Status is one of spyne.const.http status, reason = status.split(' ', 1) retval.status_code = int(status) for header, value in headers: retval[header] = value environ = request.META.copy() # FIXME: No idea what these two did. # They were commented out to fix compatibility issues with # Django-1.2.x # See http://github.com/arskom/spyne/issues/222. # If you don't override wsgi.input django and spyne will read # the same buffer twice. If django read whole buffer spyne # would hang waiting for extra request data. Use DjangoServer instead # of monkeypatching wsgi.inpu. #environ['wsgi.input'] = request #environ['wsgi.multithread'] = False response = WsgiApplication.__call__(self, environ, start_response) self.set_response(retval, response) return retval def set_response(self, retval, response): retval.content = ''.join(response) class StreamingDjangoApplication(DjangoApplication): """You should use this when you're generating HUGE data as response. New in Django 1.5. """ HttpResponseObject = StreamingHttpResponse def set_response(self, retval, response): retval.streaming_content = response class DjangoServer(HttpBase): """Server talking in Django request/response objects.""" def __init__(self, app, chunked=False): super(DjangoServer, self).__init__(app, chunked=chunked) self._wsdl = None def handle_rpc(self, request, *args, **kwargs): """Handle rpc request. :params request: Django HttpRequest instance. :returns: HttpResponse instance. """ contexts = self.get_contexts(request) p_ctx, others = contexts[0], contexts[1:] if p_ctx.in_error: return self.handle_error(p_ctx, others, p_ctx.in_error) self.get_in_object(p_ctx) if p_ctx.in_error: logger.error(p_ctx.in_error) return self.handle_error(p_ctx, others, p_ctx.in_error) self.get_out_object(p_ctx) if p_ctx.out_error: return self.handle_error(p_ctx, others, p_ctx.out_error) try: self.get_out_string(p_ctx) except Exception as e: logger.exception(e) p_ctx.out_error = Fault('Server', get_fault_string_from_exception(e)) return self.handle_error(p_ctx, others, p_ctx.out_error) have_protocol_headers = (isinstance(p_ctx.out_protocol, HttpRpc) and p_ctx.out_header_doc is not None) if have_protocol_headers: p_ctx.transport.resp_headers.update(p_ctx.out_header_doc) if p_ctx.descriptor and p_ctx.descriptor.mtom: raise NotImplementedError if self.chunked: response = StreamingHttpResponse(p_ctx.out_string) else: response = HttpResponse(''.join(p_ctx.out_string)) p_ctx.close() return self.response(response, p_ctx, others) def handle_wsdl(self, request, *args, **kwargs): """Return services WSDL.""" ctx = HttpMethodContext(self, request, 'text/xml; charset=utf-8') if self.doc.wsdl11 is None: raise Http404('WSDL is not available') if self._wsdl is None: # Interface document building is not thread safe so we don't use # server interface document shared between threads. Instead we # create and build interface documents in current thread. This # section can be safely repeated in another concurrent thread. doc = AllYourInterfaceDocuments(self.app.interface) doc.wsdl11.build_interface_document(request.build_absolute_uri()) self._wsdl = doc.wsdl11.get_interface_document() ctx.transport.wsdl = self._wsdl ctx.close() response = HttpResponse(ctx.transport.wsdl) return self.response(response, ctx, ()) def handle_error(self, p_ctx, others, error): """Serialize errors to an iterable of strings and return them. :param p_ctx: Primary (non-aux) context. :param others: List if auxiliary contexts (can be empty). :param error: One of ctx.{in,out}_error. """ if p_ctx.transport.resp_code is None: p_ctx.transport.resp_code = \ p_ctx.out_protocol.fault_to_http_response_code(error) self.get_out_string(p_ctx) resp = HttpResponse(''.join(p_ctx.out_string)) return self.response(resp, p_ctx, others, error) def get_contexts(self, request): """Generate contexts for rpc request. :param request: Django HttpRequest instance. :returns: generated contexts """ initial_ctx = HttpMethodContext(self, request, self.app.out_protocol.mime_type) initial_ctx.in_string = request.body in_string_charset = request.encoding or settings.DEFAULT_CHARSET return self.generate_contexts(initial_ctx, in_string_charset) def response(self, response, p_ctx, others, error=None): """Populate response with transport headers and finalize it. :param response: Django HttpResponse. :param p_ctx: Primary (non-aux) context. :param others: List if auxiliary contexts (can be empty). :param error: One of ctx.{in,out}_error. :returns: Django HttpResponse """ for h, v in p_ctx.transport.resp_headers.items(): if v is not None: response[h] = v if p_ctx.transport.resp_code: response.status_code = int(p_ctx.transport.resp_code[:3]) try: process_contexts(self, others, p_ctx, error=error) except Exception as e: # Report but ignore any exceptions from auxiliary methods. logger.exception(e) return response class DjangoView(object): """Represent spyne service as Django class based view.""" application = None server = None services = () tns = 'spyne.application' name = 'Application' in_protocol = Soap11(validator='lxml') out_protocol = Soap11() interface = None chunked = False http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] def __init__(self, server, **kwargs): self.server = server for key, value in kwargs.items(): setattr(self, key, value) @classmethod def as_view(cls, **initkwargs): """Register application, server and create new view. :returns: callable view function """ # sanitize keyword arguments for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) get = lambda key: initkwargs.get(key) or getattr(cls, key) application = get('application') or Application( services=get('services'), tns=get('tns'), name=get('name'), in_protocol=get('in_protocol'), out_protocol=get('out_protocol'), interface=get('interface') ) server = get('server') or DjangoServer(application, chunked=get('chunked')) def view(request, *args, **kwargs): self = cls(server=server, **initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs return self.dispatch(request, *args, **kwargs) # take name and docstring from class update_wrapper(view, cls, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return view @csrf_exempt def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs) def get(self, request, *args, **kwargs): return self.server.handle_wsdl(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.server.handle_rpc(request, *args, **kwargs) def http_method_not_allowed(self, request, *args, **kwargs): logger.warning('Method Not Allowed (%s): %s', request.method, request.path, extra={'status_code': 405, 'request': self.request}) return HttpResponseNotAllowed(self._allowed_methods()) def options(self, request, *args, **kwargs): """Handle responding to requests for the OPTIONS HTTP verb.""" response = HttpResponse() response['Allow'] = ', '.join(self._allowed_methods()) response['Content-Length'] = '0' return response def _allowed_methods(self): return [m.upper() for m in self.http_method_names if hasattr(self, m)] spyne-2.11.0/spyne/server/http.py0000644000175000001440000001537612345433230016714 0ustar plqusers00000000000000 # # 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 # from collections import defaultdict from spyne import TransportContext from spyne import MethodContext from spyne.protocol.http import HttpPattern from spyne.server import ServerBase from spyne.const.http import gen_body_redirect, HTTP_301, HTTP_302 class HttpTransportContext(TransportContext): """The abstract base class that is used in the transport attribute of the :class:`HttpMethodContext` class and its subclasses.""" def __init__(self, parent, transport, request, content_type): super(HttpTransportContext, self).__init__(parent, transport, 'http') self.req = request """HTTP Request. This is transport-specific""" self.resp_headers = {} """HTTP Response headers.""" self.mime_type = content_type self.resp_code = None """HTTP Response code.""" self.wsdl = None """The WSDL document that is being returned. Only relevant when handling WSDL requests.""" self.wsdl_error = None """The error when handling WSDL requests.""" def get_mime_type(self): return self.resp_headers.get('Content-Type', None) def set_mime_type(self, what): self.resp_headers['Content-Type'] = what def respond(self, resp_code, **kwargs): self.resp_code = resp_code if resp_code in (HTTP_301, HTTP_302): l = kwargs.pop('location') self.resp_headers['Location'] = l self.parent.out_string = [gen_body_redirect(resp_code, l)] self.mime_type = 'text/html' else: # So that deserialization is skipped. self.parent.out_string = [] def get_cookie(self, key): raise NotImplementedError() mime_type = property( lambda self: self.get_mime_type(), lambda self, what: self.set_mime_type(what), ) """Provides an easy way to set outgoing mime type. Synonym for `content_type`""" content_type = mime_type """Provides an easy way to set outgoing mime type. Synonym for `mime_type`""" class HttpMethodContext(MethodContext): """The Http-Specific method context. Http-Specific information is stored in the transport attribute using the :class:`HttpTransportContext` class. """ default_transport_context = HttpTransportContext def __init__(self, transport, req_env, content_type): super(HttpMethodContext, self).__init__(transport) self.transport = self.default_transport_context(self, transport, req_env, content_type) """Holds the WSGI-specific information""" def set_out_protocol(self, what): self._out_protocol = what if isinstance(self.transport, HttpTransportContext): self.transport.set_mime_type(what.mime_type) out_protocol = property(MethodContext.get_out_protocol, set_out_protocol) """Assigning an out protocol overrides the mime type of the transport.""" class HttpBase(ServerBase): transport = 'http://schemas.xmlsoap.org/soap/http' def __init__(self, app, chunked=False, max_content_length=2 * 1024 * 1024, block_length=8 * 1024): super(HttpBase, self).__init__(app) self.chunked = chunked self.max_content_length = max_content_length self.block_length = block_length self._http_patterns = set() for k, v in self.app.interface.service_method_map.items(): # p_ stands for primary p_method_descriptor = v[0] for patt in p_method_descriptor.patterns: if isinstance(patt, HttpPattern): self._http_patterns.add(patt) # this makes sure similar addresses with patterns are evaluated after # addresses with wildcards, which puts the more specific addresses to # the front. self._http_patterns = list(reversed(sorted(self._http_patterns, key=lambda x: (x.address, x.host) ))) def match_pattern(self, ctx, method='', path='', host=''): """Sets ctx.method_request_string if there's a match. It's O(n) which means you should keep your number of patterns as low as possible. :param ctx: A MethodContext instance :param method: The verb in the HTTP Request (GET, POST, etc.) :param host: The contents of the ``Host:`` header :param path: Path but not the arguments. (i.e. stuff before '?', if it's there) """ if not path.startswith('/'): path = '/' + path params = defaultdict(list) for patt in self._http_patterns: assert isinstance(patt, HttpPattern) if patt.verb is not None: match = patt.verb_re.match(method) if match is None: continue if not (match.span() == (0, len(method))): continue for k,v in match.groupdict().items(): params[k].append(v) if patt.host is not None: match = patt.host_re.match(host) if match is None: continue if not (match.span() == (0, len(host))): continue for k,v in match.groupdict().items(): params[k].append(v) address = patt.address if address is None: address = ctx.descriptor.name if address: match = patt.address_re.match(path) if match is None: continue if not (match.span() == (0, len(path))): continue for k,v in match.groupdict().items(): params[k].append(v) ctx.method_request_string = '{%s}%s' % (self.app.interface.get_tns(), patt.endpoint.name) break return params @property def has_patterns(self): return len(self._http_patterns) > 0 spyne-2.11.0/spyne/server/msgpack.py0000644000175000001440000001073312345433230017352 0ustar plqusers00000000000000# encoding: utf8 # # 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 # from __future__ import absolute_import import logging logger = logging.getLogger(__name__) import msgpack from spyne import MethodContext, TransportContext from spyne.auxproc import process_contexts from spyne.error import ValidationError from spyne.model import Fault from spyne.server import ServerBase from spyne.util.six import string_types OUT_RESPONSE_NO_ERROR = 0 OUT_RESPONSE_CLIENT_ERROR = 1 OUT_RESPONSE_SERVER_ERROR = 2 IN_REQUEST = 1 def _process_v1_msg(prot, msg): header = None body = msg[1] if not isinstance(body, string_types): raise ValidationError(body, "Body must be a bytestream.") if len(msg) > 2: header = msg[2] if not isinstance(header, dict): raise ValidationError(header, "Header must be a dict.") for k,v in header.items(): header[k] = msgpack.unpackb(v) ctx = MessagePackMethodContext(prot) ctx.in_string = [body] ctx.transport.in_header = header return ctx class MessagePackTransportContext(TransportContext): def __init__(self, parent, transport): super(MessagePackTransportContext, self).__init__(parent, transport) self.in_header = None self.protocol = None class MessagePackMethodContext(MethodContext): def __init__(self, transport): super(MessagePackMethodContext, self).__init__(transport) self.transport = MessagePackTransportContext(self, transport) class MessagePackServerBase(ServerBase): """Contains the transport protocol logic but not the transport itself. Subclasses should implement logic to move bitstreams in and out of this class.""" def __init__(self, app): super(MessagePackServerBase, self).__init__(app) self._version_map = { IN_REQUEST: _process_v1_msg } def produce_contexts(self, msg): """msg = [IN_REQUEST, body, header]""" if not isinstance(msg, list): raise ValidationError("Request must be a list") if not len(msg) >= 2: raise ValidationError("Request must have at least two elements.") if not isinstance(msg[0], int): raise ValidationError("Request version must be an integer.") processor = self._version_map.get(msg[0], None) if processor is None: raise ValidationError("Unknown request version") initial_ctx = processor(self, msg) contexts = self.generate_contexts(initial_ctx) return contexts[0], contexts[1:] def process_contexts(self, contexts): p_ctx, others = contexts[0], contexts[1:] if p_ctx.in_error: return self.handle_error(p_ctx, others, p_ctx.in_error) self.get_in_object(p_ctx) if p_ctx.in_error: logger.error(p_ctx.in_error) return self.handle_error(p_ctx, others, p_ctx.in_error) self.get_out_object(p_ctx) if p_ctx.out_error: return self.handle_error(p_ctx, others, p_ctx.out_error) try: self.get_out_string(p_ctx) except Exception as e: logger.exception(e) contexts.out_error = Fault('Server', "Internal serialization Error.") return self.handle_error(contexts, others, contexts.out_error) def handle_error(self, p_ctx, others, error): self.get_out_string(p_ctx) try: process_contexts(self, others, p_ctx, error=error) except Exception as e: # Report but ignore any exceptions from auxiliary methods. logger.exception(e) def handle_transport_error(self, error): return msgpack.pack(str(error)) def pack(self, ctx): ctx.out_string = msgpack.packb({OUT_RESPONSE_NO_ERROR: ''.join(ctx.out_string)}), spyne-2.11.0/spyne/server/null.py0000644000175000001440000001240612345433230016676 0ustar plqusers00000000000000 # # 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 # """The ``spyne.server.null`` module contains the NullServer class and its helper objects. The name comes from the "null modem connection". Look it up. """ import logging logger = logging.getLogger(__name__) from spyne import MethodContext from spyne.client import Factory from spyne.const.ansi_color import LIGHT_RED from spyne.const.ansi_color import LIGHT_BLUE from spyne.const.ansi_color import END_COLOR from spyne.server import ServerBase _big_header = ('=' * 40) + LIGHT_RED _big_footer = END_COLOR + ('=' * 40) _small_header = ('-' * 20) + LIGHT_BLUE _small_footer = END_COLOR + ('-' * 20) class NullServer(ServerBase): """A server that doesn't support any transport at all -- it's implemented to test services without having to run a server. It implicitly uses the 'sync' auxiliary processing mode. Note that: 1) ``**kwargs`` overwrite ``*args``. 2) You can do: :: logging.getLogger('spyne.server.null').setLevel(logging.CRITICAL) to hide context delimiters in logs. """ transport = 'noconn://null.spyne' def __init__(self, app, ostr=False, locale='C'): super(NullServer, self).__init__(app) self.service = _FunctionProxy(self, self.app) self.factory = Factory(self.app) self.ostr = ostr self.locale = locale def get_wsdl(self): return self.app.get_interface_document(self.url) def set_options(self, **kwargs): self.service.in_header = kwargs.get('soapheaders', self.service.in_header) class _FunctionProxy(object): def __init__(self, server, app): self.__app = app self.__server = server self.in_header = None def __getattr__(self, key): return _FunctionCall(self.__app, self.__server, key, self.in_header, self.__server.ostr, self.__server.locale) def __getitem__(self, key): return self.__getattr__(key) class _FunctionCall(object): def __init__(self, app, server, key, in_header, ostr, locale): self.app = app self.__key = key self.__server = server self.__in_header = in_header self.__ostr = ostr self.__locale = locale def __call__(self, *args, **kwargs): initial_ctx = MethodContext(self) initial_ctx.method_request_string = self.__key initial_ctx.in_header = self.__in_header initial_ctx.transport.type = NullServer.transport initial_ctx.locale = self.__locale contexts = self.app.in_protocol.generate_method_contexts(initial_ctx) cnt = 0 retval = None logger.warning( "%s start request %s" % (_big_header, _big_footer) ) for ctx in contexts: # this reconstruction is quite costly. I wonder whether it's a # problem though. _type_info = ctx.descriptor.in_message._type_info ctx.in_object = [None] * len(_type_info) for i in range(len(args)): ctx.in_object[i] = args[i] for i,k in enumerate(_type_info.keys()): val = kwargs.get(k, None) if val is not None: ctx.in_object[i] = val if cnt == 0: p_ctx = ctx else: ctx.descriptor.aux.initialize_context(ctx, p_ctx, error=None) # do logging.getLogger('spyne.server.null').setLevel(logging.CRITICAL) # to hide the following logger.warning( "%s start context %s" % (_small_header, _small_footer) ) logger.warning( "%r.%r" % (ctx.service_class, ctx.descriptor.function) ) try: self.app.process_request(ctx) finally: logger.warning( "%s end context %s" % (_small_header, _small_footer) ) if ctx.out_error: raise ctx.out_error else: if len(ctx.descriptor.out_message._type_info) == 0: _retval = None elif len(ctx.descriptor.out_message._type_info) == 1: _retval = ctx.out_object[0] else: _retval = ctx.out_object if cnt == 0 and self.__ostr: self.__server.get_out_string(ctx) _retval = ctx.out_string if cnt == 0: retval = _retval else: ctx.close() cnt += 1 p_ctx.close() logger.warning( "%s end request %s" % (_big_header, _big_footer) ) return retval spyne-2.11.0/spyne/server/pyramid.py0000644000175000001440000000365112345433230017373 0ustar plqusers00000000000000# encoding: utf-8 # # 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 # """The ``spyne.server.pyramid`` module contains a Pyramid-compatible Http transport. It's a thin wrapper around :class:`spyne.server.wsgi.WsgiApplication`. """ from __future__ import absolute_import from pyramid.response import Response from spyne.server.wsgi import WsgiApplication class PyramidApplication(WsgiApplication): """Pyramid View Wrapper. Use this for regular RPC""" def __call__(self, request): retval = Response() def start_response(status, headers): status, reason = status.split(' ', 1) retval.status_int = int(status) for header, value in headers: retval.headers[header] = value response = WsgiApplication.__call__(self, request.environ, start_response) retval.body = "".join(response) return retval def set_response(self, retval, response): retval.body = "".join(response) class StreamingPyramidApplication(WsgiApplication): """You should use this when you're generating HUGE data as response.""" def set_response(self, retval, response): retval.app_iter = response spyne-2.11.0/spyne/server/wsgi.py0000644000175000001440000004722512345433230016704 0ustar plqusers00000000000000 # # 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 # """ A server that uses http as transport via wsgi. It doesn't contain any server logic. """ import logging logger = logging.getLogger(__name__) import cgi import threading import itertools try: from urllib.parse import unquote except ImportError: # Python 2 from urlparse import unquote try: from werkzeug.formparser import parse_form_data except ImportError as import_error: def parse_form_data(*args, **kwargs): raise import_error from spyne.util.six import string_types, BytesIO, PY3 if PY3: from http.cookies import SimpleCookie else: from Cookie import SimpleCookie from spyne.application import get_fault_string_from_exception from spyne.auxproc import process_contexts from spyne.error import RequestTooLongError from spyne.model.binary import File from spyne.model.fault import Fault from spyne.protocol.http import HttpRpc from spyne.server.http import HttpBase from spyne.server.http import HttpMethodContext from spyne.server.http import HttpTransportContext from spyne.util import reconstruct_url from spyne.util.odict import odict from spyne.const.ansi_color import LIGHT_GREEN from spyne.const.ansi_color import END_COLOR from spyne.const.http import HTTP_200 from spyne.const.http import HTTP_404 from spyne.const.http import HTTP_500 try: from spyne.protocol.soap.mime import apply_mtom except ImportError as e: def apply_mtom(*args, **kwargs): raise e def _parse_qs(qs): pairs = (s2 for s1 in qs.split('&') for s2 in s1.split(';')) retval = odict() for name_value in pairs: if name_value is None or len(name_value) == 0: continue nv = name_value.split('=', 1) if len(nv) != 2: # Handle case of a control-name with no equal sign nv.append(None) name = unquote(nv[0].replace('+', ' ')) value = None if nv[1] is not None: value = unquote(nv[1].replace('+', ' ')) l = retval.get(name, None) if l is None: l = retval[name] = [] l.append(value) return retval def _get_http_headers(req_env): retval = {} for k, v in req_env.items(): if k.startswith("HTTP_"): retval[k[5:].lower()]= [v] return retval def _gen_http_headers(headers): retval = [] for k,v in headers.items(): if isinstance(v, (list,tuple)): for v2 in v: retval.append((k,v2)) else: retval.append((k,v)) return retval class WsgiTransportContext(HttpTransportContext): """The class that is used in the transport attribute of the :class:`WsgiMethodContext` class.""" def __init__(self, parent, transport, req_env, content_type): super(WsgiTransportContext, self).__init__(parent, transport, req_env, content_type) self.req_env = self.req """WSGI Request environment""" self.req_method = req_env.get('REQUEST_METHOD', None) """HTTP Request verb, as a convenience to users.""" def get_cookie(self, key): cookie_string = self.req_env.get('HTTP_COOKIE', None) if cookie_string is None: return cookie = SimpleCookie() cookie.load(cookie_string) return cookie.get(key, None).value class WsgiMethodContext(HttpMethodContext): """The WSGI-Specific method context. WSGI-Specific information is stored in the transport attribute using the :class:`WsgiTransportContext` class. """ def __init__(self, transport, req_env, content_type): super(WsgiMethodContext, self).__init__(transport, req_env, content_type) self.transport = WsgiTransportContext(self, transport, req_env, content_type) """Holds the WSGI-specific information""" class WsgiApplication(HttpBase): """A `PEP-3333 `_ compliant callable class. If you want to have a hard-coded URL in the wsdl document, this is how to do it: :: wsgi_app = WsgiApplication(...) wsgi_app.doc.wsdl11.build_interface_document("http://example.com") This is not strictly necessary -- if you don't do this, Spyne will get the URL from the first request, build the wsdl on-the-fly and cache it as a string in memory for later requests. However, if you want to make sure you only have this url on the WSDL, this is how to do it. Note that if your client takes the information in the Wsdl document seriously (not all do), all requests will go to the designated url above even when you get the Wsdl from another location, which can make testing a bit difficult. Use in moderation. Supported events: * ``wsdl`` Called right before the wsdl data is returned to the client. * ``wsdl_exception`` Called right after an exception is thrown during wsdl generation. The exception object is stored in ctx.transport.wsdl_error attribute. * ``wsgi_call`` Called first when the incoming http request is identified as a rpc request. * ``wsgi_return`` Called right before the output stream is returned to the WSGI handler. * ``wsgi_exception`` Called right before returning the exception to the client. * ``wsgi_close`` Called after the whole data has been returned to the client. It's called both from success and error cases. """ def __init__(self, app, chunked=True, max_content_length=2 * 1024 * 1024, block_length=8 * 1024): super(WsgiApplication, self).__init__(app, chunked, max_content_length, block_length) self._mtx_build_interface_document = threading.Lock() self._wsdl = None if self.doc.wsdl11 is not None: self._wsdl = self.doc.wsdl11.get_interface_document() def __call__(self, req_env, start_response, wsgi_url=None): """This method conforms to the WSGI spec for callable wsgi applications (PEP 333). It looks in environ['wsgi.input'] for a fully formed rpc message envelope, will deserialize the request parameters and call the method on the object returned by the get_handler() method. """ url = wsgi_url if url is None: url = reconstruct_url(req_env).split('.wsdl')[0] if self.is_wsdl_request(req_env): return self.handle_wsdl_request(req_env, start_response, url) else: return self.handle_rpc(req_env, start_response) def is_wsdl_request(self, req_env): # Get the wsdl for the service. Assume path_info matches pattern: # /stuff/stuff/stuff/serviceName.wsdl or # /stuff/stuff/stuff/serviceName/?wsdl return ( req_env['REQUEST_METHOD'].upper() == 'GET' and ( req_env['QUERY_STRING'].lower() == 'wsdl' or req_env['PATH_INFO'].endswith('.wsdl') ) ) def handle_wsdl_request(self, req_env, start_response, url): ctx = WsgiMethodContext(self, req_env, 'text/xml; charset=utf-8') if self.doc.wsdl11 is None: start_response(HTTP_404, _gen_http_headers(ctx.transport.resp_headers)) return [HTTP_404] if self._wsdl is None: self._wsdl = self.doc.wsdl11.get_interface_document() ctx.transport.wsdl = self._wsdl if ctx.transport.wsdl is None: try: self._mtx_build_interface_document.acquire() ctx.transport.wsdl = self._wsdl if ctx.transport.wsdl is None: self.doc.wsdl11.build_interface_document(url) ctx.transport.wsdl = self._wsdl = \ self.doc.wsdl11.get_interface_document() except Exception as e: logger.exception(e) ctx.transport.wsdl_error = e self.event_manager.fire_event('wsdl_exception', ctx) start_response(HTTP_500, _gen_http_headers(ctx.transport.resp_headers)) return [HTTP_500] finally: self._mtx_build_interface_document.release() self.event_manager.fire_event('wsdl', ctx) ctx.transport.resp_headers['Content-Length'] = \ str(len(ctx.transport.wsdl)) start_response(HTTP_200, _gen_http_headers(ctx.transport.resp_headers)) ctx.close() return [ctx.transport.wsdl] def handle_error(self, p_ctx, others, error, start_response): """Serialize errors to an iterable of strings and return them. :param p_ctx: Primary (non-aux) context. :param others: List if auxiliary contexts (can be empty). :param error: One of ctx.{in,out}_error. :param start_response: See the WSGI spec for more info. """ if p_ctx.transport.resp_code is None: p_ctx.transport.resp_code = \ p_ctx.out_protocol.fault_to_http_response_code(error) self.get_out_string(p_ctx) p_ctx.out_string = [b''.join(p_ctx.out_string)] p_ctx.transport.resp_headers['Content-Length'] = \ str(len(p_ctx.out_string[0])) self.event_manager.fire_event('wsgi_exception', p_ctx) start_response(p_ctx.transport.resp_code, _gen_http_headers(p_ctx.transport.resp_headers)) try: process_contexts(self, others, p_ctx, error=error) except Exception as e: # Report but ignore any exceptions from auxiliary methods. logger.exception(e) return itertools.chain(p_ctx.out_string, self.__finalize(p_ctx)) def handle_rpc(self, req_env, start_response): initial_ctx = WsgiMethodContext(self, req_env, self.app.out_protocol.mime_type) self.event_manager.fire_event('wsgi_call', initial_ctx) initial_ctx.in_string, in_string_charset = \ self.__reconstruct_wsgi_request(req_env) contexts = self.generate_contexts(initial_ctx, in_string_charset) p_ctx, others = contexts[0], contexts[1:] if p_ctx.in_error: return self.handle_error(p_ctx, others, p_ctx.in_error, start_response) self.get_in_object(p_ctx) if p_ctx.in_error: logger.error(p_ctx.in_error) return self.handle_error(p_ctx, others, p_ctx.in_error, start_response) self.get_out_object(p_ctx) if p_ctx.out_error: return self.handle_error(p_ctx, others, p_ctx.out_error, start_response) if p_ctx.transport.resp_code is None: p_ctx.transport.resp_code = HTTP_200 try: self.get_out_string(p_ctx) except Exception as e: logger.exception(e) p_ctx.out_error = Fault('Server', get_fault_string_from_exception(e)) return self.handle_error(p_ctx, others, p_ctx.out_error, start_response) if isinstance(p_ctx.out_protocol, HttpRpc) and \ p_ctx.out_header_doc is not None: p_ctx.transport.resp_headers.update(p_ctx.out_header_doc) if p_ctx.descriptor and p_ctx.descriptor.mtom: # when there is more than one return type, the result is # encapsulated inside a list. when there's just one, the result # is returned in a non-encapsulated form. the apply_mtom always # expects the objects to be inside an iterable, hence the # following test. out_type_info = p_ctx.descriptor.out_message._type_info if len(out_type_info) == 1: p_ctx.out_object = [p_ctx.out_object] p_ctx.transport.resp_headers, p_ctx.out_string = apply_mtom( p_ctx.transport.resp_headers, p_ctx.out_string, p_ctx.descriptor.out_message._type_info.values(), p_ctx.out_object, ) self.event_manager.fire_event('wsgi_return', p_ctx) if self.chunked: # the user has not set a content-length, so we delete it as the # input is just an iterable. if 'Content-Length' in p_ctx.transport.resp_headers: del p_ctx.transport.resp_headers['Content-Length'] else: p_ctx.out_string = [''.join(p_ctx.out_string)] # if the out_string is a generator function, this hack makes the user # code run until first yield, which lets it set response headers and # whatnot before calling start_response. Is there a better way? try: len(p_ctx.out_string) # generator? # nope p_ctx.transport.resp_headers['Content-Length'] = \ str(sum([len(a) for a in p_ctx.out_string])) start_response(p_ctx.transport.resp_code, _gen_http_headers(p_ctx.transport.resp_headers)) retval = itertools.chain(p_ctx.out_string, self.__finalize(p_ctx)) except TypeError: retval_iter = iter(p_ctx.out_string) try: first_chunk = next(retval_iter) except StopIteration: first_chunk = '' start_response(p_ctx.transport.resp_code, _gen_http_headers(p_ctx.transport.resp_headers)) retval = itertools.chain([first_chunk], retval_iter, self.__finalize(p_ctx)) try: process_contexts(self, others, p_ctx, error=None) except Exception as e: # Report but ignore any exceptions from auxiliary methods. logger.exception(e) return retval def __finalize(self, p_ctx): p_ctx.close() self.event_manager.fire_event('wsgi_close', p_ctx) return () def __reconstruct_wsgi_request(self, http_env): """Reconstruct http payload using information in the http header.""" content_type = http_env.get("CONTENT_TYPE") charset = None if content_type is not None: # fyi, here's what the parse_header function returns: # >>> import cgi; cgi.parse_header("text/xml; charset=utf-8") # ('text/xml', {'charset': 'utf-8'}) content_type = cgi.parse_header(content_type) charset = content_type[1].get('charset', None) return self.__wsgi_input_to_iterable(http_env), charset def __wsgi_input_to_iterable(self, http_env): istream = http_env.get('wsgi.input') length = str(http_env.get('CONTENT_LENGTH', self.max_content_length)) if len(length) == 0: length = 0 else: length = int(length) if length > self.max_content_length: raise RequestTooLongError() bytes_read = 0 while bytes_read < length: bytes_to_read = min(self.block_length, length - bytes_read) if bytes_to_read + bytes_read > self.max_content_length: raise RequestTooLongError() data = istream.read(bytes_to_read) if data is None or data == '': break bytes_read += len(data) yield data def generate_map_adapter(self, ctx): """This function runs on first request because it needs the `'SERVER_NAME'` from the wsgi request environment. """ try: self._mtx_build_map_adapter.acquire() if self._map_adapter is None: # If url map is not bound before, bind url_map req_env = ctx.transport.req_env self._map_adapter = self._http_patterns.bind( req_env['SERVER_NAME'], "/") finally: self._mtx_build_map_adapter.release() def decompose_incoming_envelope(self, prot, ctx, message): """This function is only called by the HttpRpc protocol to have the wsgi environment parsed into ``ctx.in_body_doc`` and ``ctx.in_header_doc``. """ params = {} wsgi_env = ctx.in_document if self.has_patterns: # http://legacy.python.org/dev/peps/pep-0333/#url-reconstruction domain = wsgi_env.get('HTTP_HOST', None) if domain is None: domain = wsgi_env['SERVER_NAME'] else: domain = domain.partition(':')[0] # strip port info params = self.match_pattern(ctx, wsgi_env.get('REQUEST_METHOD', ''), wsgi_env.get('PATH_INFO', ''), domain, ) if ctx.method_request_string is None: ctx.method_request_string = '{%s}%s' % ( prot.app.interface.get_tns(), wsgi_env['PATH_INFO'].split('/')[-1]) logger.debug("%sMethod name: %r%s" % (LIGHT_GREEN, ctx.method_request_string, END_COLOR)) ctx.in_header_doc = _get_http_headers(wsgi_env) ctx.in_body_doc = _parse_qs(wsgi_env['QUERY_STRING']) for k, v in params.items(): if k in ctx.in_body_doc: ctx.in_body_doc[k].extend(v) else: ctx.in_body_doc[k] = list(v) verb = wsgi_env['REQUEST_METHOD'].upper() if verb in ('POST', 'PUT', 'PATCH'): stream, form, files = parse_form_data(wsgi_env, stream_factory=prot.stream_factory) for k, v in form.lists(): val = ctx.in_body_doc.get(k, []) val.extend(v) ctx.in_body_doc[k] = val for k, v in files.items(): val = ctx.in_body_doc.get(k, []) mime_type = v.headers.get('Content-Type', 'application/octet-stream') path = getattr(v.stream, 'name', None) if path is None: val.append(File.Value(name=v.filename, type=mime_type, data=[v.stream.getvalue()])) else: v.stream.seek(0) val.append(File.Value(name=v.filename, type=mime_type, path=path, handle=v.stream)) ctx.in_body_doc[k] = val for k, v in ctx.in_body_doc.items(): if v == ['']: ctx.in_body_doc[k] = [None] spyne-2.11.0/spyne/server/zeromq.py0000644000175000001440000001143012346140504017235 0ustar plqusers00000000000000# # 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 # """The ``spyne.server.zeromq`` module contains a server implementation that uses ZeroMQ (zmq.REP) as transport. """ import threading import zmq from spyne.auxproc import process_contexts from spyne._base import MethodContext from spyne.server import ServerBase class ZmqMethodContext(MethodContext): def __init__(self, app): super(ZmqMethodContext, self).__init__(app) self.transport.type = 'zmq' class ZeroMQServer(ServerBase): """The ZeroMQ server transport.""" transport = 'http://rfc.zeromq.org/' def __init__(self, app, app_url, wsdl_url=None, ctx=None, socket=None): if ctx and socket and ctx is not socket.context: raise ValueError("ctx should be the same as socket.context") super(ZeroMQServer, self).__init__(app) self.app_url = app_url self.wsdl_url = wsdl_url if ctx: self.ctx = ctx elif socket: self.ctx = socket.context else: self.ctx = zmq.Context() if socket: self.zmq_socket = socket else: self.zmq_socket = self.ctx.socket(zmq.REP) self.zmq_socket.bind(app_url) def __handle_wsdl_request(self): return self.app.get_interface_document(self.url) def serve_forever(self): """Runs the ZeroMQ server.""" while True: error = None initial_ctx = ZmqMethodContext(self) initial_ctx.in_string = [self.zmq_socket.recv()] contexts = self.generate_contexts(initial_ctx) p_ctx, others = contexts[0], contexts[1:] if p_ctx.in_error: p_ctx.out_object = p_ctx.in_error error = p_ctx.in_error else: self.get_in_object(p_ctx) if p_ctx.in_error: p_ctx.out_object = p_ctx.in_error error = p_ctx.in_error else: self.get_out_object(p_ctx) if p_ctx.out_error: p_ctx.out_object = p_ctx.out_error error = p_ctx.out_error self.get_out_string(p_ctx) process_contexts(self, others, error) self.zmq_socket.send(b''.join(p_ctx.out_string)) p_ctx.close() class ZeroMQThreadPoolServer(object): """Create a ZeroMQ server transport with several background workers, allowing asynchronous calls. More details on the pattern http://zguide.zeromq.org/page:all#Shared-Queue-DEALER-and-ROUTER-sockets""" def __init__(self, app, app_url, pool_size, wsdl_url=None, ctx=None, socket=None): if ctx and socket and ctx is not socket.context: raise ValueError("ctx should be the same as socket.context") self.app = app if ctx: self.ctx = ctx elif socket: self.ctx = socket.context else: self.ctx = zmq.Context() if socket: self.frontend = socket else: self.frontend = self.ctx.socket(zmq.ROUTER) self.frontend.bind(app_url) be_url = 'inproc://{tns}.{name}'.format(tns=self.app.tns, name=self.app.name) self.pool = [] self.background_jobs = [] for i in range(pool_size): worker, job = self.create_worker(i, be_url) self.pool.append(worker) self.background_jobs.append(job) self.backend = self.ctx.socket(zmq.DEALER) self.backend.bind(be_url) def create_worker(self, i, be_url): socket = self.ctx.socket(zmq.REP) socket.connect(be_url) worker = ZeroMQServer(self.app, be_url, socket=socket) job = threading.Thread(target=worker.serve_forever) job.daemon = True return worker, job def serve_forever(self): """Runs the ZeroMQ server.""" for job in self.background_jobs: job.start() zmq.device(zmq.QUEUE, self.frontend, self.backend) # We never get here... self.frontend.close() self.backend.close() spyne-2.11.0/spyne/test/0000755000175000001440000000000012352131452015017 5ustar plqusers00000000000000spyne-2.11.0/spyne/test/interface/0000755000175000001440000000000012352131452016757 5ustar plqusers00000000000000spyne-2.11.0/spyne/test/interface/wsdl/0000755000175000001440000000000012352131452017730 5ustar plqusers00000000000000spyne-2.11.0/spyne/test/interface/wsdl/__init__.py0000644000175000001440000000510112342627562022051 0ustar plqusers00000000000000 # # 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 # from spyne.application import Application from spyne.interface.wsdl import Wsdl11 from spyne.protocol.soap import Soap11 import spyne.const.xml_ns as ns def build_app(service_list, tns, name): app = Application(service_list, tns, name=name, in_protocol=Soap11(), out_protocol=Soap11()) app.transport = 'http://schemas.xmlsoap.org/soap/http' return app class AppTestWrapper(): def __init__(self, application): self.url = 'http:/localhost:7789/wsdl' self.service_string = '{%s}service' % ns.wsdl self.port_string = '{%s}port' % ns.wsdl self.soap_binding_string = '{%s}binding' % ns.soap self.operation_string = '{%s}operation' % ns.wsdl self.port_type_string = '{%s}portType' % ns.wsdl self.binding_string = '{%s}binding' % ns.wsdl self.app = application self.interface_doc = Wsdl11(self.app.interface) self.interface_doc.build_interface_document(self.url) self.wsdl = self.interface_doc.get_interface_document() def get_service_list(self): return self.interface_doc.root_elt.findall(self.service_string) def get_port_list(self, service): from lxml import etree print((etree.tostring(service, pretty_print=True))) return service.findall(self.port_string) def get_soap_bindings(self, binding): return binding.findall(self.soap_binding_string) def get_port_types(self): return self.interface_doc.root_elt.findall(self.port_type_string) def get_port_operations(self, port_type): return port_type.findall(self.operation_string) def get_bindings(self): return self.interface_doc.root_elt.findall(self.binding_string) def get_binding_operations(self, binding): return [o for o in binding.iterfind(self.operation_string)] spyne-2.11.0/spyne/test/interface/wsdl/defult_services.py0000644000175000001440000000302712342627562023505 0ustar plqusers00000000000000 # # 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 # from spyne.model.primitive import String from spyne.service import ServiceBase from spyne.decorator import rpc def TDefaultPortService(): class DefaultPortService(ServiceBase): @rpc(String, _returns=String) def echo_default_port_service(self, string): return string return DefaultPortService def TDefaultPortServiceMultipleMethods(): class DefaultPortServiceMultipleMethods(ServiceBase): @rpc(String, _returns=String) def echo_one(self, string): return string @rpc(String, _returns=String) def echo_two(self, string): return string @rpc(String, _returns=String) def echo_three(self, string): return string return DefaultPortServiceMultipleMethods spyne-2.11.0/spyne/test/interface/wsdl/port_service_services.py0000644000175000001440000000750712342627562024735 0ustar plqusers00000000000000 # # 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 # from spyne.model.primitive import String from spyne.service import ServiceBase from spyne.decorator import rpc def TS1(): class S1(ServiceBase): name = 'S1Fools' __namespace__ = 'Hippity' @rpc(String, _returns=String) def echo_string_s1(self, string): return string return S1 def TS2(): class S2(ServiceBase): name = 'S2Fools' __namespace__ = 'Hoppity' @rpc(String, _returns=String) def bobs(self, string): return string return S2 def TS3(): class S3(ServiceBase): name = 'S3Fools' __namespace__ = 'Hoppity' __service_name__ = 'BlahService' __port_types__ = ['bobhope', 'larry'] @rpc(String, _returns=String) def echo(self, string): return string @rpc(String, _soap_port_type='bobhope', _returns=String) def echo_bob_hope(self, string): return 'Bob Hope' return S3 def TMissingRPCPortService(): class MissingRPCPortService(ServiceBase): name = 'MissingRPCPortService' __namespace__ = 'MissingRPCPortService' __service_name__ = 'MissingRPCPortService' __port_types__ = ['existing'] @rpc(String, _returns=String) def raise_exception(self, string): return string return MissingRPCPortService def TBadRPCPortService(): class BadRPCPortService(ServiceBase): name = 'MissingRPCPortService' __namespace__ = 'MissingRPCPortService' __service_name__ = 'MissingRPCPortService' __port_types__ = ['existing'] @rpc(String, _soap_port_type='existingss', _returns=String) def raise_exception(self, string): return string return BadRPCPortService def TMissingServicePortService(): class MissingServicePortService(ServiceBase): name = 'MissingRPCPortService' __namespace__ = 'MissingRPCPortService' __service_name__ = 'MissingRPCPortService' __port_types__ = ['existing'] @rpc(String, _soap_port_type='existingss', _returns=String) def raise_exception(self, string): return string return MissingServicePortService def TSinglePortService(): class SinglePortService(ServiceBase): name = 'SinglePort' __service_name__ = 'SinglePortService_ServiceInterface' __namespace__ = 'SinglePortNS' __port_types__ = ['FirstPortType'] @rpc(String, _soap_port_type='FirstPortType', _returns=String) def echo_default_port_service(self, string): return string return SinglePortService def TDoublePortService(): class DoublePortService(ServiceBase): name = 'DoublePort' __namespace__ = 'DoublePort' __port_types__ = ['FirstPort', 'SecondPort'] @rpc(String, _soap_port_type='FirstPort', _returns=String) def echo_first_port(self, string): return string @rpc(String, _soap_port_type='SecondPort', _returns=String) def echo_second_port(self, string): return string return DoublePortService spyne-2.11.0/spyne/test/interface/wsdl/test_bindings.py0000755000175000001440000001205212342627562023154 0ustar plqusers00000000000000#!/usr/bin/env python #encoding: utf8 # # 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 logging.basicConfig(level=logging.DEBUG) import unittest import spyne.const.xml_ns as ns from spyne.interface.wsdl.wsdl11 import Wsdl11 from . import build_app from .port_service_services import TS1 from .port_service_services import TSinglePortService from .port_service_services import TDoublePortService class TestWSDLBindingBehavior(unittest.TestCase): def setUp(self): self.transport = 'http://schemas.xmlsoap.org/soap/http' self.url = 'http:/localhost:7789/wsdl' self.port_type_string = '{%s}portType' % ns.wsdl self.service_string = '{%s}service' % ns.wsdl self.binding_string = '{%s}binding' % ns.wsdl self.operation_string = '{%s}operation' % ns.wsdl self.port_string = '{%s}port' % ns.wsdl def test_binding_simple(self): sa = build_app([TS1()], 'S1Port', 'TestServiceName') interface_doc = Wsdl11(sa.interface) interface_doc.build_interface_document(self.url) services = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:service', namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(services), 1) portTypes = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:portType', namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(portTypes), 1) ports = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' % "S1", namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(ports), 1) def test_binding_multiple(self): SinglePortService, DoublePortService = TSinglePortService(), TDoublePortService() sa = build_app( [SinglePortService, DoublePortService], 'MultiServiceTns', 'AppName' ) interface_doc = Wsdl11(sa.interface) interface_doc.build_interface_document(self.url) # 2 Service, # First has 1 port # Second has 2 # => need 2 service, 3 port and 3 bindings services = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:service', namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(services), 2) portTypes = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:portType', namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(portTypes), 3) bindings = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:binding', namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(bindings), 3) ports = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' % SinglePortService.__service_name__, namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(ports), 1) ports = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' % "DoublePortService", namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(ports), 2) # checking name and type #service SinglePortService for srv in (SinglePortService, DoublePortService): for port in srv.__port_types__: bindings = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:binding[@name="%s"]' % port, namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(bindings[0].get('type'), "tns:%s" % port) spyne-2.11.0/spyne/test/interface/wsdl/test_default_wsdl.py0000755000175000001440000002217712345433230024033 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 logging.basicConfig(level=logging.DEBUG) import unittest from lxml import etree from spyne.application import Application from spyne.test.interface.wsdl import AppTestWrapper from spyne.test.interface.wsdl import build_app from spyne.test.interface.wsdl.defult_services import TDefaultPortService from spyne.test.interface.wsdl.defult_services import TDefaultPortServiceMultipleMethods from spyne.const import REQUEST_SUFFIX from spyne.const import RESPONSE_SUFFIX from spyne.const import ARRAY_SUFFIX from spyne.const.xml_ns import const_nsmap from spyne.decorator import srpc from spyne.service import ServiceBase from spyne.interface.wsdl import Wsdl11 from spyne.model.complex import Array from spyne.model.complex import ComplexModel from spyne.model.complex import XmlAttribute from spyne.model.primitive import Integer from spyne.model.primitive import String from spyne.protocol.soap import Soap11 ns = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/', 'xs':'http://www.w3.org/2001/XMLSchema', } class TestDefaultWSDLBehavior(unittest.TestCase): def _default_service(self, app_wrapper, service_name): self.assertEquals(1, len(app_wrapper.get_service_list())) services = app_wrapper.get_service_list() service = services[0] # the default behavior requires that there be only a single service self.assertEquals(1, len(services)) self.assertEquals(service_name, service.get('name')) # Test the default service has the correct number of ports # the default behavior requires that there be only a single port ports = app_wrapper.get_port_list(service) self.assertEquals(len(ports), 1) def _default_port_type(self, app_wrapper, portType_name, op_count): # Verify the portType Count portTypes = app_wrapper.get_port_types() # there should be only one portType self.assertEquals(1, len(portTypes)) # Verify the portType name portType = portTypes[0] # Check the name of the port self.assertEquals(portType_name, portType.get('name')) # verify that the portType definition has the correct # number of operations ops = app_wrapper.get_port_operations(portType) self.assertEquals(op_count, len(ops)) def _default_binding(self, wrapper, binding_name, opp_count): # the default behavior is only single binding bindings = wrapper.get_bindings() self.assertEquals(1, len(bindings)) # check for the correct binding name binding = bindings[0] name = binding.get('name') self.assertEquals(binding_name, name) # Test that the default service contains the soap binding sb = wrapper.get_soap_bindings(binding) self.assertEquals(1, len(sb)) # verify the correct number of operations ops = wrapper.get_binding_operations(binding) self.assertEquals(opp_count, len(ops)) def _default_binding_methods(self, wrapper, op_count, op_names): binding = wrapper.get_bindings()[0] operations = wrapper.get_binding_operations(binding) # Check the number of operations bound to the port self.assertEquals(op_count, len(operations)) # Check the operation names are correct for op in operations: self.assertTrue(op.get('name') in op_names) def test_default_port_type(self): # Test the default port is created # Test the default port has the correct name app = build_app( [TDefaultPortService()], 'DefaultPortTest', 'DefaultPortName' ) wrapper = AppTestWrapper(app) self._default_port_type(wrapper, 'DefaultPortName', 1) def test_default_port_type_multiple(self): app = build_app( [TDefaultPortServiceMultipleMethods()], 'DefaultServiceTns', 'MultipleDefaultPortServiceApp' ) wrapper = AppTestWrapper(app) self._default_port_type(wrapper, "MultipleDefaultPortServiceApp", 3) def test_default_binding(self): app = build_app( [TDefaultPortService()], 'DefaultPortTest', 'DefaultBindingName' ) wrapper = AppTestWrapper(app) self._default_binding(wrapper, "DefaultBindingName", 1) def test_default_binding_multiple(self): app = build_app( [TDefaultPortServiceMultipleMethods()], 'DefaultPortTest', 'MultipleDefaultBindingNameApp' ) wrapper = AppTestWrapper(app) self._default_binding(wrapper, 'MultipleDefaultBindingNameApp', 3) def test_default_binding_methods(self): app = build_app( [TDefaultPortService()], 'DefaultPortTest', 'DefaultPortMethods' ) wrapper = AppTestWrapper(app) self._default_binding_methods( wrapper, 1, ['echo_default_port_service'] ) def test_bare_simple(self): class SomeService(ServiceBase): @srpc(String, _returns=String, _body_style='bare') def whatever(ss): return ss app = Application([SomeService], tns='tns') app.transport = 'None' wsdl = Wsdl11(app.interface) wsdl.build_interface_document('url') wsdl = etree.fromstring(wsdl.get_interface_document()) schema = wsdl.xpath( '/wsdl:definitions/wsdl:types/xs:schema[@targetNamespace="tns"]', namespaces=ns, ) assert len(schema) == 1 print(etree.tostring(wsdl, pretty_print=True)) elts = schema[0].xpath( 'xs:element[@name="whatever%s"]' % REQUEST_SUFFIX, namespaces=ns) assert len(elts) > 0 assert elts[0].attrib['type'] == 'xs:string' elts = schema[0].xpath( 'xs:element[@name="whatever%s"]' % RESPONSE_SUFFIX, namespaces=ns) assert len(elts) > 0 assert elts[0].attrib['type'] == 'xs:string' def test_bare_with_conflicting_types(self): class SomeService(ServiceBase): @srpc(Array(String), _returns=Array(String)) def whatever(sa): return sa @srpc(Array(String), _returns=Array(String), _body_style='bare') def whatever_bare(sa): return sa app = Application([SomeService], tns='tns') app.transport = 'None' wsdl = Wsdl11(app.interface) wsdl.build_interface_document('url') wsdl = etree.fromstring(wsdl.get_interface_document()) schema, = wsdl.xpath( '/wsdl:definitions/wsdl:types/xs:schema[@targetNamespace="tns"]', namespaces=ns, ) print(etree.tostring(schema, pretty_print=True)) assert len(schema.xpath( 'xs:complexType[@name="string%s"]' % ARRAY_SUFFIX, namespaces=ns)) > 0 elts = schema.xpath( 'xs:element[@name="whatever_bare%s"]' % REQUEST_SUFFIX, namespaces=ns) assert len(elts) > 0 assert elts[0].attrib['type'] == 'tns:string%s' % ARRAY_SUFFIX elts = schema.xpath( 'xs:element[@name="whatever_bare%s"]' % RESPONSE_SUFFIX, namespaces=ns) assert len(elts) > 0 assert elts[0].attrib['type'] == 'tns:string%s' % ARRAY_SUFFIX def test_attribute_of(self): class SomeObject(ComplexModel): c = String a = XmlAttribute(Integer, attribute_of='c') class SomeService(ServiceBase): @srpc(SomeObject) def echo_simple_bare(ss): pass app = Application([SomeService], tns='tns', in_protocol=Soap11(), out_protocol=Soap11()) app.transport = 'None' wsdl = Wsdl11(app.interface) wsdl.build_interface_document('url') wsdl = etree.fromstring(wsdl.get_interface_document()) print(etree.tostring(wsdl, pretty_print=True)) assert len(wsdl.xpath( "/wsdl:definitions/wsdl:types/xs:schema[@targetNamespace='%s']" "/xs:complexType[@name='SomeObject']/xs:sequence/xs:element[@name='c']" '/xs:complexType/xs:simpleContent/xs:extension/xs:attribute[@name="a"]' % (SomeObject.get_namespace()), namespaces=const_nsmap)) > 0 if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/interface/wsdl/test_op_req_suffix.py0000755000175000001440000002356012345433230024224 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 import unittest from webtest import TestApp from spyne.application import Application from spyne.decorator import srpc from spyne.service import ServiceBase from spyne.model.primitive import Integer,Unicode from spyne.model.complex import Iterable from spyne.protocol.soap import Soap11 from spyne.protocol.http import HttpRpc from spyne.protocol.json import JsonDocument from spyne.server.wsgi import WsgiApplication def strip_whitespace(string): return ''.join(string.split()) class TestOperationRequestSuffix(unittest.TestCase): ''' test different protocols with REQUEST_SUFFIX and _operation_name _in_message_name is a concern, will test that as well ''' default_function_name = 'echo' # output is not affected, will use soap output for all tests result_body = ''' Echo, test Echo, test ''' def get_function_names(self, suffix, _operation_name=None, _in_message_name=None): '''This tests the logic of how names are produced. Its logic should match expected behavior of the decorator. returns operation name, in message name, service name depending on args''' function_name = self.default_function_name if _operation_name is None: operation_name = function_name else: operation_name = _operation_name if _in_message_name is None: request_name = operation_name + suffix else: request_name = _in_message_name return function_name, operation_name, request_name def get_app(self, in_protocol, suffix, _operation_name = None, _in_message_name = None): '''setup testapp dependent on suffix and _in_message_name''' import spyne.const spyne.const.REQUEST_SUFFIX = suffix class EchoService(ServiceBase): srpc_kparams = {'_returns': Iterable(Unicode)} if _in_message_name: srpc_kparams['_in_message_name'] = _in_message_name if _operation_name: srpc_kparams['_operation_name'] = _operation_name @srpc(Unicode, Integer, **srpc_kparams) def echo(string, times): for i in range(times): yield 'Echo, %s' % string application = Application([EchoService], tns='spyne.examples.echo', in_protocol=in_protocol, out_protocol=Soap11() ) app = WsgiApplication(application) testapp = TestApp(app) # so that it doesn't interfere with other tests. spyne.const.REQUEST_SUFFIX = '' return testapp def assert_response_ok(self, resp): '''check the default response''' self.assertEqual(resp.status_int, 200, resp) self.assertTrue(strip_whitespace(self.result_body) in strip_whitespace(str(resp)), '{0} not in {1}'.format(self.result_body, resp)) ### application error tests ### def assert_application_error(self, suffix, _operation_name = None, _in_message_name=None): self.assertRaises(ValueError, self.get_app, Soap11(validator='lxml'), suffix, _operation_name, _in_message_name) def test_assert_application_error(self): '''check error when op namd and in name are both used''' self.assert_application_error(suffix='', _operation_name='TestOperationName', _in_message_name = 'TestMessageName') ### soap tests ### def assert_soap_ok(self, suffix, _operation_name = None, _in_message_name=None): '''helper to test soap requests''' # setup app = self.get_app(Soap11(validator='lxml'), suffix, _operation_name, _in_message_name) function_name, operation_name, request_name = self.get_function_names(suffix, _operation_name, _in_message_name) soap_input_body = """ test 2 """.format(request_name) # check wsdl wsdl = app.get('/?wsdl') self.assertEqual(wsdl.status_int, 200, wsdl) self.assertTrue(request_name in wsdl, '{0} not found in wsdl'.format(request_name)) soap_strings = [ ''.format(request_name), ''.format(request_name), ] for soap_string in soap_strings: self.assertTrue(soap_string in wsdl, '{0} not in {1}'.format(soap_string, wsdl)) if request_name != operation_name: wrong_string = ' 0 assert len(doc.xpath('/xs:schema/xs:complexType[@name="SomeObject"]' '/xs:sequence/xs:choice/xs:element[@name="one"]', namespaces={'xs': ns.xsd})) > 0 def test_customized_class_with_empty_subclass(self): class SummaryStatsOfDouble(ComplexModel): _type_info = [('Min', XmlAttribute(Integer, use='required')), ('Max', XmlAttribute(Integer, use='required')), ('Avg', XmlAttribute(Integer, use='required'))] class SummaryStats(SummaryStatsOfDouble): ''' this is an empty base class ''' class Payload(ComplexModel): _type_info = [('Stat1', SummaryStats.customize(nillable=False)), ('Stat2', SummaryStats), ('Stat3', SummaryStats), ('Dummy', Unicode)] class JackedUpService(ServiceBase): @rpc(_returns=Payload) def GetPayload(ctx): return Payload() Application([JackedUpService], tns='kickass.ns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) # if no exceptions while building the schema, no problem. # see: https://github.com/arskom/spyne/issues/226 def test_namespaced_xml_attribute(self): class Release(ComplexModel): __namespace__ = "http://usefulinc.com/ns/doap#" _type_info = [ ('about', XmlAttribute(Unicode, ns="http://www.w3.org/1999/02/22-rdf-syntax-ns#")), ] class Project(ComplexModel): __namespace__ = "http://usefulinc.com/ns/doap#" _type_info = [ ('about', XmlAttribute(Unicode, ns="http://www.w3.org/1999/02/22-rdf-syntax-ns#")), ('release', Release.customize(max_occurs=float('inf'))), ] class RdfService(ServiceBase): @rpc(Unicode, Unicode, _returns=Project) def some_call(ctx, a, b): pass Application([RdfService], tns='spynepi', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) # if no exceptions while building the schema, no problem. def test_customized_simple_type_in_xml_attribute(self): class Product(ComplexModel): __namespace__ = 'some_ns' id = XmlAttribute(Uuid) edition = Unicode edition_id = XmlAttribute(Uuid, attribute_of='edition') class SomeService(ServiceBase): @rpc(Product, _returns=Product) def echo_product(ctx, product): logging.info('edition_id: %r', product.edition_id) return product Application([SomeService], tns='some_ns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) # if no exceptions while building the schema, no problem. def test_binary_encodings(self): class Product(ComplexModel): __namespace__ = 'some_ns' hex = ByteArray(encoding='hex') base64_1 = ByteArray(encoding='base64') base64_2 = ByteArray class SomeService(ServiceBase): @rpc(Product, _returns=Product) def echo_product(ctx, product): logging.info('edition_id: %r', product.edition_id) return product app = Application([SomeService], tns='some_ns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) _ns = {'xs': ns.xsd} pref_xs = ns.const_prefmap[ns.xsd] xs = XmlSchema(app.interface) xs.build_interface_document() elt = xs.get_interface_document()['tns'].xpath( '//xs:complexType[@name="Product"]', namespaces=_ns)[0] assert elt.xpath('//xs:element[@name="base64_1"]/@type', namespaces=_ns)[0] == '%s:base64Binary' % pref_xs assert elt.xpath('//xs:element[@name="base64_2"]/@type', namespaces=_ns)[0] == '%s:base64Binary' % pref_xs assert elt.xpath('//xs:element[@name="hex"]/@type', namespaces=_ns)[0] == '%s:hexBinary' % pref_xs def test_multilevel_customized_simple_type(self): class ExampleService(ServiceBase): __tns__ = 'http://xml.company.com/ns/example/' @rpc(M(Uuid), _returns=Unicode) def say_my_uuid(ctx, uuid): return 'Your UUID: %s' % uuid Application([ExampleService], tns='kickass.ns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) # if no exceptions while building the schema, no problem. # see: http://stackoverflow.com/questions/16042132/cannot-use-mandatory-uuid-or-other-pattern-related-must-be-type-as-rpc-argumen def test_any_tag(self): logging.basicConfig(level=logging.DEBUG) class SomeType(ComplexModel): __namespace__ = "zo" anything = AnyXml(schema_tag='{%s}any' % ns.xsd, namespace='##other', process_contents='lax') docs = get_schema_documents([SomeType]) print(etree.tostring(docs['tns'], pretty_print=True)) any = docs['tns'].xpath('//xsd:any', namespaces={'xsd': ns.xsd}) assert len(any) == 1 assert any[0].attrib['namespace'] == '##other' assert any[0].attrib['processContents'] == 'lax' def _build_xml_data_test_schema(self, custom_root): tns = 'kickass.ns' class ProductEdition(ComplexModel): __namespace__ = tns id = XmlAttribute(Uuid) if custom_root: name = XmlData(Uuid) else: name = XmlData(Unicode) class Product(ComplexModel): __namespace__ = tns id = XmlAttribute(Uuid) edition = ProductEdition class ExampleService(ServiceBase): @rpc(Product, _returns=Product) def say_my_uuid(ctx, product): pass app = Application([ExampleService], tns='kickass.ns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) schema = XmlSchema(app.interface) schema.build_interface_document() schema.build_validation_schema() doc = schema.get_interface_document()['tns'] print(etree.tostring(doc, pretty_print=True)) return schema def test_xml_data_schema_doc(self): schema = self._build_xml_data_test_schema(custom_root=False) assert len(schema.get_interface_document()['tns'].xpath( '/xs:schema/xs:complexType[@name="ProductEdition"]' '/xs:simpleContent/xs:extension/xs:attribute[@name="id"]' ,namespaces={'xs': ns.xsd})) == 1 def _test_xml_data_validation(self): schema = self._build_xml_data_test_schema(custom_root=False) assert schema.validation_schema.validate(etree.fromstring(""" punk """)), schema.validation_schema.error_log.last_error def _test_xml_data_validation_custom_root(self): schema = self._build_xml_data_test_schema(custom_root=True) assert schema.validation_schema.validate(etree.fromstring(""" 00000000-0000-0000-0000-000000000002 """)), schema.validation_schema.error_log.last_error def test_subs(self): from lxml import etree from spyne.util.xml import get_schema_documents xpath = lambda o, x: o.xpath(x, namespaces={"xs": ns.xsd}) m = { "s0": "aa", "s2": "cc", "s3": "dd", } class C(ComplexModel): __namespace__ = "aa" a = Integer b = Integer(sub_name="bb") c = Integer(sub_ns="cc") d = Integer(sub_ns="dd", sub_name="dd") elt = get_schema_documents([C], "aa")['tns'] print(etree.tostring(elt, pretty_print=True)) seq, = xpath(elt, "xs:complexType/xs:sequence") assert len(seq) == 4 assert len(xpath(seq, 'xs:element[@name="a"]')) == 1 assert len(xpath(seq, 'xs:element[@name="bb"]')) == 1 # FIXME: this doesn't feel right. # check the spec to see whether it should it be prefixed. # #assert len(xpath(seq, 'xs:element[@name="{cc}c"]')) == 1 #assert len(xpath(seq, 'xs:element[@name="{dd}dd"]')) == 1 def test_mandatory(self): xpath = lambda o, x: o.xpath(x, namespaces={"xs": ns.xsd}) class C(ComplexModel): __namespace__ = "aa" foo = XmlAttribute(M(Unicode)) elt = get_schema_documents([C])['tns'] print(etree.tostring(elt, pretty_print=True)) foo, = xpath(elt, 'xs:complexType/xs:attribute[@name="foo"]') attrs = foo.attrib assert 'use' in attrs and attrs['use'] == 'required' class TestParseOwnXmlSchema(unittest.TestCase): def test_simple(self): tns = 'some_ns' class SomeGuy(ComplexModel): __namespace__ = 'some_ns' id = Integer schema = get_schema_documents([SomeGuy], tns)['tns'] print(etree.tostring(schema, pretty_print=True)) objects = parse_schema_element(schema) pprint(objects[tns].types) NewGuy = objects[tns].types["SomeGuy"] assert NewGuy.get_type_name() == SomeGuy.get_type_name() assert NewGuy.get_namespace() == SomeGuy.get_namespace() assert dict(NewGuy._type_info) == dict(SomeGuy._type_info) def test_customized_unicode(self): tns = 'some_ns' class SomeGuy(ComplexModel): __namespace__ = tns name = Unicode(max_len=10, pattern="a", min_len=5, default="aa") schema = get_schema_documents([SomeGuy], tns)['tns'] print(etree.tostring(schema, pretty_print=True)) objects = parse_schema_element(schema) pprint(objects[tns].types) NewGuy = objects['some_ns'].types["SomeGuy"] assert NewGuy._type_info['name'].Attributes.max_len == 10 assert NewGuy._type_info['name'].Attributes.min_len == 5 assert NewGuy._type_info['name'].Attributes.pattern == "a" assert NewGuy._type_info['name'].Attributes.default == "aa" def test_boolean_default(self): tns = 'some_ns' class SomeGuy(ComplexModel): __namespace__ = tns bald = Boolean(default=True) schema = get_schema_documents([SomeGuy], tns)['tns'] print(etree.tostring(schema, pretty_print=True)) objects = parse_schema_element(schema) pprint(objects[tns].types) NewGuy = objects['some_ns'].types["SomeGuy"] assert NewGuy._type_info['bald'].Attributes.default == True def test_boolean_attribute_default(self): tns = 'some_ns' class SomeGuy(ComplexModel): __namespace__ = tns bald = XmlAttribute(Boolean(default=True)) schema = get_schema_documents([SomeGuy], tns)['tns'] print(etree.tostring(schema, pretty_print=True)) objects = parse_schema_element(schema) pprint(objects[tns].types) NewGuy = objects['some_ns'].types["SomeGuy"] assert NewGuy._type_info['bald'].Attributes.default == True def test_attribute(self): tns = 'some_ns' class SomeGuy(ComplexModel): __namespace__ = tns name = XmlAttribute(Unicode) schema = get_schema_documents([SomeGuy], tns)['tns'] print(etree.tostring(schema, pretty_print=True)) objects = parse_schema_element(schema) pprint(objects[tns].types) NewGuy = objects['some_ns'].types["SomeGuy"] assert NewGuy._type_info['name'].type is Unicode def test_attribute_with_customized_type(self): tns = 'some_ns' class SomeGuy(ComplexModel): __namespace__ = tns name = XmlAttribute(Unicode(default="aa")) schema = get_schema_documents([SomeGuy], tns)['tns'] print(etree.tostring(schema, pretty_print=True)) objects = parse_schema_element(schema) pprint(objects[tns].types) NewGuy = objects['some_ns'].types["SomeGuy"] assert NewGuy._type_info['name'].type.__orig__ is Unicode assert NewGuy._type_info['name'].type.Attributes.default == "aa" def test_simple_type_explicit_customization(self): class Header(ComplexModel): test = Boolean(min_occurs=0, nillable=False) pw = Unicode.customize(min_occurs=0, nillable=False, min_len=6) class Params(ComplexModel): sendHeader = Header.customize(nillable=False, min_occurs=1) class DummyService(ServiceBase): @rpc(Params, _returns=Unicode) def loadServices(ctx, serviceParams): return '42' Application([DummyService], tns='dummy', name='DummyService', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) # if instantiation doesn't fail, test is green. class TestParseForeignXmlSchema(unittest.TestCase): def test_simple_content(self): tns = 'some_ns' schema = """ """ objects = parse_schema_string(schema) pprint(objects[tns].types) NewGuy = objects[tns].types['SomeGuy'] ti = NewGuy._type_info pprint(dict(ti)) assert issubclass(ti['_data'], XmlData) assert ti['_data'].type is Unicode assert issubclass(ti['attr'], XmlAttribute) assert ti['attr'].type is Unicode if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/interop/0000755000175000001440000000000012352131452016477 5ustar plqusers00000000000000spyne-2.11.0/spyne/test/interop/server/0000755000175000001440000000000012352131452020005 5ustar plqusers00000000000000spyne-2.11.0/spyne/test/interop/server/__init__.py0000644000175000001440000000000012342627562022117 0ustar plqusers00000000000000spyne-2.11.0/spyne/test/interop/server/_service.py0000644000175000001440000002435112345433230022164 0ustar plqusers00000000000000 # # 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 # from spyne.model.binary import Attachment from spyne.model.complex import Array from spyne.model.complex import ComplexModel from spyne.model.complex import SelfReference from spyne.model.enum import Enum from spyne.model.fault import Fault from spyne.model.primitive import AnyXml from spyne.model.primitive import AnyDict from spyne.model.primitive import Boolean from spyne.model.primitive import Time from spyne.model.primitive import Date from spyne.model.primitive import DateTime from spyne.model.primitive import Float from spyne.model.primitive import Integer from spyne.model.primitive import Duration from spyne.model.primitive import String from spyne.model.primitive import Double from spyne.service import ServiceBase from spyne.decorator import rpc from spyne.decorator import srpc from datetime import datetime import logging logger = logging.getLogger(__name__) class SimpleClass(ComplexModel): i = Integer s = String class DocumentedFault(Fault): def __init__(self): super(DocumentedFault, self).__init__( faultcode="Documented", faultstring="A documented fault", faultactor='http://faultactor.example.com', ) class OtherClass(ComplexModel): dt = DateTime d = Double b = Boolean class ClassWithSelfReference(ComplexModel): i = Integer sr = SelfReference class NestedClass(ComplexModel): __namespace__ = "punk.tunk" simple = Array(SimpleClass) s = String i = Integer f = Float other = OtherClass ai = Array(Integer) class NonNillableClass(ComplexModel): __namespace__ = "hunk.sunk" nillable = False min_occurs = 1 dt = DateTime(min_occurs=1, nillable=False) i = Integer(nillable=False) s = String(min_len=1, nillable=False) class ExtensionClass(NestedClass): __namespace__ = "bar" p = NonNillableClass l = DateTime q = Integer DaysOfWeekEnum = Enum( 'Monday', 'Tuesday', 'Wednesday', 'Friday', 'Saturday', 'Sunday', type_name = 'DaysOfWeekEnum' ) class InHeader(ComplexModel): __namespace__ = "spyne.test.interop.server" s = String i = Integer class OutHeader(ComplexModel): __namespace__ = "spyne.test.interop.server" f = Float dt = DateTime class InTraceHeader(ComplexModel): __namespace__ = "spyne.test.interop.server" client=String callDate=DateTime class OutTraceHeader(ComplexModel): __namespace__ = "spyne.test.interop.server" receiptDate = DateTime returnDate = DateTime class InteropServiceWithHeader(ServiceBase): __in_header__ = InHeader __out_header__ = OutHeader @rpc(_returns=InHeader) def echo_in_header(ctx): return ctx.in_header @rpc(_returns=OutHeader) def send_out_header(ctx): ctx.out_header = OutHeader() ctx.out_header.dt = datetime(year=2000, month=1, day=1) ctx.out_header.f = 3.141592653 return ctx.out_header class InteropServiceWithComplexHeader(ServiceBase): __in_header__ = (InHeader, InTraceHeader) __out_header__ = (OutHeader, OutTraceHeader) @rpc(_returns=(InHeader, InTraceHeader)) def echo_in_complex_header(ctx): return ctx.in_header @rpc(_returns=(OutHeader, OutTraceHeader)) def send_out_complex_header(ctx): out_header = OutHeader() out_header.dt = datetime(year=2000, month=1, day=1) out_header.f = 3.141592653 out_trace_header = OutTraceHeader() out_trace_header.receiptDate = datetime(year=2000, month=1, day=1, hour=1, minute=1, second=1, microsecond=1) out_trace_header.returnDate = datetime(year=2000, month=1, day=1, hour=1, minute=1, second=1, microsecond=100) ctx.out_header = (out_header, out_trace_header) return ctx.out_header class InteropPrimitive(ServiceBase): @srpc(AnyXml, _returns=AnyXml) def echo_any(xml): return xml @srpc(AnyDict, _returns=AnyDict) def echo_any_as_dict(xml_as_dict): return xml_as_dict @srpc(Integer, _returns=Integer) def echo_integer(i): return i @srpc(String, _returns=String) def echo_string(s): return s @srpc(DateTime, _returns=DateTime) def echo_datetime(dt): return dt @srpc(DateTime(format='ignored'), _returns=DateTime) def echo_datetime_with_invalid_format(dt): return dt @srpc(Date, _returns=Date) def echo_date(d): return d @srpc(Date(format='ignored'), _returns=Date) def echo_date_with_invalid_format(d): return d @srpc(Time, _returns=Time) def echo_time(t): return t @srpc(Time(format='ignored'), _returns=Time) def echo_time_with_invalid_format(t): return t @srpc(Float, _returns=Float) def echo_float(f): return f @srpc(Double, _returns=Double) def echo_double(f): return f @srpc(Boolean, _returns=Boolean) def echo_boolean(b): return b @srpc(DaysOfWeekEnum, _returns=DaysOfWeekEnum) def echo_enum(day): return day @srpc(Duration, _returns=Duration) def echo_duration(dur): return dur class InteropArray(ServiceBase): @srpc(Array(Integer), _returns=Array(Integer)) def echo_integer_array(ia): return ia @srpc(Array(String), _returns=Array(String)) def echo_string_array(sa): return sa @srpc(Array(DateTime), _returns=Array(DateTime)) def echo_date_time_array(dta): return dta @srpc(Array(Float), _returns=Array(Float)) def echo_float_array(fa): return fa @srpc(Array(Double), _returns=Array(Double)) def echo_double_array(da): return da @srpc(Array(Boolean), _returns=Array(Boolean)) def echo_boolean_array(ba): return ba @srpc(Boolean(max_occurs="unbounded"), _returns=Boolean(max_occurs="unbounded")) def echo_simple_boolean_array(ba): return ba @srpc(Array(Boolean), _returns=Array(Array(Boolean))) def echo_array_in_array(baa): return baa class InteropClass(ServiceBase): @srpc(SimpleClass, _returns=SimpleClass) def echo_simple_class(sc): return sc @srpc(Array(SimpleClass), _returns=Array(SimpleClass)) def echo_simple_class_array(sca): return sca @srpc(NestedClass, _returns=NestedClass) def echo_nested_class(nc): return nc @srpc(Array(NestedClass), _returns=Array(NestedClass)) def echo_nested_class_array(nca): return nca @srpc(ExtensionClass, _returns=ExtensionClass) def echo_extension_class(nc): return nc @srpc(ClassWithSelfReference, _returns=ClassWithSelfReference) def echo_class_with_self_reference(sr): return sr @srpc(Attachment, _returns=Attachment) def echo_attachment(a): assert isinstance(a, Attachment) return a @srpc(Array(Attachment), _returns=Array(Attachment)) def echo_attachment_array(aa): return aa class InteropBare(ServiceBase): @srpc(String, _returns=String, _body_style='bare') def echo_simple_bare(ss): return ss @srpc(Array(String), _returns=Array(String), _body_style='bare') def echo_complex_bare(ss): return ss @srpc(_returns=String, _body_style='bare') def empty_input_bare(): return "empty" @srpc(String, _body_style='bare') def empty_output_bare(ss): assert ss is not None class InteropException(ServiceBase): @srpc() def python_exception(): raise Exception("Possible") @srpc() def soap_exception(): raise Fault("Plausible", "A plausible fault", 'http://faultactor.example.com') @srpc(_throws=DocumentedFault) def documented_exception(): raise DocumentedFault() class InteropMisc(ServiceBase): @srpc( _returns=[ Integer, String, Integer, Array(Enum("MEMBER", type_name="RoleEnum")) ], _out_variable_names=[ 'resultCode', 'resultDescription', 'transactionId', 'roles' ] ) def complex_return(): return [1, "Test", 123, ["MEMBER"]] @srpc(_returns=Integer) def huge_number(): return 2**int(1e5) @srpc(_returns=String) def long_string(): return ('0123456789abcdef' * 16384) @srpc() def test_empty(): pass @srpc(String, Integer, DateTime) def multi_param(s, i, dt): pass @srpc(NonNillableClass, _returns=String) def non_nillable(n): return "OK" @srpc(String, _returns=String, _public_name="do_something") def do_something_else(s): return s @srpc(Integer, _returns=Array(OtherClass)) def return_other_class_array(num): for i in range(num): yield OtherClass(dt=datetime(2010, 12, 6), d=3.0, b=True) @srpc(_returns=Attachment) def return_binary_data(): return Attachment(data=''.join([chr(i) for i in range(256)])) @srpc(_returns=Integer) def return_invalid_data(): return 'a' @srpc(String, _public_name="urn:#getCustomMessages", _in_message="getCustomMessagesMsgIn", _out_message="getCustomMessagesMsgOut", _out_variable_name="CustomMessages", _returns=String) def custom_messages(s): return s services = [ InteropPrimitive, InteropArray, InteropClass, InteropMisc, InteropServiceWithHeader, InteropServiceWithComplexHeader, InteropException, InteropBare, ] spyne-2.11.0/spyne/test/interop/server/httprpc_csv_basic.py0000755000175000001440000000354312342627562024102 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.protocol.xml') logger.setLevel(logging.DEBUG) from spyne.application import Application from spyne.interface.wsdl import Wsdl11 from spyne.protocol.csv import OutCsv from spyne.protocol.http import HttpRpc from spyne.server.wsgi import WsgiApplication from spyne.test.interop.server._service import services httprpc_csv_application = Application(services, 'spyne.test.interop.server.httprpc.csv', in_protocol=HttpRpc(), out_protocol=OutCsv()) host = '127.0.0.1' port = 9750 if __name__ == '__main__': try: from wsgiref.simple_server import make_server from wsgiref.validate import validator wsgi_application = WsgiApplication(httprpc_csv_application) server = make_server(host, port, validator(wsgi_application)) logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', 9750)) logger.info('WSDL is at: /?wsdl') server.serve_forever() except ImportError: print("Error: example server code requires Python >= 2.5") spyne-2.11.0/spyne/test/interop/server/httprpc_pod_basic.py0000755000175000001440000000360712342627562024072 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 # """pod being plain old data""" import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.protocol.xml') logger.setLevel(logging.DEBUG) from spyne.application import Application from spyne.test.interop.server._service import services from spyne.protocol.http import HttpRpc from spyne.server.wsgi import WsgiApplication httprpc_soap_application = Application(services, 'spyne.test.interop.server.httprpc.pod', in_protocol=HttpRpc(), out_protocol=HttpRpc()) host = "127.0.0.1" port = 9751 def main(): try: from wsgiref.simple_server import make_server from wsgiref.validate import validator wsgi_application = WsgiApplication(httprpc_soap_application) server = make_server(host, port, validator(wsgi_application)) logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', 9751)) logger.info('WSDL is at: /?wsdl') server.serve_forever() except ImportError: print("Error: example server code requires Python >= 2.5") if __name__ == '__main__': main() spyne-2.11.0/spyne/test/interop/server/httprpc_pod_basic_twisted.py0000755000175000001440000000367212342627562025637 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 # """pod being plain old data""" import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.protocol.xml') logger.setLevel(logging.DEBUG) from spyne.application import Application from spyne.test.interop.server._service import services from spyne.protocol.http import HttpRpc from spyne.server.twisted import TwistedWebResource httprpc_soap_application = Application(services, 'spyne.test.interop.server.httprpc.pod', in_protocol=HttpRpc(), out_protocol=HttpRpc()) host = '127.0.0.1' port = 9752 def main(argv): from twisted.python import log from twisted.web.server import Site from twisted.web.static import File from twisted.internet import reactor from twisted.python import log observer = log.PythonLoggingObserver('twisted') log.startLoggingWithObserver(observer.emit, setStdout=False) wr = TwistedWebResource(httprpc_soap_application) site = Site(wr) reactor.listenTCP(port, site) logging.info("listening on: %s:%d" % (host,port)) return reactor.run() if __name__ == '__main__': import sys sys.exit(main(sys.argv)) spyne-2.11.0/spyne/test/interop/server/httprpc_soap_basic.py0000755000175000001440000000354312342627562024251 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.protocol.xml') logger.setLevel(logging.DEBUG) from spyne.application import Application from spyne.interface.wsdl import Wsdl11 from spyne.test.interop.server._service import services from spyne.protocol.http import HttpRpc from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication httprpc_soap_application = Application(services, 'spyne.test.interop.server.httprpc.soap', in_protocol=HttpRpc(), out_protocol=Soap11()) host = '127.0.0.1' port = 9753 if __name__ == '__main__': try: from wsgiref.simple_server import make_server from wsgiref.validate import validator wsgi_application = WsgiApplication(httprpc_soap_application) server = make_server(host, port, validator(wsgi_application)) logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', 9753)) logger.info('WSDL is at: /?wsdl') server.serve_forever() except ImportError: print("Error: example server code requires Python >= 2.5") spyne-2.11.0/spyne/test/interop/server/soap_http_basic.py0000755000175000001440000000361412342627562023543 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logger = logging.getLogger('spyne.test.interop.server.soap_http_basic') from spyne.server.wsgi import WsgiApplication from spyne.test.interop.server._service import services from spyne.application import Application from spyne.protocol.soap import Soap11 soap_application = Application(services, 'spyne.test.interop.server', in_protocol=Soap11(validator='lxml', cleanup_namespaces=True), out_protocol=Soap11()) host = '127.0.0.1' port = 9754 def main(): try: from wsgiref.simple_server import make_server from wsgiref.validate import validator wsgi_application = WsgiApplication(soap_application) server = make_server(host, port, validator(wsgi_application)) logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', 9754)) logger.info('WSDL is at: /?wsdl') server.serve_forever() except ImportError: print("Error: example server code requires Python >= 2.5") if __name__ == '__main__': main() spyne-2.11.0/spyne/test/interop/server/soap_http_basic_twisted.py0000755000175000001440000000317412342627562025307 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.wsgi') logger.setLevel(logging.DEBUG) from spyne.test.interop.server.soap_http_basic import soap_application from spyne.server.twisted import TwistedWebResource host = '127.0.0.1' port = 9755 def main(argv): from twisted.python import log from twisted.web.server import Site from twisted.web.static import File from twisted.internet import reactor from twisted.python import log observer = log.PythonLoggingObserver('twisted') log.startLoggingWithObserver(observer.emit, setStdout=False) wr = TwistedWebResource(soap_application) site = Site(wr) reactor.listenTCP(port, site) logging.info("listening on: %s:%d" % (host,port)) return reactor.run() if __name__ == '__main__': import sys sys.exit(main(sys.argv)) spyne-2.11.0/spyne/test/interop/server/soap_http_static.py0000755000175000001440000000355612342627562023756 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.wsgi') logger.setLevel(logging.DEBUG) import os from spyne.test.interop.server.soap_http_basic import soap_application from spyne.server.twisted import TwistedWebResource host = '127.0.0.1' port = 9756 url = 'app' def main(argv): from twisted.python import log from twisted.web.server import Site from twisted.web.static import File from twisted.internet import reactor from twisted.python import log observer = log.PythonLoggingObserver('twisted') log.startLoggingWithObserver(observer.emit, setStdout=False) static_dir = os.path.abspath('.') logging.info("registering static folder %r on /" % static_dir) root = File(static_dir) wr = TwistedWebResource(soap_application) logging.info("registering %r on /%s" % (wr, url)) root.putChild(url, wr) site = Site(root) reactor.listenTCP(port, site) logging.info("listening on: %s:%d" % (host,port)) return reactor.run() if __name__ == '__main__': import sys sys.exit(main(sys.argv)) spyne-2.11.0/spyne/test/interop/server/soap_zeromq.py0000755000175000001440000000307512345433230022727 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 from spyne.test.interop.server.soap_http_basic import soap_application from spyne.server.zeromq import ZeroMQServer host = '127.0.0.1' port = 55555 def main(): url = "tcp://%s:%d" % (host,port) logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) server = ZeroMQServer(soap_application, url) logging.info("************************") logging.info("Use Ctrl+\\ to exit if Ctrl-C does not work.") logging.info("See the 'I can't Ctrl-C my Python/Ruby application. Help!' " "question in http://www.zeromq.org/area:faq for more info.") logging.info("listening on %r" % url) logging.info("************************") server.serve_forever() if __name__ == '__main__': main()spyne-2.11.0/spyne/test/interop/__init__.py0000644000175000001440000000000012342627562020611 0ustar plqusers00000000000000spyne-2.11.0/spyne/test/interop/_test_soap_client_base.py0000644000175000001440000001766112345433230023555 0ustar plqusers00000000000000 # # 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 unittest import time import pytz from spyne.util import six if six.PY2: import thread else: import _thread as thread from spyne.model.fault import Fault from datetime import datetime import socket server_started = {} def test_port_open(port): host = '127.0.0.1' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) s.shutdown(2) return True def run_server(server_type): if server_type == 'http': from spyne.test.interop.server.soap_http_basic import main from spyne.test.interop.server.soap_http_basic import port elif server_type == 'zeromq': from spyne.test.interop.server.soap_zeromq import main from spyne.test.interop.server.soap_zeromq import port else: raise ValueError(server_type) if server_started.get(port, None) is None: def run_server(): main() thread.start_new_thread(run_server, ()) # FIXME: Does anybody have a better idea? time.sleep(2) server_started[port] = test_port_open(port) class SpyneClientTestBase(object): def setUp(self, server_type): run_server(server_type) def test_echo_boolean(self): val = True ret = self.client.service.echo_boolean(val) self.assertEquals(val, ret) val = False ret = self.client.service.echo_boolean(val) self.assertEquals(val, ret) def test_echo_simple_boolean_array(self): val = [False, False, False, True] ret = self.client.service.echo_simple_boolean_array(val) assert val == ret def test_echo_integer_array(self): val = [1, 2, 3, 4, 5] ret = self.client.service.echo_integer_array([1, 2, 3, 4, 5]) self.assertEquals(val, ret) def test_echo_string(self): val = "OK" ret = self.client.service.echo_string(val) self.assertEquals(ret, val) def test_enum(self): DaysOfWeekEnum = self.client.factory.create("DaysOfWeekEnum") val = DaysOfWeekEnum.Monday ret = self.client.service.echo_enum(val) assert val == ret def test_validation(self): non_nillable_class = self.client.factory.create( "{hunk.sunk}NonNillableClass") non_nillable_class.i = 6 non_nillable_class.s = None try: ret = self.client.service.non_nillable(non_nillable_class) raise Exception("must fail") except Fault as e: assert e.faultcode in ('senv:Client.SchemaValidationError', 'senv:Client.ValidationError') def test_echo_in_header(self): in_header = self.client.factory.create('{spyne.test.interop.server}InHeader') in_header.s = 'a' in_header.i = 3 self.client.set_options(soapheaders=in_header) ret = self.client.service.echo_in_header() self.client.set_options(soapheaders=None) self.assertEquals(in_header.s, ret.s) self.assertEquals(in_header.i, ret.i) def test_send_out_header(self): call = self.client.service.send_out_header ret = call() in_header = call.ctx.in_header self.assertTrue(isinstance(ret, type(in_header))) self.assertEquals(ret.dt, in_header.dt) self.assertEquals(ret.f, in_header.f) def _get_xml_test_val(self): return { "test_sub": { "test_subsub1": { "test_subsubsub1" : ["subsubsub1 value"] }, "test_subsub2": ["subsub2 value 1", "subsub2 value 2"], "test_subsub3": [ { "test_subsub3sub1": ["subsub3sub1 value"] }, { "test_subsub3sub2": ["subsub3sub2 value"] }, ], "test_subsub4": [], "test_subsub5": ["x"], } } def test_echo_simple_class(self): val = self.client.factory.create("{spyne.test.interop.server}SimpleClass") val.i = 45 val.s = "asd" ret = self.client.service.echo_simple_class(val) assert ret.i == val.i assert ret.s == val.s def test_echo_nested_class(self): val = self.client.factory.create("{punk.tunk}NestedClass"); val.i = 45 val.s = "asd" val.f = 12.34 val.ai = [1, 2, 3, 45, 5, 3, 2, 1, 4] val.simple = [ self.client.factory.create("{spyne.test.interop.server}SimpleClass"), self.client.factory.create("{spyne.test.interop.server}SimpleClass"), ] val.simple[0].i = 45 val.simple[0].s = "asd" val.simple[1].i = 12 val.simple[1].s = "qwe" val.other = self.client.factory.create("{spyne.test.interop.server}OtherClass"); val.other.dt = datetime.now(pytz.utc) val.other.d = 123.456 val.other.b = True ret = self.client.service.echo_nested_class(val) self.assertEquals(ret.i, val.i) self.assertEqual(ret.ai[0], val.ai[0]) self.assertEquals(ret.simple[0].s, val.simple[0].s) self.assertEqual(ret.other.dt, val.other.dt) def test_echo_extension_class(self): val = self.client.factory.create("{bar}ExtensionClass"); val.i = 45 val.s = "asd" val.f = 12.34 val.simple = [ self.client.factory.create("{spyne.test.interop.server}SimpleClass"), self.client.factory.create("{spyne.test.interop.server}SimpleClass"), ] val.simple[0].i = 45 val.simple[0].s = "asd" val.simple[1].i = 12 val.simple[1].s = "qwe" val.other = self.client.factory.create("{spyne.test.interop.server}OtherClass"); val.other.dt = datetime.now(pytz.utc) val.other.d = 123.456 val.other.b = True val.p = self.client.factory.create("{hunk.sunk}NonNillableClass"); val.p.dt = datetime(2010, 6, 2) val.p.i = 123 val.p.s = "punk" val.l = datetime(2010, 7, 2) val.q = 5 ret = self.client.service.echo_extension_class(val) print(ret) self.assertEquals(ret.i, val.i) self.assertEquals(ret.s, val.s) self.assertEquals(ret.f, val.f) self.assertEquals(ret.simple[0].i, val.simple[0].i) self.assertEquals(ret.other.dt, val.other.dt) self.assertEquals(ret.p.s, val.p.s) def test_python_exception(self): try: self.client.service.python_exception() except Exception as e: pass else: raise Exception("must fail") def test_soap_exception(self): try: self.client.service.soap_exception() except Exception as e: pass else: raise Exception("must fail") def test_complex_return(self): roles = self.client.factory.create("RoleEnum") ret = self.client.service.complex_return() self.assertEquals(ret.resultCode, 1) self.assertEquals(ret.resultDescription, "Test") self.assertEquals(ret.transactionId, 123) self.assertEquals(ret.roles[0], roles.MEMBER) if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/interop/test_django.py0000755000175000001440000001331512345433230021361 0ustar plqusers00000000000000# coding: utf-8 #!/usr/bin/env python # # 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 # from __future__ import absolute_import import datetime from django.test import TestCase, TransactionTestCase, Client from spyne.client.django import DjangoTestClient from spyne.model.fault import Fault from spyne.util.django import DjangoComplexModel from rpctest.core.models import (FieldContainer, RelatedFieldContainer, UserProfile as DjUserProfile) from rpctest.core.views import app, hello_world_service, Container class SpyneTestCase(TransactionTestCase): def setUp(self): self.client = DjangoTestClient('/hello_world/', hello_world_service.app) def _test_say_hello(self): resp = self.client.service.say_hello('Joe',5) list_resp = list(resp) self.assertEqual(len(list_resp), 5) self.assertEqual(list_resp,['Hello, Joe']*5) class DjangoViewTestCase(TestCase): def test_say_hello(self): client = DjangoTestClient('/say_hello/', app) resp = client.service.say_hello('Joe', 5) list_resp = list(resp) self.assertEqual(len(list_resp), 5) self.assertEqual(list_resp, ['Hello, Joe'] * 5) def test_response_encoding(self): client = DjangoTestClient('/say_hello/', app) response = client.service.say_hello.get_django_response('Joe', 5) self.assertTrue('Content-Type' in response) self.assertTrue(response['Content-Type'].startswith('text/xml')) def test_error(self): client = Client() response = client.post('/say_hello/', {}) self.assertContains(response, 'faultstring', status_code=500) def test_wsdl(self): client = Client() response = client.get('/say_hello/') self.assertContains(response, 'location="http://testserver/say_hello/"') class ModelTestCase(TestCase): """Test mapping between django and spyne models.""" def setUp(self): self.client = DjangoTestClient('/api/', app) def test_exclude(self): """Test if excluded field is not mapped.""" type_info = Container.get_flat_type_info(Container) self.assertIn('id', type_info) self.assertNotIn('excluded_field', type_info) def test_get_container(self): """Test mapping from Django model to spyne model.""" get_container = lambda: self.client.service.get_container(2) self.assertRaises(Fault, get_container) container = FieldContainer.objects.create(slug_field='container') FieldContainer.objects.create(slug_field='container2', foreign_key=container, one_to_one_field=container, char_field='yo') c = get_container() self.assertIsInstance(c, Container) def test_create_container(self): """Test complex input to create Django model.""" related_container = RelatedFieldContainer(id='related') new_container = FieldContainer(slug_field='container', date_field=datetime.date.today(), datetime_field=datetime.datetime.now(), time_field=datetime.time(), custom_foreign_key=related_container, custom_one_to_one_field=related_container) create_container = (lambda: self.client.service.create_container( new_container)) c = create_container() self.assertIsInstance(c, Container) self.assertEqual(c.custom_one_to_one_field_id, 'related') self.assertEqual(c.custom_foreign_key_id, 'related') self.assertRaises(Fault, create_container) def _test_create_container_unicode(self): """Test complex unicode input to create Django model.""" new_container = FieldContainer( char_field=u'спайн', text_field=u'спайн', slug_field=u'спайн', date_field=datetime.date.today(), datetime_field=datetime.datetime.now(), time_field=datetime.time() ) create_container = (lambda: self.client.service.create_container( new_container)) c = create_container() self.assertIsInstance(c, Container) self.assertRaises(Fault, create_container) def test_optional_relation_fields(self): """Test if optional_relations flag makes fields optional.""" class UserProfile(DjangoComplexModel): class Attributes(DjangoComplexModel.Attributes): django_model = DjUserProfile self.assertFalse(UserProfile._type_info['user_id'].Attributes.nullable) class UserProfile(DjangoComplexModel): class Attributes(DjangoComplexModel.Attributes): django_model = DjUserProfile django_optional_relations = True self.assertTrue(UserProfile._type_info['user_id'].Attributes.nullable) spyne-2.11.0/spyne/test/interop/test_httprpc.py0000755000175000001440000000654412345433230021611 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 unittest import time import pytz from spyne.util import six if six.PY2: import thread from urllib import urlencode from urllib2 import urlopen from urllib2 import Request from urllib2 import HTTPError else: import _thread as thread from urllib.parse import urlencode from urllib.request import urlopen from urllib.request import Request from urllib.error import HTTPError from datetime import datetime _server_started = False class TestHttpRpc(unittest.TestCase): def setUp(self): global _server_started if not _server_started: def run_server(): from spyne.test.interop.server.httprpc_pod_basic import main main() thread.start_new_thread(run_server, ()) # FIXME: Does anybody have a better idea? time.sleep(2) _server_started = True def test_404(self): url = 'http://localhost:9751/404' try: data = urlopen(url).read() except HTTPError as e: assert e.code == 404 def test_413(self): url = "http://localhost:9751" try: data = Request(url,("foo"*3*1024*1024)) except HTTPError as e: assert e.code == 413 def test_500(self): url = 'http://localhost:9751/python_exception' try: data = urlopen(url).read() except HTTPError as e: assert e.code == 500 def test_500_2(self): url = 'http://localhost:9751/soap_exception' try: data = urlopen(url).read() except HTTPError as e: assert e.code == 500 def test_echo_string(self): url = 'http://localhost:9751/echo_string?s=punk' data = urlopen(url).read() assert data == 'punk' def test_echo_integer(self): url = 'http://localhost:9751/echo_integer?i=444' data = urlopen(url).read() assert data == '444' def test_echo_datetime(self): dt = datetime.now(pytz.utc).isoformat() params = urlencode({ 'dt': dt, }) print(params) url = 'http://localhost:9751/echo_datetime?%s' % str(params) data = urlopen(url).read() assert dt == data def test_echo_datetime_tz(self): dt = datetime.now(pytz.utc).isoformat() params = urlencode({ 'dt': dt, }) print(params) url = 'http://localhost:9751/echo_datetime?%s' % str(params) data = urlopen(url).read() assert dt == data if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/interop/test_pyramid.py0000755000175000001440000000526212345433230021566 0ustar plqusers00000000000000# coding: utf-8 # # 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 unittest from wsgiref.util import setup_testing_defaults from wsgiref.validate import validator from lxml import etree from pyramid import testing from pyramid.config import Configurator from pyramid.request import Request from spyne.protocol.soap import Soap11 from spyne.service import ServiceBase from spyne.decorator import srpc from spyne import Application from spyne.model import Unicode, Integer, Iterable from spyne.server.pyramid import PyramidApplication class SpyneIntegrationTest(unittest.TestCase): """Tests for integration of Spyne into Pyramid view callable""" class HelloWorldService(ServiceBase): @srpc(Unicode, Integer, _returns=Iterable(Unicode)) def say_hello(name, times): for i in range(times): yield 'Hello, %s' % name def setUp(self): request = testing.DummyRequest() self.config = testing.setUp(request=request) def tearDown(self): testing.tearDown() def testGetWsdl(self): """Simple test for serving of WSDL by spyne through pyramid route""" application = PyramidApplication( Application([self.HelloWorldService], tns='spyne.examples.hello', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11())) config = Configurator(settings={'debug_all': True}) config.add_route('home', '/') config.add_view(application, route_name='home') wsgi_app = validator(config.make_wsgi_app()) env = { 'SCRIPT_NAME': '', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/', 'QUERY_STRING': 'wsdl', } setup_testing_defaults(env) request = Request(env) resp = request.get_response(wsgi_app) self.assert_(resp.status.startswith("200 ")) node = etree.XML(resp.body) # will throw exception if non well formed spyne-2.11.0/spyne/test/interop/test_soap_client_http.py0000755000175000001440000000252112342627562023465 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 unittest from spyne.client.http import HttpClient from spyne.test.interop._test_soap_client_base import SpyneClientTestBase from spyne.test.interop.server.soap_http_basic import soap_application from spyne.util.etreeconv import root_dict_to_etree class TestSpyneHttpClient(SpyneClientTestBase, unittest.TestCase): def setUp(self): SpyneClientTestBase.setUp(self, 'http') self.client = HttpClient('http://localhost:9754/', soap_application) self.ns = "spyne.test.interop.server" if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/interop/test_soap_client_http_twisted.py0000755000175000001440000000414612342627562025235 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 # from twisted.trial import unittest from spyne.test.interop._test_soap_client_base import run_server from spyne.client.twisted import TwistedHttpClient from spyne.test.interop.server.soap_http_basic import soap_application class TestSpyneHttpClient(unittest.TestCase): def setUp(self): run_server('http') self.ns = "spyne.test.interop.server._service" self.client = TwistedHttpClient('http://localhost:9754/', soap_application) def test_echo_boolean(self): def eb(ret): raise ret def cb(ret): assert ret == True return self.client.service.echo_boolean(True).addCallbacks(cb, eb) def test_python_exception(self): def eb(ret): print(ret) def cb(ret): assert False, "must fail: %r" % ret return self.client.service.python_exception().addCallbacks(cb, eb) def test_soap_exception(self): def eb(ret): print(type(ret)) def cb(ret): assert False, "must fail: %r" % ret return self.client.service.soap_exception().addCallbacks(cb, eb) def test_documented_exception(self): def eb(ret): print(ret) def cb(ret): assert False, "must fail: %r" % ret return self.client.service.python_exception().addCallbacks(cb, eb) spyne-2.11.0/spyne/test/interop/test_soap_client_zeromq.py0000755000175000001440000000245512345433230024017 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 unittest from spyne.client.zeromq import ZeroMQClient from spyne.test.interop._test_soap_client_base import SpyneClientTestBase from spyne.test.interop.server.soap_http_basic import soap_application class TestSpyneZmqClient(SpyneClientTestBase, unittest.TestCase): def setUp(self): SpyneClientTestBase.setUp(self, 'zeromq') self.client = ZeroMQClient('tcp://localhost:55555', soap_application) self.ns = "spyne.test.interop.server._service" if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/interop/test_suds.py0000755000175000001440000003377712345433230021113 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 unittest from suds.client import Client from suds import WebFault from datetime import datetime from spyne.test.interop._test_soap_client_base import SpyneClientTestBase import logging suds_logger = logging.getLogger('suds') suds_logger.setLevel(logging.INFO) class TestSuds(SpyneClientTestBase, unittest.TestCase): def setUp(self): SpyneClientTestBase.setUp(self, 'http') self.client = Client("http://localhost:9754/?wsdl", cache=None) self.ns = "spyne.test.interop.server" def test_echo_datetime(self): val = datetime.now() ret = self.client.service.echo_datetime(val) assert val == ret def test_echo_datetime_with_invalid_format(self): val = datetime.now() ret = self.client.service.echo_datetime_with_invalid_format(val) assert val == ret def test_echo_date(self): val = datetime.now().date() ret = self.client.service.echo_date(val) assert val == ret def test_echo_date_with_invalid_format(self): val = datetime.now().date() ret = self.client.service.echo_date_with_invalid_format(val) assert val == ret def test_echo_time(self): val = datetime.now().time() ret = self.client.service.echo_time(val) assert val == ret def test_echo_time_with_invalid_format(self): val = datetime.now().time() ret = self.client.service.echo_time_with_invalid_format(val) assert val == ret def test_echo_simple_boolean_array(self): val = [False, False, False, True] ret = self.client.service.echo_simple_boolean_array(val) assert val == ret def test_echo_boolean(self): val = True ret = self.client.service.echo_boolean(val) self.assertEquals(val, ret) val = False ret = self.client.service.echo_boolean(val) self.assertEquals(val, ret) def test_enum(self): DaysOfWeekEnum = self.client.factory.create("DaysOfWeekEnum") val = DaysOfWeekEnum.Monday ret = self.client.service.echo_enum(val) assert val == ret def test_validation(self): non_nillable_class = self.client.factory.create( "{hunk.sunk}NonNillableClass") non_nillable_class.i = 6 non_nillable_class.s = None try: self.client.service.non_nillable(non_nillable_class) except WebFault as e: pass else: raise Exception("must fail") def test_echo_integer_array(self): ia = self.client.factory.create('integerArray') ia.integer.extend([1, 2, 3, 4, 5]) self.client.service.echo_integer_array(ia) def test_echo_in_header(self): in_header = self.client.factory.create('InHeader') in_header.s = 'a' in_header.i = 3 self.client.set_options(soapheaders=in_header) ret = self.client.service.echo_in_header() self.client.set_options(soapheaders=None) print(ret) self.assertEquals(in_header.s, ret.s) self.assertEquals(in_header.i, ret.i) def test_echo_in_complex_header(self): in_header = self.client.factory.create('InHeader') in_header.s = 'a' in_header.i = 3 in_trace_header = self.client.factory.create('InTraceHeader') in_trace_header.client = 'suds' in_trace_header.callDate = datetime(year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0) self.client.set_options(soapheaders=(in_header, in_trace_header)) ret = self.client.service.echo_in_complex_header() self.client.set_options(soapheaders=None) print(ret) self.assertEquals(in_header.s, ret[0].s) self.assertEquals(in_header.i, ret[0].i) self.assertEquals(in_trace_header.client, ret[1].client) self.assertEquals(in_trace_header.callDate, ret[1].callDate) def test_send_out_header(self): out_header = self.client.factory.create('OutHeader') out_header.dt = datetime(year=2000, month=1, day=1) out_header.f = 3.141592653 ret = self.client.service.send_out_header() self.assertTrue(isinstance(ret, type(out_header))) self.assertEquals(ret.dt, out_header.dt) self.assertEquals(ret.f, out_header.f) def test_send_out_complex_header(self): out_header = self.client.factory.create('OutHeader') out_header.dt = datetime(year=2000, month=1, day=1) out_header.f = 3.141592653 out_trace_header = self.client.factory.create('OutTraceHeader') out_trace_header.receiptDate = datetime(year=2000, month=1, day=1, hour=1, minute=1, second=1, microsecond=1) out_trace_header.returnDate = datetime(year=2000, month=1, day=1, hour=1, minute=1, second=1, microsecond=100) ret = self.client.service.send_out_complex_header() self.assertTrue(isinstance(ret[0], type(out_header))) self.assertEquals(ret[0].dt, out_header.dt) self.assertEquals(ret[0].f, out_header.f) self.assertTrue(isinstance(ret[1], type(out_trace_header))) self.assertEquals(ret[1].receiptDate, out_trace_header.receiptDate) self.assertEquals(ret[1].returnDate, out_trace_header.returnDate) # Control the reply soap header (in an unelegant way but this is the # only way with suds) soapheaders = self.client.last_received().getChild("Envelope").getChild("Header") soap_out_header = soapheaders.getChild('OutHeader') self.assertEquals('T'.join((out_header.dt.date().isoformat(), out_header.dt.time().isoformat())), soap_out_header.getChild('dt').getText()) self.assertEquals(unicode(out_header.f), soap_out_header.getChild('f').getText()) soap_out_trace_header = soapheaders.getChild('OutTraceHeader') self.assertEquals('T'.join((out_trace_header.receiptDate.date().isoformat(), out_trace_header.receiptDate.time().isoformat())), soap_out_trace_header.getChild('receiptDate').getText()) self.assertEquals('T'.join((out_trace_header.returnDate.date().isoformat(), out_trace_header.returnDate.time().isoformat())), soap_out_trace_header.getChild('returnDate').getText()) def test_echo_string(self): test_string = "OK" ret = self.client.service.echo_string(test_string) self.assertEquals(ret, test_string) def __get_xml_test_val(self): return { "test_sub": { "test_subsub1": { "test_subsubsub1" : ["subsubsub1 value"] }, "test_subsub2": ["subsub2 value 1", "subsub2 value 2"], "test_subsub3": [ { "test_subsub3sub1": ["subsub3sub1 value"] }, { "test_subsub3sub2": ["subsub3sub2 value"] }, ], "test_subsub4": [], "test_subsub5": ["x"], } } def test_echo_simple_class(self): val = self.client.factory.create("{spyne.test.interop.server}SimpleClass") val.i = 45 val.s = "asd" ret = self.client.service.echo_simple_class(val) assert ret.i == val.i assert ret.s == val.s def test_echo_class_with_self_reference(self): val = self.client.factory.create("{spyne.test.interop.server}ClassWithSelfReference") val.i = 45 val.sr = self.client.factory.create("{spyne.test.interop.server}ClassWithSelfReference") val.sr.i = 50 val.sr.sr = None ret = self.client.service.echo_class_with_self_reference(val) assert ret.i == val.i assert ret.sr.i == val.sr.i def test_echo_nested_class(self): val = self.client.factory.create("{punk.tunk}NestedClass"); val.i = 45 val.s = "asd" val.f = 12.34 val.ai = self.client.factory.create("integerArray") val.ai.integer.extend([1, 2, 3, 45, 5, 3, 2, 1, 4]) val.simple = self.client.factory.create("{spyne.test.interop.server}SimpleClassArray") val.simple.SimpleClass.append(self.client.factory.create("{spyne.test.interop.server}SimpleClass")) val.simple.SimpleClass.append(self.client.factory.create("{spyne.test.interop.server}SimpleClass")) val.simple.SimpleClass[0].i = 45 val.simple.SimpleClass[0].s = "asd" val.simple.SimpleClass[1].i = 12 val.simple.SimpleClass[1].s = "qwe" val.other = self.client.factory.create("{spyne.test.interop.server}OtherClass"); val.other.dt = datetime.now() val.other.d = 123.456 val.other.b = True ret = self.client.service.echo_nested_class(val) self.assertEquals(ret.i, val.i) self.assertEqual(ret.ai[0], val.ai[0]) self.assertEquals(ret.simple.SimpleClass[0].s, val.simple.SimpleClass[0].s) self.assertEqual(ret.other.dt, val.other.dt) def test_huge_number(self): self.assertEquals(self.client.service.huge_number(), 2**int(1e5)) def test_long_string(self): self.assertEquals(self.client.service.long_string(), ('0123456789abcdef' * 16384)) def test_long_string(self): self.client.service.test_empty() def test_echo_extension_class(self): val = self.client.factory.create("{bar}ExtensionClass") val.i = 45 val.s = "asd" val.f = 12.34 val.simple = self.client.factory.create("{spyne.test.interop.server}SimpleClassArray") val.simple.SimpleClass.append(self.client.factory.create("{spyne.test.interop.server}SimpleClass")) val.simple.SimpleClass.append(self.client.factory.create("{spyne.test.interop.server}SimpleClass")) val.simple.SimpleClass[0].i = 45 val.simple.SimpleClass[0].s = "asd" val.simple.SimpleClass[1].i = 12 val.simple.SimpleClass[1].s = "qwe" val.other = self.client.factory.create("{spyne.test.interop.server}OtherClass"); val.other.dt = datetime.now() val.other.d = 123.456 val.other.b = True val.p = self.client.factory.create("{hunk.sunk}NonNillableClass"); val.p.dt = datetime(2010, 6, 2) val.p.i = 123 val.p.s = "punk" val.l = datetime(2010, 7, 2) val.q = 5 ret = self.client.service.echo_extension_class(val) print(ret) self.assertEquals(ret.i, val.i) self.assertEquals(ret.s, val.s) self.assertEquals(ret.f, val.f) self.assertEquals(ret.simple.SimpleClass[0].i, val.simple.SimpleClass[0].i) self.assertEquals(ret.other.dt, val.other.dt) self.assertEquals(ret.p.s, val.p.s) def test_python_exception(self): try: self.client.service.python_exception() raise Exception("must fail") except WebFault as e: pass def test_soap_exception(self): try: self.client.service.soap_exception() raise Exception("must fail") except WebFault as e: pass def test_complex_return(self): ret = self.client.service.complex_return() self.assertEquals(ret.resultCode, 1) self.assertEquals(ret.resultDescription, "Test") self.assertEquals(ret.transactionId, 123) self.assertEquals(ret.roles.RoleEnum[0], "MEMBER") def test_return_invalid_data(self): try: self.client.service.return_invalid_data() raise Exception("must fail") except: pass def test_custom_messages(self): ret = self.client.service.custom_messages("test") assert ret == 'test' def test_echo_simple_bare(self): ret = self.client.service.echo_simple_bare("test") assert ret == 'test' # # This test is disabled because suds does not create the right request # object. Opening the first tag below is wrong. # # # # # # # abc # def # # # # # # The right request looks like this: # # # abc # def # # def _test_echo_complex_bare(self): val = ['abc','def'] ia = self.client.factory.create('stringArray') ia.string.extend(val) ret = self.client.service.echo_complex_bare(ia) assert ret == val if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/interop/test_wsi.py0000755000175000001440000001524712342627562020741 0ustar plqusers00000000000000#!/usr/bin/env python # # WS-I interoperability test http://www.ws-i.org/deliverables/workinggroup.aspx?wg=testingtools # latest download: http://www.ws-i.org/Testing/Tools/2005/06/WSI_Test_Java_Final_1.1.zip # # Before launching this test, you should download the zip file and unpack it in this # directory this should create the wsi-test-tools directory. # # Adapted from http://thestewscope.wordpress.com/2008/08/19/ruby-wrapper-for-ws-i-analyzer-tools/ # from Luca Dariz # import os import string from lxml import etree CONFIG_FILE = 'config.xml' SPYNE_TEST_NS = 'spyne.test.interop.server' SPYNE_TEST_PORT = 'Application' SPYNE_REPORT_FILE = 'wsi-report-spyne.xml' WSI_ANALYZER_CONFIG_TEMPLATE=string.Template(""" false ${ASSERTIONS_FILE} ${PORT_NAME} ${PORT_NAME} ${PORT_NAME} ${PORT_NAME} ${PORT_NAME} ${PORT_NAME} ${PORT_NAME} ${WSDL_URI} """) #This must be changed to point to the physical root of the wsi-installation WSI_HOME_TAG = "WSI_HOME" WSI_HOME_VAL = "wsi-test-tools" WSI_JAVA_HOME_TAG = "WSI_JAVA_HOME" WSI_JAVA_HOME_VAL = WSI_HOME_VAL+"/java" WSI_JAVA_OPTS_TAG = "WSI_JAVA_OPTS" WSI_JAVA_OPTS_VAL = " -Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser" WSI_TEST_ASSERTIONS_FILE = WSI_HOME_VAL+"/common/profiles/SSBP10_BP11_TAD.xml" WSI_STYLESHEET_FILE = WSI_HOME_VAL+"/common/xsl/report.xsl" WSI_EXECUTION_COMMAND = "java ${WSI_JAVA_OPTS} -Dwsi.home=${WSI_HOME} -cp ${WSI_CP}\ org.wsi.test.analyzer.BasicProfileAnalyzer -config " WSIClasspath=[ WSI_JAVA_HOME_VAL+"/lib/wsi-test-tools.jar", WSI_JAVA_HOME_VAL+"/lib", WSI_JAVA_HOME_VAL+"/lib/xercesImpl.jar", WSI_JAVA_HOME_VAL+"/lib/xmlParserAPIs.jar", WSI_JAVA_HOME_VAL+"/lib/wsdl4j.jar", WSI_JAVA_HOME_VAL+"/lib/uddi4j.jar", WSI_JAVA_HOME_VAL+"/lib/axis.jar", WSI_JAVA_HOME_VAL+"/lib/jaxrpc.jar", WSI_JAVA_HOME_VAL+"/lib/saaj.jar", WSI_JAVA_HOME_VAL+"/lib/commons-discovery.jar", WSI_JAVA_HOME_VAL+"/lib/commons-logging.jar" ] WSI_CLASSPATH_TAG = "WSI_CP" WSI_CLASSPATH_VAL = ':'.join(WSIClasspath) def configure_env(): os.environ[WSI_HOME_TAG] = WSI_HOME_VAL os.environ[WSI_JAVA_HOME_TAG] = WSI_JAVA_HOME_VAL os.environ[WSI_JAVA_OPTS_TAG] = WSI_JAVA_OPTS_VAL os.environ[WSI_CLASSPATH_TAG] = WSI_CLASSPATH_VAL def create_config(wsdl_uri, config_file): print(("Creating config for wsdl at %s ...\n" %wsdl_uri)) # extract target elements service = 'ValidatingApplication' port = 'ValidatingApplication' # for wsdl service declarations: # create config(service, port) vars = {'REPORT_FILE':SPYNE_REPORT_FILE, 'STYLESHEET_FILE':WSI_STYLESHEET_FILE, 'ASSERTIONS_FILE':WSI_TEST_ASSERTIONS_FILE, 'WSDL_NAMESPACE':SPYNE_TEST_NS, 'PORT_NAME':SPYNE_TEST_PORT, 'WSDL_URI':wsdl_uri} config = WSI_ANALYZER_CONFIG_TEMPLATE.substitute(vars) f = open(config_file, 'w') f.write(config) f.close() def analyze_wsdl(config_file): # execute ws-i tests # don't execute Analyzer.sh directly since it needs bash os.system(WSI_EXECUTION_COMMAND + config_file) # parse result e = etree.parse(SPYNE_REPORT_FILE).getroot() summary = etree.ETXPath('{%s}summary' %e.nsmap['wsi-report'])(e) if summary: # retrieve overall result of the test result = summary[0].get('result') if result == 'failed': outs = etree.ETXPath('{%s}artifact' %(e.nsmap['wsi-report'],))(e) # filter for the object describing the wsdl test desc = [o for o in outs if o.get('type') == 'description'][0] # loop over every group test for entry in desc.iterchildren(): # loop over every single test for test in entry.iterchildren(): # simply print the error if there is one # an html can be generated using files in wsi-test-tools/common/xsl if test.get('result') == 'failed': fail_msg = etree.ETXPath('{%s}failureMessage' %e.nsmap['wsi-report'])(test) fail_det = etree.ETXPath('{%s}failureDetail' %e.nsmap['wsi-report'])(test) if fail_msg: print(('\nFAILURE in test %s\n' %test.get('id'))) print((fail_msg[0].text)) if fail_det: print('\nFAILURE MSG\n') print((fail_det[0].text)) from spyne.test.interop._test_soap_client_base import run_server if __name__ == '__main__': run_server('http') configure_env() create_config('http://localhost:9754/?wsdl', CONFIG_FILE) analyze_wsdl(CONFIG_FILE) spyne-2.11.0/spyne/test/model/0000755000175000001440000000000012352131452016117 5ustar plqusers00000000000000spyne-2.11.0/spyne/test/model/__init__.py0000644000175000001440000000140612342627562020244 0ustar plqusers00000000000000 # # 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 # spyne-2.11.0/spyne/test/model/test_binary.py0000755000175000001440000000301512345433230021017 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 unittest from lxml import etree from spyne.protocol.soap import Soap11 from spyne.model.binary import ByteArray from spyne.model.binary import _bytes_join import spyne.const.xml_ns ns_xsd = spyne.const.xml_ns.xsd ns_test = 'test_namespace' class TestBinary(unittest.TestCase): def setUp(self): self.data = map(chr, range(256)) def test_data(self): element = etree.Element('test') Soap11().to_parent(None, ByteArray, self.data, element, ns_test) print(etree.tostring(element, pretty_print=True)) element = element[0] a2 = Soap11().from_element(None, ByteArray, element) self.assertEquals(_bytes_join(self.data), _bytes_join(a2)) if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/model/test_complex.py0000755000175000001440000005370212345433230021212 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 pytz import datetime import unittest from pprint import pprint from lxml import etree from base64 import b64encode from spyne import Application from spyne import rpc from spyne import mrpc from spyne import ServiceBase from spyne.const import xml_ns from spyne.error import ResourceNotFoundError from spyne.interface import Interface from spyne.interface.wsdl import Wsdl11 from spyne.protocol import ProtocolBase from spyne.protocol.soap import Soap11 from spyne.server.null import NullServer from spyne.model import ByteArray from spyne.model import Array from spyne.model import ComplexModel from spyne.model import SelfReference from spyne.model import XmlData from spyne.model import XmlAttribute from spyne.model import Unicode from spyne.model import DateTime from spyne.model import Float from spyne.model import Integer from spyne.model import String from spyne.protocol.dictdoc import SimpleDictDocument from spyne.protocol.xml import XmlDocument from spyne.test import FakeApp ns_test = 'test_namespace' class Address(ComplexModel): street = String city = String zip = Integer since = DateTime lattitude = Float longitude = Float Address.resolve_namespace(Address, __name__) class Person(ComplexModel): name = String birthdate = DateTime age = Integer addresses = Array(Address) titles = Array(String) Person.resolve_namespace(Person, __name__) class Employee(Person): employee_id = Integer salary = Float Employee.resolve_namespace(Employee, __name__) class Level2(ComplexModel): arg1 = String arg2 = Float Level2.resolve_namespace(Level2, __name__) class Level3(ComplexModel): arg1 = Integer Level3.resolve_namespace(Level3, __name__) class Level4(ComplexModel): arg1 = String Level4.resolve_namespace(Level4, __name__) class Level1(ComplexModel): level2 = Level2 level3 = Array(Level3) level4 = Array(Level4) Level1.resolve_namespace(Level1, __name__) class TestComplexModel(unittest.TestCase): def test_simple_class(self): a = Address() a.street = '123 happy way' a.city = 'badtown' a.zip = 32 a.lattitude = 4.3 a.longitude = 88.0 element = etree.Element('test') XmlDocument().to_parent(None, Address, a, element, ns_test) element = element[0] self.assertEquals(5, len(element.getchildren())) a.since = datetime.datetime(year=2011, month=12, day=31, tzinfo=pytz.utc) element = etree.Element('test') XmlDocument().to_parent(None, Address, a, element, ns_test) element = element[0] self.assertEquals(6, len(element.getchildren())) r = XmlDocument().from_element(None, Address, element) self.assertEquals(a.street, r.street) self.assertEquals(a.city, r.city) self.assertEquals(a.zip, r.zip) self.assertEquals(a.lattitude, r.lattitude) self.assertEquals(a.longitude, r.longitude) self.assertEquals(a.since, r.since) def test_nested_class(self): # FIXME: this test is incomplete p = Person() element = etree.Element('test') XmlDocument().to_parent(None, Person, p, element, ns_test) element = element[0] self.assertEquals(None, p.name) self.assertEquals(None, p.birthdate) self.assertEquals(None, p.age) self.assertEquals(None, p.addresses) def test_class_array(self): peeps = [] names = ['bob', 'jim', 'peabody', 'mumblesleeves'] dob = datetime.datetime(1979, 1, 1, tzinfo=pytz.utc) for name in names: a = Person() a.name = name a.birthdate = dob a.age = 27 peeps.append(a) type = Array(Person) type.resolve_namespace(type, __name__) element = etree.Element('test') XmlDocument().to_parent(None, type, peeps, element, ns_test) element = element[0] self.assertEquals(4, len(element.getchildren())) peeps2 = XmlDocument().from_element(None, type, element) for i in range(0, 4): self.assertEquals(peeps2[i].name, names[i]) self.assertEquals(peeps2[i].birthdate, dob) def test_class_nested_array(self): peeps = [] names = ['bob', 'jim', 'peabody', 'mumblesleves'] for name in names: a = Person() a.name = name a.birthdate = datetime.datetime(1979, 1, 1) a.age = 27 a.addresses = [] for i in range(0, 25): addr = Address() addr.street = '555 downtown' addr.city = 'funkytown' a.addresses.append(addr) peeps.append(a) type = Array(Person) type.resolve_namespace(type, __name__) element = etree.Element('test') XmlDocument().to_parent(None, type, peeps, element, ns_test) element = element[0] self.assertEquals(4, len(element.getchildren())) peeps2 = XmlDocument().from_element(None, type, element) for peep in peeps2: self.assertEquals(27, peep.age) self.assertEquals(25, len(peep.addresses)) self.assertEquals('funkytown', peep.addresses[18].city) def test_complex_class(self): l = Level1() l.level2 = Level2() l.level2.arg1 = 'abcd' l.level2.arg2 = 1.444 l.level3 = [] l.level4 = [] for i in range(0, 100): a = Level3() a.arg1 = i l.level3.append(a) for i in range(0, 4): a = Level4() a.arg1 = str(i) l.level4.append(a) element = etree.Element('test') XmlDocument().to_parent(None, Level1, l, element, ns_test) element = element[0] l1 = XmlDocument().from_element(None, Level1, element) self.assertEquals(l1.level2.arg1, l.level2.arg1) self.assertEquals(l1.level2.arg2, l.level2.arg2) self.assertEquals(len(l1.level4), len(l.level4)) self.assertEquals(100, len(l.level3)) class X(ComplexModel): __namespace__ = 'tns' x = Integer(nillable=True, max_occurs='unbounded') class Y(X): __namespace__ = 'tns' y = Integer class TestIncompleteInput(unittest.TestCase): def test_x(self): x = X() x.x = [1, 2] element = etree.Element('test') XmlDocument().to_parent(None, X, x, element, 'tns') msg = element[0] r = XmlDocument().from_element(None, X, msg) self.assertEqual(r.x, [1, 2]) def test_y_fromxml(self): x = X() x.x = [1, 2] element = etree.Element('test') XmlDocument().to_parent(None, X, x, element, 'tns') msg = element[0] r = XmlDocument().from_element(None, Y, msg) self.assertEqual(r.x, [1, 2]) def test_y_toxml(self): y = Y() y.x = [1, 2] y.y = 38 element = etree.Element('test') XmlDocument().to_parent(None, Y, y, element, 'tns') msg = element[0] r = XmlDocument().from_element(None, Y, msg) class SisMsg(ComplexModel): data_source = String(nillable=False, min_occurs=1, max_occurs=1, max_len=50) direction = String(nillable=False, min_occurs=1, max_occurs=1, max_len=50) interface_name = String(nillable=False, min_occurs=1, max_occurs=1, max_len=50) crt_dt = DateTime(nillable=False) class EncExtractXs(ComplexModel): __min_occurs__ = 1 __max_occurs__ = 1 mbr_idn = Integer(nillable=False, min_occurs=1, max_occurs=1, max_len=18) enc_idn = Integer(nillable=False, min_occurs=1, max_occurs=1, max_len=18) hist_idn = Integer(nillable=False, min_occurs=1, max_occurs=1, max_len=18) class TestXmlAttribute(unittest.TestCase): def assertIsNotNone(self, obj, msg=None): """Stolen from Python 2.7 stdlib.""" if obj is None: standardMsg = 'unexpectedly None' self.fail(self._formatMessage(msg, standardMsg)) def test_add_to_schema(self): class CM(ComplexModel): i = Integer s = String a = XmlAttribute(String) app = FakeApp() app.tns = 'tns' CM.resolve_namespace(CM, app.tns) interface = Interface(app) interface.add_class(CM) wsdl = Wsdl11(interface) wsdl.build_interface_document('http://a-aaaa.com') pref = CM.get_namespace_prefix(interface) type_def = wsdl.get_schema_info(pref).types[CM.get_type_name()] attribute_def = type_def.find('{%s}attribute' % xml_ns.xsd) print(etree.tostring(type_def, pretty_print=True)) self.assertIsNotNone(attribute_def) self.assertEqual(attribute_def.get('name'), 'a') self.assertEqual(attribute_def.get('type'), CM.a.type.get_type_name_ns(interface)) def test_b64_non_attribute(self): class PacketNonAttribute(ComplexModel): __namespace__ = 'myns' Data = ByteArray test_string = 'yo test data' b64string = b64encode(test_string) gg = PacketNonAttribute(Data=test_string) element = etree.Element('test') Soap11().to_parent(None, PacketNonAttribute, gg, element, gg.get_namespace()) element = element[0] #print etree.tostring(element, pretty_print=True) data = element.find('{%s}Data' % gg.get_namespace()).text self.assertEquals(data, b64string) s1 = Soap11().from_element(None, PacketNonAttribute, element) assert s1.Data[0] == test_string def test_b64_attribute(self): class PacketAttribute(ComplexModel): __namespace__ = 'myns' Data = XmlAttribute(ByteArray, use='required') test_string = 'yo test data' b64string = b64encode(test_string) gg = PacketAttribute(Data=test_string) element = etree.Element('test') Soap11().to_parent(None, PacketAttribute, gg, element, gg.get_namespace()) element = element[0] #print etree.tostring(element, pretty_print=True) self.assertEquals(element.attrib['Data'], b64string) s1 = Soap11().from_element(None, PacketAttribute, element) assert s1.Data[0] == test_string def test_customized_type(self): class SomeClass(ComplexModel): a = XmlAttribute(Integer(ge=4)) class SomeService(ServiceBase): @rpc(SomeClass) def some_call(ctx, some_class): pass app = Application([SomeService], 'some_tns') class TestSimpleTypeRestrictions(unittest.TestCase): def test_simple_type_info(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = CM i = Integer s = String sti = CCM.get_simple_type_info(CCM) pprint(sti) assert "i" in sti assert sti["i"].path == ('i',) assert sti["i"].type is Integer assert sti["s"].parent is CCM assert "s" in sti assert sti["s"].path == ('s',) assert sti["s"].type is String assert sti["s"].parent is CCM assert "c.i" in sti assert sti["c.i"].path == ('c','i') assert sti["c.i"].type is Integer assert sti["c.i"].parent is CM assert "c.s" in sti assert sti["c.s"].path == ('c','s') assert sti["c.s"].type is String assert sti["c.s"].parent is CM def test_simple_type_info_conflicts(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = CM c_i = Float try: CCM.get_simple_type_info(CCM, hier_delim='_') except ValueError: pass else: raise Exception("must fail") class TestFlatDict(unittest.TestCase): def test_basic(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = CM i = Integer s = String val = CCM(i=5, s='a', c=CM(i=7, s='b')) d = SimpleDictDocument().object_to_simple_dict(CCM, val) assert d['i'] == 5 assert d['s'] == 'a' assert d['c.i'] == 7 assert d['c.s'] == 'b' assert len(d) == 4 def test_array_not_none(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = Array(CM) val = CCM(c=[CM(i=i, s='b'*(i+1)) for i in range(2)]) d = SimpleDictDocument().object_to_simple_dict(CCM, val) print(d) assert d['c[0].i'] == 0 assert d['c[0].s'] == 'b' assert d['c[1].i'] == 1 assert d['c[1].s'] == 'bb' assert len(d) == 4 def test_array_none(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = Array(CM) val = CCM() d = SimpleDictDocument().object_to_simple_dict(CCM, val) print(d) assert len(d) == 0 def test_array_nested(self): class CM(ComplexModel): i = Array(Integer) class CCM(ComplexModel): c = Array(CM) val = CCM(c=[CM(i=range(i)) for i in range(2, 4)]) d = SimpleDictDocument().object_to_simple_dict(CCM, val) pprint(d) assert d['c[0].i'] == [0,1] assert d['c[1].i'] == [0,1,2] assert len(d) == 2 class TestSelfRefence(unittest.TestCase): def test_canonical_case(self): class TestSelfReference(ComplexModel): self_reference = SelfReference assert (TestSelfReference._type_info['self_reference'] is TestSelfReference) class SoapService(ServiceBase): @rpc(_returns=TestSelfReference) def view_categories(ctx): pass Application([SoapService], 'service.soap', in_protocol=ProtocolBase(), out_protocol=ProtocolBase()) def test_self_referential_array_workaround(self): from spyne.util.dictdoc import get_object_as_dict class Category(ComplexModel): id = Integer(min_occurs=1, max_occurs=1, nillable=False) Category._type_info['children'] = Array(Category) parent = Category() parent.children = [Category(id=0), Category(id=1)] d = get_object_as_dict(parent, Category) pprint(d) assert d['children'][0]['id'] == 0 assert d['children'][1]['id'] == 1 class SoapService(ServiceBase): @rpc(_returns=Category) def view_categories(ctx): pass Application([SoapService], 'service.soap', in_protocol=ProtocolBase(), out_protocol=ProtocolBase()) def test_canonical_array(self): class Category(ComplexModel): id = Integer(min_occurs=1, max_occurs=1, nillable=False) children = Array(SelfReference) parent = Category() parent.children = [Category(id=1), Category(id=2)] sr, = Category._type_info['children']._type_info.values() assert issubclass(sr, Category) def test_array_type_name(self): assert Array(String, type_name='punk').__type_name__ == 'punk' def test_ctor_kwargs(self): class Category(ComplexModel): id = Integer(min_occurs=1, max_occurs=1, nillable=False) children = Array(Unicode) v = Category(id=5, children=['a','b']) assert v.id == 5 assert v.children == ['a', 'b'] def test_ctor_args(self): class Category(ComplexModel): id = XmlData(Integer(min_occurs=1, max_occurs=1, nillable=False)) children = Array(Unicode) v = Category(id=5, children=['a','b']) assert v.id == 5 assert v.children == ['a', 'b'] v = Category(5, children=['a','b']) assert v.id == 5 assert v.children == ['a', 'b'] def test_ctor_args_2(self): class Category(ComplexModel): children = Array(Unicode) class BetterCategory(Category): sub_category = Unicode v = BetterCategory(children=['a','b'], sub_category='aaa') assert v.children == ['a', 'b'] assert v.sub_category == 'aaa' class TestMemberRpc(unittest.TestCase): def test_simple(self): class SomeComplexModel(ComplexModel): @mrpc() def put(self, ctx): return "PUNK!!!" methods = SomeComplexModel.Attributes.methods print(methods) assert 'put' in methods def test_simple_customize(self): class SomeComplexModel(ComplexModel): @mrpc() def put(self, ctx): return "PUNK!!!" methods = SomeComplexModel.customize(zart='zurt').Attributes.methods print(methods) assert 'put' in methods def test_simple_with_fields(self): class SomeComplexModel(ComplexModel): a = Integer @mrpc() def put(self, ctx): return "PUNK!!!" methods = SomeComplexModel.Attributes.methods print(methods) assert 'put' in methods def test_simple_with_explicit_fields(self): class SomeComplexModel(ComplexModel): _type_info = [('a', Integer)] @mrpc() def put(self, ctx): return "PUNK!!!" methods = SomeComplexModel.Attributes.methods print(methods) assert 'put' in methods def test_native_call(self): v = 'whatever' class SomeComplexModel(ComplexModel): @mrpc() def put(self, ctx): return v assert SomeComplexModel().put(None) == v def test_interface(self): class SomeComplexModel(ComplexModel): @mrpc() def member_method(self, ctx): pass methods = SomeComplexModel.Attributes.methods print(methods) assert 'member_method' in methods class SomeService(ServiceBase): @rpc(_returns=SomeComplexModel) def service_method(ctx): return SomeComplexModel() app = Application([SomeService], 'some_ns') mmm = __name__ + '.SomeComplexModel.member_method' assert mmm in app.interface.method_id_map def test_interface_mult(self): class SomeComplexModel(ComplexModel): @mrpc() def member_method(self, ctx): pass methods = SomeComplexModel.Attributes.methods print(methods) assert 'member_method' in methods class SomeService(ServiceBase): @rpc(_returns=SomeComplexModel) def service_method(ctx): return SomeComplexModel() @rpc(_returns=SomeComplexModel.customize(type_name='zon')) def service_method_2(ctx): return SomeComplexModel() app = Application([SomeService], 'some_ns') mmm = __name__ + '.SomeComplexModel.member_method' assert mmm in app.interface.method_id_map def test_remote_call_error(self): from spyne import mrpc v = 'deger' class SomeComplexModel(ComplexModel): @mrpc(_returns=SelfReference) def put(self, ctx): return v class SomeService(ServiceBase): @rpc(_returns=SomeComplexModel) def get(ctx): return SomeComplexModel() null = NullServer(Application([SomeService], tns='some_tns')) try: null.service.put() except ResourceNotFoundError: pass else: raise Exception("Must fail with: \"Requested resource " "'{spyne.test.model.test_complex}SomeComplexModel' not found\"") def test_signature(self): class SomeComplexModel(ComplexModel): @mrpc() def member_method(self, ctx): pass methods = SomeComplexModel.Attributes.methods # we use __orig__ because implicit classes are .customize(validate_freq=False)'d assert methods['member_method'].in_message._type_info[0].__orig__ is SomeComplexModel def test_self_reference(self): from spyne import mrpc class SomeComplexModel(ComplexModel): @mrpc(_returns=SelfReference) def method(self, ctx): pass methods = SomeComplexModel.Attributes.methods assert methods['method'].out_message._type_info[0] is SomeComplexModel def test_remote_call_success(self): from spyne import mrpc class SomeComplexModel(ComplexModel): i = Integer @mrpc(_returns=SelfReference) def echo(self, ctx): return self class SomeService(ServiceBase): @rpc(_returns=SomeComplexModel) def get(ctx): return SomeComplexModel() null = NullServer(Application([SomeService], tns='some_tns')) v = SomeComplexModel(i=5) assert null.service['SomeComplexModel.echo'](v) is v def test_order(self): class CM(ComplexModel): _type_info = [ ('a', Integer), ('c', Integer(order=0)) ] assert CM._type_info.keys() == ['c', 'a'] if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/model/test_enum.py0000755000175000001440000001202112345433230020474 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 # from spyne.model.complex import ComplexModel import unittest from pprint import pprint from spyne.application import Application from spyne.const.xml_ns import xsd as _ns_xsd from spyne.interface.wsdl.wsdl11 import Wsdl11 from spyne.model.complex import Array from spyne.protocol.xml import XmlDocument from spyne.protocol.soap.soap11 import Soap11 from spyne.server.wsgi import WsgiApplication from spyne.service import ServiceBase from spyne.decorator import rpc from spyne.model.enum import Enum from lxml import etree vals = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', ] DaysOfWeekEnum = Enum( 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', type_name = 'DaysOfWeekEnum', ) class TestService(ServiceBase): @rpc(DaysOfWeekEnum, _returns=DaysOfWeekEnum) def get_the_day(self, day): return DaysOfWeekEnum.Sunday class Test(ComplexModel): days = DaysOfWeekEnum(max_occurs=7) class TestEnum(unittest.TestCase): def setUp(self): self.app = Application([TestService], 'tns', in_protocol=Soap11(), out_protocol=Soap11()) self.app.transport = 'test' self.server = WsgiApplication(self.app) self.wsdl = Wsdl11(self.app.interface) self.wsdl.build_interface_document('prot://url') def test_wsdl(self): wsdl = self.wsdl.get_interface_document() elt = etree.fromstring(wsdl) simple_type = elt.xpath('//xs:simpleType', namespaces=self.app.interface.nsmap)[0] print((etree.tostring(elt, pretty_print=True))) print(simple_type) self.assertEquals(simple_type.attrib['name'], 'DaysOfWeekEnum') self.assertEquals(simple_type[0].tag, "{%s}restriction" % _ns_xsd) self.assertEquals([e.attrib['value'] for e in simple_type[0]], vals) def test_serialize(self): mo = DaysOfWeekEnum.Monday print((repr(mo))) elt = etree.Element('test') XmlDocument().to_parent(None, DaysOfWeekEnum, mo, elt, 'test_namespace') elt = elt[0] ret = XmlDocument().from_element(None, DaysOfWeekEnum, elt) self.assertEquals(mo, ret) def test_serialize_complex_array(self): days = [ DaysOfWeekEnum.Monday, DaysOfWeekEnum.Tuesday, DaysOfWeekEnum.Wednesday, DaysOfWeekEnum.Thursday, DaysOfWeekEnum.Friday, DaysOfWeekEnum.Saturday, DaysOfWeekEnum.Sunday, ] days_xml = [ ('{tns}DaysOfWeekEnum', 'Monday'), ('{tns}DaysOfWeekEnum', 'Tuesday'), ('{tns}DaysOfWeekEnum', 'Wednesday'), ('{tns}DaysOfWeekEnum', 'Thursday'), ('{tns}DaysOfWeekEnum', 'Friday'), ('{tns}DaysOfWeekEnum', 'Saturday'), ('{tns}DaysOfWeekEnum', 'Sunday'), ] DaysOfWeekEnumArray = Array(DaysOfWeekEnum) DaysOfWeekEnumArray.__namespace__ = 'tns' elt = etree.Element('test') XmlDocument().to_parent(None, DaysOfWeekEnumArray, days, elt, 'test_namespace') elt = elt[0] ret = XmlDocument().from_element(None, Array(DaysOfWeekEnum), elt) assert days == ret print((etree.tostring(elt, pretty_print=True))) pprint(self.app.interface.nsmap) assert days_xml == [ (e.tag, e.text) for e in elt.xpath('//tns:DaysOfWeekEnum', namespaces=self.app.interface.nsmap)] def test_serialize_simple_array(self): t = Test(days=[ DaysOfWeekEnum.Monday, DaysOfWeekEnum.Tuesday, DaysOfWeekEnum.Wednesday, DaysOfWeekEnum.Thursday, DaysOfWeekEnum.Friday, DaysOfWeekEnum.Saturday, DaysOfWeekEnum.Sunday, ]) Test.resolve_namespace(Test, 'tns') elt = etree.Element('test') XmlDocument().to_parent(None, Test, t, elt, 'test_namespace') elt = elt[0] print((etree.tostring(elt, pretty_print=True))) ret = XmlDocument().from_element(None, Test, elt) self.assertEquals(t.days, ret.days) if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/model/test_exception.py0000755000175000001440000001621412345433230021536 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 unittest from spyne.test import FakeApp from spyne.interface import Interface from spyne.interface.wsdl import Wsdl11 from spyne.protocol.xml import XmlDocument from spyne.model.fault import Fault class FaultTests(unittest.TestCase): def test_ctor_defaults(self): fault = Fault() self.assertEqual(fault.faultcode, 'Server') self.assertEqual(fault.faultstring, 'Fault') self.assertEqual(fault.faultactor, '') self.assertEqual(fault.detail, None) self.assertEqual(repr(fault), "Fault(Server: 'Fault')") def test_ctor_faultcode_w_senv_prefix(self): fault = Fault(faultcode='Other') self.assertEqual(fault.faultcode, 'Other') self.assertEqual(repr(fault), "Fault(Other: 'Fault')") def test_ctor_explicit_faultstring(self): fault = Fault(faultstring='Testing') self.assertEqual(fault.faultstring, 'Testing') self.assertEqual(repr(fault), "Fault(Server: 'Testing')") def test_ctor_no_faultstring_overridden_get_type_name(self): class Derived(Fault): def get_type_name(self): return 'Overridden' fault = Derived() self.assertEqual(fault.faultstring, 'Overridden') self.assertEqual(repr(fault), "Fault(Server: 'Overridden')") def test_to_parent_wo_detail(self): from lxml.etree import Element import spyne.const.xml_ns ns_soap_env = spyne.const.xml_ns.soap_env element = Element('testing') fault = Fault() cls = Fault XmlDocument().to_parent(None, cls, fault, element, 'urn:ignored') (child,) = element.getchildren() self.assertEqual(child.tag, '{%s}Fault' % ns_soap_env) self.assertEqual(child.find('faultcode').text, 'senv:Server') self.assertEqual(child.find('faultstring').text, 'Fault') self.assertEqual(child.find('faultactor').text, '') self.failIf(child.findall('detail')) def test_to_parent_w_detail(self): from lxml.etree import Element element = Element('testing') detail = Element('something') fault = Fault(detail=detail) cls = Fault XmlDocument().to_parent(None, cls, fault, element, 'urn:ignored') (child,) = element.getchildren() self.failUnless(child.find('detail').find('something') is detail) def test_from_xml_wo_detail(self): from lxml.etree import Element from lxml.etree import SubElement import spyne.const.xml_ns ns_soap_env = spyne.const.xml_ns.soap_env element = Element('{%s}Fault' % ns_soap_env) fcode = SubElement(element, 'faultcode') fcode.text = 'senv:other' fstr = SubElement(element, 'faultstring') fstr.text = 'Testing' actor = SubElement(element, 'faultactor') actor.text = 'phreddy' fault = XmlDocument().from_element(None, Fault, element) self.assertEqual(fault.faultcode, 'senv:other') self.assertEqual(fault.faultstring, 'Testing') self.assertEqual(fault.faultactor, 'phreddy') self.assertEqual(fault.detail, None) def test_from_xml_w_detail(self): from lxml.etree import Element from lxml.etree import SubElement import spyne.const.xml_ns ns_soap_env = spyne.const.xml_ns.soap_env element = Element('{%s}Fault' % ns_soap_env) fcode = SubElement(element, 'faultcode') fcode.text = 'senv:other' fstr = SubElement(element, 'faultstring') fstr.text = 'Testing' actor = SubElement(element, 'faultactor') actor.text = 'phreddy' detail = SubElement(element, 'detail') fault = XmlDocument().from_element(None, Fault, element) self.failUnless(fault.detail is detail) def test_add_to_schema_no_extends(self): import spyne.const.xml_ns ns_xsd = spyne.const.xml_ns.xsd class cls(Fault): __namespace__='ns' @classmethod def get_type_name_ns(self, app): return 'testing:My' interface = Interface(FakeApp()) interface.add_class(cls) pref = cls.get_namespace_prefix(interface) wsdl = Wsdl11(interface) wsdl.build_interface_document('prot://addr') schema = wsdl.get_schema_info(pref) self.assertEqual(len(schema.types), 1) c_cls = interface.classes['{ns}cls'] c_elt = schema.types[0] self.failUnless(c_cls is cls) self.assertEqual(c_elt.tag, '{%s}complexType' % ns_xsd) self.assertEqual(c_elt.get('name'), 'cls') self.assertEqual(len(schema.elements), 1) e_elt = schema.elements.values()[0] self.assertEqual(e_elt.tag, '{%s}element' % ns_xsd) self.assertEqual(e_elt.get('name'), 'cls') self.assertEqual(e_elt.get('type'), 'testing:My') self.assertEqual(len(e_elt), 0) def test_add_to_schema_w_extends(self): import spyne.const.xml_ns ns_xsd = spyne.const.xml_ns.xsd class base(Fault): __namespace__ = 'ns' @classmethod def get_type_name_ns(self, app): return 'testing:Base' class cls(Fault): __namespace__ = 'ns' @classmethod def get_type_name_ns(self, app): return 'testing:My' interface = Interface(FakeApp()) interface.add_class(cls) pref = cls.get_namespace_prefix(interface) wsdl = Wsdl11(interface) wsdl.build_interface_document('prot://addr') schema = wsdl.get_schema_info(pref) self.assertEqual(len(schema.types), 1) self.assertEqual(len(interface.classes), 1) c_cls = next(iter(interface.classes.values())) c_elt = next(iter(schema.types.values())) self.failUnless(c_cls is cls) self.assertEqual(c_elt.tag, '{%s}complexType' % ns_xsd) self.assertEqual(c_elt.get('name'), 'cls') from lxml import etree print(etree.tostring(c_elt, pretty_print=True)) self.assertEqual(len(c_elt), 0) class DummySchemaEntries: def __init__(self, app): self.app = app self._complex_types = [] self._elements = [] def add_complex_type(self, cls, ct): self._complex_types.append((cls, ct)) def add_element(self, cls, elt): self._elements.append((cls, elt)) if __name__ == '__main__': #pragma NO COVERAGE unittest.main() spyne-2.11.0/spyne/test/model/test_include.py0000755000175000001440000000546012345433230021164 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 unittest try: from urllib import quote_plus except ImportError: from urllib.parse import quote_plus from lxml import etree from spyne.model.complex import ComplexModel from spyne.model.primitive import Integer from spyne.model.primitive import String from spyne.protocol.xml import XmlDocument from spyne.protocol.soap.mime import _join_attachment from spyne.const import xml_ns as ns # Service Classes class DownloadPartFileResult(ComplexModel): ErrorCode = Integer ErrorMessage = String Data = String # Tests class TestInclude(unittest.TestCase): def test_bytes_join_attachment(self): href_id="http://tempuri.org/1/634133419330914808" payload="ANJNSLJNDYBC SFDJNIREMX:CMKSAJN" envelope = ''' 0 ''' % quote_plus(href_id) (joinedmsg, numreplaces) = _join_attachment(href_id, envelope, payload) soaptree = etree.fromstring(joinedmsg) body = soaptree.find("{%s}Body" % ns.soap_env) response = body.getchildren()[0] result = response.getchildren()[0] r = XmlDocument().from_element(None, DownloadPartFileResult, result) self.assertEquals(payload, r.Data) if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/model/test_primitive.py0000755000175000001440000006046212345433230021554 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 re import datetime import unittest import pytz import uuid from datetime import timedelta from lxml import etree from spyne.util import total_seconds from spyne.const import xml_ns as ns from spyne.model import Null, AnyDict, Uuid from spyne.model.complex import Array from spyne.model.complex import ComplexModel from spyne.model.primitive import Date from spyne.model.primitive import Time from spyne.model.primitive import Boolean from spyne.model.primitive import DateTime from spyne.model.primitive import Duration from spyne.model.primitive import Float from spyne.model.primitive import Integer from spyne.model.primitive import UnsignedInteger from spyne.model.primitive import Unicode from spyne.model.primitive import String from spyne.model.primitive import Decimal from spyne.protocol import ProtocolBase from spyne.protocol.xml import XmlDocument ns_test = 'test_namespace' from spyne.model import ModelBase class TestPrimitive(unittest.TestCase): def test_nillable_quirks(self): assert ModelBase.Attributes.nillable == True class Attributes(ModelBase.Attributes): nillable = False nullable = False assert Attributes.nillable == False assert Attributes.nullable == False class Attributes(ModelBase.Attributes): nillable = True assert Attributes.nillable == True assert Attributes.nullable == True class Attributes(ModelBase.Attributes): nillable = False assert Attributes.nillable == False assert Attributes.nullable == False class Attributes(ModelBase.Attributes): nullable = True assert Attributes.nillable == True assert Attributes.nullable == True class Attributes(ModelBase.Attributes): nullable = False assert Attributes.nillable == False assert Attributes.nullable == False class Attributes(ModelBase.Attributes): nullable = False class Attributes(Attributes): pass assert Attributes.nullable == False def test_nillable_inheritance_quirks(self): class Attributes(ModelBase.Attributes): nullable = False class AttrMixin: pass class NewAttributes(Attributes, AttrMixin): pass assert NewAttributes.nullable is False class AttrMixin: pass class NewAttributes(AttrMixin, Attributes): pass assert NewAttributes.nullable is False def test_decimal(self): assert Decimal(10,4).Attributes.total_digits == 10 assert Decimal(10,4).Attributes.fraction_digits == 4 def test_decimal_format(self): f = 123456 str_format='${0}' element = etree.Element('test') XmlDocument().to_parent(None, Decimal(str_format=str_format), f, element, ns_test) element = element[0] self.assertEquals(element.text, '$123456') def test_string(self): s = String() element = etree.Element('test') XmlDocument().to_parent(None, String, 'value', element, ns_test) element=element[0] self.assertEquals(element.text, 'value') value = XmlDocument().from_element(None, String, element) self.assertEquals(value, 'value') def test_datetime(self): n = datetime.datetime.now(pytz.utc) element = etree.Element('test') XmlDocument().to_parent(None, DateTime, n, element, ns_test) element = element[0] self.assertEquals(element.text, n.isoformat()) dt = XmlDocument().from_element(None, DateTime, element) self.assertEquals(n, dt) def test_datetime_format(self): n = datetime.datetime.now().replace(microsecond=0) format = "%Y %m %d %H %M %S" element = etree.Element('test') XmlDocument().to_parent(None, DateTime(format=format), n, element, ns_test) element = element[0] assert element.text == datetime.datetime.strftime(n, format) dt = XmlDocument().from_element(None, DateTime(format=format), element) assert n == dt def test_date_format(self): t = datetime.date.today() format = "%Y %m %d" element = etree.Element('test') XmlDocument().to_parent(None, Date(format=format), t, element, ns_test) assert element[0].text == datetime.date.strftime(t, format) dt = XmlDocument().from_element(None, Date(format=format), element[0]) assert t == dt def test_datetime_timezone(self): import pytz n = datetime.datetime.now(pytz.timezone('EST')) element = etree.Element('test') cls = DateTime(as_timezone=pytz.utc, timezone=False) XmlDocument().to_parent(None, cls, n, element, ns_test) element = element[0] c = n.astimezone(pytz.utc).replace(tzinfo=None) self.assertEquals(element.text, c.isoformat()) dt = XmlDocument().from_element(None, cls, element) assert dt.tzinfo is not None dt = dt.replace(tzinfo=None) self.assertEquals(c, dt) def test_date_timezone(self): elt = etree.Element('wot') elt.text = '2013-08-09+02:00' dt = XmlDocument().from_element(None, Date, elt) print("ok without validation.") dt = XmlDocument(validator='soft').from_element(None, Date, elt) print(dt) def test_time(self): n = datetime.time(1, 2, 3, 4) ret = ProtocolBase().to_string(Time, n) self.assertEquals(ret, n.isoformat()) dt = ProtocolBase().from_string(Time, ret) self.assertEquals(n, dt) def test_date(self): n = datetime.date(2011,12,13) ret = ProtocolBase().to_string(Date, n) self.assertEquals(ret, n.isoformat()) dt = ProtocolBase().from_string(Date, ret) self.assertEquals(n, dt) def test_utcdatetime(self): datestring = '2007-05-15T13:40:44Z' e = etree.Element('test') e.text = datestring dt = XmlDocument().from_element(None, DateTime, e) self.assertEquals(dt.year, 2007) self.assertEquals(dt.month, 5) self.assertEquals(dt.day, 15) datestring = '2007-05-15T13:40:44.003Z' e = etree.Element('test') e.text = datestring dt = XmlDocument().from_element(None, DateTime, e) self.assertEquals(dt.year, 2007) self.assertEquals(dt.month, 5) self.assertEquals(dt.day, 15) def test_integer(self): i = 12 integer = Integer() element = etree.Element('test') XmlDocument().to_parent(None, Integer, i, element, ns_test) element = element[0] self.assertEquals(element.text, '12') value = XmlDocument().from_element(None, integer, element) self.assertEquals(value, i) def test_limits(self): try: ProtocolBase().from_string(Integer, "1" * (Integer.__max_str_len__ + 1)) except: pass else: raise Exception("must fail.") ProtocolBase().from_string(UnsignedInteger, "-1") # This is not supposed to fail. try: UnsignedInteger.validate_native(-1) # This is supposed to fail. except: pass else: raise Exception("must fail.") def test_large_integer(self): i = 128375873458473 integer = Integer() element = etree.Element('test') XmlDocument().to_parent(None, Integer, i, element, ns_test) element = element[0] self.assertEquals(element.text, '128375873458473') value = XmlDocument().from_element(None, integer, element) self.assertEquals(value, i) def test_float(self): f = 1.22255645 element = etree.Element('test') XmlDocument().to_parent(None, Float, f, element, ns_test) element = element[0] self.assertEquals(element.text, repr(f)) f2 = XmlDocument().from_element(None, Float, element) self.assertEquals(f2, f) def test_array(self): type = Array(String) type.resolve_namespace(type, "zbank") values = ['a', 'b', 'c', 'd', 'e', 'f'] element = etree.Element('test') XmlDocument().to_parent(None, type, values, element, ns_test) element = element[0] self.assertEquals(len(values), len(element.getchildren())) values2 = XmlDocument().from_element(None, type, element) self.assertEquals(values[3], values2[3]) def test_array_empty(self): type = Array(String) type.resolve_namespace(type, "zbank") values = [] element = etree.Element('test') XmlDocument().to_parent(None, type, values, element, ns_test) element = element[0] self.assertEquals(len(values), len(element.getchildren())) values2 = XmlDocument().from_element(None, type, element) self.assertEquals(len(values2), 0) def test_unicode(self): s = u'\x34\x55\x65\x34' self.assertEquals(4, len(s)) element = etree.Element('test') XmlDocument().to_parent(None, String, s, element, 'test_ns') element = element[0] value = XmlDocument().from_element(None, String, element) self.assertEquals(value, s) def test_unicode_pattern_mult_cust(self): assert Unicode(pattern='a').Attributes.pattern == 'a' assert Unicode(pattern='a')(5).Attributes.pattern == 'a' def test_unicode_nullable_mult_cust_false(self): assert Unicode(nullable=False).Attributes.nullable == False assert Unicode(nullable=False)(5).Attributes.nullable == False def test_unicode_nullable_mult_cust_true(self): assert Unicode(nullable=True).Attributes.nullable == True assert Unicode(nullable=True)(5).Attributes.nullable == True def test_null(self): element = etree.Element('test') XmlDocument().to_parent(None, Null, None, element, ns_test) print(etree.tostring(element)) element = element[0] self.assertTrue( bool(element.attrib.get('{%s}nil' % ns.xsi)) ) value = XmlDocument().from_element(None, Null, element) self.assertEquals(None, value) def test_point(self): from spyne.model.primitive import _get_point_pattern a=re.compile(_get_point_pattern(2)) assert a.match('POINT (10 40)') is not None assert a.match('POINT(10 40)') is not None assert a.match('POINT(10.0 40)') is not None assert a.match('POINT(1.310e4 40)') is not None def test_multipoint(self): from spyne.model.primitive import _get_multipoint_pattern a=re.compile(_get_multipoint_pattern(2)) assert a.match('MULTIPOINT (10 40, 40 30, 20 20, 30 10)') is not None # FIXME: #assert a.match('MULTIPOINT ((10 40), (40 30), (20 20), (30 10))') is not None def test_linestring(self): from spyne.model.primitive import _get_linestring_pattern a=re.compile(_get_linestring_pattern(2)) assert a.match('LINESTRING (30 10, 10 30, 40 40)') is not None def test_multilinestring(self): from spyne.model.primitive import _get_multilinestring_pattern a=re.compile(_get_multilinestring_pattern(2)) assert a.match('''MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))''') is not None def test_polygon(self): from spyne.model.primitive import _get_polygon_pattern a=re.compile(_get_polygon_pattern(2)) assert a.match('POLYGON ((30 10, 10 20, 20 40, 40 40, 30 10))') is not None def test_multipolygon(self): from spyne.model.primitive import _get_multipolygon_pattern a=re.compile(_get_multipolygon_pattern(2)) assert a.match('''MULTIPOLYGON (((30 20, 10 40, 45 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))''') is not None assert a.match('''MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 45 20, 30 5, 10 10, 10 30, 20 35), (30 20, 20 25, 20 15, 30 20)))''') is not None def test_boolean(self): b = etree.Element('test') XmlDocument().to_parent(None, Boolean, True, b, ns_test) b = b[0] self.assertEquals('true', b.text) b = etree.Element('test') XmlDocument().to_parent(None, Boolean, 0, b, ns_test) b = b[0] self.assertEquals('false', b.text) b = etree.Element('test') XmlDocument().to_parent(None, Boolean, 1, b, ns_test) b = b[0] self.assertEquals('true', b.text) b = XmlDocument().from_element(None, Boolean, b) self.assertEquals(b, True) b = etree.Element('test') XmlDocument().to_parent(None, Boolean, False, b, ns_test) b = b[0] self.assertEquals('false', b.text) b = XmlDocument().from_element(None, Boolean, b) self.assertEquals(b, False) b = etree.Element('test') XmlDocument().to_parent(None, Boolean, None, b, ns_test) b = b[0] self.assertEquals('true', b.get('{%s}nil' % ns.xsi)) b = XmlDocument().from_element(None, Boolean, b) self.assertEquals(b, None) def test_new_type(self): """Customized primitives go into namespace based on module name.""" custom_type = Unicode(pattern='123') self.assertEqual(custom_type.get_namespace(), custom_type.__module__) def test_default_nullable(self): """Test if default nullable changes nullable attribute.""" try: self.assertTrue(Unicode.Attributes.nullable) orig_default = Unicode.Attributes.NULLABLE_DEFAULT Unicode.Attributes.NULLABLE_DEFAULT = False self.assertFalse(Unicode.Attributes.nullable) self.assertFalse(Unicode.Attributes.nillable) finally: Unicode.Attributes.NULLABLE_DEFAULT = orig_default self.assertEqual(Unicode.Attributes.nullable, orig_default) def test_simple_type_explicit_customization(self): assert Unicode(max_len=5).__extends__ is not None assert Unicode.customize(max_len=5).__extends__ is not None def test_anydict_customization(self): from spyne.model import json assert isinstance(AnyDict.customize(store_as='json').Attributes.store_as, json) def test_uuid_serialize(self): value = uuid.UUID('12345678123456781234567812345678') assert ProtocolBase().to_string(Uuid, value) == \ '12345678-1234-5678-1234-567812345678' assert ProtocolBase().to_string(Uuid(serialize_as='hex'), value) == \ '12345678123456781234567812345678' assert ProtocolBase().to_string(Uuid(serialize_as='urn'), value) == \ 'urn:uuid:12345678-1234-5678-1234-567812345678' assert ProtocolBase().to_string(Uuid(serialize_as='bytes'), value) == \ '\x124Vx\x124Vx\x124Vx\x124Vx' assert ProtocolBase().to_string(Uuid(serialize_as='bytes_le'), value) == \ 'xV4\x124\x12xV\x124Vx\x124Vx' assert ProtocolBase().to_string(Uuid(serialize_as='fields'), value) == \ (305419896, 4660, 22136, 18, 52, 95073701484152) assert ProtocolBase().to_string(Uuid(serialize_as='int'), value) == \ 24197857161011715162171839636988778104 def test_uuid_deserialize(self): value = uuid.UUID('12345678123456781234567812345678') assert ProtocolBase().from_string(Uuid, '12345678-1234-5678-1234-567812345678') == value assert ProtocolBase().from_string(Uuid(serialize_as='hex'), '12345678123456781234567812345678') == value assert ProtocolBase().from_string(Uuid(serialize_as='urn'), 'urn:uuid:12345678-1234-5678-1234-567812345678') == value assert ProtocolBase().from_string(Uuid(serialize_as='bytes'), '\x124Vx\x124Vx\x124Vx\x124Vx') == value assert ProtocolBase().from_string(Uuid(serialize_as='bytes_le'), 'xV4\x124\x12xV\x124Vx\x124Vx') == value assert ProtocolBase().from_string(Uuid(serialize_as='fields'), (305419896, 4660, 22136, 18, 52, 95073701484152)) == value assert ProtocolBase().from_string(Uuid(serialize_as='int'), 24197857161011715162171839636988778104) == value def test_datetime_serialize_as(self): i = 1234567890123456 v = datetime.datetime.fromtimestamp(i / 1e6) assert ProtocolBase().to_string( DateTime(serialize_as='sec'), v) == i//1e6 assert ProtocolBase().to_string( DateTime(serialize_as='sec_float'), v) == i/1e6 assert ProtocolBase().to_string( DateTime(serialize_as='msec'), v) == i//1e3 assert ProtocolBase().to_string( DateTime(serialize_as='msec_float'), v) == i/1e3 assert ProtocolBase().to_string( DateTime(serialize_as='usec'), v) == i def test_datetime_deserialize(self): i = 1234567890123456 v = datetime.datetime.fromtimestamp(i / 1e6) assert ProtocolBase().from_string( DateTime(serialize_as='sec'), i//1e6) == \ datetime.datetime.fromtimestamp(i//1e6) assert ProtocolBase().from_string( DateTime(serialize_as='sec_float'), i/1e6) == v assert ProtocolBase().from_string( DateTime(serialize_as='msec'), i//1e3) == \ datetime.datetime.fromtimestamp(i/1e3//1000) assert ProtocolBase().from_string( DateTime(serialize_as='msec_float'), i/1e3) == v assert ProtocolBase().from_string( DateTime(serialize_as='usec'), i) == v ### Duration Data Type ## http://www.w3schools.com/schema/schema_dtypes_date.asp # Duration Data type # The time interval is specified in the following form "PnYnMnDTnHnMnS" where: # P indicates the period (required) # nY indicates the number of years # nM indicates the number of months # nD indicates the number of days # T indicates the start of a time section (*required* if you are going to # specify hours, minutes, seconds or microseconds) # nH indicates the number of hours # nM indicates the number of minutes # nS indicates the number of seconds class SomeBlob(ComplexModel): __namespace__ = 'myns' howlong = Duration() class TestDurationPrimitive(unittest.TestCase): def test_onehour_oneminute_onesecond(self): answer = 'PT1H1M1S' gg = SomeBlob() gg.howlong = timedelta(hours=1, minutes=1, seconds=1) element = etree.Element('test') XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) element = element[0] print(gg.howlong) print(etree.tostring(element, pretty_print=True)) assert element[0].text == answer data = element.find('{%s}howlong' % gg.get_namespace()).text self.assertEquals(data, answer) s1 = XmlDocument().from_element(None, SomeBlob, element) assert total_seconds(s1.howlong) == total_seconds(gg.howlong) def test_4suite(self): # borrowed from 4Suite tests_seconds = [ (0, u'PT0S'), (1, u'PT1S'), (59, u'PT59S'), (60, u'PT1M'), (3599, u'PT59M59S'), (3600, u'PT1H'), (86399, u'PT23H59M59S'), (86400, u'P1D'), (86400*60, u'P60D'), (86400*400, u'P400D') ] for secs, answer in tests_seconds: gg = SomeBlob() gg.howlong = timedelta(seconds=secs) element = etree.Element('test') XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) element = element[0] print(gg.howlong) print(etree.tostring(element, pretty_print=True)) assert element[0].text == answer data = element.find('{%s}howlong' % gg.get_namespace()).text self.assertEquals(data, answer) s1 = XmlDocument().from_element(None, SomeBlob, element) assert total_seconds(s1.howlong) == secs for secs, answer in tests_seconds: if secs > 0: secs *= -1 answer = '-' + answer gg = SomeBlob() gg.howlong = timedelta(seconds=secs) element = etree.Element('test') XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) element = element[0] print(gg.howlong) print(etree.tostring(element, pretty_print=True)) assert element[0].text == answer data = element.find('{%s}howlong' % gg.get_namespace()).text self.assertEquals(data, answer) s1 = XmlDocument().from_element(None, SomeBlob, element) assert total_seconds(s1.howlong) == secs def test_duration_positive_seconds_only(self): answer = 'PT35S' gg = SomeBlob() gg.howlong = timedelta(seconds=35) element = etree.Element('test') XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) element = element[0] print(gg.howlong) print(etree.tostring(element, pretty_print=True)) assert element[0].text == answer data = element.find('{%s}howlong' % gg.get_namespace()).text self.assertEquals(data, answer) s1 = XmlDocument().from_element(None, SomeBlob, element) assert total_seconds(s1.howlong) == total_seconds(gg.howlong) def test_duration_positive_minutes_and_seconds_only(self): answer = 'PT5M35S' gg = SomeBlob() gg.howlong = timedelta(minutes=5, seconds=35) element = etree.Element('test') XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) element = element[0] print(gg.howlong) print(etree.tostring(element, pretty_print=True)) assert element[0].text == answer data = element.find('{%s}howlong' % gg.get_namespace()).text self.assertEquals(data, answer) s1 = XmlDocument().from_element(None, SomeBlob, element) assert total_seconds(s1.howlong) == total_seconds(gg.howlong) def test_duration_positive_milliseconds_only(self): answer = 'PT0.666000S' gg = SomeBlob() gg.howlong = timedelta(milliseconds=666) element = etree.Element('test') XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) element = element[0] print(gg.howlong) print(etree.tostring(element, pretty_print=True)) assert element[0].text == answer data = element.find('{%s}howlong' % gg.get_namespace()).text self.assertEquals(data, answer) s1 = XmlDocument().from_element(None, SomeBlob, element) assert total_seconds(s1.howlong) == total_seconds(gg.howlong) def test_duration_xml_duration(self): dur = datetime.timedelta(days=5 + 30 + 365, hours=1, minutes=1, seconds=12, microseconds=8e5) str1 = 'P400DT3672.8S' str2 = 'P1Y1M5DT1H1M12.8S' self.assertEquals(dur, ProtocolBase().from_string(Duration, str1)) self.assertEquals(dur, ProtocolBase().from_string(Duration, str2)) self.assertEquals(dur, ProtocolBase().from_string(Duration, ProtocolBase().to_string(Duration, dur))) if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/multipython/0000755000175000001440000000000012352131452017413 5ustar plqusers00000000000000spyne-2.11.0/spyne/test/multipython/model/0000755000175000001440000000000012352131452020513 5ustar plqusers00000000000000spyne-2.11.0/spyne/test/multipython/model/__init__.py0000644000175000001440000000000012345433230022613 0ustar plqusers00000000000000spyne-2.11.0/spyne/test/multipython/model/test_complex.py0000644000175000001440000001403512345433230023577 0ustar plqusers00000000000000# coding: utf-8 # # 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 # """Complex model tests runnable on different Python implementations.""" import unittest from spyne.model.complex import (ComplexModel, ComplexModelMeta, ComplexModelBase, Array) from spyne.model.primitive import Unicode, Integer, String from spyne.util.six import add_metaclass class DeclareOrder_declare(ComplexModel.customize(declare_order='declared')): field3 = Integer field1 = Integer field2 = Integer class MyComplexModelMeta(ComplexModelMeta): """Custom complex model metaclass.""" def __new__(mcs, name, bases, attrs): attrs['new_field'] = Unicode attrs['field1'] = Unicode new_cls = super(MyComplexModelMeta, mcs).__new__(mcs, name, bases, attrs) return new_cls @add_metaclass(MyComplexModelMeta) class MyComplexModel(ComplexModelBase): """Custom complex model class.""" class Attributes(ComplexModelBase.Attributes): declare_order = 'declared' class MyModelWithDeclaredOrder(MyComplexModel): """Test model for complex model with custom metaclass.""" class Attributes(MyComplexModel.Attributes): declare_order = 'declared' field3 = Integer field1 = Integer field2 = Integer class TestComplexModel(unittest.TestCase): def test_add_field(self): class C(ComplexModel): u = Unicode C.append_field('i', Integer) assert C._type_info['i'] is Integer def test_insert_field(self): class C(ComplexModel): u = Unicode C.insert_field(0, 'i', Integer) assert C._type_info.keys() == ['i', 'u'] def test_variants(self): class C(ComplexModel): u = Unicode CC = C.customize(child_attrs=dict(u=dict(min_len=5))) print(dict(C.Attributes._variants.items())) r, = C.Attributes._variants assert r is CC assert CC.Attributes.parent_variant is C C.append_field('i', Integer) assert C._type_info['i'] is Integer assert CC._type_info['i'] is Integer def test_child_customization(self): class C(ComplexModel): u = Unicode CC = C.customize(child_attrs=dict(u=dict(min_len=5))) assert CC._type_info['u'].Attributes.min_len == 5 assert C._type_info['u'].Attributes.min_len != 5 def test_array_customization(self): CC = Array(Unicode).customize( serializer_attrs=dict(min_len=5), punks='roll', ) assert CC.Attributes.punks == 'roll' assert CC._type_info[0].Attributes.min_len == 5 def test_array_customization_complex(self): class C(ComplexModel): u = Unicode CC = Array(C).customize( punks='roll', serializer_attrs=dict(bidik=True) ) assert CC.Attributes.punks == 'roll' assert CC._type_info[0].Attributes.bidik == True def test_delayed_child_customization_append(self): class C(ComplexModel): u = Unicode CC = C.customize(child_attrs=dict(i=dict(ge=5))) CC.append_field('i', Integer) assert CC._type_info['i'].Attributes.ge == 5 assert not 'i' in C._type_info def test_delayed_child_customization_insert(self): class C(ComplexModel): u = Unicode CC = C.customize(child_attrs=dict(i=dict(ge=5))) CC.insert_field(1, 'i', Integer) assert CC._type_info['i'].Attributes.ge == 5 assert not 'i' in C._type_info def test_array_member_name(self): print(Array(String, member_name="punk")._type_info) assert 'punk' in Array(String, member_name="punk")._type_info def test_customize(self): class Base(ComplexModel): class Attributes(ComplexModel.Attributes): prop1 = 3 prop2 = 6 Base2 = Base.customize(prop1=4) self.assertNotEquals(Base.Attributes.prop1, Base2.Attributes.prop1) self.assertEquals(Base.Attributes.prop2, Base2.Attributes.prop2) class Derived(Base): class Attributes(Base.Attributes): prop3 = 9 prop4 = 12 Derived2 = Derived.customize(prop1=5, prop3=12) self.assertEquals(Base.Attributes.prop1, 3) self.assertEquals(Base2.Attributes.prop1, 4) self.assertEquals(Derived.Attributes.prop1, 3) self.assertEquals(Derived2.Attributes.prop1, 5) self.assertNotEquals(Derived.Attributes.prop3, Derived2.Attributes.prop3) self.assertEquals(Derived.Attributes.prop4, Derived2.Attributes.prop4) Derived3 = Derived.customize(prop3=12) Base.prop1 = 4 # changes made to bases propagate, unless overridden self.assertEquals(Derived.Attributes.prop1, Base.Attributes.prop1) self.assertNotEquals(Derived2.Attributes.prop1, Base.Attributes.prop1) self.assertEquals(Derived3.Attributes.prop1, Base.Attributes.prop1) def test_declare_order(self): self.assertEquals(["field3", "field1", "field2"], list(DeclareOrder_declare._type_info)) self.assertEquals(["field3", "field1", "field2", "new_field"], list(MyModelWithDeclaredOrder._type_info)) if __name__ == '__main__': import sys sys.exit(unittest.main()) spyne-2.11.0/spyne/test/multipython/__init__.py0000644000175000001440000000000012345433230021513 0ustar plqusers00000000000000spyne-2.11.0/spyne/test/protocol/0000755000175000001440000000000012352131452016660 5ustar plqusers00000000000000spyne-2.11.0/spyne/test/protocol/__init__.py0000644000175000001440000000140612345433230020773 0ustar plqusers00000000000000 # # 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 # spyne-2.11.0/spyne/test/protocol/_test_dictdoc.py0000644000175000001440000012302312352126507022047 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 unittest import uuid import pytz import decimal from spyne.util import six from spyne.util.dictdoc import get_object_as_dict if six.PY3: long = int from datetime import datetime from datetime import date from datetime import time from datetime import timedelta import lxml.etree import lxml.html from lxml.builder import E from spyne import MethodContext from spyne.service import ServiceBase from spyne.server import ServerBase from spyne.application import Application from spyne.decorator import srpc, rpc from spyne.error import ValidationError from spyne.model.binary import binary_encoding_handlers, File from spyne.model.complex import ComplexModel from spyne.model.complex import Iterable from spyne.model.fault import Fault from spyne.protocol import ProtocolBase from spyne.model.binary import ByteArray from spyne.model.primitive import Decimal from spyne.model.primitive import Integer from spyne.model.primitive import String from spyne.model.primitive import DateTime from spyne.model.primitive import Mandatory from spyne.model.primitive import AnyXml from spyne.model.primitive import AnyHtml from spyne.model.primitive import AnyDict from spyne.model.primitive import Unicode from spyne.model.primitive import AnyUri from spyne.model.primitive import ImageUri from spyne.model.primitive import Double from spyne.model.primitive import Integer8 from spyne.model.primitive import Time from spyne.model.primitive import Date from spyne.model.primitive import Duration from spyne.model.primitive import Boolean from spyne.model.primitive import Uuid from spyne.model.primitive import Point from spyne.model.primitive import Line from spyne.model.primitive import Polygon from spyne.model.primitive import MultiPoint from spyne.model.primitive import MultiLine from spyne.model.primitive import MultiPolygon def TDry(serializer, _DictDocumentChild, dumps_kwargs=None): if not dumps_kwargs: dumps_kwargs = {} def _dry_me(services, d, ignore_wrappers=False, complex_as=dict, just_ctx=False, just_in_object=False, validator=None): app = Application(services, 'tns', in_protocol=_DictDocumentChild(validator=validator), out_protocol=_DictDocumentChild( ignore_wrappers=ignore_wrappers, complex_as=complex_as), ) server = ServerBase(app) initial_ctx = MethodContext(server) initial_ctx.in_string = [serializer.dumps(d, **dumps_kwargs)] ctx, = server.generate_contexts(initial_ctx) if not just_ctx: server.get_in_object(ctx) if not just_in_object: server.get_out_object(ctx) server.get_out_string(ctx) return ctx return _dry_me def TDictDocumentTest(serializer, _DictDocumentChild, dumps_kwargs=None): if not dumps_kwargs: dumps_kwargs = {} _dry_me = TDry(serializer, _DictDocumentChild, dumps_kwargs) class Test(unittest.TestCase): def test_complex_with_only_primitive_fields(self): class SomeComplexModel(ComplexModel): i = Integer s = Unicode class SomeService(ServiceBase): @srpc(SomeComplexModel, _returns=SomeComplexModel) def some_call(scm): return SomeComplexModel(i=5, s='5x') ctx = _dry_me([SomeService], {"some_call":[]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": {"SomeComplexModel": {"i": 5, "s": "5x"}}}}, **dumps_kwargs) print(s) print(d) assert s == d def test_complex(self): class CM(ComplexModel): i = Integer s = Unicode class CCM(ComplexModel): c = CM i = Integer s = Unicode class SomeService(ServiceBase): @srpc(CCM, _returns=CCM) def some_call(ccm): return CCM(c=ccm.c, i=ccm.i, s=ccm.s) ctx = _dry_me([SomeService], {"some_call": {"ccm": {"c":{"i":3, "s": "3x"}, "i":4, "s": "4x"}} }) ret = serializer.loads(''.join(ctx.out_string)) print(ret) d = ret['some_callResponse']['some_callResult']['CCM'] assert d['i'] == 4 assert d['s'] == '4x' assert d['c']['CM']['i'] == 3 assert d['c']['CM']['s'] == '3x' def test_multiple_list(self): class SomeService(ServiceBase): @srpc(Unicode(max_occurs=Decimal('inf')), _returns=Unicode(max_occurs=Decimal('inf'))) def some_call(s): return s ctx = _dry_me([SomeService], {"some_call":[["a","b"]]}) assert ''.join(ctx.out_string) == serializer.dumps( {"some_callResponse": {"some_callResult": ["a", "b"]}}, **dumps_kwargs) def test_multiple_dict(self): class SomeService(ServiceBase): @srpc(Unicode(max_occurs=Decimal('inf')), _returns=Unicode(max_occurs=Decimal('inf'))) def some_call(s): return s ctx = _dry_me([SomeService], {"some_call":{"s":["a","b"]}}) assert ''.join(ctx.out_string) == serializer.dumps( {"some_callResponse": {"some_callResult": ["a", "b"]}}, **dumps_kwargs) def test_multiple_dict_array(self): class SomeService(ServiceBase): @srpc(Iterable(Unicode), _returns=Iterable(Unicode)) def some_call(s): return s ctx = _dry_me([SomeService], {"some_call":{"s":["a","b"]}}) assert list(ctx.out_string) == [serializer.dumps( {"some_callResponse": {"some_callResult": ["a", "b"]}}, **dumps_kwargs)] def test_multiple_dict_complex_array(self): class CM(ComplexModel): i = Integer s = Unicode class CCM(ComplexModel): c = CM i = Integer s = Unicode class ECM(CCM): d = DateTime class SomeService(ServiceBase): @srpc(Iterable(ECM), _returns=Iterable(ECM)) def some_call(ecm): return ecm ctx = _dry_me([SomeService], { "some_call": {"ecm": [{ "c": {"i":3, "s": "3x"}, "i":4, "s": "4x", "d": "2011-12-13T14:15:16Z" }] }}) print(ctx.in_object) ret = serializer.loads(''.join(ctx.out_string)) print(ret) assert ret["some_callResponse"]['some_callResult'] assert ret["some_callResponse"]['some_callResult'][0] assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["c"] assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["c"]["CM"]["i"] == 3 assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["c"]["CM"]["s"] == "3x" assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["i"] == 4 assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["s"] == "4x" assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["d"] == "2011-12-13T14:15:16+00:00" def test_invalid_request(self): class SomeService(ServiceBase): @srpc(Integer, String, DateTime) def yay(i,s,d): print(i,s,d) ctx = _dry_me([SomeService], {"some_call": {"yay": []}}, just_in_object=True) print(ctx.in_error) assert ctx.in_error.faultcode == 'Client.ResourceNotFound' def test_invalid_string(self): class SomeService(ServiceBase): @srpc(Integer, String, DateTime) def yay(i,s,d): print(i, s, d) ctx = _dry_me([SomeService], {"yay": {"s": 1}}, validator='soft', just_in_object=True) assert ctx.in_error.faultcode == 'Client.ValidationError' def test_invalid_number(self): class SomeService(ServiceBase): @srpc(Integer, String, DateTime) def yay(i,s,d): print(i,s,d) ctx = _dry_me([SomeService], {"yay": ["s", "B"]}, validator='soft', just_in_object=True) assert ctx.in_error.faultcode == 'Client.ValidationError' def test_missing_value(self): class SomeService(ServiceBase): @srpc(Integer, Unicode, Mandatory.DateTime) def yay(i,s,d): print(i,s,d) ctx = _dry_me([SomeService], {"yay": [1, "B"]}, validator='soft', just_in_object=True) print(ctx.in_error.faultstring) assert ctx.in_error.faultcode == 'Client.ValidationError' assert ctx.in_error.faultstring.endswith("at least 1 times.") def test_invalid_datetime(self): class SomeService(ServiceBase): @srpc(Integer, String, Mandatory.DateTime) def yay(i,s,d): print(i,s,d) ctx = _dry_me([SomeService],{"yay": {"d":"a2011"}},validator='soft', just_in_object=True) assert ctx.in_error.faultcode == 'Client.ValidationError' def test_fault_to_dict(self): class SomeService(ServiceBase): @srpc(_returns=String) def some_call(): raise Fault() _dry_me([SomeService], {"some_call":[]}) def test_prune_none_and_optional(self): class SomeObject(ComplexModel): i = Integer s = String(min_occurs=1) class SomeService(ServiceBase): @srpc(_returns=SomeObject) def some_call(): return SomeObject() ctx = _dry_me([SomeService], {"some_call":[]}) ret = serializer.loads(''.join(ctx.out_string)) assert ret == {"some_callResponse": {'some_callResult': {'SomeObject':{'s': None}}}} def test_any_xml(self): d = lxml.etree.tostring(E('{ns1}x', E('{ns2}Y', "some data"))) class SomeService(ServiceBase): @srpc(AnyXml, _returns=AnyXml) def some_call(p): print(p) print(type(p)) assert type(p) == lxml.etree._Element return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_any_html(self): d = lxml.html.tostring(E('div', E('span', "something"))) class SomeService(ServiceBase): @srpc(AnyHtml, _returns=AnyHtml) def some_call(p): print(p) print(type(p)) assert type(p) == lxml.html.HtmlElement return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_any_dict(self): d = {'helo': 213, 'data': {'nested': [12, 0.3]}} class SomeService(ServiceBase): @srpc(AnyDict, _returns=AnyDict) def some_call(p): print(p) print(type(p)) assert type(p) == dict return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_unicode(self): d = u'some string' class SomeService(ServiceBase): @srpc(Unicode, _returns=Unicode) def some_call(p): print(p) print(type(p)) assert type(p) == unicode return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_string(self): d = 'some string' class SomeService(ServiceBase): @srpc(String(encoding='utf8'), _returns=String) def some_call(p): print(p) print(type(p)) assert isinstance(p, str) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_any_uri(self): d = 'http://example.com/?asd=b12&df=aa#tag' class SomeService(ServiceBase): @srpc(AnyUri, _returns=AnyUri) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_image_uri(self): d = 'http://example.com/funny.gif' class SomeService(ServiceBase): @srpc(ImageUri, _returns=ImageUri) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_decimal(self): d = decimal.Decimal('1e100') if _DictDocumentChild._decimal_as_string: d = str(d) class SomeService(ServiceBase): @srpc(Decimal, _returns=Decimal) def some_call(p): print(p) print(type(p)) assert type(p) == decimal.Decimal return p ctx = _dry_me([SomeService], {"some_call": [d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_double(self): d = 12.3467 class SomeService(ServiceBase): @srpc(Double, _returns=Double) def some_call(p): print(p) print(type(p)) assert type(p) == float return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_integer(self): d = 5 class SomeService(ServiceBase): @srpc(Integer, _returns=Integer) def some_call(p): print(p) print(type(p)) assert type(p) == int return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_integer_way_small(self): d = -1<<1000 if _DictDocumentChild._huge_numbers_as_string: d = str(d) class SomeService(ServiceBase): @srpc(Integer, _returns=Integer) def some_call(p): print(p) print(type(p)) assert type(p) == long return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_integer_way_big(self): d = 1<<1000 if _DictDocumentChild._huge_numbers_as_string: d = str(d) class SomeService(ServiceBase): @srpc(Integer, _returns=Integer) def some_call(p): print(p) print(type(p)) assert type(p) == long return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_time(self): d = time(10, 20, 30).isoformat() class SomeService(ServiceBase): @srpc(Time, _returns=Time) def some_call(p): print(p) print(type(p)) assert type(p) == time assert p.isoformat() == d return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_date(self): d = date(2010, 9, 8).isoformat() class SomeService(ServiceBase): @srpc(Date, _returns=Date) def some_call(p): print(p) print(type(p)) assert type(p) == date assert p.isoformat() == d return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_datetime(self): d = datetime(2010, 9, 8, 7, 6, 5).isoformat() class SomeService(ServiceBase): @srpc(DateTime, _returns=DateTime(timezone=False)) def some_call(p): print(p) print(type(p)) assert type(p) == datetime assert p.replace(tzinfo=None).isoformat() == d return p ctx = _dry_me([SomeService], {"some_call":[d]}, validator='soft') s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_datetime_tz(self): d = datetime(2010, 9, 8, 7, 6, 5, tzinfo=pytz.utc).isoformat() class SomeService(ServiceBase): @srpc(DateTime, _returns=DateTime(ge=datetime(2010,1,1,tzinfo=pytz.utc))) def some_call(p): print(p) print(type(p)) assert type(p) == datetime assert p.isoformat() == d return p ctx = _dry_me([SomeService], {"some_call":[d]}, validator='soft') s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_duration(self): d = ProtocolBase().to_string(Duration, timedelta(0, 45)) class SomeService(ServiceBase): @srpc(Duration, _returns=Duration) def some_call(p): print(p) print(type(p)) assert type(p) == timedelta return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_boolean(self): d = True class SomeService(ServiceBase): @srpc(Boolean, _returns=Boolean) def some_call(p): print(p) print(type(p)) assert type(p) == bool return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_uuid(self): d = '7d2a6330-eb64-4900-8a10-38ebef415e9d' class SomeService(ServiceBase): @srpc(Uuid, _returns=Uuid) def some_call(p): print(p) print(type(p)) assert type(p) == uuid.UUID return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_point2d(self): d = 'POINT(1 2)' class SomeService(ServiceBase): @srpc(Point, _returns=Point) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_point3d(self): d = 'POINT(1 2 3)' class SomeService(ServiceBase): @srpc(Point, _returns=Point) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_line2d(self): d = 'LINESTRING(1 2, 3 4)' class SomeService(ServiceBase): @srpc(Line, _returns=Line) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_line3d(self): d = 'LINESTRING(1 2 3, 4 5 6)' class SomeService(ServiceBase): @srpc(Line, _returns=Line) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_polygon2d(self): d = 'POLYGON((1 1, 1 2, 2 2, 2 1, 1 1))' class SomeService(ServiceBase): @srpc(Polygon(2), _returns=Polygon(2)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_polygon3d(self): d = 'POLYGON((1 1 0, 1 2 0, 2 2 0, 2 1 0, 1 1 0))' class SomeService(ServiceBase): @srpc(Polygon(3), _returns=Polygon(3)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_multipoint2d(self): d = 'MULTIPOINT ((10 40), (40 30), (20 20), (30 10))' class SomeService(ServiceBase): @srpc(MultiPoint(2), _returns=MultiPoint(2)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_multipoint3d(self): d = 'MULTIPOINT (10 40 30, 40 30 10,)' class SomeService(ServiceBase): @srpc(MultiPoint(3), _returns=MultiPoint(3)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_multiline2d(self): d = 'MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))' class SomeService(ServiceBase): @srpc(MultiLine(2), _returns=MultiLine(2)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_multiline3d(self): d = 'MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))' class SomeService(ServiceBase): @srpc(MultiLine(3), _returns=MultiLine(3)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_multipolygon2d(self): d = 'MULTIPOLYGON (((30 20, 10 40, 45 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5)))' class SomeService(ServiceBase): @srpc(MultiPolygon(2), _returns=MultiPolygon(2)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_multipolygon3d(self): d = 'MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),' \ '((20 35, 45 20, 30 5, 10 10, 10 30, 20 35),' \ '(30 20, 20 25, 20 15, 30 20)))' class SomeService(ServiceBase): @srpc(MultiPolygon(3), _returns=MultiPolygon(3)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": d}}, **dumps_kwargs) print(s) print(d) assert s == d def test_generator(self): class SomeService(ServiceBase): @srpc(_returns=Iterable(Integer)) def some_call(): return iter(range(1000)) ctx = _dry_me([SomeService], {"some_call":[]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": range(1000)}}, **dumps_kwargs) print(s) print(d) assert s == d def test_bytearray(self): dbe = _DictDocumentChild.default_binary_encoding beh = binary_encoding_handlers[dbe] data = ''.join([chr(x) for x in range(0xff)]) encoded_data = beh(data) class SomeService(ServiceBase): @srpc(ByteArray, _returns=ByteArray) def some_call(p): print(p) print(type(p)) assert isinstance(p, list) assert p == [data] return p ctx = _dry_me([SomeService], {"some_call": [encoded_data]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": encoded_data}}, **dumps_kwargs) print(repr(s)) print(repr(d)) print(repr(encoded_data)) assert s == d def test_file_data(self): # the only difference with the bytearray test is/are the types # inside @srpc dbe = _DictDocumentChild.default_binary_encoding beh = binary_encoding_handlers[dbe] data = ''.join([chr(x) for x in range(0xff)]) encoded_data = beh(data) class SomeService(ServiceBase): @srpc(File, _returns=File) def some_call(p): print(p) print(type(p)) assert isinstance(p, File.Value) assert p.data == [data] return p.data # encoded data is inside a list because that's the native value of # byte array -- a sequence of byte chunks. ctx = _dry_me([SomeService], {"some_call": [encoded_data]}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": encoded_data}}, **dumps_kwargs) print(serializer.loads(s)) print(serializer.loads(d)) print(repr(encoded_data)) assert s == d def test_file_value(self): dbe = _DictDocumentChild.default_binary_encoding beh = binary_encoding_handlers[dbe] # Prepare data v = File.Value( name='some_file.bin', type='application/octet-stream', ) file_data = ''.join([chr(x) for x in range(0xff)]) v.data = beh(file_data) class SomeService(ServiceBase): @srpc(File, _returns=File) def some_call(p): print(p) print(type(p)) assert isinstance(p, File.Value) assert p.data == [file_data] assert p.type == v.type assert p.name == v.name return p d = get_object_as_dict(v, File) assert d['name'] == v.name assert d['type'] == v.type assert d['data'] == v.data ctx = _dry_me([SomeService], {"some_call": {'p': d}}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": { 'name': v.name, 'type': v.type, 'data': v.data, }}}, **dumps_kwargs) print(serializer.loads(s)) print(serializer.loads(d)) print(v) assert serializer.loads(s) == serializer.loads(d) def test_validation_frequency(self): class SomeService(ServiceBase): @srpc(ByteArray(min_occurs=1), _returns=ByteArray) def some_call(p): pass try: _dry_me([SomeService], {"some_call": []}, validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_nullable(self): class SomeService(ServiceBase): @srpc(ByteArray(nullable=False), _returns=ByteArray) def some_call(p): pass try: _dry_me([SomeService], {"some_call": [None]}, validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_string_pattern(self): class SomeService(ServiceBase): @srpc(Uuid) def some_call(p): pass try: _dry_me([SomeService], {"some_call": ["duduk"]}, validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_integer_range(self): class SomeService(ServiceBase): @srpc(Integer(ge=0, le=5)) def some_call(p): pass try: _dry_me([SomeService], {"some_call": [10]}, validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_integer_type(self): class SomeService(ServiceBase): @srpc(Integer8) def some_call(p): pass try: _dry_me([SomeService], {"some_call": [-129]}, validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_integer_type_2(self): class SomeService(ServiceBase): @srpc(Integer8) def some_call(p): pass try: _dry_me([SomeService], {"some_call": [1.2]}, validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_freq_parent(self): class C(ComplexModel): i=Integer(min_occurs=1) s=Unicode class SomeService(ServiceBase): @srpc(C) def some_call(p): pass try: # must raise validation error for missing i _dry_me([SomeService], {"some_call": {'p':{'s':'a'}}}, validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") # must not raise anything for missing p because C has min_occurs=0 _dry_me([SomeService], {"some_call": {}}, validator='soft') def test_inheritance(self): class P(ComplexModel): identifier = Uuid signature = Unicode class C(P): foo = Unicode bar = Uuid class SomeService(ServiceBase): @rpc(_returns=C) def some_call(ctx): result = C() result.identifier = uuid.UUID(int=0) result.signature = 'yyyyyyyyyyy' result.foo = 'zzzzzz' result.bar = uuid.UUID(int=1) return result ctx = _dry_me([SomeService], {"some_call": []}) s = ''.join(ctx.out_string) d = serializer.dumps({"some_callResponse": {"some_callResult": { "C": { 'identifier': '00000000-0000-0000-0000-000000000000', 'bar': '00000000-0000-0000-0000-000000000001', 'foo': 'zzzzzz', 'signature': 'yyyyyyyyyyy' }}}}, **dumps_kwargs) print(serializer.loads(s)) print(serializer.loads(d)) assert s == d return Test spyne-2.11.0/spyne/test/protocol/test_html_microformat.py0000755000175000001440000002131012345433230023640 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 logging.basicConfig(level=logging.DEBUG) import unittest from lxml import html from spyne.application import Application from spyne.decorator import srpc from spyne.model.primitive import Integer from spyne.model.primitive import String from spyne.model.complex import Array from spyne.model.complex import ComplexModel from spyne.protocol.http import HttpRpc from spyne.protocol.html import HtmlMicroFormat from spyne.service import ServiceBase from spyne.server.wsgi import WsgiMethodContext from spyne.server.wsgi import WsgiApplication from spyne.util.test import show, call_wsgi_app_kwargs class TestHtmlMicroFormat(unittest.TestCase): def test_simple(self): class SomeService(ServiceBase): @srpc(String, _returns=String) def some_call(s): return s app = Application([SomeService], 'tns', in_protocol=HttpRpc(hier_delim='_'), out_protocol=HtmlMicroFormat()) server = WsgiApplication(app) initial_ctx = WsgiMethodContext(server, { 'QUERY_STRING': 's=s', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', }, 'some-content-type') ctx, = server.generate_contexts(initial_ctx) assert ctx.in_error is None server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) assert ''.join(ctx.out_string) == '
s
' def test_multiple_return(self): class SomeNotSoComplexModel(ComplexModel): s = String class SomeService(ServiceBase): @srpc(_returns=[Integer, String]) def some_call(): return 1, 's' app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlMicroFormat()) server = WsgiApplication(app) initial_ctx = WsgiMethodContext(server, { 'QUERY_STRING': '', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', }, 'some-content-type') ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) assert ''.join(ctx.out_string) == '
1
s
' def test_complex(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = CM i = Integer s = String class SomeService(ServiceBase): @srpc(CCM, _returns=CCM) def some_call(ccm): return CCM(c=ccm.c,i=ccm.i, s=ccm.s) app = Application([SomeService], 'tns', in_protocol=HttpRpc(hier_delim='_'), out_protocol=HtmlMicroFormat()) server = WsgiApplication(app) initial_ctx = WsgiMethodContext(server, { 'QUERY_STRING': 'ccm_c_s=abc&ccm_c_i=123&ccm_i=456&ccm_s=def', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', }, 'some-content-type') ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) # # Here's what this is supposed to return: # #
#
#
456
#
#
123
#
abc
#
#
def
#
#
# elt = html.fromstring(''.join(ctx.out_string)) print(html.tostring(elt, pretty_print=True)) resp = elt.find_class('some_callResponse') assert len(resp) == 1 res = resp[0].find_class('some_callResult') assert len(res) == 1 i = res[0].findall('div[@class="i"]') assert len(i) == 1 assert i[0].text == '456' c = res[0].findall('div[@class="c"]') assert len(c) == 1 c_i = c[0].findall('div[@class="i"]') assert len(c_i) == 1 assert c_i[0].text == '123' c_s = c[0].findall('div[@class="s"]') assert len(c_s) == 1 assert c_s[0].text == 'abc' s = res[0].findall('div[@class="s"]') assert len(s) == 1 assert s[0].text == 'def' def test_multiple(self): class SomeService(ServiceBase): @srpc(String(max_occurs='unbounded'), _returns=String) def some_call(s): print(s) return '\n'.join(s) app = Application([SomeService], 'tns', in_protocol=HttpRpc(hier_delim='_'), out_protocol=HtmlMicroFormat()) server = WsgiApplication(app) initial_ctx = WsgiMethodContext(server, { 'QUERY_STRING': 's=1&s=2', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', }, 'some-content-type') ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) assert ''.join(ctx.out_string) == ('
' '
1\n2
') ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) assert ''.join(ctx.out_string) == '
1\n2
' def test_complex_array(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = CM i = Integer s = String class SomeService(ServiceBase): @srpc(CCM, _returns=Array(CCM)) def some_call(ccm): return [CCM(c=ccm.c,i=ccm.i, s=ccm.s)] * 2 app = Application([SomeService], 'tns', in_protocol=HttpRpc(hier_delim='_'), out_protocol=HtmlMicroFormat()) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server, ccm_c_s='abc', ccm_c_i=123, ccm_i=456, ccm_s='def') # # Here's what this is supposed to return: # #
#
#
456
#
#
123
#
abc
#
#
def
#
#
#
456
#
#
123
#
abc
#
#
def
#
#
# print(out_string) elt = html.fromstring(''.join(out_string)) show(elt, "TestHtmlMicroFormat.test_complex_array") resp = elt.find_class('some_callResponse') assert len(resp) == 1 res = resp[0].find_class('some_callResult') assert len(res) == 1 assert len(res[0].find_class("CCM")) == 2 # We don't need to test the rest as the test_complex test takes care of # that if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/protocol/test_html_table.py0000755000175000001440000003430612345433230022416 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 logging.basicConfig(level=logging.DEBUG) import unittest from lxml import etree, html from spyne.application import Application from spyne.decorator import srpc from spyne.model.primitive import Integer from spyne.model.primitive import String from spyne.model.primitive import AnyUri from spyne.model.complex import Array from spyne.model.complex import ComplexModel from spyne.protocol.http import HttpRpc from spyne.protocol.html.table import HtmlColumnTable, HtmlRowTable from spyne.service import ServiceBase from spyne.server.wsgi import WsgiApplication from spyne.util.test import show, call_wsgi_app_kwargs, call_wsgi_app class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = CM i = Integer s = String class TestHtmlColumnTable(unittest.TestCase): def test_complex_array(self): class SomeService(ServiceBase): @srpc(CCM, _returns=Array(CCM)) def some_call(ccm): return [ccm] * 5 app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlColumnTable(field_name_attr='class')) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server, ccm_i='456', ccm_s='def', ccm_c_i='123', ccm_c_s='abc', ) elt = etree.fromstring(out_string) show(elt, 'TestHtmlColumnTable.test_complex_array') elt = html.fromstring(out_string) row, = elt[0] # thead cell = row.findall('th[@class="i"]') assert len(cell) == 1 assert cell[0].text == 'i' cell = row.findall('th[@class="s"]') assert len(cell) == 1 assert cell[0].text == 's' for row in elt[1]: # tbody cell = row.xpath('td[@class="i"]') assert len(cell) == 1 assert cell[0].text == '456' cell = row.xpath('td[@class="c"]//td[@class="i"]') assert len(cell) == 1 assert cell[0].text == '123' cell = row.xpath('td[@class="c"]//td[@class="s"]') assert len(cell) == 1 assert cell[0].text == 'abc' cell = row.xpath('td[@class="s"]') assert len(cell) == 1 assert cell[0].text == 'def' def test_string_array(self): class SomeService(ServiceBase): @srpc(String(max_occurs='unbounded'), _returns=Array(String)) def some_call(s): return s app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlColumnTable()) server = WsgiApplication(app) out_string = call_wsgi_app(server, body_pairs=(('s', '1'), ('s', '2'))) elt = etree.fromstring(out_string) show(elt, "TestHtmlColumnTable.test_string_array") assert out_string == \ '' \ '' \ '' \ '
some_callResponse
1
2
' def test_anyuri_string(self): _link = "http://arskom.com.tr/" class C(ComplexModel): c = AnyUri class SomeService(ServiceBase): @srpc(_returns=Array(C)) def some_call(): return [C(c=_link)] app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlColumnTable(field_name_attr='class')) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server) elt = html.fromstring(out_string) show(elt, "TestHtmlColumnTable.test_anyuri_string") assert elt.xpath('//td[@class="c"]')[0][0].tag == 'a' assert elt.xpath('//td[@class="c"]')[0][0].attrib['href'] == _link def test_anyuri_uri_value(self): _link = "http://arskom.com.tr/" _text = "Arskom" class C(ComplexModel): c = AnyUri class SomeService(ServiceBase): @srpc(_returns=Array(C)) def some_call(): return [C(c=AnyUri.Value(_link, text=_text))] app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlColumnTable(field_name_attr='class')) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server) elt = html.fromstring(out_string) print(html.tostring(elt, pretty_print=True)) assert elt.xpath('//td[@class="c"]')[0][0].tag == 'a' assert elt.xpath('//td[@class="c"]')[0][0].text == _text assert elt.xpath('//td[@class="c"]')[0][0].attrib['href'] == _link class TestHtmlRowTable(unittest.TestCase): def test_anyuri_string(self): _link = "http://arskom.com.tr/" class C(ComplexModel): c = AnyUri class SomeService(ServiceBase): @srpc(_returns=C) def some_call(): return C(c=_link) app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlRowTable(field_name_attr='class')) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server) elt = html.fromstring(out_string) print(html.tostring(elt, pretty_print=True)) assert elt.xpath('//td[@class="c"]')[0][0].tag == 'a' assert elt.xpath('//td[@class="c"]')[0][0].attrib['href'] == _link def test_anyuri_uri_value(self): _link = "http://arskom.com.tr/" _text = "Arskom" class C(ComplexModel): c = AnyUri class SomeService(ServiceBase): @srpc(_returns=C) def some_call(): return C(c=AnyUri.Value(_link, text=_text)) app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlRowTable(field_name_attr='class')) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server) elt = html.fromstring(out_string) print(html.tostring(elt, pretty_print=True)) assert elt.xpath('//td[@class="c"]')[0][0].tag == 'a' assert elt.xpath('//td[@class="c"]')[0][0].text == _text assert elt.xpath('//td[@class="c"]')[0][0].attrib['href'] == _link def test_complex(self): class SomeService(ServiceBase): @srpc(CCM, _returns=CCM) def some_call(ccm): return ccm app = Application([SomeService], 'tns', in_protocol=HttpRpc(hier_delim="_"), out_protocol=HtmlRowTable(field_name_attr='class')) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server, 'some_call', ccm_c_s='abc', ccm_c_i='123', ccm_i='456', ccm_s='def') elt = html.fromstring(out_string) show(elt, "TestHtmlRowTable.test_complex") # Here's what this is supposed to return """
i 456
c
i 123
s abc
s def
""" print(html.tostring(elt, pretty_print=True)) resp = elt.find_class('CCM') assert len(resp) == 1 assert elt.xpath('tbody/tr/th[@class="i"]/text()')[0] == 'i' assert elt.xpath('tbody/tr/td[@class="i"]/text()')[0] == '456' assert elt.xpath('tbody/tr/td[@class="c"]//th[@class="i"]/text()')[0] == 'i' assert elt.xpath('tbody/tr/td[@class="c"]//td[@class="i"]/text()')[0] == '123' assert elt.xpath('tbody/tr/td[@class="c"]//th[@class="s"]/text()')[0] == 's' assert elt.xpath('tbody/tr/td[@class="c"]//td[@class="s"]/text()')[0] == 'abc' assert elt.xpath('tbody/tr/th[@class="s"]/text()')[0] == 's' assert elt.xpath('tbody/tr/td[@class="s"]/text()')[0] == 'def' def test_string_array(self): class SomeService(ServiceBase): @srpc(String(max_occurs='unbounded'), _returns=Array(String)) def some_call(s): return s app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlRowTable()) server = WsgiApplication(app) out_string = call_wsgi_app(server, body_pairs=(('s', '1'), ('s', '2')) ) show(html.fromstring(out_string), 'TestHtmlRowTable.test_string_array') assert out_string == \ '
' \ '' \ '' \ '' \ '' \ '' \ '
string' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '
1
2
' \ '
' \ '
' def test_string_array_no_header(self): class SomeService(ServiceBase): @srpc(String(max_occurs='unbounded'), _returns=Array(String)) def some_call(s): return s app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlRowTable(produce_header=False)) server = WsgiApplication(app) out_string = call_wsgi_app(server, body_pairs=(('s', '1'), ('s', '2')) ) #FIXME: Needs a proper test with xpaths and all. show(html.fromstring(out_string), 'TestHtmlRowTable.test_string_array_no_header') assert out_string == \ '
' \ '' \ '' \ '' \ '' \ '
' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '
1
2
' \ '
' \ '
' def test_complex_array(self): v = [ CM(i=1, s='a'), CM(i=2, s='b'), CM(i=3, s='c'), CM(i=4, s='d'), ] class SomeService(ServiceBase): @srpc(_returns=Array(CM)) def some_call(): return v app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlRowTable()) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server) show(html.fromstring(out_string), 'TestHtmlRowTable.test_complex_array') #FIXME: Needs a proper test with xpaths and all. assert out_string == \ '
' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '
i1
sa
' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '
i2
sb
' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '
i3
sc
' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '
i4
sd
' \ '
' if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/protocol/test_http.py0000755000175000001440000005473512345433230021272 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 logging.basicConfig(level=logging.DEBUG) import unittest from spyne.util import six if six.PY3: from io import StringIO from http.cookies import SimpleCookie else: from StringIO import StringIO from Cookie import SimpleCookie from datetime import datetime from wsgiref.validate import validator as wsgiref_validator from spyne.model.complex import Array from spyne.server.wsgi import _parse_qs from spyne.application import Application from spyne.error import ValidationError from spyne.const.http import HTTP_200 from spyne.decorator import rpc from spyne.decorator import srpc from spyne.model.binary import ByteArray from spyne.model.primitive import DateTime from spyne.model.primitive import Uuid from spyne.model.primitive import String from spyne.model.primitive import Integer from spyne.model.primitive import Integer8 from spyne.model.complex import ComplexModel from spyne.protocol.http import HttpRpc from spyne.protocol.http import HttpPattern from spyne.service import ServiceBase from spyne.server.wsgi import WsgiApplication from spyne.server.wsgi import WsgiMethodContext from spyne.util.test import call_wsgi_app_kwargs class TestString(unittest.TestCase): def setUp(self): class SomeService(ServiceBase): @srpc(String, _returns=String) def echo_string(s): return s app = Application([SomeService], 'tns', in_protocol=HttpRpc(validator='soft'), out_protocol=HttpRpc(), ) self.app = WsgiApplication(app) def test_without_content_type(self): headers = None ret = call_wsgi_app_kwargs(self.app, 'echo_string', headers, s="string") assert ret == 'string' def test_without_encoding(self): headers = {'CONTENT_TYPE':'text/plain'} ret = call_wsgi_app_kwargs(self.app, 'echo_string', headers, s="string") assert ret == 'string' def test_with_encoding(self): headers = {'CONTENT_TYPE':'text/plain; charset=utf8'} ret = call_wsgi_app_kwargs(self.app, 'echo_string', headers, s="string") assert ret == 'string' class TestSimpleDictDocument(unittest.TestCase): def test_own_parse_qs_01(self): assert dict(_parse_qs('')) == {} def test_own_parse_qs_02(self): assert dict(_parse_qs('p')) == {'p': [None]} def test_own_parse_qs_03(self): assert dict(_parse_qs('p=')) == {'p': ['']} def test_own_parse_qs_04(self): assert dict(_parse_qs('p=1')) == {'p': ['1']} def test_own_parse_qs_05(self): assert dict(_parse_qs('p=1&')) == {'p': ['1']} def test_own_parse_qs_06(self): assert dict(_parse_qs('p=1&q')) == {'p': ['1'], 'q': [None]} def test_own_parse_qs_07(self): assert dict(_parse_qs('p=1&q=')) == {'p': ['1'], 'q': ['']} def test_own_parse_qs_08(self): assert dict(_parse_qs('p=1&q=2')) == {'p': ['1'], 'q': ['2']} def test_own_parse_qs_09(self): assert dict(_parse_qs('p=1&q=2&p')) == {'p': ['1', None], 'q': ['2']} def test_own_parse_qs_10(self): assert dict(_parse_qs('p=1&q=2&p=')) == {'p': ['1', ''], 'q': ['2']} def test_own_parse_qs_11(self): assert dict(_parse_qs('p=1&q=2&p=3')) == {'p': ['1', '3'], 'q': ['2']} def _test(services, qs, validator='soft', strict_arrays=False): app = Application(services, 'tns', in_protocol=HttpRpc(validator=validator, strict_arrays=strict_arrays), out_protocol=HttpRpc()) server = WsgiApplication(app) initial_ctx = WsgiMethodContext(server, { 'QUERY_STRING': qs, 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': "localhost", }, 'some-content-type') ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) if ctx.in_error is not None: raise ctx.in_error server.get_out_object(ctx) if ctx.out_error is not None: raise ctx.out_error server.get_out_string(ctx) return ctx class TestValidation(unittest.TestCase): def test_validation_frequency(self): class SomeService(ServiceBase): @srpc(ByteArray(min_occurs=1), _returns=ByteArray) def some_call(p): pass try: _test([SomeService], '', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def _test_validation_frequency_simple_bare(self): class SomeService(ServiceBase): @srpc(ByteArray(min_occurs=1), _body_style='bare', _returns=ByteArray) def some_call(p): pass try: _test([SomeService], '', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_frequency_complex_bare_parent(self): class C(ComplexModel): i=Integer(min_occurs=1) s=String class SomeService(ServiceBase): @srpc(C, _body_style='bare') def some_call(p): pass # must not complain about missing s _test([SomeService], 'i=5', validator='soft') # must raise validation error for missing i try: _test([SomeService], 's=a', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") # must raise validation error for missing i try: _test([SomeService], '', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_frequency_parent(self): class C(ComplexModel): i=Integer(min_occurs=1) s=String class SomeService(ServiceBase): @srpc(C) def some_call(p): pass # must not complain about missing s _test([SomeService], 'p.i=5', validator='soft') try: # must raise validation error for missing i _test([SomeService], 'p.s=a', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") # must not raise anything for missing p because C has min_occurs=0 _test([SomeService], '', validator='soft') def test_validation_array(self): class C(ComplexModel): i=Integer(min_occurs=1) s=String class SomeService(ServiceBase): @srpc(Array(C)) def some_call(p): pass # must not complain about missing s _test([SomeService], 'p[0].i=5', validator='soft') try: # must raise validation error for missing i _test([SomeService], 'p[0].s=a', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") # must not raise anything for missing p because C has min_occurs=0 _test([SomeService], '', validator='soft') def test_validation_array_index_jump_error(self): class C(ComplexModel): i=Integer class SomeService(ServiceBase): @srpc(Array(C), _returns=String) def some_call(p): return repr(p) try: # must raise validation error for index jump from 0 to 2 even without # any validation _test([SomeService], 'p[0].i=42&p[2].i=42&', strict_arrays=True) except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_array_index_jump_tolerate(self): class C(ComplexModel): i=Integer class SomeService(ServiceBase): @srpc(Array(C), _returns=String) def some_call(p): return repr(p) # must not raise validation error for index jump from 0 to 2 and ignore # element with index 1 ret = _test([SomeService], 'p[0].i=0&p[2].i=2&', strict_arrays=False) assert ret.out_object[0] == '[C(i=0), C(i=2)]' # even if they arrive out-of-order. ret = _test([SomeService], 'p[2].i=2&p[0].i=0&', strict_arrays=False) assert ret.out_object[0] == '[C(i=0), C(i=2)]' def test_validation_nested_array(self): class CC(ComplexModel): d = DateTime class C(ComplexModel): i=Integer(min_occurs=1) cc=Array(CC) class SomeService(ServiceBase): @srpc(Array(C)) def some_call(p): print(p) # must not complain about missing s _test([SomeService], 'p[0].i=5', validator='soft') try: # must raise validation error for missing i _test([SomeService], 'p[0].cc[0].d=2013-01-01', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") # must not raise anything for missing p because C has min_occurs=0 _test([SomeService], '', validator='soft') def test_validation_nullable(self): class SomeService(ServiceBase): @srpc(ByteArray(nullable=False), _returns=ByteArray) def some_call(p): pass try: _test([SomeService], 'p', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_string_pattern(self): class SomeService(ServiceBase): @srpc(Uuid) def some_call(p): pass try: _test([SomeService], "p=duduk", validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_integer_range(self): class SomeService(ServiceBase): @srpc(Integer(ge=0, le=5)) def some_call(p): pass try: _test([SomeService], 'p=10', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_integer_type(self): class SomeService(ServiceBase): @srpc(Integer8) def some_call(p): pass try: _test([SomeService], "p=-129", validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_integer_type_2(self): class SomeService(ServiceBase): @srpc(Integer8) def some_call(p): pass try: _test([SomeService], "p=1.2", validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") class Test(unittest.TestCase): def test_multiple_return(self): class SomeService(ServiceBase): @srpc(_returns=[Integer, String]) def some_call(): return 1, 's' try: _test([SomeService], '') except TypeError: pass else: raise Exception("Must fail with: HttpRpc does not support complex " "return types.") def test_primitive_only(self): class SomeComplexModel(ComplexModel): i = Integer s = String class SomeService(ServiceBase): @srpc(SomeComplexModel, _returns=SomeComplexModel) def some_call(scm): return SomeComplexModel(i=5, s='5x') try: _test([SomeService], '') except TypeError: pass else: raise Exception("Must fail with: HttpRpc does not support complex " "return types.") def test_complex(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String), ] class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", CM), ("s", String), ] class SomeService(ServiceBase): @srpc(CCM, _returns=String) def some_call(ccm): return repr(CCM(c=ccm.c, i=ccm.i, s=ccm.s)) ctx = _test([SomeService], '&ccm.i=1&ccm.s=s&ccm.c.i=3&ccm.c.s=cs') assert ctx.out_string[0] == "CCM(i=1, c=CM(i=3, s='cs'), s='s')" def test_multiple(self): class SomeService(ServiceBase): @srpc(String(max_occurs='unbounded'), _returns=String) def some_call(s): return '\n'.join(s) ctx = _test([SomeService], '&s=1&s=2') assert ''.join(ctx.out_string) == '1\n2' def test_nested_flatten(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String), ] class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", CM), ("s", String), ] class SomeService(ServiceBase): @srpc(CCM, _returns=String) def some_call(ccm): return repr(ccm) ctx = _test([SomeService], '&ccm.i=1&ccm.s=s&ccm.c.i=3&ccm.c.s=cs') print(ctx.out_string) assert ''.join(ctx.out_string) == "CCM(i=1, c=CM(i=3, s='cs'), s='s')" def test_nested_flatten_with_multiple_values_1(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String), ] class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", CM), ("s", String), ] class SomeService(ServiceBase): @srpc(CCM.customize(max_occurs=2), _returns=String) def some_call(ccm): return repr(ccm) ctx = _test([SomeService], 'ccm[0].i=1&ccm[0].s=s' '&ccm[0].c.i=1&ccm[0].c.s=a' '&ccm[1].c.i=2&ccm[1].c.s=b') s = ''.join(ctx.out_string) assert s == "[CCM(i=1, c=CM(i=1, s='a'), s='s'), CCM(c=CM(i=2, s='b'))]" def test_nested_flatten_with_multiple_values_2(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String), ] class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", CM.customize(max_occurs=2)), ("s", String), ] class SomeService(ServiceBase): @srpc(CCM, _returns=String) def some_call(ccm): return repr(ccm) ctx = _test([SomeService], 'ccm.i=1&ccm.s=s' '&ccm.c[0].i=1&ccm.c[0].s=a' '&ccm.c[1].i=2&ccm.c[1].s=b') s = ''.join(list(ctx.out_string)) assert s == "CCM(i=1, c=[CM(i=1, s='a'), CM(i=2, s='b')], s='s')" def test_nested_flatten_with_complex_array(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String), ] class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", Array(CM)), ("s", String), ] class SomeService(ServiceBase): @srpc(CCM, _returns=String) def some_call(ccm): return repr(ccm) ctx = _test([SomeService], 'ccm.i=1&ccm.s=s' '&ccm.c[0].i=1&ccm.c[0].s=a' '&ccm.c[1].i=2&ccm.c[1].s=b') s = ''.join(list(ctx.out_string)) assert s == "CCM(i=1, c=[CM(i=1, s='a'), CM(i=2, s='b')], s='s')" def test_nested_2_flatten_with_primitive_array(self): class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", Array(String)), ("s", String), ] class SomeService(ServiceBase): @srpc(Array(CCM), _returns=String) def some_call(ccm): return repr(ccm) ctx = _test([SomeService], 'ccm[0].i=1&ccm[0].s=s' '&ccm[0].c=a' '&ccm[0].c=b') s = ''.join(list(ctx.out_string)) assert s == "[CCM(i=1, c=['a', 'b'], s='s')]" def test_nested_flatten_with_primitive_array(self): class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", Array(String)), ("s", String), ] class SomeService(ServiceBase): @srpc(CCM, _returns=String) def some_call(ccm): return repr(ccm) ctx = _test([SomeService], 'ccm.i=1&ccm.s=s' '&ccm.c=a' '&ccm.c=b') s = ''.join(list(ctx.out_string)) assert s == "CCM(i=1, c=['a', 'b'], s='s')" ctx = _test([SomeService], 'ccm.i=1' '&ccm.s=s' '&ccm.c[1]=b' '&ccm.c[0]=a') s = ''.join(list(ctx.out_string)) assert s == "CCM(i=1, c=['a', 'b'], s='s')" ctx = _test([SomeService], 'ccm.i=1' '&ccm.s=s' '&ccm.c[0]=a' '&ccm.c[1]=b') s = ''.join(list(ctx.out_string)) assert s == "CCM(i=1, c=['a', 'b'], s='s')" def test_cookie_parse(self): string = 'some_string' class RequestHeader(ComplexModel): some_field = String class SomeService(ServiceBase): __in_header__ = RequestHeader @rpc(String) def some_call(ctx, s): assert ctx.in_header.some_field == string def start_response(code, headers): assert code == HTTP_200 c = SimpleCookie() c['some_field'] = string ''.join(wsgiref_validator(WsgiApplication(Application([SomeService], 'tns', in_protocol=HttpRpc(parse_cookie=True), out_protocol=HttpRpc())))({ 'SCRIPT_NAME': '', 'QUERY_STRING': '', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', 'SERVER_PORT': "9999", 'HTTP_COOKIE': str(c), 'wsgi.url_scheme': 'http', 'wsgi.version': (1,0), 'wsgi.input': StringIO(), 'wsgi.errors': StringIO(), 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': True, }, start_response)) def test_http_headers(self): d = datetime(year=2013, month=1, day=1) string = ['hey', 'yo'] class ResponseHeader(ComplexModel): _type_info = { 'Set-Cookie': String(max_occurs='unbounded'), 'Expires': DateTime } class SomeService(ServiceBase): __out_header__ = ResponseHeader @rpc(String) def some_call(ctx, s): assert s is not None ctx.out_header = ResponseHeader(**{'Set-Cookie': string, 'Expires': d}) def start_response(code, headers): assert len([s for s in string if ('Set-Cookie', s) in headers]) == len(string) assert dict(headers)['Expires'] == 'Tue, 01 Jan 2013 00:00:00 GMT' ret = ''.join(wsgiref_validator(WsgiApplication(Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HttpRpc())))({ 'SCRIPT_NAME': '', 'QUERY_STRING': '&s=foo', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', 'SERVER_PORT': "9999", 'wsgi.url_scheme': 'http', 'wsgi.version': (1,0), 'wsgi.input': StringIO(), 'wsgi.errors': StringIO(), 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': True, }, start_response)) assert ret == '' class TestHttpPatterns(unittest.TestCase): def test_rules(self): _int = 5 _fragment = 'some_fragment' class SomeService(ServiceBase): @srpc(Integer, _returns=Integer, _patterns=[ HttpPattern('/%s/'% _fragment)]) def some_call(some_int): assert some_int == _int app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HttpRpc()) server = WsgiApplication(app) environ = { 'QUERY_STRING': '', 'PATH_INFO': '/%s/%d' % (_fragment, _int), 'SERVER_PATH':"/", 'SERVER_NAME': "localhost", 'wsgi.url_scheme': 'http', 'SERVER_PORT': '9000', 'REQUEST_METHOD': 'GET', } initial_ctx = WsgiMethodContext(server, environ, 'some-content-type') ctx, = server.generate_contexts(initial_ctx) foo = [] for i in server._http_patterns: foo.append(i) assert len(foo) == 1 print(foo) assert ctx.descriptor is not None server.get_in_object(ctx) assert ctx.in_error is None server.get_out_object(ctx) assert ctx.out_error is None if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/protocol/test_json.py0000755000175000001440000001361612345433230021255 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 unittest try: import simplejson as json except ImportError: import json from spyne import MethodContext from spyne import Application from spyne import rpc,srpc from spyne import ServiceBase from spyne.model import Integer from spyne.model import ComplexModel from spyne.protocol.json import JsonP from spyne.protocol.json import JsonDocument from spyne.protocol.json import JsonEncoder from spyne.protocol.json import _SpyneJsonRpc1 from spyne.server import ServerBase from spyne.server.null import NullServer from spyne.test.protocol._test_dictdoc import TDictDocumentTest from spyne.test.protocol._test_dictdoc import TDry TestDictDocument = TDictDocumentTest(json, JsonDocument, dumps_kwargs=dict(cls=JsonEncoder)) _dry_sjrpc1 = TDry(json, _SpyneJsonRpc1) class TestSpyneJsonRpc1(unittest.TestCase): def test_call(self): class SomeService(ServiceBase): @srpc(Integer, _returns=Integer) def yay(i): print(i) return i ctx = _dry_sjrpc1([SomeService], {"ver": 1, "body": {"yay": {"i":5}}}, True) print(ctx) print(list(ctx.out_string)) assert ctx.out_document == {"ver": 1, "body": 5} def test_call_with_header(self): class SomeHeader(ComplexModel): i = Integer class SomeService(ServiceBase): __in_header__ = SomeHeader @rpc(Integer, _returns=Integer) def yay(ctx, i): print(ctx.in_header) return ctx.in_header.i ctx = _dry_sjrpc1([SomeService], {"ver": 1, "body": {"yay": None}, "head": {"i":5}}, True) print(ctx) print(list(ctx.out_string)) assert ctx.out_document == {"ver": 1, "body": 5} def test_error(self): class SomeHeader(ComplexModel): i = Integer class SomeService(ServiceBase): __in_header__ = SomeHeader @rpc(Integer, Integer, _returns=Integer) def div(ctx, dividend, divisor): return dividend / divisor ctx = _dry_sjrpc1([SomeService], {"ver": 1, "body": {"div": [4,0]}}, True) print(ctx) print(list(ctx.out_string)) assert ctx.out_document == {"ver": 1, "fault": { 'faultcode': 'Server', 'faultstring': 'Internal Error'}} class TestJsonDocument(unittest.TestCase): def test_out_kwargs(self): class SomeService(ServiceBase): @srpc() def yay(): pass app = Application([SomeService], 'tns', in_protocol=JsonDocument(), out_protocol=JsonDocument()) assert 'cls' in app.out_protocol.kwargs assert not ('cls' in app.in_protocol.kwargs) app = Application([SomeService], 'tns', in_protocol=JsonDocument(), out_protocol=JsonDocument(cls='hey')) assert app.out_protocol.kwargs['cls'] == 'hey' assert not ('cls' in app.in_protocol.kwargs) def test_invalid_input(self): class SomeService(ServiceBase): @srpc() def yay(): pass app = Application([SomeService], 'tns', in_protocol=JsonDocument(), out_protocol=JsonDocument()) server = ServerBase(app) initial_ctx = MethodContext(server) initial_ctx.in_string = ['{'] ctx, = server.generate_contexts(initial_ctx) assert ctx.in_error.faultcode == 'Client.JsonDecodeError' class TestJsonP(unittest.TestCase): def test_callback_name(self): callback_name = 'some_callback' retval = 42 class SomeService(ServiceBase): @srpc(_returns=Integer) def yay(): return retval app = Application([SomeService], 'tns', in_protocol=JsonDocument(), out_protocol=JsonP(callback_name)) server = NullServer(app, ostr=True) assert ''.join(server.service.yay()) == '%s(%d);' % (callback_name, retval); def illustrate_wrappers(self): from spyne.model.complex import ComplexModel, Array from spyne.model.primitive import Unicode class Permission(ComplexModel): _type_info = [ ('application', Unicode), ('feature', Unicode), ] class SomeService(ServiceBase): @srpc(_returns=Array(Permission)) def yay(): return [ Permission(application='app', feature='f1'), Permission(application='app', feature='f2') ] app = Application([SomeService], 'tns', in_protocol=JsonDocument(), out_protocol=JsonDocument(ignore_wrappers=False)) server = NullServer(app, ostr=True) print(''.join(server.service.yay())) # assert false if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/protocol/test_msgpack.py0000755000175000001440000001012412345433230021720 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 unittest import logging logging.basicConfig(level=logging.DEBUG) from spyne.util.six import BytesIO import msgpack from spyne import MethodContext from spyne.application import Application from spyne.decorator import rpc from spyne.decorator import srpc from spyne.service import ServiceBase from spyne.model.complex import Array from spyne.model.primitive import String from spyne.model.complex import ComplexModel from spyne.model.primitive import Unicode from spyne.protocol.msgpack import MessagePackDocument from spyne.protocol.msgpack import MessagePackRpc from spyne.server import ServerBase from spyne.server.wsgi import WsgiApplication from spyne.test.protocol._test_dictdoc import TDictDocumentTest from spyne.test.test_service import start_response TestMessagePackDocument = TDictDocumentTest(msgpack, MessagePackDocument) class TestMessagePackRpc(unittest.TestCase): def test_invalid_input(self): class SomeService(ServiceBase): @srpc() def yay(): pass app = Application([SomeService], 'tns', in_protocol=MessagePackDocument(), out_protocol=MessagePackDocument()) server = ServerBase(app) initial_ctx = MethodContext(server) initial_ctx.in_string = ['{'] ctx, = server.generate_contexts(initial_ctx) assert ctx.in_error.faultcode == 'Client.MessagePackDecodeError' def test_rpc(self): data = {"a":"b", "c": "d"} class KeyValuePair(ComplexModel): key = Unicode value = Unicode class Service(ServiceBase): @rpc(String(max_occurs='unbounded'), _returns=Array(KeyValuePair), _in_variable_names={ 'keys': 'key' } ) def get_values(ctx, keys): for k in keys: yield KeyValuePair(key=k, value=data[k]) application = Application([Service], in_protocol=MessagePackRpc(), out_protocol=MessagePackRpc(ignore_wrappers=False), name='Service', tns='tns' ) server = WsgiApplication(application) input_string = msgpack.packb([0,0,"get_values", [["a","c"]] ]) input_stream = BytesIO(input_string) ret = server({ 'CONTENT_LENGTH': str(len(input_string)), 'CONTENT_TYPE': 'application/x-msgpack', 'HTTP_CONNECTION': 'close', 'HTTP_CONTENT_LENGTH': str(len(input_string)), 'HTTP_CONTENT_TYPE': 'application/x-msgpack', 'PATH_INFO': '/', 'QUERY_STRING': '', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '7000', 'REQUEST_METHOD': 'POST', 'wsgi.url_scheme': 'http', 'wsgi.input': input_stream, }, start_response) ret = b''.join(ret) print(repr(ret)) print(msgpack.unpackb(ret)) s = msgpack.packb([1, 0, None, {'get_valuesResponse': { 'get_valuesResult': [ {"KeyValuePair": {'value': 'b', 'key': 'a'}}, {"KeyValuePair": {'value': 'd', 'key': 'c'}}, ] }} ]) print(s) assert ret == s if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/protocol/test_soap.py0000755000175000001440000003305712345433230021247 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 # # # Most of the service tests are performed through the interop tests. # import datetime import unittest from lxml import etree import pytz from spyne import MethodContext from spyne.application import Application from spyne.decorator import rpc from spyne.interface.wsdl import Wsdl11 from spyne.model.complex import Array from spyne.model.complex import ComplexModel from spyne.model.primitive import Unicode from spyne.model.primitive import DateTime, Date from spyne.model.primitive import Float from spyne.model.primitive import Integer from spyne.model.primitive import String from spyne.model.fault import Fault from spyne.protocol.soap import Soap11 from spyne.service import ServiceBase from spyne.server import ServerBase from spyne.protocol.soap import _from_soap from spyne.protocol.soap import _parse_xml_string Application.transport = 'test' def start_response(code, headers): print(code, headers) class Address(ComplexModel): __namespace__ = "TestService" street = String city = String zip = Integer since = DateTime laditude = Float longitude = Float class Person(ComplexModel): __namespace__ = "TestService" name = String birthdate = DateTime age = Integer addresses = Array(Address) titles = Array(String) class Request(ComplexModel): __namespace__ = "TestService" param1 = String param2 = Integer class Response(ComplexModel): __namespace__ = "TestService" param1 = Float class TypeNS1(ComplexModel): __namespace__ = "TestService.NS1" s = String i = Integer class TypeNS2(ComplexModel): __namespace__ = "TestService.NS2" d = DateTime f = Float class MultipleNamespaceService(ServiceBase): @rpc(TypeNS1, TypeNS2) def a(ctx, t1, t2): return "OK" class TestService(ServiceBase): @rpc(String, _returns=String) def aa(ctx, s): return s @rpc(String, Integer, _returns=DateTime) def a(ctx, s, i): return datetime.datetime.now() @rpc(Person, String, Address, _returns=Address) def b(ctx, p, s, a): return Address() @rpc(Person, isAsync=True) def d(ctx, Person): pass @rpc(Person, isCallback=True) def e(ctx, Person): pass @rpc(String, String, String, _returns=String, _in_variable_names={'_from': 'from', '_self': 'self', '_import': 'import'}, _out_variable_name="return") def f(ctx, _from, _self, _import): return '1234' class MultipleReturnService(ServiceBase): @rpc(String, _returns=(String, String, String)) def multi(ctx, s): return s, 'a', 'b' class TestSingle(unittest.TestCase): def setUp(self): self.app = Application([TestService], 'tns', in_protocol=Soap11(), out_protocol=Soap11()) self.app.transport = 'null.spyne' self.srv = TestService() wsdl = Wsdl11(self.app.interface) wsdl.build_interface_document('URL') self.wsdl_str = wsdl.get_interface_document() self.wsdl_doc = etree.fromstring(self.wsdl_str) def test_portypes(self): porttype = self.wsdl_doc.find('{http://schemas.xmlsoap.org/wsdl/}portType') self.assertEquals( len(self.srv.public_methods), len(porttype.getchildren())) def test_override_param_names(self): for n in ['self', 'import', 'return', 'from']: assert n in self.wsdl_str, '"%s" not in self.wsdl_str' class TestMultiple(unittest.TestCase): def setUp(self): self.app = Application([MultipleReturnService], 'tns', in_protocol=Soap11(), out_protocol=Soap11()) self.app.transport = 'none' self.wsdl = Wsdl11(self.app.interface) self.wsdl.build_interface_document('URL') def test_multiple_return(self): message_class = list(MultipleReturnService.public_methods.values())[0].out_message message = message_class() self.assertEquals(len(message._type_info), 3) sent_xml = etree.Element('test') self.app.out_protocol.to_parent(None, message_class, ('a', 'b', 'c'), sent_xml, MultipleReturnService.get_tns()) sent_xml = sent_xml[0] print((etree.tostring(sent_xml, pretty_print=True))) response_data = self.app.out_protocol.from_element(None, message_class, sent_xml) self.assertEquals(len(response_data), 3) self.assertEqual(response_data[0], 'a') self.assertEqual(response_data[1], 'b') self.assertEqual(response_data[2], 'c') class TestSoap(unittest.TestCase): def test_simple_message(self): m = ComplexModel.produce( namespace=None, type_name='myMessage', members={'s': String, 'i': Integer} ) m.resolve_namespace(m, 'test') m_inst = m(s="a", i=43) e = etree.Element('test') Soap11().to_parent(None, m, m_inst, e, m.get_namespace()) e=e[0] self.assertEquals(e.tag, '{%s}myMessage' % m.get_namespace()) self.assertEquals(e.find('{%s}s' % m.get_namespace()).text, 'a') self.assertEquals(e.find('{%s}i' % m.get_namespace()).text, '43') values = Soap11().from_element(None, m, e) self.assertEquals('a', values.s) self.assertEquals(43, values.i) def test_href(self): # the template. Start at pos 0, some servers complain if # xml tag is not in the first line. envelope_string = ''' somemachine someuser machine2 user2 ''' root, xmlids = _parse_xml_string(envelope_string, etree.XMLParser(), 'utf8') header, payload = _from_soap(root, xmlids) # quick and dirty test href reconstruction self.assertEquals(len(payload[0]), 2) def test_namespaces(self): m = ComplexModel.produce( namespace="some_namespace", type_name='myMessage', members={'s': String, 'i': Integer}, ) mi = m() mi.s = 'a' e = etree.Element('test') Soap11().to_parent(None, m, mi, e, m.get_namespace()) e=e[0] self.assertEquals(e.tag, '{some_namespace}myMessage') def test_class_to_parent(self): m = ComplexModel.produce( namespace=None, type_name='myMessage', members={'p': Person} ) m.resolve_namespace(m, "punk") m_inst = m() m_inst.p = Person() m_inst.p.name = 'steve-o' m_inst.p.age = 2 m_inst.p.addresses = [] element=etree.Element('test') Soap11().to_parent(None, m, m_inst, element, m.get_namespace()) element=element[0] self.assertEquals(element.tag, '{%s}myMessage' % m.get_namespace()) self.assertEquals(element[0].find('{%s}name' % Person.get_namespace()).text, 'steve-o') self.assertEquals(element[0].find('{%s}age' % Person.get_namespace()).text, '2') self.assertEquals( len(element[0].find('{%s}addresses' % Person.get_namespace())), 0) p1 = Soap11().from_element(None, m, element)[0] self.assertEquals(p1.name, m_inst.p.name) self.assertEquals(p1.age, m_inst.p.age) self.assertEquals(p1.addresses, []) def test_datetime_fixed_format(self): # Soap should ignore formats n = datetime.datetime.now(pytz.utc).replace(microsecond=0) format = "%Y %m %d %H %M %S" element = etree.Element('test') Soap11().to_parent(None, DateTime(format=format), n, element, 'some_namespace') assert element[0].text == n.isoformat() dt = Soap11().from_element(None, DateTime(format=format), element[0]) assert n == dt def test_date_with_tzoffset(self): for iso_d in ('2013-04-05', '2013-04-05+02:00', '2013-04-05-02:00', '2013-04-05Z'): d = Soap11().from_string(Date, iso_d) assert isinstance(d, datetime.date) == True assert d.year == 2013 assert d.month == 4 assert d.day == 5 def test_to_parent_nested(self): m = ComplexModel.produce( namespace=None, type_name='myMessage', members={'p':Person} ) m.resolve_namespace(m, "m") p = Person() p.name = 'steve-o' p.age = 2 p.addresses = [] for i in range(0, 100): a = Address() a.street = '123 happy way' a.zip = i a.laditude = '45.22' a.longitude = '444.234' p.addresses.append(a) m_inst = m(p=p) element=etree.Element('test') Soap11().to_parent(None, m, m_inst, element, m.get_namespace()) element=element[0] self.assertEquals('{%s}myMessage' % m.get_namespace(), element.tag) addresses = element[0].find('{%s}addresses' % Person.get_namespace()) self.assertEquals(100, len(addresses)) self.assertEquals('0', addresses[0].find('{%s}zip' % Address.get_namespace()).text) def test_fault_deserialization_missing_fault_actor(self): element = etree.fromstring(""" soap:Client Some String Some_Policy """) ret = Soap11().from_element(None, Fault, element[0][0]) assert ret.faultcode == "soap:Client" # TestSoapHeader supporting classes. # SOAP Header Elements defined by WS-Addressing. NAMESPACE_ADDRESSING = 'http://www.w3.org/2005/08/addressing' class Action (Unicode): __type_name__ = "Action" __namespace__ = NAMESPACE_ADDRESSING class MessageID (Unicode): __type_name__ = "MessageID" __namespace__ = NAMESPACE_ADDRESSING class RelatesTo (Unicode): __type_name__ = "RelatesTo" __namespace__ = NAMESPACE_ADDRESSING class SOAPServiceWithHeader(ServiceBase): @rpc(Unicode, _in_header=(Action, MessageID, RelatesTo), _out_variable_names= 'status', _returns=Unicode ) def someRequest(ctx, response): print (response) return 'OK' class TestSoapHeader(unittest.TestCase): def setUp(self): self.app = Application([SOAPServiceWithHeader], 'tns', in_protocol=Soap11(), out_protocol=Soap11()) def test_soap_input_header(self): server = ServerBase(self.app) initial_ctx = MethodContext(server) initial_ctx.in_string = [ '' '' '/SomeAction' 'SomeMessageID' 'SomeRelatesToID' '' '' '' 'OK' '' '' '' ] ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) self.assertEquals(ctx.in_header[0], '/SomeAction') self.assertEquals(ctx.in_header[1], 'SomeMessageID') self.assertEquals(ctx.in_header[2], 'SomeRelatesToID') if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/protocol/test_xml.py0000755000175000001440000005610212352126507021105 0ustar plqusers00000000000000#!/usr/bin/env python # encoding: utf-8 # # 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 # from __future__ import print_function import logging logging.basicConfig(level=logging.DEBUG) import unittest import decimal import datetime from pprint import pprint from lxml import etree from webtest import TestApp from webtest.app import AppError from spyne import MethodContext, rpc from spyne._base import FakeContext from spyne.const import RESULT_SUFFIX from spyne.service import ServiceBase from spyne.server import ServerBase from spyne.application import Application from spyne.decorator import srpc from spyne.util.six import StringIO from spyne.model import Fault from spyne.model.primitive import Integer from spyne.model.primitive import Decimal from spyne.model.primitive import Unicode from spyne.model.primitive import Date from spyne.model.primitive import DateTime from spyne.model.complex import XmlData from spyne.model.complex import Array from spyne.model.complex import ComplexModel from spyne.model.complex import XmlAttribute from spyne.model.complex import Mandatory as M from spyne.protocol.xml import XmlDocument from spyne.protocol.xml import SchemaValidationError from spyne.util.xml import get_xml_as_object, get_object_as_xml from spyne.server.wsgi import WsgiApplication class TestXml(unittest.TestCase): def test_empty_string(self): class a(ComplexModel): b = Unicode elt = etree.fromstring('') o = get_xml_as_object(elt, a) assert o.b == '' def test_xml_data(self): class C(ComplexModel): a = XmlData(Unicode) b = XmlAttribute(Unicode) class SomeService(ServiceBase): @srpc(C, _returns=C) def some_call(c): assert c.a == 'a' assert c.b == 'b' return c app = Application([SomeService], "tns", name="test_attribute_of", in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = ServerBase(app) initial_ctx = MethodContext(server) initial_ctx.in_string = [ '' 'a' '' ] ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) print(ctx.out_string) pprint(app.interface.nsmap) ret = etree.fromstring(''.join(ctx.out_string)).xpath( '//tns:some_call' + RESULT_SUFFIX, namespaces=app.interface.nsmap)[0] print(etree.tostring(ret, pretty_print=True)) assert ret.text == "a" assert ret.attrib['b'] == "b" def test_attribute_of(self): class C(ComplexModel): a = Unicode b = XmlAttribute(Unicode, attribute_of="a") class SomeService(ServiceBase): @srpc(C, _returns=C) def some_call(c): assert c.a == 'a' assert c.b == 'b' return c app = Application([SomeService], "tns", name="test_attribute_of", in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = ServerBase(app) initial_ctx = MethodContext(server) initial_ctx.in_string = [ '' '' 'a' '' '' ] ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) ret = ''.join(ctx.out_string) print(ret) ret = etree.fromstring(ret) ret = ret.xpath('//s0:a', namespaces=app.interface.nsmap)[0] print(etree.tostring(ret, pretty_print=True)) assert ret.text == "a" assert ret.attrib['b'] == "b" def test_multiple_attribute_of_multiple_rpc(self): """ Tests the following: 1. Support for multiple attributes on a single element. 2. Correctness of attribute definition -- extension applied to correct 'attribute_of' element. 3. Another class/rpc with same element/attribute name works correctly. """ class CMA(ComplexModel): a = Unicode ab = XmlAttribute(Unicode, attribute_of="a") ac = XmlAttribute(Unicode, attribute_of="a") ad = XmlAttribute(Integer, attribute_of="a") b = Integer bb = XmlAttribute(Unicode, attribute_of="b") class CMB(ComplexModel): b = Integer bb = XmlAttribute(Unicode, attribute_of="b") class SomeService(ServiceBase): @srpc(CMA, _returns=CMA) def some_call(cma): assert cma.a == 'a' assert cma.ab == 'b' assert cma.ac == 'c' assert cma.ad == 5 assert cma.b == 9 assert cma.bb == "attrib bb" return cma @srpc(CMB, _returns=CMB) def another_call(cmb): assert cmb.b == 9 assert cmb.bb == 'attrib bb' return cmb app = Application([SomeService], "tns", name="test_multiple_attribute_of", in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = ServerBase(app) # test some_call(CMA) initial_ctx = MethodContext(server) initial_ctx.in_string = [ '' '' 'a' '9' '' '' ] ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) ret = ''.join(ctx.out_string) print(ret) ret = etree.fromstring(ret) ret = ret.xpath('//s0:a', namespaces=app.interface.nsmap)[0] print(etree.tostring(ret, pretty_print=True)) assert ret.text == "a" assert ret.attrib['ab'] == "b" assert ret.attrib['ac'] == "c" assert int(ret.attrib['ad']) == 5 ret = ret.xpath('//s0:b', namespaces=app.interface.nsmap)[0] print(etree.tostring(ret, pretty_print=True)) assert ret.text == "9" assert ret.attrib['bb'] == "attrib bb" # test another_call(CMB) initial_ctx = MethodContext(server) initial_ctx.in_string = [ '' '' '9' '' '' ] ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) ret = ''.join(ctx.out_string) print(ret) ret = etree.fromstring(ret) ret = ret.xpath('//s0:b', namespaces=app.interface.nsmap)[0] print(etree.tostring(ret, pretty_print=True)) assert ret.text == "9" assert ret.attrib['bb'] == "attrib bb" def test_attribute_of_multi(self): class C(ComplexModel): e = Unicode(max_occurs='unbounded') a = XmlAttribute(Unicode, attribute_of="e") class SomeService(ServiceBase): @srpc(C, _returns=C) def some_call(c): assert c.e == ['e0', 'e1'] assert c.a == ['a0', 'a1'] return c app = Application([SomeService], "tns", name="test_attribute_of", in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = ServerBase(app) initial_ctx = MethodContext(server) initial_ctx.method_request_string = '{test_attribute_of}some_call' initial_ctx.in_string = [ '' '' 'e0' 'e1' '' '' ] ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) ret = etree.fromstring(''.join(ctx.out_string)).xpath('//s0:e', namespaces=app.interface.nsmap) print(etree.tostring(ret[0], pretty_print=True)) print(etree.tostring(ret[1], pretty_print=True)) assert ret[0].text == "e0" assert ret[0].attrib['a'] == "a0" assert ret[1].text == "e1" assert ret[1].attrib['a'] == "a1" def test_attribute_ns(self): class a(ComplexModel): b = Unicode c = XmlAttribute(Unicode, ns="spam", attribute_of="b") class SomeService(ServiceBase): @srpc(_returns=a) def some_call(): return a(b="foo",c="bar") app = Application([SomeService], "tns", in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = ServerBase(app) initial_ctx = MethodContext(server) initial_ctx.in_string = [''] ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) elt = etree.fromstring(''.join(ctx.out_string)) target = elt.xpath('//s0:b', namespaces=app.interface.nsmap)[0] print(etree.tostring(elt, pretty_print=True)) assert target.attrib['{%s}c' % app.interface.nsmap["s1"]] == "bar" def test_wrapped_array(self): parent = etree.Element('parent') val = ['a', 'b'] cls = Array(Unicode, namespace='tns') XmlDocument().to_parent(None, cls, val, parent, 'tns') print(etree.tostring(parent, pretty_print=True)) xpath = parent.xpath('//x:stringArray/x:string/text()', namespaces={'x': 'tns'}) assert xpath == val def test_simple_array(self): class cls(ComplexModel): __namespace__ = 'tns' s = Unicode(max_occurs='unbounded') val = cls(s=['a', 'b']) parent = etree.Element('parent') XmlDocument().to_parent(None, cls, val, parent, 'tns') print(etree.tostring(parent, pretty_print=True)) xpath = parent.xpath('//x:cls/x:s/text()', namespaces={'x': 'tns'}) assert xpath == val.s def test_decimal(self): d = decimal.Decimal('1e100') class SomeService(ServiceBase): @srpc(Decimal(120,4), _returns=Decimal) def some_call(p): print(p) print(type(p)) assert type(p) == decimal.Decimal assert d == p return p app = Application([SomeService], "tns", in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = ServerBase(app) initial_ctx = MethodContext(server) initial_ctx.in_string = ['

%s

' % d] ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) elt = etree.fromstring(''.join(ctx.out_string)) print(etree.tostring(elt, pretty_print=True)) target = elt.xpath('//tns:some_callResult/text()', namespaces=app.interface.nsmap)[0] assert target == str(d) def test_subs(self): from lxml import etree from spyne.util.xml import get_xml_as_object from spyne.util.xml import get_object_as_xml m = { "s0": "aa", "s2": "cc", "s3": "dd", } class C(ComplexModel): __namespace__ = "aa" a = Integer b = Integer(sub_name="bb") c = Integer(sub_ns="cc") d = Integer(sub_ns="dd", sub_name="dd") elt = get_object_as_xml(C(a=1, b=2, c=3, d=4), C) print(etree.tostring(elt, pretty_print=True)) assert elt.xpath("s0:a/text()", namespaces=m) == ["1"] assert elt.xpath("s0:bb/text()", namespaces=m) == ["2"] assert elt.xpath("s2:c/text()", namespaces=m) == ["3"] assert elt.xpath("s3:dd/text()", namespaces=m) == ["4"] c = get_xml_as_object(elt, C) print(c) assert c.a == 1 assert c.b == 2 assert c.c == 3 assert c.d == 4 def test_sub_attributes(self): from lxml import etree from spyne.util.xml import get_xml_as_object from spyne.util.xml import get_object_as_xml m = { "s0": "aa", "s2": "cc", "s3": "dd", } class C(ComplexModel): __namespace__ = "aa" a = XmlAttribute(Integer) b = XmlAttribute(Integer(sub_name="bb")) c = XmlAttribute(Integer(sub_ns="cc")) d = XmlAttribute(Integer(sub_ns="dd", sub_name="dd")) elt = get_object_as_xml(C(a=1, b=2, c=3, d=4), C) print(etree.tostring(elt, pretty_print=True)) assert elt.xpath("//*/@a") == ["1"] assert elt.xpath("//*/@bb") == ["2"] assert elt.xpath("//*/@s2:c", namespaces=m) == ["3"] assert elt.xpath("//*/@s3:dd", namespaces=m) == ["4"] c = get_xml_as_object(elt, C) print(c) assert c.a == 1 assert c.b == 2 assert c.c == 3 assert c.d == 4 def test_dates(self): d = Date xml_dates = [ etree.fromstring('2013-04-05'), etree.fromstring('2013-04-05+02:00'), etree.fromstring('2013-04-05-02:00'), etree.fromstring('2013-04-05Z'), ] for xml_date in xml_dates: c = get_xml_as_object(xml_date, d) assert isinstance(c, datetime.date) == True assert c.year == 2013 assert c.month == 4 assert c.day == 5 def test_datetime_usec(self): fs = etree.fromstring d = get_xml_as_object(fs('2013-04-05T06:07:08.123456'), DateTime) assert d.microsecond == 123456 # rounds up d = get_xml_as_object(fs('2013-04-05T06:07:08.1234567'), DateTime) assert d.microsecond == 123457 # rounds down d = get_xml_as_object(fs('2013-04-05T06:07:08.1234564'), DateTime) assert d.microsecond == 123456 # rounds up as well d = get_xml_as_object(fs('2013-04-05T06:07:08.1234565'), DateTime) assert d.microsecond == 123457 def _get_ctx(self, server, in_string): initial_ctx = MethodContext(server) initial_ctx.in_string = in_string ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) return ctx def test_mandatory_elements(self): class SomeService(ServiceBase): @srpc(M(Unicode), _returns=Unicode) def some_call(s): assert s == 'hello' return s app = Application([SomeService], "tns", name="test_mandatory_elements", in_protocol=XmlDocument(validator='lxml'), out_protocol=XmlDocument()) server = ServerBase(app) # Valid call with all mandatory elements in ctx = self._get_ctx(server, [ '' 'hello' '' ]) server.get_out_object(ctx) server.get_out_string(ctx) ret = etree.fromstring(''.join(ctx.out_string)).xpath( '//tns:some_call%s/text()' % RESULT_SUFFIX, namespaces=app.interface.nsmap)[0] assert ret == 'hello' # Invalid call ctx = self._get_ctx(server, [ '' # no mandatory elements here... '' ]) self.assertRaises(SchemaValidationError, server.get_out_object, ctx) def test_unicode_chars_in_exception(self): class SomeService(ServiceBase): @srpc(Unicode(pattern=u'x'), _returns=Unicode) def some_call(s): test(never,reaches,here) app = Application([SomeService], "tns", name="test_mandatory_elements", in_protocol=XmlDocument(validator='lxml'), out_protocol=XmlDocument()) server = WsgiApplication(app) req = ( '' 'Ğ' '' ) resp = server({ 'QUERY_STRING': '', 'PATH_INFO': '/', 'REQUEST_METHOD': 'POST', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80', 'wsgi.input': StringIO(req), "wsgi.url_scheme": 'http', }, lambda x,y: print(x,y)) assert 'Ğ' in ''.join(resp) def test_mandatory_subelements(self): class C(ComplexModel): foo = M(Unicode) class SomeService(ServiceBase): @srpc(C.customize(min_occurs=1), _returns=Unicode) def some_call(c): assert c is not None assert c.foo == 'hello' return c.foo app = Application( [SomeService], "tns", name="test_mandatory_subelements", in_protocol=XmlDocument(validator='lxml'), out_protocol=XmlDocument()) server = ServerBase(app) ctx = self._get_ctx(server, [ '' # no mandatory elements at all... '' ]) self.assertRaises(SchemaValidationError, server.get_out_object, ctx) ctx = self._get_ctx(server, [ '' '' # no mandatory elements here... '' '' ]) self.assertRaises(SchemaValidationError, server.get_out_object, ctx) def test_mandatory_element_attributes(self): class C(ComplexModel): bar = XmlAttribute(M(Unicode)) class SomeService(ServiceBase): @srpc(C.customize(min_occurs=1), _returns=Unicode) def some_call(c): assert c is not None assert hasattr(c, 'foo') assert c.foo == 'hello' return c.foo app = Application( [SomeService], "tns", name="test_mandatory_element_attributes", in_protocol=XmlDocument(validator='lxml'), out_protocol=XmlDocument()) server = ServerBase(app) ctx = self._get_ctx(server, [ '' # no mandatory elements at all... '' ]) self.assertRaises(SchemaValidationError, server.get_out_object, ctx) ctx = self._get_ctx(server, [ '' '' # no mandatory elements here... '' '' ]) self.assertRaises(SchemaValidationError, server.get_out_object, ctx) class TestIncremental(unittest.TestCase): def test_one(self): class SomeComplexModel(ComplexModel): s = Unicode i = Integer v = SomeComplexModel(s='a', i=1), class SomeService(ServiceBase): @rpc(_returns=SomeComplexModel) def get(ctx): return v desc = SomeService.public_methods['get'] ctx = FakeContext(out_object=v, descriptor=desc) ostr = ctx.out_stream = StringIO() XmlDocument(Application([SomeService], __name__)) \ .serialize(ctx, XmlDocument.RESPONSE) elt = etree.fromstring(ostr.getvalue()) print(etree.tostring(elt, pretty_print=True)) assert elt.xpath('x:getResult/x:i/text()', namespaces={'x':__name__}) == ['1'] assert elt.xpath('x:getResult/x:s/text()', namespaces={'x':__name__}) == ['a'] def test_many(self): class SomeComplexModel(ComplexModel): s = Unicode i = Integer v = [ SomeComplexModel(s='a', i=1), SomeComplexModel(s='b', i=2), SomeComplexModel(s='c', i=3), SomeComplexModel(s='d', i=4), SomeComplexModel(s='e', i=5), ] class SomeService(ServiceBase): @rpc(_returns=Array(SomeComplexModel)) def get(ctx): return v desc = SomeService.public_methods['get'] ctx = FakeContext(out_object=[v], descriptor=desc) ostr = ctx.out_stream = StringIO() XmlDocument(Application([SomeService], __name__)) \ .serialize(ctx, XmlDocument.RESPONSE) elt = etree.fromstring(ostr.getvalue()) print(etree.tostring(elt, pretty_print=True)) assert elt.xpath('x:getResult/x:SomeComplexModel/x:i/text()', namespaces={'x': __name__}) == ['1', '2', '3', '4', '5'] assert elt.xpath('x:getResult/x:SomeComplexModel/x:s/text()', namespaces={'x': __name__}) == ['a', 'b', 'c', 'd', 'e'] def test_bare_sub_name_ns(self): class Action (ComplexModel): class Attributes(ComplexModel.Attributes): sub_ns = "SOME_NS" sub_name = "Action" data = XmlData(Unicode) must_understand = XmlAttribute(Unicode) elt = get_object_as_xml(Action("x", must_understand="y"), Action) eltstr = etree.tostring(elt) print(eltstr) assert eltstr == b'x' def test_null_mandatory_attribute(self): class Action (ComplexModel): data = XmlAttribute(M(Unicode)) elt = get_object_as_xml(Action(), Action) eltstr = etree.tostring(elt) print(eltstr) assert eltstr == b'' def test_fault_detail_as_dict(self): elt = get_object_as_xml(Fault(detail={"this": "that"}), Fault) eltstr = etree.tostring(elt) print(eltstr) assert b'that' in eltstr if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/protocol/test_yaml.py0000755000175000001440000000355312342627562021257 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 unittest from spyne.test.protocol._test_dictdoc import TDictDocumentTest from spyne.protocol.yaml import YamlDocument from spyne import MethodContext from spyne.application import Application from spyne.decorator import srpc from spyne.service import ServiceBase from spyne.server import ServerBase from spyne.protocol.yaml import yaml yaml.dumps = yaml.dump yaml.loads = yaml.load TestYamlDocument = TDictDocumentTest(yaml, YamlDocument, YamlDocument().out_kwargs) class Test(unittest.TestCase): def test_invalid_input(self): class SomeService(ServiceBase): @srpc() def yay(): pass app = Application([SomeService], 'tns', in_protocol=YamlDocument(), out_protocol=YamlDocument()) server = ServerBase(app) initial_ctx = MethodContext(server) initial_ctx.in_string = ['{'] ctx, = server.generate_contexts(initial_ctx) assert ctx.in_error.faultcode == 'Client.YamlDecodeError' if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/transport/0000755000175000001440000000000012352131452017053 5ustar plqusers00000000000000spyne-2.11.0/spyne/test/transport/__init__.py0000644000175000001440000000000012345433230021153 0ustar plqusers00000000000000spyne-2.11.0/spyne/test/transport/test_msgpack.py0000644000175000001440000000605012345433230022113 0ustar plqusers00000000000000 # # 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 msgpack from spyne import Application, ServiceBase, rpc from spyne.model import Unicode from spyne.protocol.msgpack import MessagePackDocument from twisted.trial import unittest class TestMessagePackServer(unittest.TestCase): def gen_prot(self, app): from spyne.server.twisted.msgpack import TwistedMessagePackProtocol from twisted.test.proto_helpers import StringTransportWithDisconnection prot = TwistedMessagePackProtocol(app) transport = StringTransportWithDisconnection() prot.makeConnection(transport) transport.protocol = prot return prot def test_roundtrip(self): v = "yaaay!" class SomeService(ServiceBase): @rpc(Unicode, _returns=Unicode) def yay(ctx, u): return u app = Application([SomeService], 'tns', in_protocol=MessagePackDocument(), out_protocol=MessagePackDocument()) prot = self.gen_prot(app) request = msgpack.packb({'yay': [v]}) prot.dataReceived(msgpack.packb([1, request])) val = prot.transport.value() print repr(val) val = msgpack.unpackb(val) print repr(val) self.assertEquals(val, {0: msgpack.packb(v)}) def test_roundtrip_deferred(self): from twisted.internet import reactor from twisted.internet.task import deferLater v = "yaaay!" p_ctx = [] class SomeService(ServiceBase): @rpc(Unicode, _returns=Unicode) def yay(ctx, u): def _cb(): return u p_ctx.append(ctx) return deferLater(reactor, 0.1, _cb) app = Application([SomeService], 'tns', in_protocol=MessagePackDocument(), out_protocol=MessagePackDocument()) prot = self.gen_prot(app) request = msgpack.packb({'yay': [v]}) def _ccb(_): val = prot.transport.value() print repr(val) val = msgpack.unpackb(val) print repr(val) self.assertEquals(val, {0: msgpack.packb(v)}) prot.dataReceived(msgpack.packb([1, request])) return p_ctx[0].out_object[0].addCallback(_ccb) spyne-2.11.0/spyne/test/__init__.py0000644000175000001440000000174512345433230017140 0ustar plqusers00000000000000 # # 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 # class FakeApp(object): transport = 'transport' tns = 'tns' name = 'name' services = [] import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.util.appreg').setLevel(logging.INFO) spyne-2.11.0/spyne/test/regen_wsdl.py0000755000175000001440000000156112345433230017531 0ustar plqusers00000000000000#!/usr/bin/env python from lxml import etree from spyne.test.sort_wsdl import sort_wsdl from spyne.interface.wsdl import Wsdl11 from spyne.test.interop.server._service import services from spyne.application import Application app = Application(services, 'spyne.test.interop.server') app.transport = 'http://schemas.xmlsoap.org/soap/http' wsdl = Wsdl11(app.interface) wsdl.build_interface_document('http://localhost:9754/') elt = etree.ElementTree(etree.fromstring(wsdl.get_interface_document())) sort_wsdl(elt) s = etree.tostring(elt) # minidom's serialization seems to put attributes in alphabetic order. # this is exactly what we want here. from xml.dom.minidom import parseString doc = parseString(s) s = doc.toprettyxml(indent=' ', newl='\n', encoding='utf8') s = s.replace(" xmlns:","\n xmlns:") open('wsdl.xml', 'w').write(s) print('wsdl.xml written') spyne-2.11.0/spyne/test/sort_wsdl.py0000755000175000001440000000541212345433230017417 0ustar plqusers00000000000000#!/usr/bin/python # # 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 # """Quick hack to sort the wsdl. it's helpful when comparing the wsdl output from two spyne versions. """ ns_wsdl = "http://schemas.xmlsoap.org/wsdl/" ns_schema = "http://www.w3.org/2001/XMLSchema" import sys from lxml import etree def cache_order(l, ns): return dict([ ("{%s}%s" % (ns, a), l.index(a)) for a in l]) wsdl_order = ('types', 'message', 'service', 'portType', 'binding') wsdl_order = cache_order(wsdl_order, ns_wsdl) schema_order = ('import', 'element', 'simpleType', 'complexType', 'attribute') schema_order = cache_order(schema_order, ns_schema) parser = etree.XMLParser(remove_blank_text=True) def main(): tree = etree.parse(sys.stdin, parser=parser) sort_wsdl(tree) tree.write(sys.stdout, encoding="UTF-8", xml_declaration=True) return 0 def sort_wsdl(tree): l0 = [] type_node = None for e in tree.getroot(): if e.tag == "{%s}types" % ns_wsdl: assert type_node is None type_node = e else: l0.append(e) e.getparent().remove(e) l0.sort(key=lambda e: (wsdl_order[e.tag], e.attrib['name'])) for e in l0: tree.getroot().append(e) for e in tree.getroot(): if e.tag in ("{%s}portType" % ns_wsdl, "{%s}binding" % ns_wsdl, "{%s}operation" % ns_wsdl): nodes = [] for p in e.getchildren(): nodes.append(p) nodes.sort(key=lambda e: e.attrib.get('name', '0')) for p in nodes: e.append(p) schemas = [] for e in type_node: schemas.append(e) e.getparent().remove(e) schemas.sort(key=lambda e: e.attrib["targetNamespace"]) for s in schemas: type_node.append(s) for s in schemas: nodes = [] for e in s: nodes.append(e) e.getparent().remove(e) nodes.sort(key=lambda e: (schema_order[e.tag], e.attrib.get('name', '\0'))) for e in nodes: s.append(e) if __name__ == '__main__': sys.exit(main()) spyne-2.11.0/spyne/test/test_null_server.py0000755000175000001440000000657512342627562021023 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 unittest from lxml import etree from spyne.interface.wsdl import Wsdl11 from spyne.protocol.xml import XmlDocument from spyne.model.complex import Array from spyne.model.primitive import String from spyne.application import Application from spyne.decorator import srpc from spyne.service import ServiceBase from spyne.server.null import NullServer class TestNullServer(unittest.TestCase): def test_call(self): queue = set() class MessageService(ServiceBase): @srpc(String) def send_message(s): queue.add(s) application = Application([MessageService], 'some_tns', in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = NullServer(application) server.service.send_message("zabaaa") assert set(["zabaaa"]) == queue def test_call(self): queue = set() class MessageService(ServiceBase): @srpc(String, String) def send_message(s, k): queue.add((s,k)) application = Application([MessageService], 'some_tns', in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = NullServer(application) queue.clear() server.service.send_message("zabaaa", k="hobaa") assert set([("zabaaa","hobaa")]) == queue queue.clear() server.service.send_message(k="hobaa") assert set([(None,"hobaa")]) == queue queue.clear() server.service.send_message("zobaaa", s="hobaa") assert set([("hobaa", None)]) == queue def test_ostr(self): queue = set() class MessageService(ServiceBase): @srpc(String, String, _returns=Array(String)) def send_message(s, k): queue.add((s,k)) return [s,k] application = Application([MessageService], 'some_tns', in_protocol=XmlDocument(), out_protocol=XmlDocument()) ostr_server = NullServer(application, ostr=True) queue.clear() ret = ostr_server.service.send_message("zabaaa", k="hobaa") assert set([("zabaaa","hobaa")]) == queue assert etree.fromstring(''.join(ret)).xpath('//tns:string/text()', namespaces=application.interface.nsmap) == ['zabaaa', 'hobaa'] queue.clear() ostr_server.service.send_message(k="hobaa") assert set([(None,"hobaa")]) == queue queue.clear() ostr_server.service.send_message("zobaaa", s="hobaa") assert set([("hobaa", None)]) == queue if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/test_service.py0000755000175000001440000003155412345433230020104 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 # # # Most of the service tests are performed through the interop tests. # import logging logging.basicConfig(level=logging.DEBUG) import unittest from spyne.util import six from lxml import etree if six.PY3: from io import StringIO else: from StringIO import StringIO from spyne.const import RESPONSE_SUFFIX from spyne.model.primitive import NATIVE_MAP from spyne.application import Application from spyne.auxproc.sync import SyncAuxProc from spyne.auxproc.thread import ThreadAuxProc from spyne.decorator import rpc from spyne.decorator import srpc from spyne.model import Array from spyne.model import SelfReference from spyne.model import Iterable from spyne.model import ComplexModel from spyne.model import String from spyne.model import Unicode from spyne.protocol.http import HttpRpc from spyne.protocol.soap import Soap11 from spyne.server.null import NullServer from spyne.server.wsgi import WsgiApplication from spyne.service import ServiceBase Application.transport = 'test' def start_response(code, headers): print(code, headers) class MultipleMethods1(ServiceBase): @srpc(String) def multi(s): return "%r multi 1" % s class MultipleMethods2(ServiceBase): @srpc(String) def multi(s): return "%r multi 2" % s class TestMultipleMethods(unittest.TestCase): def test_single_method(self): try: Application([MultipleMethods1,MultipleMethods2], 'tns', in_protocol=Soap11(), out_protocol=Soap11()) except ValueError: pass else: raise Exception('must fail.') def test_simple_aux_nullserver(self): data = [] class Service(ServiceBase): @srpc(String) def call(s): data.append(s) class AuxService(ServiceBase): __aux__ = SyncAuxProc() @srpc(String) def call(s): data.append(s) app = Application([Service, AuxService], 'tns','name', Soap11(), Soap11()) server = NullServer(app) server.service.call("hey") assert data == ['hey', 'hey'] def test_namespace_in_message_name(self): class S(ServiceBase): @srpc(String, _in_message_name='{tns}inMessageName') def call(s): pass app = Application([S], 'tns', 'name', Soap11(), Soap11()) def test_simple_aux_wsgi(self): data = [] class Service(ServiceBase): @srpc(String, _returns=String) def call(s): data.append(s) class AuxService(ServiceBase): __aux__ = SyncAuxProc() @srpc(String, _returns=String) def call(s): data.append(s) app = Application([Service, AuxService], 'tns', in_protocol=HttpRpc(), out_protocol=HttpRpc()) server = WsgiApplication(app) server({ 'QUERY_STRING': 's=hey', 'PATH_INFO': '/call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', }, start_response, "http://null") assert data == ['hey', 'hey'] def test_thread_aux_wsgi(self): import logging logging.basicConfig(level=logging.DEBUG) data = set() class Service(ServiceBase): @srpc(String, _returns=String) def call(s): data.add(s) class AuxService(ServiceBase): __aux__ = ThreadAuxProc() @srpc(String, _returns=String) def call(s): data.add(s + "aux") app = Application([Service, AuxService], 'tns', in_protocol=HttpRpc(), out_protocol=HttpRpc()) server = WsgiApplication(app) server({ 'QUERY_STRING': 's=hey', 'PATH_INFO': '/call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', }, start_response, "http://null") import time time.sleep(1) assert data == set(['hey', 'heyaux']) def test_mixing_primary_and_aux_methods(self): try: class Service(ServiceBase): @srpc(String, _returns=String, _aux=ThreadAuxProc()) def call(s): pass @srpc(String, _returns=String) def mall(s): pass except Exception: pass else: raise Exception("must fail with 'Exception: you can't mix aux and " "non-aux methods in a single service definition.'") def __run_service(self, service): app = Application([service], 'tns', in_protocol=HttpRpc(), out_protocol=Soap11()) server = WsgiApplication(app) return_string = ''.join(server({ 'QUERY_STRING': '', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', }, start_response, "http://null")) elt = etree.fromstring(''.join(return_string)) print(etree.tostring(elt, pretty_print=True)) return elt, app.interface.nsmap def test_settings_headers_from_user_code(self): class RespHeader(ComplexModel): __namespace__ = 'tns' Elem1 = String # test header in service definition class SomeService(ServiceBase): __out_header__ = RespHeader @rpc() def some_call(ctx): ctx.out_header = RespHeader() ctx.out_header.Elem1 = 'Test1' elt, nsmap = self.__run_service(SomeService) query = '/senv:Envelope/senv:Header/tns:RespHeader/tns:Elem1/text()' assert elt.xpath(query, namespaces=nsmap)[0] == 'Test1' # test header in decorator class SomeService(ServiceBase): @rpc(_out_header=RespHeader) def some_call(ctx): ctx.out_header = RespHeader() ctx.out_header.Elem1 = 'Test1' elt, nsmap = self.__run_service(SomeService) query = '/senv:Envelope/senv:Header/tns:RespHeader/tns:Elem1/text()' assert elt.xpath(query, namespaces=nsmap)[0] == 'Test1' # test no header class SomeService(ServiceBase): @rpc() def some_call(ctx): ctx.out_header = RespHeader() ctx.out_header.Elem1 = 'Test1' elt, nsmap = self.__run_service(SomeService) query = '/senv:Envelope/senv:Header/tns:RespHeader/tns:Elem1/text()' assert len(elt.xpath(query, namespaces=nsmap)) == 0 class TestNativeTypes(unittest.TestCase): def test_native_types(self): for t in NATIVE_MAP: class SomeService(ServiceBase): @rpc(t) def some_call(ctx, arg): pass nt, = SomeService.public_methods['some_call'].in_message._type_info.values() assert issubclass(nt, NATIVE_MAP[t]) def test_native_types_in_arrays(self): for t in NATIVE_MAP: class SomeService(ServiceBase): @rpc(Array(t)) def some_call(ctx, arg): pass nt, = SomeService.public_methods['some_call'].in_message._type_info.values() nt, = nt._type_info.values() assert issubclass(nt, NATIVE_MAP[t]) class TestBodyStyle(unittest.TestCase): def test_soap_bare_empty_output(self): class SomeService(ServiceBase): @rpc(String, _body_style='bare') def some_call(ctx, s): assert s == 'abc' app = Application([SomeService], 'tns', in_protocol=Soap11(), out_protocol=Soap11(cleanup_namespaces=True)) req = """ abc """ server = WsgiApplication(app) resp = etree.fromstring(''.join(server({ 'QUERY_STRING': '', 'PATH_INFO': '/call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', 'wsgi.input': StringIO(req) }, start_response, "http://null"))) print(etree.tostring(resp, pretty_print=True)) assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' assert len(resp[0]) == 1 assert resp[0][0].tag == '{tns}some_call'+ RESPONSE_SUFFIX assert len(resp[0][0]) == 0 def test_soap_bare_empty_input(self): class SomeService(ServiceBase): @rpc(_body_style='bare', _returns=String) def some_call(ctx): return 'abc' app = Application([SomeService], 'tns', in_protocol=Soap11(), out_protocol=Soap11(cleanup_namespaces=True)) req = """ """ server = WsgiApplication(app) resp = etree.fromstring(''.join(server({ 'QUERY_STRING': '', 'PATH_INFO': '/call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', 'wsgi.input': StringIO(req) }, start_response, "http://null"))) print(etree.tostring(resp, pretty_print=True)) assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' assert resp[0][0].tag == '{tns}some_call' + RESPONSE_SUFFIX assert resp[0][0].text == 'abc' def test_implicit_class_conflict(self): class someCallResponse(ComplexModel): __namespace__ = 'tns' s = String class SomeService(ServiceBase): @rpc(someCallResponse, _returns=String) def someCall(ctx, x): return ['abc', 'def'] try: Application([SomeService], 'tns', in_protocol=Soap11(), out_protocol=Soap11(cleanup_namespaces=True)) except ValueError as e: print(e) else: raise Exception("must fail.") def test_soap_bare_wrapped_array_output(self): class SomeService(ServiceBase): @rpc(_body_style='bare', _returns=Array(String)) def some_call(ctx): return ['abc', 'def'] app = Application([SomeService], 'tns', in_protocol=Soap11(), out_protocol=Soap11(cleanup_namespaces=True)) req = """ """ server = WsgiApplication(app) resp = etree.fromstring(''.join(server({ 'QUERY_STRING': '', 'PATH_INFO': '/call', 'REQUEST_METHOD': 'GET', 'wsgi.input': StringIO(req) }, start_response, "http://null"))) print(etree.tostring(resp, pretty_print=True)) assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' assert resp[0][0].tag == '{tns}some_call' + RESPONSE_SUFFIX assert resp[0][0][0].text == 'abc' assert resp[0][0][1].text == 'def' def test_array_iterable(self): class SomeService(ServiceBase): @rpc(Array(Unicode), Iterable(Unicode)) def some_call(ctx, a, b): pass app = Application([SomeService], 'tns', in_protocol=Soap11(), out_protocol=Soap11(cleanup_namespaces=True)) server = WsgiApplication(app) def test_invalid_self_reference(self): try: class SomeService(ServiceBase): @rpc(_returns=SelfReference) def method(ctx): pass except ValueError: pass else: raise Exception("Must fail with: " "'SelfReference can't be used inside @rpc and its ilk'") if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/test_soft_validation.py0000755000175000001440000001443712342627562021644 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 # # # Most of the service tests are performed through the interop tests. # from spyne.server.wsgi import WsgiApplication import unittest from spyne.protocol.soap import Soap11 from spyne.application import Application from spyne.decorator import srpc from spyne.error import ValidationError from spyne.service import ServiceBase from spyne.protocol.http import HttpRpc from spyne.protocol.soap import Soap11 from spyne.interface.wsdl import Wsdl11 from spyne.model.primitive import Integer from spyne.model.primitive import String from spyne.server import ServerBase from spyne import MethodContext from spyne.server.wsgi import WsgiMethodContext Application.transport = 'test' class TestValidationString(unittest.TestCase): def test_min_len(self): StrictType = String(min_len=3) self.assertEquals(StrictType.validate_string(StrictType, 'aaa'), True) self.assertEquals(StrictType.validate_string(StrictType, 'a'), False) def test_max_len(self): StrictType = String(max_len=3) self.assertEquals(StrictType.validate_string(StrictType, 'a'), True) self.assertEquals(StrictType.validate_string(StrictType, 'aaa'), True) self.assertEquals(StrictType.validate_string(StrictType, 'aaaa'), False) def test_pattern(self): StrictType = String(pattern='[a-z]') self.assertEquals(StrictType.validate_string(StrictType, 'a'), True) self.assertEquals(StrictType.validate_string(StrictType, 'a1'), False) self.assertEquals(StrictType.validate_string(StrictType, '1'), False) class TestValidationInteger(unittest.TestCase): def test_lt(self): StrictType = Integer(lt=3) self.assertEquals(StrictType.validate_native(StrictType, 2), True) self.assertEquals(StrictType.validate_native(StrictType, 3), False) def test_le(self): StrictType = Integer(le=3) self.assertEquals(StrictType.validate_native(StrictType, 2), True) self.assertEquals(StrictType.validate_native(StrictType, 3), True) self.assertEquals(StrictType.validate_native(StrictType, 4), False) def test_gt(self): StrictType = Integer(gt=3) self.assertEquals(StrictType.validate_native(StrictType, 4), True) self.assertEquals(StrictType.validate_native(StrictType, 3), False) def test_ge(self): StrictType = Integer(ge=3) self.assertEquals(StrictType.validate_native(StrictType, 3), True) self.assertEquals(StrictType.validate_native(StrictType, 2), False) class TestHttpRpcSoftValidation(unittest.TestCase): def setUp(self): class SomeService(ServiceBase): @srpc(String(pattern='a')) def some_method(s): pass @srpc(String(pattern='a', max_occurs=2)) def some_other_method(s): pass self.application = Application([SomeService], in_protocol=HttpRpc(validator='soft'), out_protocol=Soap11(), name='Service', tns='tns', ) def __get_ctx(self, mn, qs): server = WsgiApplication(self.application) ctx = WsgiMethodContext(server, { 'QUERY_STRING': qs, 'PATH_INFO': '/%s' % mn, 'REQUEST_METHOD': "GET", 'SERVER_NAME': 'localhost', }, 'some-content-type') ctx, = server.generate_contexts(ctx) server.get_in_object(ctx) return ctx def test_http_rpc(self): ctx = self.__get_ctx('some_method', 's=1') self.assertEquals(ctx.in_error.faultcode, 'Client.ValidationError') ctx = self.__get_ctx('some_method', 's=a') self.assertEquals(ctx.in_error, None) ctx = self.__get_ctx('some_other_method', 's=1') self.assertEquals(ctx.in_error.faultcode, 'Client.ValidationError') ctx = self.__get_ctx('some_other_method', 's=1&s=2') self.assertEquals(ctx.in_error.faultcode, 'Client.ValidationError') ctx = self.__get_ctx('some_other_method', 's=1&s=2&s=3') self.assertEquals(ctx.in_error.faultcode, 'Client.ValidationError') ctx = self.__get_ctx('some_other_method', 's=a&s=a&s=a') self.assertEquals(ctx.in_error.faultcode, 'Client.ValidationError') ctx = self.__get_ctx('some_other_method', 's=a&s=a') self.assertEquals(ctx.in_error, None) ctx = self.__get_ctx('some_other_method', 's=a') self.assertEquals(ctx.in_error, None) ctx = self.__get_ctx('some_other_method', '') self.assertEquals(ctx.in_error, None) class TestSoap11SoftValidation(unittest.TestCase): def test_basic(self): class SomeService(ServiceBase): @srpc(String(pattern='a')) def some_method(s): pass application = Application([SomeService], in_protocol=Soap11(validator='soft'), out_protocol=Soap11(), name='Service', tns='tns', ) server = ServerBase(application) ctx = MethodContext(server) ctx.in_string = [u""" OK """] ctx, = server.generate_contexts(ctx) server.get_in_object(ctx) self.assertEquals(isinstance(ctx.in_error, ValidationError), True) if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/test_sqlalchemy.py0000755000175000001440000006165012345433230020606 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 logging.basicConfig(level=logging.DEBUG) import unittest import sqlalchemy from sqlalchemy import create_engine from sqlalchemy import MetaData from sqlalchemy import Column from sqlalchemy import Table from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import mapper from sqlalchemy.orm import sessionmaker from spyne.model import XmlAttribute, File from spyne.model import XmlData from spyne.model import ComplexModel from spyne.model import Array from spyne.model import Integer32 from spyne.model import Unicode from spyne.model import Integer from spyne.model import Enum from spyne.model import TTableModel from spyne.model.binary import HybridFileStore from spyne.model.complex import xml from spyne.model.complex import table TableModel = TTableModel() class TestSqlAlchemySchema(unittest.TestCase): def setUp(self): logging.getLogger('sqlalchemy').setLevel(logging.DEBUG) self.engine = create_engine('sqlite:///:memory:') self.session = sessionmaker(bind=self.engine)() self.metadata = TableModel.Attributes.sqla_metadata = MetaData() self.metadata.bind = self.engine def test_schema(self): class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True, autoincrement=False) s = Unicode(64, unique=True) i = Integer32(64, index=True) t = SomeClass.__table__ self.metadata.create_all() # not needed, just nice to see. assert t.c.id.primary_key == True assert t.c.id.autoincrement == False indexes = list(t.indexes) indexes.sort(key=lambda idx: idx.columns) for idx in indexes: assert 'i' in idx.columns or 's' in idx.columns if 's' in idx.columns: assert idx.unique def test_nested_sql(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = ( {"sqlite_autoincrement": True}, ) id = Integer32(primary_key=True) o = SomeOtherClass.customize(store_as='table') self.metadata.create_all() soc = SomeOtherClass(s='ehe') sc = SomeClass(o=soc) self.session.add(sc) self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(1) print(sc_db) assert sc_db.o.s == 'ehe' assert sc_db.o_id == 1 sc_db.o = None self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(1) assert sc_db.o == None assert sc_db.o_id == None def test_nested_sql_array_as_table(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as='table') self.metadata.create_all() soc1 = SomeOtherClass(s='ehe1') soc2 = SomeOtherClass(s='ehe2') sc = SomeClass(others=[soc1, soc2]) self.session.add(sc) self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(1) assert sc_db.others[0].s == 'ehe1' assert sc_db.others[1].s == 'ehe2' self.session.close() def test_nested_sql_array_as_multi_table(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as=table(multi=True)) self.metadata.create_all() soc1 = SomeOtherClass(s='ehe1') soc2 = SomeOtherClass(s='ehe2') sc = SomeClass(others=[soc1, soc2]) self.session.add(sc) self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(1) assert sc_db.others[0].s == 'ehe1' assert sc_db.others[1].s == 'ehe2' self.session.close() def test_nested_sql_array_as_multi_table_with_backref(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as=table(multi=True, backref='some_classes')) self.metadata.create_all() soc1 = SomeOtherClass(s='ehe1') soc2 = SomeOtherClass(s='ehe2') sc = SomeClass(others=[soc1, soc2]) self.session.add(sc) self.session.commit() self.session.close() soc_db = self.session.query(SomeOtherClass).all() assert soc_db[0].some_classes[0].id == 1 assert soc_db[1].some_classes[0].id == 1 self.session.close() def test_nested_sql_array_as_xml(self): class SomeOtherClass(ComplexModel): id = Integer32 s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as='xml') self.metadata.create_all() soc1 = SomeOtherClass(s='ehe1') soc2 = SomeOtherClass(s='ehe2') sc = SomeClass(others=[soc1, soc2]) self.session.add(sc) self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(1) assert sc_db.others[0].s == 'ehe1' assert sc_db.others[1].s == 'ehe2' self.session.close() def test_nested_sql_array_as_xml_no_ns(self): class SomeOtherClass(ComplexModel): id = Integer32 s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as=xml(no_ns=True)) self.metadata.create_all() soc1 = SomeOtherClass(s='ehe1') soc2 = SomeOtherClass(s='ehe2') sc = SomeClass(others=[soc1, soc2]) self.session.add(sc) self.session.commit() self.session.close() sc_xml = self.session.connection().execute("select others from some_class") \ .fetchall()[0][0] from lxml import etree assert etree.fromstring(sc_xml).tag == 'SomeOtherClassArray' self.session.close() def test_inheritance(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(SomeOtherClass): numbers = Array(Integer32).store_as(xml(no_ns=True, root_tag='a')) self.metadata.create_all() sc = SomeClass(id=5, s='s', numbers=[1,2,3,4]) self.session.add(sc) self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(5) assert sc_db.numbers == [1, 2, 3, 4] self.session.close() sc_db = self.session.query(SomeOtherClass).get(5) assert sc_db.id == 5 try: sc_db.numbers except AttributeError: pass else: raise Exception("must fail") self.session.close() def test_sqlalchemy_inheritance(self): # no spyne code is involved here. # this is just to test test the sqlalchemy behavior that we rely on. class Employee(object): def __init__(self, name): self.name = name def __repr__(self): return self.__class__.__name__ + " " + self.name class Manager(Employee): def __init__(self, name, manager_data): self.name = name self.manager_data = manager_data def __repr__(self): return ( self.__class__.__name__ + " " + self.name + " " + self.manager_data ) class Engineer(Employee): def __init__(self, name, engineer_info): self.name = name self.engineer_info = engineer_info def __repr__(self): return ( self.__class__.__name__ + " " + self.name + " " + self.engineer_info ) employees_table = Table('employees', self.metadata, Column('employee_id', sqlalchemy.Integer, primary_key=True), Column('name', sqlalchemy.String(50)), Column('manager_data', sqlalchemy.String(50)), Column('engineer_info', sqlalchemy.String(50)), Column('type', sqlalchemy.String(20), nullable=False) ) employee_mapper = mapper(Employee, employees_table, polymorphic_on=employees_table.c.type, polymorphic_identity='employee') manager_mapper = mapper(Manager, inherits=employee_mapper, polymorphic_identity='manager') engineer_mapper = mapper(Engineer, inherits=employee_mapper, polymorphic_identity='engineer') self.metadata.create_all() manager = Manager('name', 'data') self.session.add(manager) self.session.commit() self.session.close() assert self.session.query(Employee).with_polymorphic('*').filter_by(employee_id=1).one().type == 'manager' def test_inheritance_polymorphic_with_non_nullables_in_subclasses(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} # this is sqlite-specific __mapper_args__ = ( (), {'polymorphic_on': 't', 'polymorphic_identity': 1}, ) id = Integer32(primary_key=True) t = Integer32(nillable=False) s = Unicode(64, nillable=False) class SomeClass(SomeOtherClass): __mapper_args__ = ( (), {'polymorphic_identity': 2}, ) i = Integer(nillable=False) self.metadata.create_all() assert SomeOtherClass.__table__.c.s.nullable == False # this should be nullable to let other classes be added. # spyne still checks this constraint when doing input validation. # spyne should generate a constraint to check this at database level as # well. assert SomeOtherClass.__table__.c.i.nullable == True soc = SomeOtherClass(s='s') self.session.add(soc) self.session.commit() soc_id = soc.id try: sc = SomeClass(i=5) self.session.add(sc) self.session.commit() except IntegrityError: self.session.rollback() else: raise Exception("Must fail with IntegrityError.") sc2 = SomeClass(s='s') # this won't fail. should it? self.session.add(sc2) self.session.commit() self.session.expunge_all() assert self.session.query(SomeOtherClass).with_polymorphic('*').filter_by(id=soc_id).one().t == 1 self.session.close() def test_inheritance_polymorphic(self): class SomeOtherClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} # this is sqlite-specific __mapper_args__ = {'polymorphic_on': 't', 'polymorphic_identity': 1} id = Integer32(primary_key=True) s = Unicode(64) t = Integer32(nillable=False) class SomeClass(SomeOtherClass): __mapper_args__ = {'polymorphic_identity': 2} numbers = Array(Integer32).store_as(xml(no_ns=True, root_tag='a')) self.metadata.create_all() sc = SomeClass(id=5, s='s', numbers=[1,2,3,4]) self.session.add(sc) self.session.commit() self.session.close() assert self.session.query(SomeOtherClass).with_polymorphic('*').filter_by(id=5).one().t == 2 self.session.close() def test_nested_sql_array_as_json(self): class SomeOtherClass(ComplexModel): id = Integer32 s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as='json') self.metadata.create_all() soc1 = SomeOtherClass(s='ehe1') soc2 = SomeOtherClass(s='ehe2') sc = SomeClass(others=[soc1, soc2]) self.session.add(sc) self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(1) assert sc_db.others[0].s == 'ehe1' assert sc_db.others[1].s == 'ehe2' self.session.close() def test_modifiers(self): class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} i = XmlAttribute(Integer32(pk=True)) s = XmlData(Unicode(64)) self.metadata.create_all() self.session.add(SomeClass(s='s')) self.session.commit() self.session.expunge_all() ret = self.session.query(SomeClass).get(1) assert ret.i == 1 # redundant assert ret.s == 's' def test_default_ctor(self): class SomeOtherClass(ComplexModel): id = Integer32 s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as='json') f = Unicode(32, default='uuu') self.metadata.create_all() self.session.add(SomeClass()) self.session.commit() self.session.expunge_all() assert self.session.query(SomeClass).get(1).f == 'uuu' def test_default_ctor_with_sql_relationship(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = ( {"sqlite_autoincrement": True}, ) id = Integer32(primary_key=True) o = SomeOtherClass.customize(store_as='table') self.metadata.create_all() self.session.add(SomeClass()) self.session.commit() def test_store_as_index(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = ( {"sqlite_autoincrement": True}, ) id = Integer32(primary_key=True) o = SomeOtherClass.customize(store_as='table', index='btree') self.metadata.create_all() idx, = SomeClass.__table__.indexes assert 'o_id' in idx.columns def test_scalar_collection(self): class SomeClass(TableModel): __tablename__ = 'some_class' id = Integer32(primary_key=True) values = Array(Unicode).store_as('table') self.metadata.create_all() self.session.add(SomeClass(id=1, values=['a', 'b', 'c'])) self.session.commit() sc = self.session.query(SomeClass).get(1) assert sc.values == ['a', 'b', 'c'] del sc sc = self.session.query(SomeClass).get(1) sc.values.append('d') self.session.commit() del sc sc = self.session.query(SomeClass).get(1) assert sc.values == ['a', 'b', 'c', 'd'] sc = self.session.query(SomeClass).get(1) sc.values = sc.values[1:] self.session.commit() del sc sc = self.session.query(SomeClass).get(1) assert sc.values == ['b', 'c', 'd'] def test_multiple_fk(self): class SomeChildClass(TableModel): __tablename__ = 'some_child_class' id = Integer32(primary_key=True) s = Unicode(64) i = Integer32 class SomeClass(TableModel): __tablename__ = 'some_class' id = Integer32(primary_key=True) children = Array(SomeChildClass).store_as('table') mirror = SomeChildClass.store_as('table') self.metadata.create_all() children = [ SomeChildClass(s='p', i=600), SomeChildClass(s='|', i=10), SomeChildClass(s='q', i=9), ] sc = SomeClass(children=children) self.session.add(sc) self.session.flush() sc.mirror = children[1] self.session.commit() del sc sc = self.session.query(SomeClass).get(1) assert ''.join([scc.s for scc in sc.children]) == 'p|q' assert sum([scc.i for scc in sc.children]) == 619 def test_reflection(self): class SomeChildClass(TableModel): __tablename__ = 'some_child_class' id = Integer32(primary_key=True) s = Unicode(64) i = Integer32 class SomeClass(TableModel): __tablename__ = 'some_class' id = Integer32(primary_key=True) children = Array(SomeChildClass).store_as('xml') mirror = SomeChildClass.store_as('json') metadata2 = MetaData() metadata2.bind = self.engine metadata2.reflect() def _test_sqlalchemy_remapping(self): class SomeTable(TableModel): __tablename__ = 'some_table' id = Integer32(pk=True) i = Integer32 s = Unicode(32) class SomeTableSubset(TableModel): __table__ = SomeTable.__table__ id = Integer32(pk=True) # sqla session doesn't work without pk i = Integer32 class SomeTableOtherSubset(TableModel): __table__ = SomeTable.__table__ _type_info = [(k,v) for k, v in SomeTable._type_info.items() if k in ('id', 's')] self.session.add(SomeTable(id=1,i=2,s='s')) self.session.commit() st = self.session.query(SomeTableSubset).get(1) sts = self.session.query(SomeTableOtherSubset).get(1) stos = self.session.query(SomeTableSubset).get(1) sts.i = 3 sts.s = 'ss' # will not be flushed to db self.session.commit() assert st.s == 's' assert stos.i == 3 def test_file_storage(self): class C(TableModel): __tablename__ = "c" id = Integer32(pk=True) f = File(store_as=HybridFileStore('store', 'json')) self.metadata.create_all() c = C(f=File.Value(name="name", type="type", data=["data"])) self.session.add(c) self.session.flush() self.session.commit() c = self.session.query(C).get(1) print(c) assert c.f.name == "name" assert c.f.type == "type" assert str(c.f.data[0][:]) == "data" def test_add_field_complex_existing_column(self): class C(TableModel): __tablename__ = "c" u = Unicode(pk=True) class D(TableModel): __tablename__ = "d" d = Integer32(pk=True) c = C.store_as('table') C.append_field('d', D.store_as('table')) assert C.Attributes.sqla_mapper.get_property('d').argument is D def _test_add_field_complex_explicit_existing_column(self): class C(TableModel): __tablename__ = "c" id = Integer32(pk=True) # c already also produces c_id. this is undefined behaviour, one of them # gets ignored, whichever comes first. class D(TableModel): __tablename__ = "d" id = Integer32(pk=True) c = C.store_as('table') c_id = Integer32(15) def test_add_field_complex_circular_array(self): class C(TableModel): __tablename__ = "cc" id = Integer32(pk=True) class D(TableModel): __tablename__ = "dd" id = Integer32(pk=True) c = Array(C).customize(store_as=table(right='dd_id')) C.append_field('d', D.customize(store_as=table(left='dd_id'))) self.metadata.create_all() c1, c2 = C(id=1), C(id=2) d = D(id=1, c=[c1,c2]) self.session.add(d) self.session.commit() assert c1.d.id == 1 def test_add_field_complex_new_column(self): class C(TableModel): __tablename__ = "c" u = Unicode(pk=True) class D(TableModel): __tablename__ = "d" id = Integer32(pk=True) C.append_field('d', D.store_as('table')) assert C.Attributes.sqla_mapper.get_property('d').argument is D assert isinstance(C.Attributes.sqla_table.c['d_id'].type, sqlalchemy.Integer) def test_add_field_array(self): class C(TableModel): __tablename__ = "c" id = Integer32(pk=True) class D(TableModel): __tablename__ = "d" id = Integer32(pk=True) C.append_field('d', Array(D).store_as('table')) assert C.Attributes.sqla_mapper.get_property('d').argument is D print(repr(D.Attributes.sqla_table)) assert isinstance(D.Attributes.sqla_table.c['c_id'].type, sqlalchemy.Integer) def test_add_field_array_many(self): class C(TableModel): __tablename__ = "c" id = Integer32(pk=True) class D(TableModel): __tablename__ = "d" id = Integer32(pk=True) C.append_field('d', Array(D).store_as(table(multi='c_d'))) assert C.Attributes.sqla_mapper.get_property('d').argument is D rel_table = C.Attributes.sqla_metadata.tables['c_d'] assert 'c_id' in rel_table.c assert 'd_id' in rel_table.c def test_add_field_complex_cust(self): class C(TableModel): __tablename__ = "c" id = Integer32(pk=True) class D(TableModel): __tablename__ = "d" id = Integer32(pk=True) c = Array(C).store_as('table') C.append_field('d', D.customize( nullable=False, store_as=table(left='d_id'), )) assert C.__table__.c['d_id'].nullable == False class TestSqlAlchemySchemaWithPostgresql(unittest.TestCase): def setUp(self): self.metadata = TableModel.Attributes.sqla_metadata = MetaData() def test_enum(self): table_name = "test_enum" enums = ('SUBSCRIBED', 'UNSUBSCRIBED', 'UNCONFIRMED') class SomeClass(TableModel): __tablename__ = table_name id = Integer32(primary_key=True) e = Enum(*enums, type_name='status_choices') t = self.metadata.tables[table_name] assert 'e' in t.c assert t.c.e.type.enums == enums if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/test_sqlalchemy_deprecated.py0000755000175000001440000002760212345433230022765 0ustar plqusers00000000000000#!/usr/bin/env python # # 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 logging.basicConfig(level=logging.DEBUG) import unittest import sqlalchemy from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm.mapper import Mapper from sqlalchemy import create_engine from sqlalchemy import MetaData from sqlalchemy import Column from sqlalchemy import Table from sqlalchemy import ForeignKey from sqlalchemy.orm import mapper from sqlalchemy.orm import relationship from sqlalchemy.orm import sessionmaker from sqlalchemy.schema import UniqueConstraint from spyne.application import Application from spyne.decorator import rpc from spyne.model import ComplexModel from spyne.model import Array from spyne.model import Unicode from spyne.model import Integer from spyne.model.table import TableModel from spyne.protocol.http import HttpRpc from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication from spyne.server.wsgi import WsgiMethodContext # # Deprecated Table Model Tests # class TestSqlAlchemy(unittest.TestCase): def setUp(self): self.metadata = MetaData() self.DeclarativeBase = declarative_base(metadata=self.metadata) self.engine = create_engine('sqlite:///:memory:', echo=True) self.Session = sessionmaker(bind=self.engine) def tearDown(self): del self.metadata del self.DeclarativeBase del self.engine del self.Session def test_declarative(self): from sqlalchemy import Integer from sqlalchemy import String class DbObject(TableModel, self.DeclarativeBase): __tablename__ = 'db_object' id = Column(Integer, primary_key=True) s = Column(String) self.metadata.create_all(self.engine) def test_mapper(self): import sqlalchemy class Address(self.DeclarativeBase): __tablename__ = 'address' id = Column(sqlalchemy.Integer, primary_key=True) email = Column(sqlalchemy.String(50)) user_id = Column(sqlalchemy.Integer, ForeignKey('user.id')) class User(self.DeclarativeBase): __tablename__ = 'user' id = Column(sqlalchemy.Integer, primary_key=True) name = Column(sqlalchemy.String(50)) addresses = relationship("Address", backref="user") self.metadata.create_all(self.engine) import spyne.model.primitive class AddressDetail(ComplexModel): id = spyne.model.primitive.Integer user_name = spyne.model.primitive.String address = spyne.model.primitive.String @classmethod def mapper(cls, meta): user_t = meta.tables['user'] address_t = meta.tables['address'] cls._main_t = user_t.join(address_t) cls._properties = { 'id': address_t.c.id, 'user_name': user_t.c.name, 'address': address_t.c.email, } cls._mapper = mapper(cls, cls._main_t, include_properties=cls._properties.values(), properties=cls._properties, primary_key=[address_t.c.id] ) AddressDetail.mapper(self.metadata) def test_custom_mapper(self): class CustomMapper(Mapper): def __init__(self, class_, local_table, *args, **kwargs): super(CustomMapper, self).__init__(class_, local_table, *args, **kwargs) # Do not configure primary keys to check that CustomerMapper is # actually used def _configure_pks(self): pass def custom_mapper(class_, local_table=None, *args, **params): return CustomMapper(class_, local_table, *args, **params) CustomDeclarativeBase = declarative_base(metadata=self.metadata, mapper=custom_mapper) class User(CustomDeclarativeBase): __tablename__ = 'user' # CustomMapper should not fail because of no primary key name = Column(sqlalchemy.String(50)) self.metadata.create_all(self.engine) def test_rpc(self): import sqlalchemy from sqlalchemy import sql class KeyValuePair(TableModel, self.DeclarativeBase): __tablename__ = 'key_value_store' __namespace__ = 'punk' key = Column(sqlalchemy.String(100), nullable=False, primary_key=True) value = Column(sqlalchemy.String, nullable=False) self.metadata.create_all(self.engine) import hashlib session = self.Session() for i in range(1, 10): key = str(i) m = hashlib.md5() m.update(key) value = m.hexdigest() session.add(KeyValuePair(key=key, value=value)) session.commit() from spyne.service import ServiceBase from spyne.model.complex import Array from spyne.model.primitive import String class Service(ServiceBase): @rpc(String(max_occurs='unbounded'), _returns=Array(KeyValuePair), _in_variable_names={ 'keys': 'key' } ) def get_values(ctx, keys): session = self.Session() return session.query(KeyValuePair).filter(sql.and_( KeyValuePair.key.in_(keys) )).order_by(KeyValuePair.key) application = Application([Service], in_protocol=HttpRpc(), out_protocol=Soap11(), name='Service', tns='tns' ) server = WsgiApplication(application) initial_ctx = WsgiMethodContext(server, { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'key=1&key=2&key=3', 'PATH_INFO': '/get_values', 'SERVER_NAME': 'localhost', }, 'some-content-type') ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) i = 0 for e in ctx.out_document[0][0][0]: i+=1 key = str(i) m = hashlib.md5() m.update(key) value = m.hexdigest() _key = e.find('{%s}key' % KeyValuePair.get_namespace()) _value = e.find('{%s}value' % KeyValuePair.get_namespace()) print((_key, _key.text)) print((_value, _value.text)) self.assertEquals(_key.text, key) self.assertEquals(_value.text, value) def test_late_mapping(self): import sqlalchemy user_t = Table('user', self.metadata, Column('id', sqlalchemy.Integer, primary_key=True), Column('name', sqlalchemy.String), ) class User(TableModel, self.DeclarativeBase): __table__ = user_t self.assertEquals(User._type_info['id'].__type_name__, 'integer') self.assertEquals(User._type_info['name'].__type_name__, 'string') def test_default_ctor(self): import sqlalchemy class User1Mixin(object): id = Column(sqlalchemy.Integer, primary_key=True) name = Column(sqlalchemy.String(256)) class User1(self.DeclarativeBase, TableModel, User1Mixin): __tablename__ = 'spyne_user1' mail = Column(sqlalchemy.String(256)) u = User1(id=1, mail="a@b.com", name='dummy') assert u.id == 1 assert u.mail == "a@b.com" assert u.name == "dummy" class User2Mixin(object): id = Column(sqlalchemy.Integer, primary_key=True) name = Column(sqlalchemy.String(256)) class User2(TableModel, self.DeclarativeBase, User2Mixin): __tablename__ = 'spyne_user2' mail = Column(sqlalchemy.String(256)) u = User2(id=1, mail="a@b.com", name='dummy') assert u.id == 1 assert u.mail == "a@b.com" assert u.name == "dummy" def test_mixin_inheritance(self): import sqlalchemy class UserMixin(object): id = Column(sqlalchemy.Integer, primary_key=True) name = Column(sqlalchemy.String(256)) class User(self.DeclarativeBase, TableModel, UserMixin): __tablename__ = 'spyne_user_mixin' mail = Column(sqlalchemy.String(256)) assert 'mail' in User._type_info assert 'name' in User._type_info assert 'id' in User._type_info def test_same_table_inheritance(self): import sqlalchemy class User(self.DeclarativeBase, TableModel): __tablename__ = 'spyne_user_sti' id = Column(sqlalchemy.Integer, primary_key=True) name = Column(sqlalchemy.String(256)) class UserMail(User): mail = Column(sqlalchemy.String(256)) assert 'mail' in UserMail._type_info assert 'name' in UserMail._type_info assert 'id' in UserMail._type_info def test_relationship_array(self): import sqlalchemy class Permission(TableModel, self.DeclarativeBase): __tablename__ = 'spyne_user_permission' id = Column(sqlalchemy.Integer, primary_key=True) user_id = Column(sqlalchemy.Integer, ForeignKey("spyne_user.id")) class User(TableModel, self.DeclarativeBase): __tablename__ = 'spyne_user' id = Column(sqlalchemy.Integer, primary_key=True) permissions = relationship(Permission) class Address(self.DeclarativeBase, TableModel): __tablename__ = 'spyne_address' id = Column(sqlalchemy.Integer, primary_key=True) address = Column(sqlalchemy.String(256)) user_id = Column(sqlalchemy.Integer, ForeignKey(User.id), nullable=False) user = relationship(User) assert 'permissions' in User._type_info assert issubclass(User._type_info['permissions'], Array) assert issubclass(User._type_info['permissions']._type_info.values()[0], Permission) #Address().user = None #User().permissions = None # This fails, and actually is supposed to fail. class TestSpyne2Sqlalchemy(unittest.TestCase): def test_table(self): class SomeClass(ComplexModel): __metadata__ = MetaData() __tablename__ = 'some_class' i = Integer(primary_key=True) t = SomeClass.Attributes.sqla_table assert t.c['i'].type.__class__ is sqlalchemy.DECIMAL def test_table_args(self): class SomeClass(ComplexModel): __metadata__ = MetaData() __tablename__ = 'some_class' __table_args__ = ( UniqueConstraint('j'), ) i = Integer(primary_key=True) j = Unicode(64) t = SomeClass.Attributes.sqla_table assert isinstance(t.c['j'].type, sqlalchemy.Unicode) for c in t.constraints: if isinstance(c, UniqueConstraint): assert list(c.columns) == [t.c.j] break else: raise Exception("UniqueConstraint is missing.") if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/test/test_util.py0000755000175000001440000002465512345433230017425 0ustar plqusers00000000000000#!/usr/bin/env python import unittest import pytz import decimal from pprint import pprint from decimal import Decimal as D from datetime import datetime from lxml import etree from spyne.const import MAX_STRING_FIELD_LENGTH from spyne.decorator import srpc from spyne.application import Application from spyne.model.complex import XmlAttribute from spyne.model.complex import ComplexModel from spyne.model.complex import Iterable from spyne.model.complex import Array from spyne.model.primitive import Decimal from spyne.model.primitive import DateTime from spyne.model.primitive import Integer from spyne.model.primitive import Unicode from spyne.service import ServiceBase from spyne.util import AttrDict, AttrDictColl from spyne.util.protocol import deserialize_request_string from spyne.util.dictdoc import get_dict_as_object, get_object_as_yaml, \ get_object_as_json from spyne.util.dictdoc import get_object_as_dict from spyne.util.xml import get_object_as_xml from spyne.util.xml import get_xml_as_object from spyne.util.xml import get_schema_documents from spyne.util.xml import get_validation_schema class TestXml(unittest.TestCase): def test_serialize(self): class C(ComplexModel): __namespace__ = "tns" i = Integer s = Unicode c = C(i=5, s="x") ret = get_object_as_xml(c, C) print(etree.tostring(ret)) assert ret.tag == "{tns}C" ret = get_object_as_xml(c, C, "X") print(etree.tostring(ret)) assert ret.tag == "{tns}X" ret = get_object_as_xml(c, C, "X", no_namespace=True) print(etree.tostring(ret)) assert ret.tag == "X" ret = get_object_as_xml(c, C, no_namespace=True) print(etree.tostring(ret)) assert ret.tag == "C" def test_deserialize(self): class Punk(ComplexModel): __namespace__ = 'some_namespace' a = Unicode b = Integer c = Decimal d = DateTime class Foo(ComplexModel): __namespace__ = 'some_other_namespace' a = Unicode b = Integer c = Decimal d = DateTime e = XmlAttribute(Integer) def __eq__(self, other): # remember that this is a test object assert ( self.a == other.a and self.b == other.b and self.c == other.c and self.d == other.d and self.e == other.e ) return True docs = get_schema_documents([Punk, Foo]) pprint(docs) assert docs['s0'].tag == '{http://www.w3.org/2001/XMLSchema}schema' assert docs['tns'].tag == '{http://www.w3.org/2001/XMLSchema}schema' print() print("the other namespace %r:" % docs['tns'].attrib['targetNamespace']) assert docs['tns'].attrib['targetNamespace'] == 'some_namespace' print(etree.tostring(docs['tns'], pretty_print=True)) print() print("the other namespace %r:" % docs['s0'].attrib['targetNamespace']) assert docs['s0'].attrib['targetNamespace'] == 'some_other_namespace' print(etree.tostring(docs['s0'], pretty_print=True)) print() foo = Foo(a=u'a', b=1, c=decimal.Decimal('3.4'), d=datetime(2011,2,20,tzinfo=pytz.utc), e=5) doc = get_object_as_xml(foo, Foo) print(etree.tostring(doc, pretty_print=True)) foo_back = get_xml_as_object(doc, Foo) assert foo_back == foo # as long as it doesn't fail, it's ok. get_validation_schema([Punk, Foo]) class TestCDict(unittest.TestCase): def test_cdict(self): from spyne.util.cdict import cdict class A(object): pass class B(A): pass class E(B): pass class F(E): pass class C(object): pass class D: pass d = cdict({A: "fun", object: "base", F: 'zan'}) assert d[A] == 'fun' assert d[B] == 'fun' assert d[C] == 'base' assert d[F] == 'zan' try: d[D] except KeyError: pass else: raise Exception("Must fail.") class TestLogRepr(unittest.TestCase): def test_log_repr_simple(self): from spyne.model.complex import ComplexModel from spyne.model.primitive import String from spyne.util.web import log_repr class Z(ComplexModel): z=String l = MAX_STRING_FIELD_LENGTH + 100 print(log_repr(Z(z="a" * l))) print("Z(z='%s'(...))" % ('a' * MAX_STRING_FIELD_LENGTH)) assert log_repr(Z(z="a" * l)) == "Z(z='%s'(...))" % \ ('a' * MAX_STRING_FIELD_LENGTH) assert log_repr(['a','b','c'], Array(String)) == "['a', 'b', 'c']" def test_log_repr_complex(self): from spyne.model import ByteArray from spyne.model import File from spyne.model.complex import ComplexModel from spyne.model.primitive import String from spyne.util.web import log_repr class Z(ComplexModel): _type_info = [ ('f', File(logged=False)), ('t', ByteArray(logged=False)), ('z', Array(String)), ] l = MAX_STRING_FIELD_LENGTH + 100 val = Z(z=["abc"] * l, t=['t'], f=File.Value(name='aaa', data=['t'])) print(repr(val)) print assert log_repr(val) == "Z(z=['abc', 'abc', 'abc', 'abc', (...)])" class TestDeserialize(unittest.TestCase): def test_deserialize(self): from spyne.protocol.soap import Soap11 class SomeService(ServiceBase): @srpc(Integer, _returns=Iterable(Integer)) def some_call(yo): return range(yo) app = Application([SomeService], 'tns', in_protocol=Soap11(), out_protocol=Soap11()) meat = 30 string = """ %s """ % meat obj = deserialize_request_string(string, app) assert obj.yo == meat class TestEtreeDict(unittest.TestCase): longMessage = True def test_simple(self): from lxml.etree import tostring from spyne.util.etreeconv import root_dict_to_etree assert tostring(root_dict_to_etree({'a':{'b':'c'}})) == 'c' def test_not_sized(self): from lxml.etree import tostring from spyne.util.etreeconv import root_dict_to_etree complex_value = root_dict_to_etree({'a':{'b':1}}) self.assertEqual(tostring(complex_value), '1', "The integer should be properly rendered in the etree") complex_none = root_dict_to_etree({'a':{'b':None}}) self.assertEqual(tostring(complex_none), '', "None should not be rendered in the etree") simple_value = root_dict_to_etree({'a': 1}) self.assertEqual(tostring(simple_value), '1', "The integer should be properly rendered in the etree") none_value = root_dict_to_etree({'a': None}) self.assertEqual(tostring(none_value), '', "None should not be rendered in the etree") string_value = root_dict_to_etree({'a': 'lol'}) self.assertEqual(tostring(string_value), 'lol', "A string should be rendered as a string") complex_string_value = root_dict_to_etree({'a': {'b': 'lol'}}) self.assertEqual(tostring(complex_string_value), 'lol', "A string should be rendered as a string") class TestDictDoc(unittest.TestCase): def test_the(self): class C(ComplexModel): __namespace__ = "tns" i = Integer s = Unicode a = Array(DateTime) def __eq__(self, other): print("Yaaay!") return self.i == other.i and \ self.s == other.s and \ self.a == other.a c = C(i=5, s="x", a=[datetime(2011,12,22, tzinfo=pytz.utc)]) for iw, ca in ((False,dict), (True,dict), (False,list), (True, list)): print() print('complex_as:', ca) d = get_object_as_dict(c, C, complex_as=ca) print(d) o = get_dict_as_object(d, C, complex_as=ca) print(o) print(c) assert o == c class TestAttrDict(unittest.TestCase): def test_attr_dict(self): assert AttrDict(a=1)['a'] == 1 def test_attr_dict_coll(self): assert AttrDictColl('SomeDict').SomeDict.NAME == 'SomeDict' assert AttrDictColl('SomeDict').SomeDict(a=1)['a'] == 1 assert AttrDictColl('SomeDict').SomeDict(a=1).NAME == 'SomeDict' class TestYaml(unittest.TestCase): def test_deser(self): class C(ComplexModel): a = Unicode b = Decimal ret = get_object_as_yaml(C(a='burak', b=D(30)), C) assert ret == """C: a: burak b: '30' """ class TestJson(unittest.TestCase): def test_deser(self): class C(ComplexModel): a = Unicode b = Decimal ret = get_object_as_json(C(a='burak', b=D(30)), C) assert ret == '["burak", "30"]' ret = get_object_as_json(C(a='burak', b=D(30)), C, complex_as=dict) assert ret == '{"a": "burak", "b": "30"}' class TestFifo(unittest.TestCase): def test_msgpack_fifo(self): import msgpack v1 = [1, 2, 3, 4] v2 = [5, 6, 7, 8] v3 = {"a": 9, "b": 10, "c": 11} s1 = msgpack.packb(v1) s2 = msgpack.packb(v2) s3 = msgpack.packb(v3) unpacker = msgpack.Unpacker() unpacker.feed(s1) unpacker.feed(s2) unpacker.feed(s3[:4]) assert iter(unpacker).next() == v1 assert iter(unpacker).next() == v2 try: iter(unpacker).next() except StopIteration: pass else: raise Exception("must fail") unpacker.feed(s3[4:]) assert iter(unpacker).next() == v3 if __name__ == '__main__': unittest.main() spyne-2.11.0/spyne/util/0000755000175000001440000000000012352131452015015 5ustar plqusers00000000000000spyne-2.11.0/spyne/util/oset/0000755000175000001440000000000012352131452015767 5ustar plqusers00000000000000spyne-2.11.0/spyne/util/oset/__init__.py0000644000175000001440000000041412342627562020112 0ustar plqusers00000000000000 """The ``spyne.util.oset`` package contains implementation of an ordered set that works on Python versions 2.4 through 2.7. """ try: from spyne.util.oset.new import oset except (SyntaxError, ImportError, AttributeError): from spyne.util.oset.old import oset spyne-2.11.0/spyne/util/oset/new.py0000644000175000001440000000421112342627562017143 0ustar plqusers00000000000000# http://code.activestate.com/recipes/576694/ import collections KEY, PREV, NEXT = list(range(3)) """This module contains an ordered set implementation from http://code.activestate.com/recipes/576694/ """ class oset(collections.MutableSet): """An ordered set implementation.""" def __init__(self, iterable=None): self.end = end = [] end += [None, end, end] # sentinel node for doubly linked list self.map = {} # key --> [key, prev, next] if iterable is not None: self |= iterable def __len__(self): return len(self.map) def __contains__(self, key): return key in self.map def add(self, key): if key not in self.map: end = self.end curr = end[PREV] curr[NEXT] = end[PREV] = self.map[key] = [key, curr, end] def discard(self, key): if key in self.map: key, prev, next = self.map.pop(key) prev[NEXT] = next next[PREV] = prev def __iter__(self): end = self.end curr = end[NEXT] while curr is not end: yield curr[KEY] curr = curr[NEXT] def __reversed__(self): end = self.end curr = end[PREV] while curr is not end: yield curr[KEY] curr = curr[PREV] def pop(self, last=True): if not self: raise KeyError('set is empty') key = next(reversed(self)) if last else next(iter(self)) self.discard(key) return key def __repr__(self): if not self: return '%s()' % (self.__class__.__name__,) return '%s(%r)' % (self.__class__.__name__, list(self)) def __eq__(self, other): if isinstance(other, OrderedSet): return len(self) == len(other) and list(self) == list(other) return set(self) == set(other) def __del__(self): self.clear() # remove circular references if __name__ == '__main__': print((oset('abracadabra'))) stuff = oset() stuff.add(1) print(stuff) stuff.add(1) print(stuff) print((oset('simsalabim'))) spyne-2.11.0/spyne/util/oset/old.py0000644000175000001440000001441612342627562017140 0ustar plqusers00000000000000#!/usr/bin/env python #http://code.activestate.com/recipes/528878-ordered-set/ import weakref class oset(object): """ A linked-list with a uniqueness constraint and O(1) lookups/removal. Modification during iteration is partially supported. If you remove the just yielded element, it will go on to what was the next element. If you remove the next element, it will use the new next element. If you remove both, you get an error. """ def __init__(self, iterable=(), allow_move=True): self._map = {} self._start = _SentinalNode() self._end = _SentinalNode() self._start.next = self._end self._end.prev = self._start self._allow_move = allow_move self.extend(iterable) def __contains__(self, element): return element in self._map def __eq__(self, other): raise TypeError("OrderedSet does not support comparisons") def __hash__(self): raise TypeError("OrderedSet is not hashable") def __iter__(self): curnode = self._start nextnode = curnode.next while True: if hasattr(curnode, 'next'): curnode = curnode.next elif hasattr(nextnode, 'next'): curnode = nextnode else: raise RuntimeError("OrderedSet modified inappropriately " "during iteration") if isinstance(curnode, _SentinalNode): return nextnode = curnode.next yield curnode.content def __reversed__(self): curnode = self._end prevnode = curnode.prev while True: if hasattr(curnode, 'prev'): curnode = curnode.prev elif hasattr(prevnode, 'prev'): curnode = prevnode else: raise RuntimeError("OrderedSet modified inappropriately " "during iteration") if isinstance(curnode, _SentinalNode): return prevnode = curnode.prev yield curnode.content def __len__(self): return len(self._map) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, list(self)) def add(self, element): """An alias for :func:`spyne.util.oset.old.oset.append`.""" self.append(element) def append(self, element): """Add an element to the right side of the OrderedSet.""" self._insertatnode(self._end.prev, element) def appendleft(self, element): """Add an element to the left side of the OrderedSet.""" self._insertatnode(self._start, element) def clear(self): """Remove all elements from the OrderedSet.""" while self: self.pop() def extend(self, iterable): """Extend the right side of the OrderedSet with elements from the iterable.""" for element in iterable: self.append(element) def extendleft(self, iterable): """Extend the left side of the OrderedSet with elements from the iterable.""" for element in iterable: self.appendleft(element) def insertleft(self, poselement, element): """Inserts element immediately left of poselement's position.""" self._insertatnode(self._map[poselement].prev, element) def insertright(self, poselement, element): """Inserts element immediately right of poselement's position.""" self._insertatnode(self._map[poselement], element) def _insertatnode(self, node, element): left = node right = node.next #start by determining if element exists already. Need to be careful #if node or node.next contains the element to be added existingNode = self._map.get(element) if existingNode: if not self._allow_move: raise ValueError("element already exists") if existingNode == left or existingNode == right: return #nothing to do. NB more than just optimisation #not optimal. element removed from map only to be added again self.remove(element) newnode = _Node() newnode.content = element newnode.prev = right.prev newnode.next = right right.prev = newnode left.next = newnode self._map[element] = newnode def pop(self): """Remove and return the rightmost element.""" element = self._end.prev.content self.remove(element) return element def popleft(self): """Remove and return the leftmost element.""" element = self._start.next.content self.remove(element) return element def remove(self, element): """Remove element from the OrderedSet.""" node = self._map.pop(element) assert not isinstance(node, _SentinalNode) left = node.prev right = node.next left.next = right right.prev = node.prev del node.prev del node.next class _Node(object): __slots__ = '_prev', 'next', 'content', '__weakref__' # A weakref is used for prev so as to avoid creating cycles. def _prev_get(self): return self._prev() def _prev_set(self, value): self._prev = weakref.ref(value) def _prev_del(self): del self._prev prev = property(_prev_get, _prev_set, _prev_del) class _SentinalNode(_Node): __slots__ = [] __test__ = { '__foo__': """ >>> oset(range(10)) oset([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> list(reversed(oset(range(10)))) [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] >>> stuff = oset() >>> stuff.extendleft(range(20, 25)) >>> stuff.pop() 20 >>> stuff oset([24, 23, 22, 21]) >>> stuff.insertleft(23, 99) >>> stuff oset([24, 99, 23, 22, 21]) >>> stuff.remove(21) >>> stuff oset([24, 99, 23, 22]) >>> len(stuff) 4 >>> 23 in stuff True >>> 44 in stuff False >>> oset([1, 2, 3, 2]) oset([1, 3, 2]) >>> oset([1, 2, 3, 2], allow_move=False) Traceback (most recent call last): ... ValueError: element already exists """, } def _test(): import doctest doctest.testmod() if __name__ == '__main__': _test() spyne-2.11.0/spyne/util/__init__.py0000644000175000001440000001613512345433230017135 0ustar plqusers00000000000000 # # 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__) import sys import datetime from collections import deque from inspect import isgeneratorfunction try: from urllib import splittype from urllib import splithost from urllib import quote except ImportError: # Python 3 from urllib.parse import splittype from urllib.parse import splithost from urllib.parse import quote def split_url(url): """Splits a url into (uri_scheme, host[:port], path)""" scheme, remainder = splittype(url) host, path = splithost(remainder) return scheme.lower(), host, path def reconstruct_url(environ, protocol=True, server_name=True, path=True, query_string=True): """Rebuilds the calling url from values found in the environment. This algorithm was found via PEP 333, the wsgi spec and contributed by Ian Bicking. """ url = '' if protocol: url = environ['wsgi.url_scheme'] + '://' if server_name: if environ.get('HTTP_HOST'): url += environ['HTTP_HOST'] else: url += environ['SERVER_NAME'] if environ['wsgi.url_scheme'] == 'https': if environ['SERVER_PORT'] != '443': url += ':' + environ['SERVER_PORT'] else: if environ['SERVER_PORT'] != '80': url += ':' + environ['SERVER_PORT'] if path: if (quote(environ.get('SCRIPT_NAME', '')) == '/' and quote(environ.get('PATH_INFO', ''))[0] == '/'): #skip this if it is only a slash pass elif quote(environ.get('SCRIPT_NAME', ''))[0:2] == '//': url += quote(environ.get('SCRIPT_NAME', ''))[1:] else: url += quote(environ.get('SCRIPT_NAME', '')) url += quote(environ.get('PATH_INFO', '')) if query_string: if environ.get('QUERY_STRING'): url += '?' + environ['QUERY_STRING'] return url def check_pyversion(*minversion): return sys.version_info[:3] >= minversion class Break(Exception): """Raised for breaking out of infinite loops inside coroutines.""" pass def coroutine(func): assert isgeneratorfunction(func) def start(*args, **kwargs): ret = func(*args, **kwargs) try: next(ret) except StopIteration: return None except Exception as e: logger.exception(e) return ret return start class memoize(object): """A memoization decorator that keeps caching until reset.""" def __init__(self, func): self.func = func self.memo = {} def __call__(self, *args, **kwargs): key = self.get_key(args, kwargs) if not key in self.memo: self.memo[key] = self.func(*args, **kwargs) return self.memo[key] def get_key(self, args, kwargs): return tuple(args), tuple(kwargs.items()) def reset(self): self.memo = {} class memoize_id(memoize): """A memoization decorator that keeps caching until reset for unhashable types. It works on id()'s of objects instead.""" def get_key(self, args, kwargs): return tuple([id(a) for a in args]), \ tuple([ (k,id(v)) for k,v in kwargs.items()]) def sanitize_args(a): try: args, kwargs = a if isinstance(args, tuple) and isinstance(kwargs, dict): return args, dict(kwargs) except (TypeError, ValueError): args, kwargs = (), {} if a is not None: if isinstance(a, dict): args = tuple() kwargs = a elif isinstance(a, tuple): if isinstance(a[-1], dict): args, kwargs = a[0:-1], a[-1] else: args = a kwargs = {} return args, kwargs if sys.version > '3': def _bytes_join(val, joiner=''): return bytes(joiner).join(val) else: def _bytes_join(val, joiner=''): return joiner.join(val) if hasattr(datetime.timedelta, 'total_seconds'): total_seconds = datetime.timedelta.total_seconds else: def total_seconds(td): return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 def TAttrDict(default=None): class AttrDict(object): def __init__(self, *args, **kwargs): self.__data = dict(*args, **kwargs) def __call__(self, **kwargs): retval = AttrDict(self.__data.items()) for k,v in kwargs.items(): setattr(retval, k, v) return retval def __setattr__(self, key, value): if key == "_AttrDict__data": return object.__setattr__(self, key, value) if key == 'items': raise ValueError("'items' is part of dict interface") self.__data[key] = value def __setitem__(self, key, value): self.__data[key] = value def __iter__(self): return iter(self.__data) def items(self): return self.__data.items() def get(self, key, *args): return self.__data.get(key, *args) def update(self, d): return self.__data.update(d) def __repr__(self): return "AttrDict(%s)" % ', '.join(['%s=%r' % (k, v) for k,v in sorted(self.__data.items(), key=lambda x:x[0])]) if default is None: def __getattr__(self, key): return self.__data[key] def __getitem__(self, key): return self.__data[key] else: def __getitem__(self, key): if key in self.__data: return self.__data[key] else: return default() def __getattr__(self, key): if key in ("_AttrDict__data", 'items', 'get', 'update'): return object.__getattribute__(self, '__data') if key in self.__data: return self.__data[key] else: return default() return AttrDict AttrDict = TAttrDict() DefaultAttrDict = TAttrDict(lambda: None) class AttrDictColl(object): AttrDictImpl = DefaultAttrDict def __init__(self, *args): for a in args: setattr(self, a, AttrDictColl.AttrDictImpl(NAME=a)) spyne-2.11.0/spyne/util/_twisted_ws.py0000644000175000001440000004403712345433230017733 0ustar plqusers00000000000000# -*- test-case-name: twisted.web.test.test_websockets -*- # Copyright (c) Twisted Matrix Laboratories. # 2011-2012 Oregon State University Open Source Lab # 2011-2012 Corbin Simpson # # See LICENSE for details. """ The WebSockets protocol (RFC 6455), provided as a resource which wraps a factory. """ __all__ = ["WebSocketsResource", "IWebSocketsProtocol", "IWebSocketsResource", "WebSocketsProtocol", "WebSocketsProtocolWrapper"] from hashlib import sha1 from struct import pack, unpack from zope.interface import implementer, Interface, providedBy, directlyProvides from twisted.python import log from twisted.python.constants import Flags, FlagConstant from twisted.internet.protocol import Protocol from twisted.internet.interfaces import IProtocol from twisted.web.resource import IResource from twisted.web.server import NOT_DONE_YET class _WSException(Exception): """ Internal exception for control flow inside the WebSockets frame parser. """ class CONTROLS(Flags): """ Control frame specifiers. """ CONTINUE = FlagConstant(0) TEXT = FlagConstant(1) BINARY = FlagConstant(2) CLOSE = FlagConstant(8) PING = FlagConstant(9) PONG = FlagConstant(10) # The GUID for WebSockets, from RFC 6455. _WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" def _makeAccept(key): """ Create an B{accept} response for a given key. @type key: C{str} @param key: The key to respond to. @rtype: C{str} @return: An encoded response. """ return sha1("%s%s" % (key, _WS_GUID)).digest().encode("base64").strip() def _mask(buf, key): """ Mask or unmask a buffer of bytes with a masking key. @type buf: C{str} @param buf: A buffer of bytes. @type key: C{str} @param key: The masking key. Must be exactly four bytes. @rtype: C{str} @return: A masked buffer of bytes. """ key = [ord(i) for i in key] buf = list(buf) for i, char in enumerate(buf): buf[i] = chr(ord(char) ^ key[i % 4]) return "".join(buf) def _makeFrame(buf, opcode, fin, mask=None): """ Make a frame. This function always creates unmasked frames, and attempts to use the smallest possible lengths. @type buf: C{str} @param buf: A buffer of bytes. @type opcode: C{CONTROLS} @param opcode: Which type of frame to create. @rtype: C{str} @return: A packed frame. """ bufferLength = len(buf) if mask is not None: lengthMask = 0x80 else: lengthMask = 0 if bufferLength > 0xffff: length = "%s%s" % (chr(lengthMask | 0x7f), pack(">Q", bufferLength)) elif bufferLength > 0x7d: length = "%s%s" % (chr(lengthMask | 0x7e), pack(">H", bufferLength)) else: length = chr(lengthMask | bufferLength) if fin: header = 0x80 else: header = 0x01 header = chr(header | opcode.value) if mask is not None: buf = "%s%s" % (mask, _mask(buf, mask)) frame = "%s%s%s" % (header, length, buf) return frame def _parseFrames(frameBuffer, needMask=True): """ Parse frames in a highly compliant manner. @param frameBuffer: A buffer of bytes. @type frameBuffer: C{list} @param needMask: If C{True}, refuse any frame which is not masked. @type needMask: C{bool} """ start = 0 payload = "".join(frameBuffer) while True: # If there's not at least two bytes in the buffer, bail. if len(payload) - start < 2: break # Grab the header. This single byte holds some flags and an opcode header = ord(payload[start]) if header & 0x70: # At least one of the reserved flags is set. Pork chop sandwiches! raise _WSException("Reserved flag in frame (%d)" % header) fin = header & 0x80 # Get the opcode, and translate it to a local enum which we actually # care about. opcode = header & 0xf try: opcode = CONTROLS.lookupByValue(opcode) except ValueError: raise _WSException("Unknown opcode %d in frame" % opcode) # Get the payload length and determine whether we need to look for an # extra length. length = ord(payload[start + 1]) masked = length & 0x80 if not masked and needMask: # The client must mask the data sent raise _WSException("Received data not masked") length &= 0x7f # The offset we'll be using to walk through the frame. We use this # because the offset is variable depending on the length and mask. offset = 2 # Extra length fields. if length == 0x7e: if len(payload) - start < 4: break length = payload[start + 2:start + 4] length = unpack(">H", length)[0] offset += 2 elif length == 0x7f: if len(payload) - start < 10: break # Protocol bug: The top bit of this long long *must* be cleared; # that is, it is expected to be interpreted as signed. length = payload[start + 2:start + 10] length = unpack(">Q", length)[0] offset += 8 if masked: if len(payload) - (start + offset) < 4: # This is not strictly necessary, but it's more explicit so # that we don't create an invalid key. break key = payload[start + offset:start + offset + 4] offset += 4 if len(payload) - (start + offset) < length: break data = payload[start + offset:start + offset + length] if masked: data = _mask(data, key) if opcode == CONTROLS.CLOSE: if len(data) >= 2: # Gotta unpack the opcode and return usable data here. data = unpack(">H", data[:2])[0], data[2:] else: # No reason given; use generic data. data = 1000, "No reason given" yield opcode, data, bool(fin) start += offset + length if len(payload) > start: frameBuffer[:] = [payload[start:]] else: frameBuffer[:] = [] class IWebSocketsProtocol(IProtocol): """ A protocol which understands the WebSockets interface. @since: 13.1 """ def sendFrame(opcode, data, fin): """ Send a frame. """ def frameReceived(opcode, data, fin): """ Callback when a frame is received. """ def loseConnection(): """ Close the connection sending a close frame first. """ @implementer(IWebSocketsProtocol) class WebSocketsProtocol(Protocol): """ @since: 13.1 """ _disconnecting = False _buffer = None def connectionMade(self): """ Log the new connection and initialize the buffer list. """ log.msg("Opening connection with %s" % self.transport.getPeer()) self._buffer = [] def _parseFrames(self): """ Find frames in incoming data and pass them to the underlying protocol. """ for frame in _parseFrames(self._buffer): opcode, data, fin = frame if opcode in (CONTROLS.CONTINUE, CONTROLS.TEXT, CONTROLS.BINARY): # Business as usual. Decode the frame, if we have a decoder. # Pass the frame to the underlying protocol. self.frameReceived(opcode, data, fin) elif opcode == CONTROLS.CLOSE: # The other side wants us to close. reason, text = data log.msg("Closing connection: %r (%d)" % (text, reason)) # Close the connection. self.transport.loseConnection() return elif opcode == CONTROLS.PING: # 5.5.2 PINGs must be responded to with PONGs. # 5.5.3 PONGs must contain the data that was sent with the # provoking PING. self.transport.write(_makeFrame(data, CONTROLS.PONG, True)) def frameReceived(self, opcode, data, fin): """ Callback to implement. """ raise NotImplementedError() def sendFrame(self, opcode, data, fin): """ Build a frame packet and send it over the wire. """ packet = _makeFrame(data, opcode, fin) self.transport.write(packet) def dataReceived(self, data): """ Append the data to the buffer list and parse the whole. """ self._buffer.append(data) try: self._parseFrames() except _WSException: # Couldn't parse all the frames, something went wrong, let's bail. log.err() self.transport.loseConnection() def loseConnection(self): """ Close the connection. This includes telling the other side we're closing the connection. If the other side didn't signal that the connection is being closed, then we might not see their last message, but since their last message should, according to the spec, be a simple acknowledgement, it shouldn't be a problem. """ # Send a closing frame. It's only polite. (And might keep the browser # from hanging.) if not self._disconnecting: frame = _makeFrame("", CONTROLS.CLOSE, True) self.transport.write(frame) self._disconnecting = True self.transport.loseConnection() class WebSocketsProtocolWrapper(WebSocketsProtocol): """ A protocol wrapper which provides L{IWebSocketsProtocol} by making messages as data frames. @since: 13.1 """ def __init__(self, wrappedProtocol, defaultOpcode=CONTROLS.TEXT): self.wrappedProtocol = wrappedProtocol self.defaultOpcode = defaultOpcode def makeConnection(self, transport): """ Upon connection, provides the transport interface, and forwards ourself as the transport to C{self.wrappedProtocol}. """ directlyProvides(self, providedBy(transport)) WebSocketsProtocol.makeConnection(self, transport) self.wrappedProtocol.makeConnection(self) def connectionMade(self): """ Initialize the list of messages. """ WebSocketsProtocol.connectionMade(self) self._messages = [] def write(self, data): """ Write to the websocket protocol, transforming C{data} in a frame. """ self.sendFrame(self.defaultOpcode, data, True) def writeSequence(self, data): """ Send all chunks from C{data} using C{write}. """ for chunk in data: self.write(chunk) def __getattr__(self, name): """ Forward all non-local attributes and methods to C{self.transport}. """ return getattr(self.transport, name) def frameReceived(self, opcode, data, fin): """ FOr each frame received, accumulate the data (ignoring the opcode), and forwarding the messages if C{fin} is set. """ self._messages.append(data) if fin: content = "".join(self._messages) self._messages[:] = [] self.wrappedProtocol.dataReceived(content) def connectionLost(self, reason): """ Forward C{connectionLost} to C{self.wrappedProtocol}. """ self.wrappedProtocol.connectionLost(reason) class IWebSocketsResource(Interface): """ A WebSockets resource. @since: 13.1 """ def lookupProtocol(protocolNames, request): """ Build a protocol instance for the given protocol options and request. The returned protocol is plugged to the HTTP transport, and the returned protocol name, if specified, is used as I{Sec-WebSocket-Protocol} value. If the protocol provides L{IWebSocketsProtocol}, it will be connected directly, otherwise it will be wrapped by L{WebSocketsProtocolWrapper}. @param protocolNames: The asked protocols from the client. @type protocolNames: C{list} of C{str} @param request: The connecting client request. @type request: L{IRequest} @return: A tuple of (protocol, matched protocol name or C{None}). @rtype: C{tuple} """ @implementer(IResource, IWebSocketsResource) class WebSocketsResource(object): """ A resource for serving a protocol through WebSockets. This class wraps a factory and connects it to WebSockets clients. Each connecting client will be connected to a new protocol of the factory. Due to unresolved questions of logistics, this resource cannot have children. @param factory: The factory producing either L{IWebSocketsProtocol} or L{IProtocol} providers, which will be used by the default C{lookupProtocol} implementation. @type factory: L{twisted.internet.protocol.Factory} @since: 13.1 """ isLeaf = True def __init__(self, factory): self._factory = factory def getChildWithDefault(self, name, request): """ Reject attempts to retrieve a child resource. All path segments beyond the one which refers to this resource are handled by the WebSocket connection. """ raise RuntimeError( "Cannot get IResource children from WebSocketsResource") def putChild(self, path, child): """ Reject attempts to add a child resource to this resource. The WebSocket connection handles all path segments beneath this resource, so L{IResource} children can never be found. """ raise RuntimeError( "Cannot put IResource children under WebSocketsResource") def lookupProtocol(self, protocolNames, request): """ Build a protocol instance for the given protocol names and request. This default implementation ignores the protocol names and just return a protocol instance built by C{self._factory}. @param protocolNames: The asked protocols from the client. @type protocolNames: C{list} of C{str} @param request: The connecting client request. @type request: L{Request} @return: A tuple of (protocol, C{None}). @rtype: C{tuple} """ protocol = self._factory.buildProtocol(request.transport.getPeer()) return protocol, None def render(self, request): """ Render a request. We're not actually rendering a request. We are secretly going to handle a WebSockets connection instead. @param request: The connecting client request. @type request: L{Request} @return: a string if the request fails, otherwise C{NOT_DONE_YET}. """ request.defaultContentType = None # If we fail at all, we'll fail with 400 and no response. failed = False if request.method != "GET": # 4.2.1.1 GET is required. failed = True print('request.method', request.method) upgrade = request.getHeader("Upgrade") if upgrade is None or "websocket" not in upgrade.lower(): # 4.2.1.3 Upgrade: WebSocket is required. failed = True print('request.getHeader("Upgrade")', request.getHeader("Upgrade")) connection = request.getHeader("Connection") if connection is None or "upgrade" not in connection.lower(): # 4.2.1.4 Connection: Upgrade is required. failed = True print('request.getHeader("Connection")', request.getHeader("Connection")) key = request.getHeader("Sec-WebSocket-Key") if key is None: # 4.2.1.5 The challenge key is required. failed = True print('request.getHeader("Sec-WebSocket-Key")', request.getHeader("Sec-WebSocket-Key")) version = request.getHeader("Sec-WebSocket-Version") if version != "13": # 4.2.1.6 Only version 13 works. failed = True # 4.4 Forward-compatible version checking. request.setHeader("Sec-WebSocket-Version", "13") print('request.getHeader("Sec-WebSocket-Version")', request.getHeader("Sec-WebSocket-Version")) if failed: request.setResponseCode(400) return "" askedProtocols = request.requestHeaders.getRawHeaders( "Sec-WebSocket-Protocol") protocol, protocolName = self.lookupProtocol(askedProtocols, request) # If a protocol is not created, we deliver an error status. if not protocol: request.setResponseCode(502) return "" # We are going to finish this handshake. We will return a valid status # code. # 4.2.2.5.1 101 Switching Protocols request.setResponseCode(101) # 4.2.2.5.2 Upgrade: websocket request.setHeader("Upgrade", "WebSocket") # 4.2.2.5.3 Connection: Upgrade request.setHeader("Connection", "Upgrade") # 4.2.2.5.4 Response to the key challenge request.setHeader("Sec-WebSocket-Accept", _makeAccept(key)) # 4.2.2.5.5 Optional codec declaration if protocolName: request.setHeader("Sec-WebSocket-Protocol", protocolName) # Provoke request into flushing headers and finishing the handshake. request.write("") # And now take matters into our own hands. We shall manage the # transport's lifecycle. transport, request.transport = request.transport, None if not IWebSocketsProtocol.providedBy(protocol): protocol = WebSocketsProtocolWrapper(protocol) # Connect the transport to our factory, and make things go. We need to # do some stupid stuff here; see #3204, which could fix it. if request.isSecure(): # Secure connections wrap in TLSMemoryBIOProtocol too. transport.protocol.wrappedProtocol = protocol else: transport.protocol = protocol protocol.makeConnection(transport) return NOT_DONE_YET spyne-2.11.0/spyne/util/appreg.py0000644000175000001440000000510212342627562016656 0ustar plqusers00000000000000 # # 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 # """ Module that contains the Spyne Application Registry. """ import logging logger = logging.getLogger(__name__) _applications = {} try: from collections import namedtuple _ApplicationMetaData = namedtuple("_ApplicationMetaData", ['app', 'inst_stack', 'null', 'ostr']) except ImportError: # python 2.5 class _ApplicationMetaData: def __init__(self, app, inst_stack, null, ostr): self.app = app self.inst_stack = inst_stack self.null = null self.ostr = ostr def register_application(app): key = (app.tns, app.name) from spyne.server.null import NullServer try: import traceback stack = traceback.format_stack() except ImportError: stack = None prev = _applications.get(key, None) if prev is not None: if hash(prev.app) == hash(app): logger.debug("Application %r previously registered as %r is the same" " as %r. Skipping." % (prev.app, key, app)) prev.inst_stack.append(stack) else: logger.warning("Overwriting application %r(%r)." % (key, app)) if prev.inst_stack is not None: stack_traces = [] for s in prev.inst_stack: if s is not None: stack_traces.append(''.join(s)) logger.debug("Stack trace of the instantiation:\n%s" % '====================\n'.join(stack_traces)) _applications[key] = _ApplicationMetaData(app=app, inst_stack=[stack], null=NullServer(app), ostr=NullServer(app, ostr=True)) logger.debug("Registering %r as %r" % (app, key)) def get_application(tns, name): return _applications.get((tns, name), None) spyne-2.11.0/spyne/util/cdict.py0000644000175000001440000000436012345433230016461 0ustar plqusers00000000000000 # # 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 # """cdict (ClassDict) is a funny kind of dict that tries to return the values for the base classes of a key when the entry for the key is not found. It is not a generalized dictionary that can handle any type of key -- it relies on spyne.model api to look for classes. >>> from spyne.util.cdict import cdict >>> class A(object): ... pass ... >>> class B(A): ... pass ... >>> class C(object): ... pass ... >>> class D: ... pass ... >>> d=cdict({A: "fun", object: "base"}) >>> print d[A] fun >>> print d {: 'fun', : 'base'} >>> print d[B] fun >>> print d {: 'fun', : 'fun', : 'base'} >>> print d[C] base >>> print d {: 'fun', : 'fun', : 'base', : 'base'} >>> print d[D] Traceback (most recent call last): File "", line 1, in File "/home/plq/src/github/plq/spyne/src/spyne/util/cdict.py", line 77, in __getitem__ raise e KeyError: >>> """ import logging logger = logging.getLogger(__name__) class cdict(dict): def __getitem__(self, cls): try: return dict.__getitem__(self, cls) except KeyError as e: for b in cls.__bases__: try: retval = self[b] self[cls] = retval return retval except KeyError: pass raise e spyne-2.11.0/spyne/util/color.py0000644000175000001440000000322712345433230016512 0ustar plqusers00000000000000 # # 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 # try: import colorama R = lambda s: ''.join((colorama.Fore.RED, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) G = lambda s: ''.join((colorama.Fore.GREEN, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) B = lambda s: ''.join((colorama.Fore.BLUE, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) Y = lambda s: ''.join((colorama.Fore.YELLOW, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) M = lambda s: ''.join((colorama.Fore.MAGENTA, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) except ImportError: R = lambda s: s G = lambda s: s B = lambda s: s Y = lambda s: s M = lambda s: s spyne-2.11.0/spyne/util/dictdoc.py0000644000175000001440000000741112345433230017004 0ustar plqusers00000000000000 # # 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 # from spyne._base import FakeContext from spyne.protocol.dictdoc import HierDictDocument from spyne.protocol.dictdoc import SimpleDictDocument try: from spyne.protocol.json import JsonDocument except ImportError as e: def JsonDocument(*args, **kwargs): raise e try: from spyne.protocol.yaml import YamlDocument except ImportError as e: def YamlDocument(*args, **kwargs): raise e from spyne.model.primitive import Double from spyne.model.primitive import Boolean from spyne.model.primitive import Decimal from spyne.model.primitive import Integer class _UtilProtocol(HierDictDocument): def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, # DictDocument specific ignore_wrappers=True, complex_as=dict, ordered=False): super(_UtilProtocol, self).__init__(app, validator, mime_type, ignore_uncap, ignore_wrappers, complex_as, ordered) self._from_string_handlers[Double] = lambda cls, val: val self._from_string_handlers[Boolean] = lambda cls, val: val self._from_string_handlers[Decimal] = lambda cls, val: val self._from_string_handlers[Integer] = lambda cls, val: val self._to_string_handlers[Double] = lambda cls, val: val self._to_string_handlers[Boolean] = lambda cls, val: val self._to_string_handlers[Decimal] = lambda cls, val: val self._to_string_handlers[Integer] = lambda cls, val: val def get_dict_as_object(d, cls, ignore_wrappers=True, complex_as=list, protocol=_UtilProtocol): return protocol(ignore_wrappers=ignore_wrappers, complex_as=complex_as)._doc_to_object(cls, d) def get_object_as_dict(o, cls, ignore_wrappers=True, complex_as=dict, protocol=_UtilProtocol): return protocol(ignore_wrappers=ignore_wrappers, complex_as=complex_as)._object_to_doc(cls, o) def get_object_as_simple_dict(o, cls, hier_delim='_'): return SimpleDictDocument(hier_delim=hier_delim) \ .object_to_simple_dict(cls, o) def get_object_as_json(o, cls, ignore_wrappers=True, complex_as=list, encoding='utf8'): prot = JsonDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as) ctx = FakeContext(out_document=[prot._object_to_doc(cls,o)]) prot.create_out_string(ctx, encoding) return ''.join(ctx.out_string) def get_object_as_yaml(o, cls, ignore_wrappers=False, complex_as=dict, encoding='utf8'): prot = YamlDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as) ctx = FakeContext(out_document=[prot._object_to_doc(cls,o)]) prot.create_out_string(ctx, encoding) return ''.join(ctx.out_string) spyne-2.11.0/spyne/util/django.py0000644000175000001440000003457112345433230016644 0ustar plqusers00000000000000# encoding: utf-8 # # 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 # """Support for Django model <-> spyne type mapping. This module is EXPERIMENTAL. Tests and patches are welcome. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) import re from django.core.exceptions import ImproperlyConfigured from django.core.validators import (slug_re, comma_separated_int_list_re, URLValidator) from spyne.model.complex import ComplexModelMeta, ComplexModelBase from spyne.model import primitive from spyne.util.odict import odict from spyne.util.six import add_metaclass email_re = re.compile( # dot-atom r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # quoted-string, see also http://tools.ietf.org/html/rfc2822#section-3.2.5 r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]' r'|\\[\001-\011\013\014\016-\177])*"' r')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+' r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)$)' # domain # literal form, ipv4 address (SMTP 4.1.3) r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)' r'(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE) class BaseDjangoFieldMapper(object): """Abstrace base class for field mappers.""" @staticmethod def is_field_nullable(field, **kwargs): """Return True if django field is nullable.""" return field.null def map(self, field, **kwargs): """Map field to spyne model. :param field: Django Field instance :param kwargs: Extra params to configure spyne model :returns: tuple (field attribute name, mapped spyne model) """ params = kwargs.copy() if field.max_length: params['max_len'] = field.max_length nullable = self.is_field_nullable(field, **kwargs) required = not (field.has_default() or nullable or field.primary_key) if field.has_default(): params['default'] = field.get_default() spyne_model = self.get_spyne_model(field, **kwargs) customized_model = spyne_model(nullable=nullable, min_occurs=int(required), **params) return (field.attname, customized_model) def get_spyne_model(self, field, **kwargs): """Return spyne model for given Django field.""" raise NotImplementedError class DjangoFieldMapper(BaseDjangoFieldMapper): """Basic mapper for django fields.""" def __init__(self, spyne_model): """Django field mapper constructor.""" self.spyne_model = spyne_model def get_spyne_model(self, field, **kwargs): """Return configured spyne model.""" return self.spyne_model class DecimalMapper(DjangoFieldMapper): """Mapper for DecimalField.""" def map(self, field, **kwargs): """Map DecimalField to spyne model. :returns: tuple (field attribute name, mapped spyne model) """ params = kwargs.copy() params.update({ 'total_digits': field.max_digits, 'fraction_digits': field.decimal_places, }) return super(DecimalMapper, self).map(field, **params) class RelationMapper(BaseDjangoFieldMapper): """Mapper for relation fields (ForeignKey, OneToOneField).""" def __init__(self, django_model_mapper): """Constructor for relation field mapper.""" self.django_model_mapper = django_model_mapper @staticmethod def is_field_nullable(field, **kwargs): """Return True if `optional_relations` is set. Otherwise use basic behaviour. """ optional_relations = kwargs.get('optional_relations', False) return (optional_relations or BaseDjangoFieldMapper.is_field_nullable(field, **kwargs)) def get_spyne_model(self, field, **kwargs): """Return spyne model configured by related field.""" related_field = field.rel.get_related_field() field_type = related_field.get_internal_type() field_mapper = self.django_model_mapper.get_field_mapper(field_type) _, related_spyne_model = field_mapper.map(related_field, **kwargs) return related_spyne_model class DjangoModelMapper(object): r"""Mapper from django models to spyne complex models. You can extend it registering new field types: :: class NullBooleanMapper(DjangoFieldMapper): def map(self, field, **kwargs): params = kwargs.copy() # your mapping logic goes here return super(NullBooleanMapper, self).map(field, **params) default_model_mapper.register_field_mapper('NullBooleanField', \ NullBooleanMapper(primitive.Boolean)) You may subclass it if you want different mapping logic for different Django models. """ field_mapper_class = DjangoFieldMapper class UnknownFieldMapperException(Exception): """Raises when there is no field mapper for given django_type.""" def __init__(self, django_spyne_models=()): """Register field mappers in internal registry.""" self._registry = {} for django_type, spyne_model in django_spyne_models: self.register(django_type, spyne_model) def get_field_mapper(self, django_type): """Get mapper registered for given django_type. :param django_type: Django internal field type :returns: registered mapper :raises: :exc:`UnknownFieldMapperException` """ try: return self._registry[django_type] except KeyError: raise self.UnknownFieldMapperException( 'No mapper for field type {0}'.format(django_type)) def register(self, django_type, spyne_model): """Register default field mapper for django_type and spyne_model. :param django_type: Django internal field type :param spyne_model: Spyne model, usually primitive """ field_mapper = self.field_mapper_class(spyne_model) self.register_field_mapper(django_type, field_mapper) def register_field_mapper(self, django_type, field_mapper): """Register field mapper for django_type. :param django_type: Django internal field type :param field_mapper: :class:`DjangoFieldMapper` instance """ self._registry[django_type] = field_mapper @staticmethod def _get_fields(django_model, exclude=None): field_names = set(exclude) if exclude is not None else set() meta = django_model._meta # pylint: disable=W0212 unknown_fields_names = field_names.difference( meta.get_all_field_names()) if unknown_fields_names: raise ImproperlyConfigured( 'Unknown field names: {0}' .format(', '.join(unknown_fields_names))) return [field for field in meta.fields if field.name not in field_names] def map(self, django_model, exclude=None, **kwargs): """Prepare dict of model fields mapped to spyne models. :param django_model: Django model class. :param exclude: list of fields excluded from mapping. :param kwargs: extra kwargs are passed to all field mappers :returns: dict mapping attribute names to spyne models :raises: :exc:`UnknownFieldMapperException` """ field_map = odict() for field in self._get_fields(django_model, exclude): field_type = field.get_internal_type() try: field_mapper = self._registry[field_type] except KeyError: # mapper for this field is not registered if not (field.has_default() or field.null): # field is required raise self.UnknownFieldMapperException( 'No mapper for field type {0}'.format(field_type)) else: # skip this field logger.info('Field {0} is skipped from mapping.') continue attr_name, spyne_model = field_mapper.map(field, **kwargs) field_map[attr_name] = spyne_model return field_map def strip_regex_metachars(pattern): """Strip ^ and $ from pattern begining and end. According to http://www.w3.org/TR/xmlschema-0/#regexAppendix XMLSchema expression language does not contain the metacharacters ^ and $. :returns: stripped pattern string """ start = 0 till = len(pattern) if pattern.startswith('^'): start = 1 if pattern.endswith('$'): till -= 1 return pattern[start:till] DEFAULT_FIELD_MAP = ( ('AutoField', primitive.Integer32), ('CharField', primitive.NormalizedString), ('SlugField', primitive.Unicode( type_name='Slug', pattern=strip_regex_metachars(slug_re.pattern))), ('TextField', primitive.Unicode), ('EmailField', primitive.Unicode( type_name='Email', pattern=strip_regex_metachars(email_re.pattern))), ('CommaSeparatedIntegerField', primitive.Unicode( type_name='CommaSeparatedField', pattern=strip_regex_metachars(comma_separated_int_list_re.pattern))), ('UrlField', primitive.AnyUri( type_name='Url', pattern=strip_regex_metachars(URLValidator.regex.pattern))), ('FilePathField', primitive.Unicode), ('BooleanField', primitive.Boolean), ('NullBooleanField', primitive.Boolean), ('IntegerField', primitive.Integer), ('BigIntegerField', primitive.Integer64), ('PositiveIntegerField', primitive.UnsignedInteger32), ('SmallIntegerField', primitive.Integer16), ('PositiveSmallIntegerField', primitive.UnsignedInteger16), ('FloatField', primitive.Double), ('TimeField', primitive.Time), ('DateField', primitive.Date), ('DateTimeField', primitive.DateTime), # simple fixed defaults for relation fields ('ForeignKey', primitive.Integer32), ('OneToOneField', primitive.Integer32), ) def model_mapper_factory(mapper_class, field_map): """Factory for model mappers. The factory is useful to create custom field mappers based on default one. """ model_mapper = mapper_class(field_map) # register relation field mappers that are aware of related field type model_mapper.register_field_mapper( 'ForeignKey', RelationMapper(model_mapper)) model_mapper.register_field_mapper( 'OneToOneField', RelationMapper(model_mapper)) model_mapper.register_field_mapper('DecimalField', DecimalMapper(primitive.Decimal)) return model_mapper default_model_mapper = model_mapper_factory(DjangoModelMapper, DEFAULT_FIELD_MAP) class DjangoComplexModelMeta(ComplexModelMeta): """Meta class for complex spyne models representing Django models.""" def __new__(mcs, name, bases, attrs): # pylint: disable=C0202 """Populate new complex type from configured Django model.""" super_new = super(DjangoComplexModelMeta, mcs).__new__ try: parents = [b for b in bases if issubclass(b, DjangoComplexModel)] except NameError: # we are defining DjangoComplexModel itself parents = None if not parents: # If this isn't a subclass of DjangoComplexModel, don't do # anything special. return super_new(mcs, name, bases, attrs) attributes = attrs.get('Attributes') if attributes is None: raise ImproperlyConfigured('You have to define Attributes and ' 'specify Attributes.django_model') if attributes.django_model is None: raise ImproperlyConfigured('You have to define django_model ' 'attribute in Attributes') mapper = getattr(attributes, 'django_mapper', default_model_mapper) attributes.django_mapper = mapper exclude = getattr(attributes, 'django_exclude', None) optional_relations = getattr(attributes, 'django_optional_relations', False) spyne_attrs = mapper.map(attributes.django_model, exclude=exclude, optional_relations=optional_relations) spyne_attrs.update(attrs) return super_new(mcs, name, bases, spyne_attrs) @add_metaclass(DjangoComplexModelMeta) class DjangoComplexModel(ComplexModelBase): """Base class with Django model mapping support. Sample usage: :: class PersonType(DjangoComplexModel): class Attributes(DjangoComplexModel.Attributes): django_model = Person Attribute :attr:`django_model` is required for Django model mapping machinery. You can customize your types defining custom type fields: :: class PersonType(DjangoComplexModel): gender = primitive.Unicode(pattern='^[FM]$') class Attributes(DjangoComplexModel.Attributes): django_model = Person There is an option to specify custom mapper: :: class PersonType(DjangoComplexModel): class Attributes(DjangoComplexModel.Attributes): django_model = Person django_mapper = my_custom_mapper You can also exclude some fields from mapping: :: class PersonType(DjangoComplexModel): class Attributes(DjangoComplexModel.Attributes): django_model = Person django_exclude = ['phone'] You may set `django_optional_relations`` attribute flag to indicate that relation fields (ForeignKey, OneToOneField) of your model are optional. This is useful when you want to create base and related instances in remote procedure. In this case primary key of base model is not yet available. """ spyne-2.11.0/spyne/util/email.py0000644000175000001440000000514112345433230016460 0ustar plqusers00000000000000 # # 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 # from __future__ import absolute_import import logging logger = logging.getLogger(__name__) import getpass import inspect import traceback import smtplib from socket import gethostname from email.Utils import formatdate from email.mime.text import MIMEText def email_exception(exception_address, message=""): # http://stackoverflow.com/questions/1095601/find-module-name-of-the-originating-exception-in-python frm = inspect.trace()[-1] mod = inspect.getmodule(frm[0]) module_name = mod.__name__ if mod else frm[1] sender = 'robot@spyne.io' receivers = [exception_address] error_str = ("%s\n\n%s" % (message, traceback.format_exc())) msg = MIMEText(error_str.encode('utf8'), 'plain', 'utf8') msg['To'] = exception_address msg['From'] = 'Spyne ' msg['Date'] = formatdate() msg['Subject'] = "(%s@%s) %s" % (getpass.getuser(), gethostname(), module_name) try: smtp_object = smtplib.SMTP('localhost') smtp_object.sendmail(sender, receivers, msg.as_string()) logger.error("Error email sent") except Exception as e: logger.error("Error: unable to send email") logger.exception(e) def email_text(addresses, sender=None, subject='', message=""): sender = 'robot@spyne.io' receivers = addresses if sender is None: sender = 'Spyne ' error_str = ("%s\n\n%s" % (message, traceback.format_exc())) msg = MIMEText(error_str.encode('utf8'), 'plain', 'utf8') msg['To'] = ';'.join(addresses) msg['From'] = sender msg['Date'] = formatdate() msg['Subject'] = subject smtp_object = smtplib.SMTP('localhost') smtp_object.sendmail(sender, receivers, msg.as_string()) logger.info("Text email sent to: %r. Text: %s " % (addresses, message[100:].replace('\n', ' '))) spyne-2.11.0/spyne/util/etreeconv.py0000644000175000001440000000771212345433230017371 0ustar plqusers00000000000000 # # 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 # """This module contains the utility methods that convert an ElementTree hierarchy to python dicts and vice versa. """ import collections from spyne.util import six from lxml import etree from spyne.util.odict import odict def root_dict_to_etree(d): """Converts a dictionary to an xml hiearchy. Just like a valid xml document, the dictionary must have a single element. The format of the child dictionaries is the same as :func:`dict_to_etree`. """ assert len(d) == 1 key, = d.keys() retval = etree.Element(key) for val in d.values(): break if val is None: return retval if isinstance(val, dict) or isinstance(val, odict): dict_to_etree(val, retval) elif not isinstance(val, collections.Sized) or isinstance(val, six.string_types): retval.text=str(val) else: for a in val: dict_to_etree(a, retval) return retval def dict_to_etree(d, parent): """Takes a the dict whose value is either None or an instance of dict, odict or an iterable. The iterables can contain either other dicts/odicts or str/unicode instances. """ for k, v in d.items(): if v is None: etree.SubElement(parent, k) elif isinstance(v, six.string_types): etree.SubElement(parent, k).text = v elif isinstance(v, dict) or isinstance(v, odict): child = etree.SubElement(parent, k) dict_to_etree(v, child) elif not isinstance(v, collections.Sized): etree.SubElement(parent, k).text = str(v) elif len(v) == 0: etree.SubElement(parent, k) else: for e in v: child=etree.SubElement(parent, k) if isinstance(e, dict) or isinstance(e, odict): dict_to_etree(e, child) else: child.text=str(e) def root_etree_to_dict(element, iterable=(list, list.append)): """Takes an xml root element and returns the corresponding dict. The second argument is a pair of iterable type and the function used to add elements to the iterable. The xml attributes are ignored. """ return {element.tag: iterable[0]([etree_to_dict(element, iterable)])} def etree_to_dict(element, iterable=(list, list.append)): """Takes an xml root element and returns the corresponding dict. The second argument is a pair of iterable type and the function used to add elements to the iterable. The xml attributes are ignored. """ if (element.text is None) or element.text.isspace(): retval = odict() for elt in element: if not (elt.tag in retval): retval[elt.tag] = iterable[0]() iterable[1](retval[elt.tag], etree_to_dict(elt, iterable)) else: retval = element.text return retval def etree_strip_namespaces(element): """Removes any namespace information form the given element recursively.""" retval = etree.Element(element.tag.rpartition('}')[-1]) retval.text = element.text for a in element.attrib: retval.attrib[a.rpartition('}')[-1]] = element.attrib[a] for e in element: retval.append(etree_strip_namespaces(e)) return retval spyne-2.11.0/spyne/util/http.py0000644000175000001440000000406112345433230016350 0ustar plqusers00000000000000# encoding: utf8 # # 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 # # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. import time from time import strftime from time import gmtime from collections import deque # This is a modified version of twisted's addCookie def generate_cookie(k, v, max_age=None, domain=None, path=None, comment=None, secure=False): """Generate a HTTP response cookie. No sanity check whatsoever is done, don't send anything other than ASCII. :param k: Cookie key. :param v: Cookie value. :param max_age: Seconds. :param domain: Domain. :param path: Path. :param comment: Whatever. :param secure: If true, appends 'Secure' to the cookie string. """ retval = deque(['%s=%s' % (k, v)]) if max_age is not None: retval.append("Max-Age=%d" % max_age) expires = time.time() + max_age retval.append("Expires=%s" % strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime(expires))) if domain is not None: retval.append("Domain=%s" % domain) if path is not None: retval.append("Path=%s" % path) if comment is not None: retval.append("Comment=%s" % comment) if secure: retval.append("Secure") return '; '.join(retval) spyne-2.11.0/spyne/util/invregexp.py0000644000175000001440000001714012345433230017402 0ustar plqusers00000000000000# # invRegex.py # # Copyright 2008, Paul McGuire # # pyparsing script to expand a regular expression into all possible matching # strings # # Supports: # - {n} and {m,n} repetition, but not unbounded + or * repetition # - ? optional elements # - [] character ranges # - () grouping # - | alternation # __all__ = ["count", "invregexp"] from pyparsing import Combine from pyparsing import Literal from pyparsing import ParseFatalException from pyparsing import ParseResults from pyparsing import ParserElement from pyparsing import SkipTo from pyparsing import Suppress from pyparsing import Word from pyparsing import nums from pyparsing import oneOf from pyparsing import opAssoc from pyparsing import operatorPrecedence from pyparsing import printables from pyparsing import srange class CharacterRangeEmitter(object): def __init__(self, chars): # remove duplicate chars in character range, but preserve original order seen = set() self.charset = "".join(seen.add(c) or c for c in chars if c not in seen) def __str__(self): return '[' + self.charset + ']' def __repr__(self): return '[' + self.charset + ']' def make_generator(self): def gen_chars(): for s in self.charset: yield s return gen_chars class OptionalEmitter(object): def __init__(self, expr): self.expr = expr def make_generator(self): def optional_gen(): yield "" for s in self.expr.make_generator()(): yield s return optional_gen class DotEmitter(object): def make_generator(self): def dot_gen(): for c in printables: yield c return dot_gen class GroupEmitter(object): def __init__(self, exprs): self.exprs = ParseResults(exprs) def make_generator(self): def group_gen(): def recurse_list(elist): if len(elist) == 1: for s in elist[0].make_generator()(): yield s else: for s in elist[0].make_generator()(): for s2 in recurse_list(elist[1:]): yield s + s2 if self.exprs: for s in recurse_list(self.exprs): yield s return group_gen class AlternativeEmitter(object): def __init__(self, exprs): self.exprs = exprs def make_generator(self): def alt_gen(): for e in self.exprs: for s in e.make_generator()(): yield s return alt_gen class LiteralEmitter(object): def __init__(self, lit): self.lit = lit def __str__(self): return "Lit:" + self.lit def __repr__(self): return "Lit:" + self.lit def make_generator(self): def lit_gen(): yield self.lit return lit_gen def handle_range(toks): return CharacterRangeEmitter(srange(toks[0])) def handle_repetition(toks): toks = toks[0] if toks[1] in "*+": raise ParseFatalException("", 0, "unbounded repetition operators not supported") if toks[1] == "?": return OptionalEmitter(toks[0]) if "count" in toks: return GroupEmitter([toks[0]] * int(toks.count)) if "minCount" in toks: mincount = int(toks.minCount) maxcount = int(toks.maxCount) optcount = maxcount - mincount if optcount: opt = OptionalEmitter(toks[0]) for i in range(1, optcount): opt = OptionalEmitter(GroupEmitter([toks[0], opt])) return GroupEmitter([toks[0]] * mincount + [opt]) else: return [toks[0]] * mincount def handle_literal(toks): lit = "" for t in toks: if t[0] == "\\": if t[1] == "t": lit += '\t' else: lit += t[1] else: lit += t return LiteralEmitter(lit) def handle_macro(toks): macroChar = toks[0][1] if macroChar == "d": return CharacterRangeEmitter("0123456789") elif macroChar == "w": return CharacterRangeEmitter(srange("[A-Za-z0-9_]")) elif macroChar == "s": return LiteralEmitter(" ") else: raise ParseFatalException("", 0, "unsupported macro character (" + macroChar + ")") def handle_sequence(toks): return GroupEmitter(toks[0]) def handle_dot(): return CharacterRangeEmitter(printables) def handle_alternative(toks): return AlternativeEmitter(toks[0]) _parser = None def parser(): global _parser if _parser is None: ParserElement.setDefaultWhitespaceChars("") lbrack, rbrack, lbrace, rbrace, lparen, rparen = map(Literal, "[]{}()") reMacro = Combine("\\" + oneOf(list("dws"))) escapedChar = ~ reMacro + Combine("\\" + oneOf(list(printables))) reLiteralChar = "".join(c for c in printables if c not in r"\[]{}().*?+|") + " \t" reRange = Combine(lbrack + SkipTo(rbrack, ignore=escapedChar) + rbrack) reLiteral = (escapedChar | oneOf(list(reLiteralChar))) reDot = Literal(".") repetition = ( (lbrace + Word(nums).setResultsName("count") + rbrace) | (lbrace + Word(nums).setResultsName("minCount") + "," + Word(nums).setResultsName("maxCount") + rbrace) | oneOf(list("*+?")) ) reRange.setParseAction(handle_range) reLiteral.setParseAction(handle_literal) reMacro.setParseAction(handle_macro) reDot.setParseAction(handle_dot) reTerm = (reLiteral | reRange | reMacro | reDot) reExpr = operatorPrecedence(reTerm, [ (repetition, 1, opAssoc.LEFT, handle_repetition), (None, 2, opAssoc.LEFT, handle_sequence), (Suppress('|'), 2, opAssoc.LEFT, handle_alternative), ]) _parser = reExpr return _parser def count(gen): """Simple function to count the number of elements returned by a generator.""" i = 0 for s in gen: i += 1 return i def invregexp(regex): """Call this routine as a generator to return all the strings that match the input regular expression. for s in invregexp("[A-Z]{3}\d{3}"): print s """ invReGenerator = GroupEmitter(parser().parseString(regex)).make_generator() return invReGenerator() def main(): tests = r""" [A-EA] [A-D]* [A-D]{3} X[A-C]{3}Y X[A-C]{3}\( X\d foobar\d\d foobar{2} foobar{2,9} fooba[rz]{2} (foobar){2} ([01]\d)|(2[0-5]) ([01]\d\d)|(2[0-4]\d)|(25[0-5]) [A-C]{1,2} [A-C]{0,3} [A-C]\s[A-C]\s[A-C] [A-C]\s?[A-C][A-C] [A-C]\s([A-C][A-C]) [A-C]\s([A-C][A-C])? [A-C]{2}\d{2} @|TH[12] @(@|TH[12])? @(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9]))? @(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9])|OH(1[0-9]?|2[0-9]?|30?|[4-9]))? (([ECMP]|HA|AK)[SD]|HS)T [A-CV]{2} A[cglmrstu]|B[aehikr]?|C[adeflmorsu]?|D[bsy]|E[rsu]|F[emr]?|G[ade]|H[efgos]?|I[nr]?|Kr?|L[airu]|M[dgnot]|N[abdeiop]?|Os?|P[abdmortu]?|R[abefghnu]|S[bcegimnr]?|T[abcehilm]|Uu[bhopqst]|U|V|W|Xe|Yb?|Z[nr] (a|b)|(x|y) (a|b) (x|y) """.split('\n') for t in tests: t = t.strip() if not t: continue print('-' * 50) print(t) try: print(count(invregexp(t))) for s in invregexp(t): print(s) except ParseFatalException as pfe: print(pfe.msg) print() continue print() if __name__ == "__main__": main() spyne-2.11.0/spyne/util/meta.py0000644000175000001440000001174112345433230016322 0ustar plqusers00000000000000# coding: utf-8 # # 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 # """Metaclass utilities for :attr:`spyne.model.complex.ComplexModelBase.Attributes.declare_order` """ import sys import inspect from functools import wraps from itertools import chain from traceback import print_stack from spyne.util.odict import odict class ClassNotFoundException(Exception): """Raise when class declaration is not found in frame stack.""" class AttributeNotFoundException(Exception): """Raise when attribute is not found in class declaration.""" class Prepareable(type): """Implement __prepare__ for Python 2. This class is used in Python 2 and Python 3 to support `six.add_metaclass` decorator that populates attributes of resulting class from plain unordered attributes dict of decorated class. Based on https://gist.github.com/DasIch/5562625 """ def __new__(cls, name, bases, attributes): try: constructor = attributes["__new__"] except KeyError: return type.__new__(cls, name, bases, attributes) def preparing_constructor(cls, name, bases, attributes): # Don't bother with this shit unless the user *explicitly* asked for # it for c in chain(bases, [cls]): if hasattr(c,'Attributes') and not \ (c.Attributes.declare_order in (None, 'random')): break else: return constructor(cls, name, bases, attributes) try: cls.__prepare__ except AttributeError: return constructor(cls, name, bases, attributes) if isinstance(attributes, odict): # we create class dynamically with passed odict return constructor(cls, name, bases, attributes) current_frame = sys._getframe() class_declaration = None while class_declaration is None: literals = list(reversed(current_frame.f_code.co_consts)) for literal in literals: if inspect.iscode(literal) and literal.co_name == name: class_declaration = literal break else: if current_frame.f_back: current_frame = current_frame.f_back else: raise ClassNotFoundException( "Can't find class declaration in any frame") def get_index(attribute_name, _names=class_declaration.co_names): try: return _names.index(attribute_name) except ValueError: if attribute_name.startswith('_'): # we don't care about the order of magic and non # public attributes return 0 else: msg = ("Can't find {0} in {1} class declaration. " .format(attribute_name, class_declaration.co_name)) msg += ("HINT: use spyne.util.odict.odict for " "class attributes if you populate them" " dynamically.") raise AttributeNotFoundException(msg) by_appearance = sorted( attributes.items(), key=lambda item: get_index(item[0]) ) namespace = cls.__prepare__(name, bases) for key, value in by_appearance: namespace[key] = value new_cls = constructor(cls, name, bases, namespace) found_module = inspect.getmodule(class_declaration) assert found_module is not None, ( 'Module is not found for class_declaration {0}, name {1}' .format(class_declaration, name)) assert found_module.__name__ == new_cls.__module__, ( 'Found wrong class declaration of {0}: {1} != {2}.' .format(name, found_module.__name__, new_cls.__module__)) return new_cls attributes["__new__"] = wraps(constructor)(preparing_constructor) return type.__new__(cls, name, bases, attributes) spyne-2.11.0/spyne/util/odict.py0000644000175000001440000000672612345433230016505 0ustar plqusers00000000000000 # # 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 # """This module contains a sort of an ordered dictionary implementation.""" class odict(dict): """Sort of an ordered dictionary implementation.""" def __init__(self, data=[]): if isinstance(data, self.__class__): self.__list = list(data.__list) super(odict, self).__init__(data) else: self.__list = [] super(odict, self).__init__() self.update(data) def __getitem__(self, key): if isinstance(key, int): return super(odict, self).__getitem__(self.__list[key]) else: return super(odict, self).__getitem__(key) def __setitem__(self, key, val): if isinstance(key, int): super(odict, self).__setitem__(self.__list[key], val) else: if not (key in self): self.__list.append(key) super(odict, self).__setitem__(key, val) assert len(self.__list) == super(odict, self).__len__(), ( repr(self.__list), super(odict, self).__repr__()) def __repr__(self): return "{%s}" % ','.join(["%r: %r" % (k, v) for k, v in self.items()]) def __str__(self): return repr(self) def __len__(self): assert len(self.__list) == super(odict, self).__len__() return len(self.__list) def __iter__(self): return iter(self.__list) def __delitem__(self, key): if not isinstance(key, int): key = self.__list.index(key) # ouch. super(odict, self).__delitem__(self.__list[key]) del self.__list[key] def __add__(self, other): self.update(other) return self def items(self): retval = [] for k in self.__list: retval.append( (k, super(odict, self).__getitem__(k)) ) return retval def iteritems(self): for k in self.__list: yield k, super(odict, self).__getitem__(k) def keys(self): return self.__list def update(self, data): if isinstance(data, (dict, odict)): data = data.items() for k, v in data: self[k] = v def values(self): retval = [] for l in self.__list: retval.append(super(odict, self).__getitem__(l)) return retval def itervalues(self): for l in self.__list: yield self[l] def get(self, key, default=None): if key in self: return self[key] return default def append(self, t): k, v = t self[k] = v def insert(self, index, item): k,v = item if k in self: del self.__list[self.__list.index(k)] self.__list.insert(index, k) super(odict, self).__setitem__(k, v) spyne-2.11.0/spyne/util/protocol.py0000644000175000001440000000240512342627562017244 0ustar plqusers00000000000000 # # 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 # """Helpers for protocol boilerplate.""" from spyne import MethodContext from spyne.server import ServerBase def deserialize_request_string(string, app): """Deserialize request string using in_protocol in application definition. Returns the corresponding native python object. """ server = ServerBase(app) initial_ctx = MethodContext(server) initial_ctx.in_string = [string] ctx = server.generate_contexts(initial_ctx)[0] server.get_in_object(ctx) return ctx.in_object spyne-2.11.0/spyne/util/simple.py0000644000175000001440000000413612342627562016677 0ustar plqusers00000000000000 # # 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 # """Contains functions that implement the most common protocol and transport combinations""" from spyne.application import Application def wsgi_soap11_application(services, tns='spyne.simple.soap', validator=None, name=None): """Wraps `services` argument inside a WsgiApplication that uses Soap 1.1 for both input and output protocols. """ from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication application = Application(services, tns, name=name, in_protocol=Soap11(validator=validator), out_protocol=Soap11()) return WsgiApplication(application) wsgi_soap_application = wsgi_soap11_application """DEPRECATED! Use :func:`wsgi_soap11_application` instead.""" def pyramid_soap11_application(services, tns='spyne.simple.soap', validator=None, name=None): """Wraps `services` argument inside a PyramidApplication that uses Soap 1.1 for both input and output protocols. """ from spyne.protocol.soap import Soap11 from spyne.server.pyramid import PyramidApplication application = Application(services, tns, name=name, in_protocol=Soap11(validator=validator), out_protocol=Soap11()) return PyramidApplication(application) spyne-2.11.0/spyne/util/six.py0000644000175000001440000005603512345433230016204 0ustar plqusers00000000000000"""Utilities for writing code that runs on Python 2 and 3""" # Copyright (c) 2010-2014 Benjamin Peterson # # 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. import operator import sys import types __author__ = "Benjamin Peterson " __version__ = "1.6.1" # Useful for very coarse version differentiation. PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 if PY3: string_types = str, integer_types = int, class_types = type, text_type = str binary_type = bytes MAXSIZE = sys.maxsize else: string_types = basestring, integer_types = (int, long) class_types = (type, types.ClassType) text_type = unicode binary_type = str if sys.platform.startswith("java"): # Jython always uses 32 bits. MAXSIZE = int((1 << 31) - 1) else: # It's possible to have sizeof(long) != sizeof(Py_ssize_t). class X(object): def __len__(self): return 1 << 31 try: len(X()) except OverflowError: # 32-bit MAXSIZE = int((1 << 31) - 1) else: # 64-bit MAXSIZE = int((1 << 63) - 1) del X def _add_doc(func, doc): """Add documentation to a function.""" func.__doc__ = doc def _import_module(name): """Import module, returning the module after the last dot.""" __import__(name) return sys.modules[name] class _LazyDescr(object): def __init__(self, name): self.name = name def __get__(self, obj, tp): try: result = self._resolve() except ImportError: # See the nice big comment in MovedModule.__getattr__. raise AttributeError("%s could not be imported " % self.name) setattr(obj, self.name, result) # Invokes __set__. # This is a bit ugly, but it avoids running this again. delattr(obj.__class__, self.name) return result class MovedModule(_LazyDescr): def __init__(self, name, old, new=None): super(MovedModule, self).__init__(name) if PY3: if new is None: new = name self.mod = new else: self.mod = old def _resolve(self): return _import_module(self.mod) def __getattr__(self, attr): # It turns out many Python frameworks like to traverse sys.modules and # try to load various attributes. This causes problems if this is a # platform-specific module on the wrong platform, like _winreg on # Unixes. Therefore, we silently pretend unimportable modules do not # have any attributes. See issues #51, #53, #56, and #63 for the full # tales of woe. # # First, if possible, avoid loading the module just to look at __file__, # __name__, or __path__. if (attr in ("__file__", "__name__", "__path__") and self.mod not in sys.modules): raise AttributeError(attr) try: _module = self._resolve() except ImportError: raise AttributeError(attr) value = getattr(_module, attr) setattr(self, attr, value) return value class _LazyModule(types.ModuleType): def __init__(self, name): super(_LazyModule, self).__init__(name) self.__doc__ = self.__class__.__doc__ def __dir__(self): attrs = ["__doc__", "__name__"] attrs += [attr.name for attr in self._moved_attributes] return attrs # Subclasses should override this _moved_attributes = [] class MovedAttribute(_LazyDescr): def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): super(MovedAttribute, self).__init__(name) if PY3: if new_mod is None: new_mod = name self.mod = new_mod if new_attr is None: if old_attr is None: new_attr = name else: new_attr = old_attr self.attr = new_attr else: self.mod = old_mod if old_attr is None: old_attr = name self.attr = old_attr def _resolve(self): module = _import_module(self.mod) return getattr(module, self.attr) class _MovedItems(_LazyModule): """Lazy loading of moved objects""" _moved_attributes = [ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), MovedAttribute("map", "itertools", "builtins", "imap", "map"), MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), MovedAttribute("reload_module", "__builtin__", "imp", "reload"), MovedAttribute("reduce", "__builtin__", "functools"), MovedAttribute("StringIO", "StringIO", "io"), MovedAttribute("UserString", "UserString", "collections"), MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), MovedModule("builtins", "__builtin__"), MovedModule("configparser", "ConfigParser"), MovedModule("copyreg", "copy_reg"), MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), MovedModule("http_cookies", "Cookie", "http.cookies"), MovedModule("html_entities", "htmlentitydefs", "html.entities"), MovedModule("html_parser", "HTMLParser", "html.parser"), MovedModule("http_client", "httplib", "http.client"), MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), MovedModule("cPickle", "cPickle", "pickle"), MovedModule("queue", "Queue"), MovedModule("reprlib", "repr"), MovedModule("socketserver", "SocketServer"), MovedModule("_thread", "thread", "_thread"), MovedModule("tkinter", "Tkinter"), MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), MovedModule("tkinter_tix", "Tix", "tkinter.tix"), MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), MovedModule("tkinter_colorchooser", "tkColorChooser", "tkinter.colorchooser"), MovedModule("tkinter_commondialog", "tkCommonDialog", "tkinter.commondialog"), MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), MovedModule("tkinter_font", "tkFont", "tkinter.font"), MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", "tkinter.simpledialog"), MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), MovedModule("xmlrpc_server", "xmlrpclib", "xmlrpc.server"), MovedModule("winreg", "_winreg"), ] for attr in _moved_attributes: setattr(_MovedItems, attr.name, attr) if isinstance(attr, MovedModule): sys.modules[__name__ + ".moves." + attr.name] = attr del attr _MovedItems._moved_attributes = _moved_attributes moves = sys.modules[__name__ + ".moves"] = _MovedItems(__name__ + ".moves") class Module_six_moves_urllib_parse(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_parse""" _urllib_parse_moved_attributes = [ MovedAttribute("ParseResult", "urlparse", "urllib.parse"), MovedAttribute("SplitResult", "urlparse", "urllib.parse"), MovedAttribute("parse_qs", "urlparse", "urllib.parse"), MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), MovedAttribute("urldefrag", "urlparse", "urllib.parse"), MovedAttribute("urljoin", "urlparse", "urllib.parse"), MovedAttribute("urlparse", "urlparse", "urllib.parse"), MovedAttribute("urlsplit", "urlparse", "urllib.parse"), MovedAttribute("urlunparse", "urlparse", "urllib.parse"), MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), MovedAttribute("quote", "urllib", "urllib.parse"), MovedAttribute("quote_plus", "urllib", "urllib.parse"), MovedAttribute("unquote", "urllib", "urllib.parse"), MovedAttribute("unquote_plus", "urllib", "urllib.parse"), MovedAttribute("urlencode", "urllib", "urllib.parse"), MovedAttribute("splitquery", "urllib", "urllib.parse"), MovedAttribute("splittype", "urllib", "urllib.parse"), MovedAttribute("splithost", "urllib", "urllib.parse"), ] for attr in _urllib_parse_moved_attributes: setattr(Module_six_moves_urllib_parse, attr.name, attr) del attr Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes sys.modules[__name__ + ".moves.urllib_parse"] = sys.modules[__name__ + ".moves.urllib.parse"] = Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse") class Module_six_moves_urllib_error(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_error""" _urllib_error_moved_attributes = [ MovedAttribute("URLError", "urllib2", "urllib.error"), MovedAttribute("HTTPError", "urllib2", "urllib.error"), MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), ] for attr in _urllib_error_moved_attributes: setattr(Module_six_moves_urllib_error, attr.name, attr) del attr Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes sys.modules[__name__ + ".moves.urllib_error"] = sys.modules[__name__ + ".moves.urllib.error"] = Module_six_moves_urllib_error(__name__ + ".moves.urllib.error") class Module_six_moves_urllib_request(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_request""" _urllib_request_moved_attributes = [ MovedAttribute("urlopen", "urllib2", "urllib.request"), MovedAttribute("install_opener", "urllib2", "urllib.request"), MovedAttribute("build_opener", "urllib2", "urllib.request"), MovedAttribute("pathname2url", "urllib", "urllib.request"), MovedAttribute("url2pathname", "urllib", "urllib.request"), MovedAttribute("getproxies", "urllib", "urllib.request"), MovedAttribute("Request", "urllib2", "urllib.request"), MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), MovedAttribute("BaseHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), MovedAttribute("FileHandler", "urllib2", "urllib.request"), MovedAttribute("FTPHandler", "urllib2", "urllib.request"), MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), MovedAttribute("urlretrieve", "urllib", "urllib.request"), MovedAttribute("urlcleanup", "urllib", "urllib.request"), MovedAttribute("URLopener", "urllib", "urllib.request"), MovedAttribute("FancyURLopener", "urllib", "urllib.request"), MovedAttribute("proxy_bypass", "urllib", "urllib.request"), ] for attr in _urllib_request_moved_attributes: setattr(Module_six_moves_urllib_request, attr.name, attr) del attr Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes sys.modules[__name__ + ".moves.urllib_request"] = sys.modules[__name__ + ".moves.urllib.request"] = Module_six_moves_urllib_request(__name__ + ".moves.urllib.request") class Module_six_moves_urllib_response(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_response""" _urllib_response_moved_attributes = [ MovedAttribute("addbase", "urllib", "urllib.response"), MovedAttribute("addclosehook", "urllib", "urllib.response"), MovedAttribute("addinfo", "urllib", "urllib.response"), MovedAttribute("addinfourl", "urllib", "urllib.response"), ] for attr in _urllib_response_moved_attributes: setattr(Module_six_moves_urllib_response, attr.name, attr) del attr Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes sys.modules[__name__ + ".moves.urllib_response"] = sys.modules[__name__ + ".moves.urllib.response"] = Module_six_moves_urllib_response(__name__ + ".moves.urllib.response") class Module_six_moves_urllib_robotparser(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_robotparser""" _urllib_robotparser_moved_attributes = [ MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), ] for attr in _urllib_robotparser_moved_attributes: setattr(Module_six_moves_urllib_robotparser, attr.name, attr) del attr Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes sys.modules[__name__ + ".moves.urllib_robotparser"] = sys.modules[__name__ + ".moves.urllib.robotparser"] = Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser") class Module_six_moves_urllib(types.ModuleType): """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" parse = sys.modules[__name__ + ".moves.urllib_parse"] error = sys.modules[__name__ + ".moves.urllib_error"] request = sys.modules[__name__ + ".moves.urllib_request"] response = sys.modules[__name__ + ".moves.urllib_response"] robotparser = sys.modules[__name__ + ".moves.urllib_robotparser"] def __dir__(self): return ['parse', 'error', 'request', 'response', 'robotparser'] sys.modules[__name__ + ".moves.urllib"] = Module_six_moves_urllib(__name__ + ".moves.urllib") def add_move(move): """Add an item to six.moves.""" setattr(_MovedItems, move.name, move) def remove_move(name): """Remove item from six.moves.""" try: delattr(_MovedItems, name) except AttributeError: try: del moves.__dict__[name] except KeyError: raise AttributeError("no such move, %r" % (name,)) if PY3: _meth_func = "__func__" _meth_self = "__self__" _func_closure = "__closure__" _func_code = "__code__" _func_defaults = "__defaults__" _func_globals = "__globals__" _iterkeys = "keys" _itervalues = "values" _iteritems = "items" _iterlists = "lists" else: _meth_func = "im_func" _meth_self = "im_self" _func_closure = "func_closure" _func_code = "func_code" _func_defaults = "func_defaults" _func_globals = "func_globals" _iterkeys = "iterkeys" _itervalues = "itervalues" _iteritems = "iteritems" _iterlists = "iterlists" try: advance_iterator = next except NameError: def advance_iterator(it): return it.next() next = advance_iterator try: callable = callable except NameError: def callable(obj): return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) if PY3: def get_unbound_function(unbound): return unbound create_bound_method = types.MethodType Iterator = object else: def get_unbound_function(unbound): return unbound.im_func def create_bound_method(func, obj): return types.MethodType(func, obj, obj.__class__) class Iterator(object): def next(self): return type(self).__next__(self) callable = callable _add_doc(get_unbound_function, """Get the function out of a possibly unbound function""") get_method_function = operator.attrgetter(_meth_func) get_method_self = operator.attrgetter(_meth_self) get_function_closure = operator.attrgetter(_func_closure) get_function_code = operator.attrgetter(_func_code) get_function_defaults = operator.attrgetter(_func_defaults) get_function_globals = operator.attrgetter(_func_globals) def iterkeys(d, **kw): """Return an iterator over the keys of a dictionary.""" return iter(getattr(d, _iterkeys)(**kw)) def itervalues(d, **kw): """Return an iterator over the values of a dictionary.""" return iter(getattr(d, _itervalues)(**kw)) def iteritems(d, **kw): """Return an iterator over the (key, value) pairs of a dictionary.""" return iter(getattr(d, _iteritems)(**kw)) def iterlists(d, **kw): """Return an iterator over the (key, [values]) pairs of a dictionary.""" return iter(getattr(d, _iterlists)(**kw)) if PY3: def b(s): return s.encode("latin-1") def u(s): return s unichr = chr if sys.version_info[1] <= 1: def int2byte(i): return bytes((i,)) else: # This is about 2x faster than the implementation above on 3.2+ int2byte = operator.methodcaller("to_bytes", 1, "big") byte2int = operator.itemgetter(0) indexbytes = operator.getitem iterbytes = iter import io StringIO = io.StringIO BytesIO = io.BytesIO else: def b(s): return s # Workaround for standalone backslash def u(s): return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") unichr = unichr int2byte = chr def byte2int(bs): return ord(bs[0]) def indexbytes(buf, i): return ord(buf[i]) def iterbytes(buf): return (ord(byte) for byte in buf) import StringIO StringIO = BytesIO = StringIO.StringIO _add_doc(b, """Byte literal""") _add_doc(u, """Text literal""") if PY3: exec_ = getattr(moves.builtins, "exec") def reraise(tp, value, tb=None): if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value else: def exec_(_code_, _globs_=None, _locs_=None): """Execute code in a namespace.""" if _globs_ is None: frame = sys._getframe(1) _globs_ = frame.f_globals if _locs_ is None: _locs_ = frame.f_locals del frame elif _locs_ is None: _locs_ = _globs_ exec("""exec _code_ in _globs_, _locs_""") exec_("""def reraise(tp, value, tb=None): raise tp, value, tb """) print_ = getattr(moves.builtins, "print", None) if print_ is None: def print_(*args, **kwargs): """The new-style print function for Python 2.4 and 2.5.""" fp = kwargs.pop("file", sys.stdout) if fp is None: return def write(data): if not isinstance(data, basestring): data = str(data) # If the file has an encoding, encode unicode with it. if (isinstance(fp, file) and isinstance(data, unicode) and fp.encoding is not None): errors = getattr(fp, "errors", None) if errors is None: errors = "strict" data = data.encode(fp.encoding, errors) fp.write(data) want_unicode = False sep = kwargs.pop("sep", None) if sep is not None: if isinstance(sep, unicode): want_unicode = True elif not isinstance(sep, str): raise TypeError("sep must be None or a string") end = kwargs.pop("end", None) if end is not None: if isinstance(end, unicode): want_unicode = True elif not isinstance(end, str): raise TypeError("end must be None or a string") if kwargs: raise TypeError("invalid keyword arguments to print()") if not want_unicode: for arg in args: if isinstance(arg, unicode): want_unicode = True break if want_unicode: newline = unicode("\n") space = unicode(" ") else: newline = "\n" space = " " if sep is None: sep = space if end is None: end = newline for i, arg in enumerate(args): if i: write(sep) write(arg) write(end) _add_doc(reraise, """Reraise an exception.""") def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" return meta("NewBase", bases, {}) def add_metaclass(metaclass): """Class decorator for creating a class with a metaclass.""" def wrapper(cls): orig_vars = cls.__dict__.copy() orig_vars.pop('__dict__', None) orig_vars.pop('__weakref__', None) slots = orig_vars.get('__slots__') if slots is not None: if isinstance(slots, str): slots = [slots] for slots_var in slots: orig_vars.pop(slots_var) return metaclass(cls.__name__, cls.__bases__, orig_vars) return wrapper spyne-2.11.0/spyne/util/sqlalchemy.py0000644000175000001440000011775012345433230017545 0ustar plqusers00000000000000 # # 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 # """Just for Postgresql, just for fun. As of yet, at least. In case it's not obvious, this module is EXPERIMENTAL. """ from __future__ import absolute_import, print_function import logging logger = logging.getLogger(__name__) import os import shutil import sqlalchemy from mmap import mmap, ACCESS_READ from spyne.util.six import string_types try: import simplejson as json except ImportError: import json from spyne.util import six from os import fstat from os.path import join, isabs, abspath, dirname, basename, isfile from uuid import uuid1 from inspect import isclass from lxml import etree from lxml import html from sqlalchemy import sql from sqlalchemy import event from sqlalchemy.schema import Column from sqlalchemy.schema import Index from sqlalchemy.schema import Table from sqlalchemy.schema import ForeignKey from sqlalchemy.orm import _mapper_registry from sqlalchemy.dialects.postgresql import FLOAT from sqlalchemy.dialects.postgresql import DOUBLE_PRECISION from sqlalchemy.dialects.postgresql.base import PGUuid from sqlalchemy.ext.compiler import compiles from sqlalchemy.orm import relationship from sqlalchemy.orm import mapper from sqlalchemy.orm.util import class_mapper from sqlalchemy.orm.exc import UnmappedClassError from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.types import UserDefinedType from spyne.error import ValidationError # internal types from spyne.model.enum import EnumBase from spyne.model.complex import XmlModifier, ComplexModel # Config types from spyne.model.complex import xml as c_xml from spyne.model.complex import json as c_json from spyne.model.complex import table as c_table from spyne.model.complex import msgpack as c_msgpack from spyne.model.binary import HybridFileStore # public types from spyne.model import SimpleModel, AnyDict from spyne.model import Enum from spyne.model import ByteArray from spyne.model import Array from spyne.model import ComplexModelBase from spyne.model import AnyXml from spyne.model import AnyHtml from spyne.model import Uuid from spyne.model import Date from spyne.model import Time from spyne.model import DateTime from spyne.model import Float from spyne.model import Double from spyne.model import Decimal from spyne.model import String from spyne.model import Unicode from spyne.model import Boolean from spyne.model import Integer from spyne.model import Integer8 from spyne.model import Integer16 from spyne.model import Integer32 from spyne.model import Integer64 from spyne.model import Point from spyne.model import Line from spyne.model import Polygon from spyne.model import MultiPoint from spyne.model import MultiLine from spyne.model import MultiPolygon from spyne.model import UnsignedInteger from spyne.model import UnsignedInteger8 from spyne.model import UnsignedInteger16 from spyne.model import UnsignedInteger32 from spyne.model import UnsignedInteger64 from spyne.model import File from spyne.util import sanitize_args from spyne.util.xml import get_object_as_xml from spyne.util.xml import get_xml_as_object from spyne.util.dictdoc import get_dict_as_object, JsonDocument from spyne.util.dictdoc import get_object_as_json # Inheritance type constants. class _SINGLE: pass class _JOINED: pass def own_mapper(cls): try: return class_mapper(cls) except UnmappedClassError: return mapper _sq2sp_type_map = { sqlalchemy.Text: String, sqlalchemy.String: String, sqlalchemy.Unicode: String, sqlalchemy.UnicodeText: String, sqlalchemy.Float: Float, sqlalchemy.Numeric: Decimal, sqlalchemy.BigInteger: Integer, sqlalchemy.Integer: Integer, sqlalchemy.SmallInteger: Integer, sqlalchemy.Binary: ByteArray, sqlalchemy.LargeBinary: ByteArray, sqlalchemy.Boolean: Boolean, sqlalchemy.DateTime: DateTime, sqlalchemy.Date: Date, sqlalchemy.Time: Time, PGUuid: Uuid } # this needs to be called whenever a new column is instantiated. def _sp_attrs_to_sqla_constraints(cls, v, col_kwargs=None, col=None): # cls is the parent class of v if v.Attributes.nullable == False and cls.__extends__ is None: if col is None: col_kwargs['nullable'] = False else: col.nullable = False @compiles(PGUuid, "sqlite") def compile_uuid_sqlite(type_, compiler, **kw): return "BLOB" class PGGeometry(UserDefinedType): """Geometry type for Postgis 2""" class PlainWkt:pass class PlainWkb:pass def __init__(self, geometry_type='GEOMETRY', srid=4326, dimension=2, format='wkt'): self.geometry_type = geometry_type.upper() self.name = 'geometry' self.srid = int(srid) self.dimension = dimension self.format = format if self.format == 'wkt': self.format = PGGeometry.PlainWkt elif self.format == 'wkb': self.format = PGGeometry.PlainWkb def get_col_spec(self): return '%s(%s,%d)' % (self.name, self.geometry_type, self.srid) def column_expression(self, col): if self.format is PGGeometry.PlainWkb: return sql.func.ST_AsBinary(col, type_=self) if self.format is PGGeometry.PlainWkt: return sql.func.ST_AsText(col, type_=self) def result_processor(self, dialect, coltype): if self.format is PGGeometry.PlainWkt: def process(value): if value is not None: return value if self.format is PGGeometry.PlainWkb: def process(value): if value is not None: return sql.func.ST_AsBinary(value, self.srid) return process def bind_expression(self, bindvalue): if self.format is PGGeometry.PlainWkt: return sql.func.ST_GeomFromText(bindvalue, self.srid) Geometry = PGGeometry @compiles(PGGeometry) def compile_geometry(type_, compiler, **kw): return '%s(%s,%d)' % (type_.name, type_.geometry_type, type_.srid) @compiles(PGGeometry, "sqlite") def compile_geometry_sqlite(type_, compiler, **kw): return "BLOB" class PGXml(UserDefinedType): def __init__(self, pretty_print=False, xml_declaration=False, encoding='UTF-8'): super(PGXml, self).__init__() self.xml_declaration = xml_declaration self.pretty_print = pretty_print self.encoding = encoding def get_col_spec(self): return "xml" def bind_processor(self, dialect): def process(value): if isinstance(value, str) or value is None: return value else: return etree.tostring(value, pretty_print=self.pretty_print, encoding=self.encoding, xml_declaration=False) return process def result_processor(self, dialect, col_type): def process(value): if value is not None: return etree.fromstring(value) else: return value return process sqlalchemy.dialects.postgresql.base.ischema_names['xml'] = PGXml class PGHtml(UserDefinedType): def __init__(self, pretty_print=False, encoding='UTF-8'): super(PGHtml, self).__init__() self.pretty_print = pretty_print self.encoding = encoding def get_col_spec(self): return "text" def bind_processor(self, dialect): def process(value): if isinstance(value, string_types) or value is None: return value else: return html.tostring(value, pretty_print=self.pretty_print, encoding=self.encoding) return process def result_processor(self, dialect, col_type): def process(value): if value is not None and len(value) > 0: return html.fromstring(value) else: return None return process class PGJson(UserDefinedType): def __init__(self, encoding='UTF-8'): self.encoding = encoding def get_col_spec(self): return "json" def bind_processor(self, dialect): def process(value): if isinstance(value, six.string_types) or value is None: return value else: return json.dumps(value, encoding=self.encoding) return process def result_processor(self, dialect, col_type): def process(value): if isinstance(value, string_types): return json.loads(value) else: return value return process sqlalchemy.dialects.postgresql.base.ischema_names['json'] = PGJson class PGObjectXml(UserDefinedType): def __init__(self, cls, root_tag_name=None, no_namespace=False): self.cls = cls self.root_tag_name = root_tag_name self.no_namespace = no_namespace def get_col_spec(self): return "xml" def bind_processor(self, dialect): def process(value): return etree.tostring(get_object_as_xml(value, self.cls, self.root_tag_name, self.no_namespace), pretty_print=False, encoding='utf8', xml_declaration=False) return process def result_processor(self, dialect, col_type): def process(value): if value is not None: return get_xml_as_object(etree.fromstring(value), self.cls) return process class PGObjectJson(UserDefinedType): def __init__(self, cls, ignore_wrappers=True, complex_as=dict): self.cls = cls self.ignore_wrappers = ignore_wrappers self.complex_as = complex_as def get_col_spec(self): return "json" def bind_processor(self, dialect): def process(value): if value is not None: return get_object_as_json(value, self.cls, ignore_wrappers=self.ignore_wrappers, complex_as=self.complex_as, ) return process def result_processor(self, dialect, col_type): def process(value): if isinstance(value, six.string_types): return get_dict_as_object(json.loads(value), self.cls, ignore_wrappers=self.ignore_wrappers, complex_as=self.complex_as, protocol=JsonDocument, ) if value is not None: return get_dict_as_object(value, self.cls, ignore_wrappers=self.ignore_wrappers, complex_as=self.complex_as, protocol=JsonDocument, ) return process class PGFileJson(PGObjectJson): class FileData(ComplexModel): _type_info = [ ('name', Unicode), ('type', Unicode), ('path', Unicode), ] def __init__(self, store): super(PGFileJson, self).__init__(PGFileJson.FileData, ignore_wrappers=True, complex_as=list) self.store = store def bind_processor(self, dialect): def process(value): if value is not None: if value.data is not None: value.path = uuid1().get_hex() fp = join(self.store, value.path) if not abspath(fp).startswith(self.store): raise ValidationError(value.path, "Path %r contains " "relative path operators (e.g. '..')") with open(fp, 'wb') as file: for d in value.data: file.write(d) elif value.handle is not None: value.path = uuid1().get_hex() fp = join(self.store, value.path) if not abspath(fp).startswith(self.store): raise ValidationError(value.path, "Path %r contains " "relative path operators (e.g. '..')") data = mmap(value.handle.fileno(), 0) # 0 = whole file with open(fp, 'wb') as out_file: out_file.write(data) elif value.path is not None: in_file_path = value.path if not isfile(in_file_path): logger.error("File path in %r not found" % value) if dirname(abspath(in_file_path)) != self.store: dest = join(self.store, uuid1().get_hex()) if value.move: shutil.move(in_file_path, dest) print("move", in_file_path, dest) else: shutil.copy(in_file_path, dest) value.path = basename(dest) value.abspath = dest else: raise ValueError("Invalid file object passed in. All of " ".data .handle and .path are None.") value.store = self.store value.abspath = join(self.store, value.path) retval = get_object_as_json(value, self.cls, ignore_wrappers=self.ignore_wrappers, complex_as=self.complex_as, ) return retval return process def result_processor(self, dialect, col_type): def process(value): retval = None if isinstance(value, six.string_types): value = json.loads(value) if value is not None: retval = get_dict_as_object(value, self.cls, ignore_wrappers=self.ignore_wrappers, complex_as=self.complex_as) retval.store = self.store retval.abspath = path = join(self.store, retval.path) ret = os.access(path, os.R_OK) retval.handle = None retval.data = [''] if ret: retval.handle = open(path, 'rb') if fstat(retval.handle.fileno()).st_size > 0: retval.data = [mmap(retval.handle.fileno(), 0, access=ACCESS_READ)] else: logger.error("File %r is not readable", path) return retval return process def get_sqlalchemy_type(cls): db_type = cls.Attributes.db_type if db_type is not None: return db_type # must be above Unicode, because Uuid is Unicode's subclass if issubclass(cls, Uuid): return PGUuid(as_uuid=True) # must be above Unicode, because Point is Unicode's subclass elif issubclass(cls, Point): return PGGeometry("POINT", dimension=cls.Attributes.dim) # must be above Unicode, because Line is Unicode's subclass elif issubclass(cls, Line): return PGGeometry("LINESTRING", dimension=cls.Attributes.dim) # must be above Unicode, because Polygon is Unicode's subclass elif issubclass(cls, Polygon): return PGGeometry("POLYGON", dimension=cls.Attributes.dim) # must be above Unicode, because MultiPoint is Unicode's subclass elif issubclass(cls, MultiPoint): return PGGeometry("MULTIPOINT", dimension=cls.Attributes.dim) # must be above Unicode, because MultiLine is Unicode's subclass elif issubclass(cls, MultiLine): return PGGeometry("MULTILINESTRING", dimension=cls.Attributes.dim) # must be above Unicode, because MultiPolygon is Unicode's subclass elif issubclass(cls, MultiPolygon): return PGGeometry("MULTIPOLYGON", dimension=cls.Attributes.dim) # must be above Unicode, because String is Unicode's subclass elif issubclass(cls, String): if cls.Attributes.max_len == String.Attributes.max_len: # Default is arbitrary-length return sqlalchemy.Text else: return sqlalchemy.String(cls.Attributes.max_len) elif issubclass(cls, Unicode): if cls.Attributes.max_len == Unicode.Attributes.max_len: # Default is arbitrary-length return sqlalchemy.UnicodeText else: return sqlalchemy.Unicode(cls.Attributes.max_len) elif issubclass(cls, EnumBase): return sqlalchemy.Enum(*cls.__values__, name=cls.__type_name__) elif issubclass(cls, AnyXml): return PGXml elif issubclass(cls, AnyHtml): return PGHtml elif issubclass(cls, AnyDict): sa = cls.Attributes.store_as if isinstance(sa, c_json): return PGJson raise NotImplementedError(dict(cls=AnyDict, store_as=sa)) elif issubclass(cls, ByteArray): return sqlalchemy.LargeBinary elif issubclass(cls, (Integer64, UnsignedInteger64)): return sqlalchemy.BigInteger elif issubclass(cls, (Integer32, UnsignedInteger32)): return sqlalchemy.Integer elif issubclass(cls, (Integer16, UnsignedInteger16)): return sqlalchemy.SmallInteger elif issubclass(cls, (Integer8, UnsignedInteger8)): return sqlalchemy.SmallInteger elif issubclass(cls, Float): return FLOAT elif issubclass(cls, Double): return DOUBLE_PRECISION elif issubclass(cls, (Integer, UnsignedInteger)): return sqlalchemy.DECIMAL elif issubclass(cls, Decimal): return sqlalchemy.DECIMAL elif issubclass(cls, Boolean): return sqlalchemy.Boolean elif issubclass(cls, Date): return sqlalchemy.Date elif issubclass(cls, DateTime): if cls.Attributes.timezone is None: if cls.Attributes.as_time_zone is None: return sqlalchemy.DateTime(timezone=True) else: return sqlalchemy.DateTime(timezone=False) else: return sqlalchemy.DateTime(timezone=cls.Attributes.timezone) elif issubclass(cls, Time): return sqlalchemy.Time elif issubclass(cls, XmlModifier): retval = get_sqlalchemy_type(cls.type) return retval def get_pk_columns(cls): """Return primary key fields of a Spyne object.""" retval = [] for k, v in cls.get_flat_type_info(cls).items(): if v.Attributes.sqla_column_args is not None and \ v.Attributes.sqla_column_args[-1].get('primary_key', False): retval.append((k,v)) return tuple(retval) if len(retval) > 0 else None def _get_col_o2o(parent, k, v, fk_col_name): """Gets key and child type and returns a column that points to the primary key of the child. """ assert v.Attributes.table_name is not None, "%r has no table name." % v col_args, col_kwargs = sanitize_args(v.Attributes.sqla_column_args) _sp_attrs_to_sqla_constraints(parent, v, col_kwargs) # get pkeys from child class pk_column, = get_pk_columns(v) # FIXME: Support multi-col keys pk_key, pk_spyne_type = pk_column pk_sqla_type = get_sqlalchemy_type(pk_spyne_type) # generate a fk to it from the current object (cls) if fk_col_name is None: fk_col_name = k + "_" + pk_key fk = ForeignKey('%s.%s' % (v.Attributes.table_name, pk_key), use_alter=True, name='%s_%s_fkey' % (v.Attributes.table_name, fk_col_name)) return Column(fk_col_name, pk_sqla_type, fk, *col_args, **col_kwargs) def _get_col_o2m(cls, fk_col_name): """Gets the parent class and returns a column that points to the primary key of the parent. Funky implementation. Yes. """ assert cls.Attributes.table_name is not None, "%r has no table name." % cls col_args, col_kwargs = sanitize_args(cls.Attributes.sqla_column_args) # get pkeys from current class pk_column, = get_pk_columns(cls) # FIXME: Support multi-col keys pk_key, pk_spyne_type = pk_column pk_sqla_type = get_sqlalchemy_type(pk_spyne_type) # generate a fk from child to the current class if fk_col_name is None: fk_col_name = '_'.join([cls.Attributes.table_name, pk_key]) # we jump through all these hoops because we must instantiate the Column # only after we're sure that it doesn't already exist and also because # tinkering with functors is always fun :) yield [(fk_col_name, pk_sqla_type)] col = Column(fk_col_name, pk_sqla_type, ForeignKey('%s.%s' % (cls.Attributes.table_name, pk_key)), *col_args, **col_kwargs) yield col def _get_cols_m2m(cls, k, child, left_fk_col_name, right_fk_col_name): """Gets the parent and child classes and returns foreign keys to both tables. These columns can be used to create a relation table.""" col_info, left_col = _get_col_o2m(cls, left_fk_col_name) right_col = _get_col_o2o(cls, k, child, right_fk_col_name) left_col.primary_key = right_col.primary_key = True return left_col, right_col class _FakeTable(object): def __init__(self, name): self.name = name self.c = {} self.columns = [] self.indexes = [] def append_column(self, col): self.columns.append(col) self.c[col.name] = col def _gen_index_info(table, col, k, v): """ :param table: sqla table :param col: sqla col :param k: field name (not necessarily == k) :param v: spyne type """ unique = v.Attributes.unique index = v.Attributes.index if unique and not index: index = True try: index_name, index_method = v.Attributes.index except (TypeError, ValueError): index_name = "%s_%s%s" % (table.name, k, '_unique' if unique else '') index_method = v.Attributes.index if index in (False, None): pass else: if index is True: index_args = (index_name, col), dict(unique=unique) else: index_args = (index_name, col), dict(unique=unique, postgresql_using=index_method) if isinstance(table, _FakeTable): table.indexes.append(index_args) else: indexes = dict([(idx.name, idx) for idx in col.table.indexes]) existing_idx = indexes.get(index_name, None) if existing_idx is None: Index(*index_args[0], **index_args[1]) else: assert existing_idx.unique == unique assert existing_idx.kwargs.get('postgresql_using') == index_method def _check_inheritance(cls, cls_bases): table_name = cls.Attributes.table_name inc = [] inheritance = None base_class = getattr(cls, '__extends__', None) if base_class is None: for b in cls_bases: if getattr(b, '_type_info', None) is not None and b.__mixin__: base_class = b if base_class is not None: base_table_name = base_class.Attributes.table_name if base_table_name is not None: if base_table_name == table_name: inheritance = _SINGLE else: inheritance = _JOINED raise NotImplementedError("Joined table inheritance is not yet " "implemented.") inc_prop = base_class.Attributes.sqla_mapper.include_properties if inc_prop is not None: inc.extend(inc_prop) exc_prop = base_class.Attributes.sqla_mapper.exclude_properties if exc_prop is not None: inc = [_p for _p in inc if not _p in exc_prop] # check whether the base classes are already mapped base_mapper = None if base_class is not None: base_mapper = base_class.Attributes.sqla_mapper if base_mapper is None: for b in cls_bases: bm = _mapper_registry.get(b, None) if bm is not None: assert base_mapper is None, "There can be only one base mapper." base_mapper = bm inheritance = _SINGLE return inheritance, base_class, base_mapper, inc def _check_table(cls): table_name = cls.Attributes.table_name metadata = cls.Attributes.sqla_metadata # check whether the object already has a table table = None if table_name in metadata.tables: table = metadata.tables[table_name] else: # We need FakeTable because table_args can contain all sorts of stuff # that can require a fully-constructed table, and we don't have that # information here yet. table = _FakeTable(table_name) return table def _add_simple_type(cls, props, table, k, v, sqla_type): col_args, col_kwargs = sanitize_args(v.Attributes.sqla_column_args) _sp_attrs_to_sqla_constraints(cls, v, col_kwargs) mp = getattr(v.Attributes, 'mapper_property', None) if not v.Attributes.exc_table: if k in table.c: col = table.c[k] else: col = Column(k, sqla_type, *col_args, **col_kwargs) table.append_column(col) _gen_index_info(table, col, k, v) if not v.Attributes.exc_mapper: props[k] = col elif mp is not None: props[k] = mp def _gen_array_m2m(cls, props, k, child, p): metadata = cls.Attributes.sqla_metadata col_own, col_child = _get_cols_m2m(cls, k, child, p.left, p.right) p.left = col_own.key p.right = col_child.key if p.multi == True: rel_table_name = '_'.join([cls.Attributes.table_name, k]) else: rel_table_name = p.multi # FIXME: Handle the case where the table already exists. rel_t = Table(rel_table_name, metadata, *(col_own, col_child)) props[k] = relationship(child, secondary=rel_t, backref=p.backref, back_populates=p.back_populates, cascade=p.cascade, lazy=p.lazy) def _gen_array_simple(cls, props, k, child_cust, p): table_name = cls.Attributes.table_name metadata = cls.Attributes.sqla_metadata # get left (fk) column info _gen_col = _get_col_o2m(cls, p.left) col_info = next(_gen_col) # gets the column name p.left, child_left_col_type = col_info[0] # FIXME: Add support for multi-column primary keys. child_left_col_name = p.left # get right(data) column info child_right_col_type = get_sqlalchemy_type(child_cust) child_right_col_name = p.right # this is the data column if child_right_col_name is None: child_right_col_name = k # get table name child_table_name = child_cust.Attributes.table_name if child_table_name is None: child_table_name = '_'.join([table_name, k]) if child_table_name in metadata.tables: child_t = metadata.tables[child_table_name] # if we have the table, we sure have the right column (data column) assert child_right_col_type.__class__ is \ child_t.c[child_right_col_name].type.__class__, "%s.%s: %r != %r" % \ (cls, child_right_col_name, child_right_col_type.__class__, child_t.c[child_right_col_name].type.__class__) # Table exists but our own foreign key doesn't. if child_left_col_name in child_t.c: assert child_left_col_type.__class__ is \ child_t.c[child_left_col_name].type.__class__, "%r != %r" % \ (child_left_col_type.__class__, child_t.c[child_left_col_name].type.__class__) else: child_left_col = next(_gen_col) _sp_attrs_to_sqla_constraints(cls, child_cust, col=child_left_col) child_t.append_column(child_left_col) else: # table does not exist, generate table child_right_col = Column(child_right_col_name, child_right_col_type) _sp_attrs_to_sqla_constraints(cls, child_cust, col=child_right_col) child_left_col = next(_gen_col) _sp_attrs_to_sqla_constraints(cls, child_cust, col=child_left_col) child_t = Table(child_table_name , metadata, Column('id', sqlalchemy.Integer, primary_key=True), child_left_col, child_right_col, ) _gen_index_info(child_t, child_right_col, child_right_col_name, child_cust) # generate temporary class for association proxy cls_name = ''.join(x.capitalize() or '_' for x in child_table_name.split('_')) # generates camelcase class name. def _i(self, *args): setattr(self, child_right_col_name, args[0]) cls_ = type("_" + cls_name, (object,), {'__init__': _i}) own_mapper(cls_)(cls_, child_t) props["_" + k] = relationship(cls_) # generate association proxy setattr(cls, k, association_proxy("_" + k, child_right_col_name)) def _gen_array_o2m(cls, props, k, child, child_cust, p): _gen_col = _get_col_o2m(cls, p.right) col_info = next(_gen_col) # gets the column name p.right, col_type = col_info[0] # FIXME: Add support for multi-column primary keys. assert p.left is None, \ "'left' is ignored in one-to-many relationships " \ "with complex types (because they already have a " \ "table). You probably meant to use 'right'." child_t = child.__table__ if p.right in child_t.c: # FIXME: This branch MUST be tested. new_col_type = child_t.c[p.right].type.__class__ assert col_type is child_t.c[p.right].type.__class__, "Existing column " \ "type %r disagrees with new column type %r" % \ (col_type, new_col_type) # if the column is already there, the decision about whether # it should be in child's mapper or not should also have been # made. # # so, not adding the child column to to child mapper # here. col = child_t.c[p.right] else: col = next(_gen_col) _sp_attrs_to_sqla_constraints(cls, child_cust, col=col) child_t.append_column(col) child.__mapper__.add_property(col.name, col) props[k] = relationship(child, foreign_keys=[col], backref=p.backref, back_populates=p.back_populates, cascade=p.cascade, lazy=p.lazy) def _is_array(v): return (v.Attributes.max_occurs > 1 or issubclass(v, Array)) def _add_complex_type(cls, props, table, k, v): if issubclass(v, File): return _add_file_type(cls, props, table, k, v) p = getattr(v.Attributes, 'store_as', None) col_args, col_kwargs = sanitize_args(v.Attributes.sqla_column_args) _sp_attrs_to_sqla_constraints(cls, v, col_kwargs) if isinstance(p, c_table): if _is_array(v): child_cust = v if issubclass(v, Array): child_cust, = v._type_info.values() child = child_cust if child_cust.__orig__ is not None: child = child_cust.__orig__ if p.multi != False: # many to many _gen_array_m2m(cls, props, k, child, p) elif issubclass(child, SimpleModel): # one to many simple type _gen_array_simple(cls, props, k, child_cust, p) else: # one to many complex type _gen_array_o2m(cls, props, k, child, child_cust, p) else: # v has the Attribute values we need whereas real_v is what the # user instantiates (thus what sqlalchemy needs) if v.__orig__ is None: # vanilla class real_v = v else: # customized class real_v = v.__orig__ assert not getattr(p, 'multi', False), ( 'Storing a single element-type using a ' 'relation table is pointless.') assert p.right is None, "'right' is ignored in a one-to-one " \ "relationship" col = _get_col_o2o(cls, k, v, p.left) p.left = col.name if col.name in table.c: col = table.c[col.name] if col_kwargs.get('nullable') is False: col.nullable = False else: table.append_column(col) rel = relationship(real_v, uselist=False, cascade=p.cascade, foreign_keys=[col], back_populates=p.back_populates, backref=p.backref, lazy=p.lazy) _gen_index_info(table, col, k, v) props[k] = rel props[col.name] = col elif isinstance(p, c_xml): if k in table.c: col = table.c[k] else: t = PGObjectXml(v, p.root_tag, p.no_ns) col = Column(k, t, *col_args, **col_kwargs) props[k] = col if not k in table.c: table.append_column(col) elif isinstance(p, c_json): if k in table.c: col = table.c[k] else: t = PGObjectJson(v, ignore_wrappers=p.ignore_wrappers, complex_as=p.complex_as) col = Column(k, t, *col_args, **col_kwargs) props[k] = col if not k in table.c: table.append_column(col) elif isinstance(p, c_msgpack): raise NotImplementedError(c_msgpack) elif p is None: pass else: raise ValueError(p) def _convert_fake_table(cls, table): metadata = cls.Attributes.sqla_metadata table_name = cls.Attributes.table_name _table = table table_args, table_kwargs = sanitize_args(cls.Attributes.sqla_table_args) table = Table(table_name, metadata, *(tuple(table.columns) + table_args), **table_kwargs) for index_args, index_kwargs in _table.indexes: Index(*index_args, **index_kwargs) return table def _gen_mapper(cls, props, table, cls_bases): """ :param cls: La Class. :param props: a dict. :param table: a Table instance. Not a _FakeTable. :param cls_bases: Class bases. """ inheritance, base_class, base_mapper, inc = _check_inheritance(cls, cls_bases) mapper_args, mapper_kwargs = sanitize_args(cls.Attributes.sqla_mapper_args) _props = mapper_kwargs.get('properties', None) if _props is None: mapper_kwargs['properties'] = props else: props.update(_props) mapper_kwargs['properties'] = props _inc = mapper_kwargs.get('include_properties', None) if _inc is None: mapper_kwargs['include_properties'] = inc + list(props.keys()) po = mapper_kwargs.get('polymorphic_on', None) if po is not None: if not isinstance(po, Column): mapper_kwargs['polymorphic_on'] = table.c[po] else: del mapper_kwargs['polymorphic_on'] if base_mapper is not None: mapper_kwargs['inherits'] = base_mapper if inheritance is not _SINGLE: mapper_args = (table,) + mapper_args cls_mapper = mapper(cls, *mapper_args, **mapper_kwargs) def on_load(target, context): d = target.__dict__ for k, v in cls.get_flat_type_info(cls).items(): if not k in d: if isclass(v) and issubclass(v, ComplexModelBase): pass else: d[k] = None event.listen(cls, 'load', on_load) return cls_mapper def _add_file_type(cls, props, table, k, v): p = getattr(v.Attributes, 'store_as', None) col_args, col_kwargs = sanitize_args(v.Attributes.sqla_column_args) _sp_attrs_to_sqla_constraints(cls, v, col_kwargs) if isinstance(p, HybridFileStore): if k in table.c: col = table.c[k] else: assert isabs(p.store) #FIXME: Add support for storage markers from spyne.model.complex if p.db_format == 'json': t = PGFileJson(p.store) else: raise NotImplementedError(p.db_format) col = Column(k, t, *col_args, **col_kwargs) props[k] = col if not k in table.c: table.append_column(col) else: raise NotImplementedError(p) def add_column(cls, k, v): """Add field to the given Spyne object also mapped as a SQLAlchemy object to a SQLAlchemy table :param cls: The class to add the column to. :param k: The column name :param v: The column type, a ModelBase subclass. """ table = cls.__table__ mapper_props = {} # Add to table t = get_sqlalchemy_type(v) if t is None: # complex model _add_complex_type(cls, mapper_props, table, k, v) else: _add_simple_type(cls, mapper_props, table, k, v, t) # Add to mapper sqla_mapper = cls.Attributes.sqla_mapper for k,v in mapper_props.items(): if not sqla_mapper.has_property(k): sqla_mapper.add_property(k, v) def gen_sqla_info(cls, cls_bases=()): """Return SQLAlchemy table object corresponding to the passed Spyne object. Also maps given class to the returned table. """ table = _check_table(cls) mapper_props = {} for k, v in cls._type_info.items(): t = get_sqlalchemy_type(v) if t is None: # complex model p = getattr(v.Attributes, 'store_as', None) if p is None: logger.debug("Skipping %s.%s.%s: %r, store_as: %r" % ( cls.get_namespace(), cls.get_type_name(), k, v, p)) else: _add_complex_type(cls, mapper_props, table, k, v) else: _add_simple_type(cls, mapper_props, table, k, v, t) if isinstance(table, _FakeTable): table = _convert_fake_table(cls, table) cls_mapper = _gen_mapper(cls, mapper_props, table, cls_bases) cls.__tablename__ = cls.Attributes.table_name cls.Attributes.sqla_mapper = cls.__mapper__ = cls_mapper cls.Attributes.sqla_table = cls.__table__ = table return table def get_spyne_type(v): """This function maps sqlalchemy types to spyne types.""" rpc_type = None if isinstance(v.type, sqlalchemy.Enum): if v.type.convert_unicode: rpc_type = Unicode(values=v.type.enums) else: rpc_type = Enum(*v.type.enums, **{'type_name': v.type.name}) elif isinstance(v.type, sqlalchemy.Unicode): rpc_type = Unicode(v.type.length) elif isinstance(v.type, sqlalchemy.UnicodeText): rpc_type = Unicode elif isinstance(v.type, sqlalchemy.Text): rpc_type = String elif isinstance(v.type, sqlalchemy.String): rpc_type = String(v.type.length) elif isinstance(v.type, (sqlalchemy.Numeric)): rpc_type = Decimal(v.type.precision, v.type.scale) elif isinstance(v.type, PGXml): rpc_type = AnyXml elif isinstance(v.type, PGHtml): rpc_type = AnyHtml elif type(v.type) in _sq2sp_type_map: rpc_type = _sq2sp_type_map[type(v.type)] else: raise Exception("Spyne type was not found. Probably _sq2sp_type_map " "needs a new entry. %r" % v) return rpc_type def gen_spyne_info(cls): table = cls.Attributes.sqla_table _type_info = cls._type_info mapper_args, mapper_kwargs = sanitize_args(cls.Attributes.sqla_mapper_args) if len(_type_info) == 0: for c in table.c: _type_info[c.name] = get_spyne_type(c) else: mapper_kwargs['include_properties'] = _type_info.keys() # Map the table to the object cls_mapper = own_mapper(cls)(cls, table, *mapper_args, **mapper_kwargs) cls.Attributes.table_name = cls.__tablename__ = table.name cls.Attributes.sqla_mapper = cls.__mapper__ = cls_mapper spyne-2.11.0/spyne/util/test.py0000644000175000001440000000400112345433230016342 0ustar plqusers00000000000000# encoding: utf8 # # 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 # from pprint import pformat try: from urllib.parse import urlencode except ImportError: # Python 2 from urllib import urlencode def _start_response(code, headers): print(code, pformat(headers)) def call_wsgi_app_kwargs(app, _mn='some_call', _headers=None, **kwargs): return call_wsgi_app(app, _mn, _headers, kwargs.items()) def call_wsgi_app(app, mn='some_call', headers=None, body_pairs=None): if headers is None: headers = {} if body_pairs is None: body_pairs = [] body_pairs = [(k,str(v)) for k,v in body_pairs] request = { 'QUERY_STRING': urlencode(body_pairs), 'PATH_INFO': '/%s' % mn, 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'spyne.test', 'SERVER_PORT': '0', 'wsgi.url_scheme': 'http', } print(headers) request.update(headers) out_string = ''.join(app(request, _start_response)) return out_string from os import mkdir from os.path import join def show(elt, tn): from lxml import html, etree out_string = etree.tostring(elt, pretty_print=True) print(out_string) try: mkdir('html') except OSError: pass open(join("html", '%s.html' % tn), 'w').write(html.tostring(elt, pretty_print=True)) spyne-2.11.0/spyne/util/toposort.py0000644000175000001440000000367112345433230017270 0ustar plqusers00000000000000# http://code.activestate.com/recipes/577413-topological-sort/ # # The MIT License (MIT) # # Copyright (c) ActiveState.com # # 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. try: from functools import reduce except: pass def toposort2(data): for k, v in data.items(): v.discard(k) # Ignore self dependencies # add items that are listed as dependencies but not as dependents to data extra_items_in_deps = reduce(set.union, data.values()) - set(data.keys()) data.update(dict([(item,set()) for item in extra_items_in_deps])) while True: ordered = set(item for item,dep in data.items() if len(dep) == 0) if len(ordered) == 0: break yield sorted(ordered, key=lambda x:repr(x)) data = dict([(item, (dep - ordered)) for item,dep in data.items() if item not in ordered]) assert not data, "A cyclic dependency exists amongst %r" % data spyne-2.11.0/spyne/util/web.py0000644000175000001440000003055712352126507016163 0ustar plqusers00000000000000 # # 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 # """ An opinionated web framework built on top of Spyne, SQLAlchemy and Twisted. If you're using this for anything serious, you're insane. """ from __future__ import absolute_import from inspect import isclass from spyne.util import six from spyne import BODY_STYLE_WRAPPED, rpc from spyne.application import Application as AppBase from spyne.const import MAX_STRING_FIELD_LENGTH, MAX_FIELD_NUM from spyne.const import MAX_ARRAY_ELEMENT_NUM from spyne.error import Fault from spyne.error import InternalError from spyne.error import ResourceNotFoundError from spyne.service import ServiceBase from spyne.util import memoize from spyne.util.email import email_exception from spyne.model import Mandatory as M, UnsignedInteger32, PushBase, Iterable, \ ModelBase, File from spyne.model import Unicode from spyne.model import Array from spyne.model import ComplexModelBase from spyne.util.sqlalchemy import PGFileJson from sqlalchemy.orm import sessionmaker from sqlalchemy.orm.exc import NoResultFound from twisted.python import log from twisted.python.threadpool import ThreadPool from twisted.internet import reactor from twisted.internet.threads import deferToThreadPool EXCEPTION_ADDRESS = None try: import colorama colorama.init() from colorama.ansi import Fore from colorama.ansi import Style RED = Fore.RED + Style.BRIGHT GREEN = Fore.GREEN + Style.BRIGHT RESET = Style.RESET_ALL except ImportError: RED = "" GREEN = "" RESET = "" class ReaderServiceBase(ServiceBase): pass class WriterServiceBase(ServiceBase): pass def _on_method_call(ctx): ctx.udc = Context(ctx.app.db, ctx.app.Session) def _on_method_context_closed(ctx): error = None if ctx.in_error is not None: error = ctx.in_error elif ctx.out_error is not None: error = ctx.out_error if error is None: om = ctx.descriptor.out_message if issubclass(om, ComplexModelBase): oo = ctx.out_object if len(om._type_info) == 0: oo = None elif len(om._type_info) == 1 and \ ctx.descriptor.body_style is BODY_STYLE_WRAPPED: om, = om._type_info.values() oo, = ctx.out_object else: oo = om.get_serialization_instance(ctx.out_object) log.msg('%s[OK]%s %r => %r' % ( GREEN, RESET, log_repr(ctx.in_object, ctx.descriptor.in_message), log_repr(oo, om), )) elif isinstance(error, Fault): log.msg('%s[CE]%s %r => %r' % (RED, RESET, ctx.in_object, error)) else: log.msg('%s[UE]%s %r => %r' % (RED, RESET, ctx.in_object, error)) if ctx.udc is not None: ctx.udc.close() class Application(AppBase): def __init__(self, services, tns, name=None, in_protocol=None, out_protocol=None, db=None): super(Application, self).__init__(services, tns, name, in_protocol, out_protocol) self.event_manager.add_listener("method_call", _on_method_call) self.event_manager.add_listener("method_context_closed", _on_method_context_closed) self.db = db self.Session = sessionmaker(bind=db, expire_on_commit=False) def call_wrapper(self, ctx): try: return ctx.service_class.call_wrapper(ctx) except NoResultFound: raise ResourceNotFoundError(ctx.in_object) except Fault as e: log.err() raise except Exception as e: log.err() # This should not happen! Let the team know via email! if EXCEPTION_ADDRESS: email_exception(EXCEPTION_ADDRESS) raise InternalError(e) def _user_callables(d): for k,v in d.items(): if callable(v) and not k in ('__init__', '__metaclass__'): yield k,v def _et(f): def _wrap(*args, **kwargs): self = args[0] try: retval = f(*args, **kwargs) self.session.expunge_all() return retval except NoResultFound: raise ResourceNotFoundError(self.ctx.in_object) except Fault as e: log.err() raise except Exception as e: log.err() # This should not happen! Let the team know via email! email_exception(EXCEPTION_ADDRESS) raise InternalError(e) return _wrap class DBThreadPool(ThreadPool): def __init__(self, engine, verbose=False): if engine.dialect.name == 'sqlite': pool_size = 1 ThreadPool.__init__(self, minthreads=1, maxthreads=1) else: ThreadPool.__init__(self) self.engine = engine reactor.callWhenRunning(self.start) def start(self): reactor.addSystemEventTrigger('during', 'shutdown', self.stop) ThreadPool.start(self) class DalMeta(type(object)): def __new__(cls, cls_name, cls_bases, cls_dict): for k, v in _user_callables(cls_dict): def _w2(_user_callable): def _wrap(*args, **kwargs): return deferToThreadPool(reactor, retval._pool, _et(_user_callable), *args, **kwargs) return _wrap cls_dict[k] = _w2(v) retval = type(object).__new__(cls, cls_name, cls_bases, cls_dict) return retval @property def bind(self): return self._db @bind.setter def bind(self, what): self._db = what self._pool = DBThreadPool(what) @six.add_metaclass(DalMeta) class DalBase(object): _db = None _pool = None def __init__(self, ctx): self.ctx = ctx self.session = ctx.udc.session if ctx.udc.session is None: self.session = ctx.udc.session = ctx.udc.Session() class Context(object): def __init__(self, db, Session=None): self.db = db self.Session = Session self.rd = None self.ru = None self.session = None def close(self): if self.session is not None: self.session.close() def log_repr(obj, cls=None, given_len=None, parent=None, from_array=False, tags=None): """Use this function if you want to serialize a ComplexModelBase instance to logs. It will: * Limit size of the String types * Limit size of Array types * Not try to iterate on iterators, push data, etc. """ if tags is None: tags = set() if obj is None: return 'None' if cls is None: cls = obj.__class__ if hasattr(obj, '__class__') and issubclass(obj.__class__, cls): cls = obj.__class__ if hasattr(cls, 'Attributes') and not cls.Attributes.logged: return "%s(...)" % cls.get_type_name() if (issubclass(cls, Array) or cls.Attributes.max_occurs > 1) and not \ from_array: if id(obj) in tags: return "%s(...)" % obj.__class__.__name__ tags.add(id(obj)) retval = [] if issubclass(cls, Array): cls, = cls._type_info.values() if isinstance(obj, PushBase): retval.append('') elif cls.Attributes.logged == 'len': l = '?' try: l = str(len(obj)) except TypeError as e: if given_len is not None: l = str(given_len) retval.append("%s[len=%s] (...)" % (cls.get_type_name(), l)) else: for i, o in enumerate(obj): retval.append(log_repr(o, cls, from_array=True, tags=tags)) if i > MAX_ARRAY_ELEMENT_NUM: retval.append("(...)") break retval = "[%s]" % (', '.join(retval)) elif issubclass(cls, ComplexModelBase): if id(obj) in tags: return "%s(...)" % obj.__class__.__name__ tags.add(id(obj)) retval = [] i = 0 for k, t in cls.get_flat_type_info(cls).items(): if i > MAX_FIELD_NUM: retval.append("(...)") break if t.Attributes.logged: v = getattr(obj, k, None) # HACK!: sometimes non-db attributes restored from database don't # get properly reinitialized. if isclass(v) and issubclass(v, ModelBase): continue if v is not None: retval.append("%s=%s" % (k, log_repr(v, t, parent=k, tags=tags))) i += 1 return "%s(%s)" % (cls.get_type_name(), ', '.join(retval)) elif issubclass(cls, Unicode) and isinstance(obj, six.string_types): if len(obj) > MAX_STRING_FIELD_LENGTH: return '%r(...)' % obj[:MAX_STRING_FIELD_LENGTH] else: return repr(obj) elif issubclass(cls, File) and isinstance(obj, PGFileJson.FileData): retval = log_repr(obj, PGFileJson.FileData, tags=tags) else: retval = repr(obj) if len(retval) > MAX_STRING_FIELD_LENGTH: retval = retval[:MAX_STRING_FIELD_LENGTH] + "(...)" return retval @memoize def TReaderService(T, T_name): class ReaderService(ReaderServiceBase): @rpc(M(UnsignedInteger32), _returns=T, _in_message_name='get_%s' % T_name, _in_variable_names={'obj_id': "%s_id" % T_name}) def get(ctx, obj_id): return ctx.udc.session.query(T).filter_by(id=obj_id).one() @rpc(_returns=Iterable(T), _in_message_name='get_all_%s' % T_name) def get_all(ctx): return ctx.udc.session.query(T).order_by(T.id) return ReaderService @memoize def TWriterService(T, T_name, put_not_found='raise'): assert put_not_found in ('raise', 'fix') if put_not_found == 'raise': def put_not_found(obj): raise ResourceNotFoundError('%s.id=%d' % (T_name, obj.id)) elif put_not_found == 'fix': def put_not_found(obj): obj.id = None class WriterService(WriterServiceBase): @rpc(M(T), _returns=UnsignedInteger32, _in_message_name='put_%s' % T_name, _in_variable_names={'obj': T_name}) def put(ctx, obj): if obj.id is None: ctx.udc.session.add(obj) ctx.udc.session.flush() # so that we get the obj.id value else: if ctx.udc.session.query(T).get(obj.id) is None: # this is to prevent the client from setting the primary key # of a new object instead of the database's own primary-key # generator. # Instead of raising an exception, you can also choose to # ignore the primary key set by the client by silently doing # obj.id = None in order to have the database assign the # primary key the traditional way. put_not_found(obj.id) else: ctx.udc.session.merge(obj) return obj.id @rpc(M(UnsignedInteger32), _in_message_name='del_%s' % T_name, _in_variable_names={'obj_id': '%s_id' % T_name}) def del_(ctx, obj_id): count = ctx.udc.session.query(T).filter_by(id=obj_id).count() if count == 0: raise ResourceNotFoundError(obj_id) ctx.udc.session.query(T).filter_by(id=obj_id).delete() return WriterService spyne-2.11.0/spyne/util/wsgi_wrapper.py0000644000175000001440000001026012345433230020100 0ustar plqusers00000000000000 # # 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 # """A Convenience module for wsgi wrapper routines.""" import logging logger = logging.getLogger(__name__) import os from spyne import Application from spyne.server.wsgi import WsgiApplication class WsgiMounter(object): """Simple mounter object for wsgi callables. Takes a dict where the keys are uri fragments and values are :class:`spyne.application.Application` instances. :param mounts: dict of :class:`spyne.application.Application` instances whose keys are url fragments. Use ``''`` or ``'/'`` as key to set the default handler. When a default handler is not set, an empty 404 page is returned. """ @staticmethod def default(e, s): s("404 Not found", []) return [] def __init__(self, mounts=None): self.mounts = {} for k, v in (mounts or {}).items(): if isinstance(v, Application): app = WsgiApplication(v) else: assert callable(v), "%r is not a valid wsgi app." % v app = v if k in ('', '/'): self.default = app else: self.mounts[k] = app def __call__(self, environ, start_response): path_info = environ.get('PATH_INFO', '') fragments = [a for a in path_info.split('/') if len(a) > 0] script = '' if len(fragments) > 0: script = fragments[0] app = self.mounts.get(script, self.default) if app is self.default: return app(environ, start_response) original_script_name = environ.get('SCRIPT_NAME', '') if len(script) > 0: script = "/" + script environ['SCRIPT_NAME'] = ''.join(('/', original_script_name, script)) pi = ''.join(('/', '/'.join(fragments[1:]))) if pi == '/': environ['PATH_INFO'] = '' else: environ['PATH_INFO'] = pi return app(environ, start_response) def run_twisted(apps, port, static_dir='.', interface='0.0.0.0'): """Twisted wrapper for the spyne.server.wsgi.WsgiApplication. Twisted can use one thread per request to run services, so code wrapped this way does not necessarily have to respect twisted way of doing things. :param apps: List of tuples containing (application, url) pairs :param port: Port to listen to. :param static_dir: The directory that contains static files. Pass `None` if you don't want to server static content. Url fragments in the `apps` argument take precedence. :param interface: The network interface to which the server binds, if not specified, it will accept connections on any interface by default. """ import twisted.web.server import twisted.web.static from twisted.web.resource import Resource from twisted.web.wsgi import WSGIResource from twisted.internet import reactor if static_dir != None: static_dir = os.path.abspath(static_dir) logging.info("registering static folder %r on /" % static_dir) root = twisted.web.static.File(static_dir) else: root = Resource() for app, url in apps: resource = WSGIResource(reactor, reactor, app) logging.info("registering %r on /%s" % (app, url)) root.putChild(url, resource) site = twisted.web.server.Site(root) reactor.listenTCP(port, site, interface=interface) logging.info("listening on: %s:%d" % (interface, port)) return reactor.run() spyne-2.11.0/spyne/util/xml.py0000644000175000001440000001123412352126507016175 0ustar plqusers00000000000000 # # 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 # """The `spyne.util.xml` module contains various Xml and Xml Schema related utility functions. """ from lxml import etree from os.path import dirname from os.path import abspath from spyne.interface import Interface from spyne.interface.xml_schema import XmlSchema from spyne.interface.xml_schema.parser import XmlSchemaParser, Thier_repr, PARSER from spyne.protocol.xml import XmlDocument class FakeApplication(object): pass def get_schema_documents(models, default_namespace=None): """Returns the schema documents in a dict whose keys are namespace prefixes and values are Element objects. :param models: A list of spyne.model classes that will be represented in the schema. """ if default_namespace is None: default_namespace = models[0].get_namespace() fake_app = FakeApplication() fake_app.tns = default_namespace fake_app.services = [] interface = Interface(fake_app) for m in models: m.resolve_namespace(m, default_namespace) interface.add_class(m) interface.populate_interface(fake_app) document = XmlSchema(interface) document.build_interface_document() return document.get_interface_document() def get_validation_schema(models, default_namespace=None): """Returns the validation schema object for the given models. :param models: A list of spyne.model classes that will be represented in the schema. """ if default_namespace is None: default_namespace = models[0].get_namespace() fake_app = FakeApplication() fake_app.tns = default_namespace fake_app.services = [] interface = Interface(fake_app) for m in models: m.resolve_namespace(m, default_namespace) interface.add_class(m) schema = XmlSchema(interface) schema.build_validation_schema() return schema.validation_schema def _dig(par): for elt in par: elt.tag = elt.tag.split('}')[-1] _dig(elt) xml_object = XmlDocument() def get_object_as_xml(inst, cls=None, root_tag_name=None, no_namespace=False): """Returns an ElementTree representation of a :class:`spyne.model.complex.ComplexModel` subclass. :param inst: The instance of the class to be serialized. :param cls: The class to be serialized. Optional. :param root_tag_name: The root tag string to use. Defaults to the output of ``value.__class__.get_type_name_ns()``. :param no_namespace: When true, namespace information is discarded. """ if cls is None: cls = inst.__class__ if cls.get_namespace() is None and no_namespace is None: no_namespace = True if no_namespace is None: no_namespace = False parent = etree.Element("parent") xml_object.to_parent(None, cls, inst, parent, cls.get_namespace(), root_tag_name) if no_namespace: _dig(parent) etree.cleanup_namespaces(parent) return parent[0] def get_xml_as_object(elt, cls): """Returns a native :class:`spyne.model.complex.ComplexModel` child from an ElementTree representation of the same class. :param elt: The xml document to be deserialized. :param cls: The class the xml document represents. """ return xml_object.from_element(None, cls, elt) def parse_schema_string(s, files={}, repr_=Thier_repr(with_ns=False)): elt = etree.fromstring(s, parser=PARSER) return XmlSchemaParser(files, repr_=repr_).parse_schema(elt) def parse_schema_element(elt, files={}, repr_=Thier_repr(with_ns=False)): return XmlSchemaParser(files, repr_=repr_).parse_schema(elt) def parse_schema_file(file_name, files={}, repr_=Thier_repr(with_ns=False)): elt = etree.fromstring(open(file_name).read(), parser=PARSER) return XmlSchemaParser(files, abspath(dirname(file_name)), repr_=repr_) \ .parse_schema(elt) spyne-2.11.0/spyne/__init__.py0000644000175000001440000000306412345433230016155 0ustar plqusers00000000000000 # # 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 # __version__ = '2.11.0' from pytz import utc as LOCAL_TZ from spyne._base import BODY_STYLE_WRAPPED from spyne._base import BODY_STYLE_BARE from spyne._base import BODY_STYLE_EMPTY from spyne._base import AuxMethodContext from spyne._base import TransportContext from spyne._base import EventContext from spyne._base import MethodContext from spyne._base import MethodDescriptor from spyne._base import EventManager from spyne.decorator import rpc from spyne.decorator import srpc from spyne.decorator import mrpc from spyne.service import ServiceBase from spyne.application import Application from spyne.model import * def _vercheck(): import sys if not hasattr(sys, "version_info") or sys.version_info < (2, 6): raise RuntimeError("Spyne requires Python 2.6 or later. Trust us.") _vercheck() spyne-2.11.0/spyne/_base.py0000644000175000001440000004711512345433230015474 0ustar plqusers00000000000000 # # 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__) from time import time from copy import copy from collections import deque from spyne.const.xml_ns import DEFAULT_NS from spyne.util.oset import oset class BODY_STYLE_WRAPPED: pass class BODY_STYLE_EMPTY: pass class BODY_STYLE_BARE: pass class AuxMethodContext(object): """Generic object that holds information specific to auxiliary methods""" def __init__(self, parent, error): self.parent = parent """Primary context that this method was bound to.""" self.error = error """Error from primary context (if any).""" class TransportContext(object): """Generic object that holds transport-specific context information""" def __init__(self, parent, transport, type=None): self.parent = parent; """The MethodContext this object belongs to""" self.itself = transport """The transport itself; i.e. a ServerBase instance.""" self.type = type """The protocol the transport uses.""" self.app = transport.app self.request_encoding = None """General purpose variable to hold the string identifier of a request encoding. It's nowadays usually 'utf-8', especially with http data""" class ProtocolContext(object): """Generic object that holds transport-specific context information""" def __init__(self, parent, transport, type=None): self.parent = parent; """The MethodContext this object belongs to""" self.itself = transport """The transport itself; i.e. a ServerBase instance.""" self.type = type """The protocol the transport uses.""" class EventContext(object): """Generic object that holds event-specific context information""" def __init__(self, parent, event_id=None): self.parent = parent self.event_id = event_id class MethodContext(object): """The base class for all RPC Contexts. Holds all information about the current state of execution of a remote procedure call. """ frozen = False def copy(self): retval = copy(self) if retval.transport is not None: retval.transport.parent = retval if retval.protocol is not None: retval.protocol.parent = retval if retval.event is not None: retval.event.parent = retval if retval.aux is not None: retval.aux.parent = retval return retval @property def method_name(self): """The public name of the method the ``method_request_string`` was matched to. """ if self.descriptor is None: return None else: return self.descriptor.name def __init__(self, transport): # metadata self.call_start = time() """The time the rpc operation was initiated in seconds-since-epoch format. Useful for benchmarking purposes.""" self.call_end = None """The time the rpc operation was completed in seconds-since-epoch format. Useful for benchmarking purposes.""" self.app = transport.app """The parent application.""" self.udc = None """The user defined context. Use it to your liking.""" self.transport = TransportContext(self, transport) """The transport-specific context. Transport implementors can use this to their liking.""" self.protocol = ProtocolContext(self, transport) """The protocol-specific context. Protocol implementors can use this to their liking.""" self.event = EventContext(self) """Event-specific context. Use this as you want, preferably only in events, as you'd probably want to separate the event data from the method data.""" self.aux = None """Auxiliary-method specific context. You can use this to share data between auxiliary sessions. This is not set in primary contexts. """ self.method_request_string = None """This is used to decide which native method to call. It is set by the protocol classes.""" self.__descriptor = None # # Input # # stream self.in_string = None """Incoming bytestream as a sequence of ``str`` or ``bytes`` instances.""" # parsed self.in_document = None """Incoming document, what you get when you parse the incoming stream.""" self.in_header_doc = None """Incoming header document of the request.""" self.in_body_doc = None """Incoming body document of the request.""" # native self.in_error = None """Native python error object. If this is set, either there was a parsing error or the incoming document was representing an exception. """ self.in_header = None """Deserialized incoming header -- a native object.""" self.in_object = None """In the request (i.e. server) case, this contains the function argument sequence for the function in the service definition class. In the response (i.e. client) case, this contains the object returned by the remote procedure call. It's always a sequence of objects: * ``[None]`` when the function has no output (client)/input (server) types. * A single-element list that wraps the return value when the function has one return type defined, * A tuple of return values in case of the function having more than one return value. The order of the argument sequence is in line with ``self.descriptor.in_message._type_info.keys()``. """ # # Output # # native self.out_object = None """In the response (i.e. server) case, this contains the native python object(s) returned by the function in the service definition class. In the request (i.e. client) case, this contains the function arguments passed to the function call wrapper. It's always a sequence of objects: * ``[None]`` when the function has no output (server)/input (client) types. * A single-element list that wraps the return value when the function has one return type defined, * A tuple of return values in case of the function having more than one return value. The order of the argument sequence is in line with ``self.descriptor.out_message._type_info.keys()``. """ self.out_header = None """Native python object set by the function in the service definition class.""" self.out_error = None """Native exception thrown by the function in the service definition class.""" # parsed self.out_body_doc = None """Serialized body object.""" self.out_header_doc = None """Serialized header object.""" self.out_document = None """out_body_doc and out_header_doc wrapped in the outgoing envelope""" # stream self.out_string = None """The pull interface to the outgoing bytestream. It's a sequence of strings (which could also be a generator).""" self.out_stream = None """The push interface to the outgoing bytestream. It's a file-like object.""" #self.out_stream = None #"""The push interface to the outgoing bytestream. It's a file-like #object.""" self.function = None """The callable of the user code.""" self.locale = None """The locale the request will use when needed for things like date formatting, html rendering and such.""" self.in_protocol = transport.app.in_protocol """The protocol that will be used to (de)serialize incoming input""" self.out_protocol = transport.app.out_protocol """The protocol that will be used to (de)serialize outgoing input""" self.frozen = True """When this is set, no new attribute can be added to this class instance. This is mostly for internal use. """ self.app.event_manager.fire_event("method_context_created", self) def get_descriptor(self): return self.__descriptor def set_descriptor(self, descriptor): self.__descriptor = descriptor self.function = descriptor.function descriptor = property(get_descriptor, set_descriptor) """The :class:``MethodDescriptor`` object representing the current method. It is only set when the incoming request was successfully mapped to a method in the public interface. The contents of this property should not be changed by the user code. """ # Deprecated. Use self.descriptor.service_class. @property def service_class(self): if self.descriptor is not None: return self.descriptor.service_class def __setattr__(self, k, v): if not self.frozen or k in self.__dict__ or k in \ ('descriptor', 'out_protocol'): object.__setattr__(self, k, v) else: raise ValueError("use the udc member for storing arbitrary data " "in the method context") def __repr__(self): retval = deque() for k, v in self.__dict__.items(): if isinstance(v, dict): ret = deque(['{']) items = sorted(v.items()) for k2, v2 in items: ret.append('\t\t%r: %r,' % (k2, v2)) ret.append('\t}') ret = '\n'.join(ret) retval.append("\n\t%s=%s" % (k, ret)) else: retval.append("\n\t%s=%r" % (k, v)) retval.append('\n)') return ''.join((self.__class__.__name__, '(', ', '.join(retval), ')')) def close(self): self.call_end = time() self.app.event_manager.fire_event("method_context_closed", self) def set_out_protocol(self, what): self._out_protocol = what def get_out_protocol(self): return self._out_protocol out_protocol = property(get_out_protocol, set_out_protocol) class MethodDescriptor(object): """This class represents the method signature of an exposed service. It is produced by the :func:`spyne.decorator.srpc` decorator. """ def __init__(self, function, in_message, out_message, doc, is_callback=False, is_async=False, mtom=False, in_header=None, out_header=None, faults=None, port_type=None, no_ctx=False, udp=None, class_key=None, aux=None, patterns=None, body_style=None, args=None, operation_name=None, no_self=None, translations=None, when=None, in_message_name_override=True, out_message_name_override=True, service_class=None, href=None): self.__real_function = function """The original callable for the user code.""" self.reset_function() self.operation_name = operation_name """The base name of an operation without the request suffix, as generated by the ``@srpc`` decorator.""" self.in_message = in_message """A :class:`spyne.model.complex.ComplexModel` subclass that defines the input signature of the user function and that was automatically generated by the ``@srpc`` decorator.""" self.name = None """The public name of the function. Equals to the type_name of the in_message.""" if body_style is BODY_STYLE_BARE: self.name = in_message.Attributes.sub_name if self.name is None: self.name = self.in_message.get_type_name() self.out_message = out_message """A :class:`spyne.model.complex.ComplexModel` subclass that defines the output signature of the user function and that was automatically generated by the ``@srpc`` decorator.""" self.doc = doc """The function docstring.""" # these are not working, so they are not documented. self.is_callback = is_callback self.is_async = is_async self.mtom = mtom #"""Flag to indicate whether to use MTOM transport with SOAP.""" self.port_type = port_type #"""The portType this function belongs to.""" self.in_header = in_header """An iterable of :class:`spyne.model.complex.ComplexModel` subclasses to denote the types of header objects that this method can accept.""" self.out_header = out_header """An iterable of :class:`spyne.model.complex.ComplexModel` subclasses to denote the types of header objects that this method can emit along with its return value.""" self.faults = faults """An iterable of :class:`spyne.model.fault.Fault` subclasses to denote the types of exceptions that this method can throw.""" self.no_ctx = no_ctx """no_ctx: Boolean flag to denote whether the user code gets an implicit :class:`spyne.MethodContext` instance as first argument.""" self.udp = udp """Short for "User Defined Properties", this is just an arbitrary python object set by the user to pass arbitrary metadata via the ``@srpc`` decorator.""" self.class_key = class_key """ The identifier of this method in its parent :class:`spyne.service.ServiceBase` subclass.""" self.aux = aux """Value to indicate what kind of auxiliary method this is. (None means primary) Primary methods block the request as long as they're running. Their return values are returned to the client. Auxiliary ones execute asyncronously after the primary method returns, and their return values are ignored by the rpc layer. """ self.patterns = patterns """This list stores patterns which will match this callable using various elements of the request protocol. Currently, the only object supported here is the :class:`spyne.protocol.http.HttpPattern` object. """ self.body_style = body_style """One of (BODY_STYLE_EMPTY, BODY_STYLE_BARE, BODY_STYLE_WRAPPED).""" self.args = args """A sequence of the names of the exposed arguments, or None.""" # FIXME: docstring yo. self.no_self = no_self """FIXME: docstring yo.""" self.service_class = service_class """The ServiceBase subclass the method belongs to, if there's any.""" self.parent_class = None """The ComplexModel subclass the method belongs to. Only set for @mrpc methods.""" # HATEOAS Stuff self.translations = translations """None or a dict of locale-translation pairs.""" self.href = href """None or a dict of locale-translation pairs.""" self.when = when """None or a callable that takes the object instance and returns a boolean value. If true, the object can process that action. """ self.in_message_name_override = in_message_name_override """When False, no mangling of in message name will be performed by later stages of the interface generation. Naturally, it will be up to you to resolve name clashes.""" self.out_message_name_override = out_message_name_override """When False, no mangling of out message name will be performed by later stages of the interface generation. Naturally, it will be up to you to resolve name clashes.""" def translate(self, locale, default): """ :param cls: class :param locale: locale string :param default: default string if no translation found :returns: translated string """ if locale is None: locale = 'en_US' if self.translations is not None: return self.translations.get(locale, default) return default @property def key(self): """The function identifier in '{namespace}name' form.""" assert not (self.in_message.get_namespace() is DEFAULT_NS) return '{%s}%s' % ( self.in_message.get_namespace(), self.in_message.get_type_name()) def reset_function(self, val=None): if val != None: self.__real_function = val self.function = self.__real_function class EventManager(object): """Spyne supports a simple event system that can be used to have repetitive boilerplate code that has to run for every method call nicely tucked away in one or more event handlers. The popular use-cases include things like database transaction management, logging and measuring performance. Various Spyne components support firing events at various stages during the processing of a request, which are documented in the relevant classes. The classes that support events are: * :class:`spyne.application.Application` * :class:`spyne.service.ServiceBase` * :class:`spyne.protocol._base.ProtocolBase` * :class:`spyne.server.wsgi.WsgiApplication` The events are stored in an ordered set. This means that the events are ran in the order they were added and adding a handler twice does not cause it to run twice. """ def __init__(self, parent, handlers={}): self.parent = parent self.handlers = dict(handlers) def add_listener(self, event_name, handler): """Register a handler for the given event name. :param event_name: The event identifier, indicated by the documentation. Usually, this is a string. :param handler: A static python function that receives a single MethodContext argument. """ handlers = self.handlers.get(event_name, oset()) handlers.add(handler) self.handlers[event_name] = handlers def fire_event(self, event_name, ctx): """Run all the handlers for a given event name. :param event_name: The event identifier, indicated by the documentation. Usually, this is a string. :param ctx: The method context. Event-related data is conventionally stored in ctx.event attribute. """ handlers = self.handlers.get(event_name, oset()) for handler in handlers: handler(ctx) class FakeContext(object): def __init__(self, app=None, descriptor=None, out_object=None, out_error=None, out_document=None, out_string=None): self.app = app self.descriptor = descriptor self.out_error = out_error self.out_object = out_object self.out_document = out_document self.out_string = out_string self.protocol = type("ProtocolContext", (object,), {})() self.transport = type("ProtocolContext", (object,), {})() spyne-2.11.0/spyne/application.py0000644000175000001440000002245012345433230016721 0ustar plqusers00000000000000 # # 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'])) from spyne import BODY_STYLE_EMPTY from spyne import BODY_STYLE_BARE from spyne import BODY_STYLE_WRAPPED from spyne.model.fault import Fault from spyne.interface import Interface from spyne import EventManager from spyne.util.appreg import register_application from spyne.error import RespawnError 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 ServiceBase 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 interface: Ignored. Kept for backwards-compatibility purposes. 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, interface=None): self.services = tuple(services) self.tns = tns self.name = name if self.name is None: self.name = self.__class__.__name__.split('.')[-1] self.event_manager = EventManager(self) self.error_handler = None self.interface = Interface(self) 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() 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 if self.out_protocol is None: from spyne.protocol import ProtocolBase self.out_protocol = ProtocolBase() 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) self.reinitialize() 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: # fire events self.event_manager.fire_event('method_call', ctx) if ctx.service_class is not None: ctx.service_class.event_manager.fire_event('method_call', ctx) # call the method ctx.out_object = self.call_wrapper(ctx) # out object is always an iterable 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: # the return value should already be wrapped by a sequence. ctx.out_object = [ctx.out_object] # fire events self.event_manager.fire_event('method_return_object', ctx) if ctx.service_class is not None: ctx.service_class.event_manager.fire_event( 'method_return_object', ctx) 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 # fire events self.event_manager.fire_event('method_exception_object', ctx) if ctx.service_class is not None: ctx.service_class.event_manager.fire_event( 'method_exception_object', ctx) except Exception as e: logger.exception(e) ctx.out_error = Fault('Server', get_fault_string_from_exception(e)) # fire events self.event_manager.fire_event('method_exception_object', ctx) if ctx.service_class is not None: ctx.service_class.event_manager.fire_event( 'method_exception_object', ctx) 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. """ 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 = [] retval = None # service rpc if ctx.descriptor.no_self: retval = ctx.descriptor.service_class.call_wrapper(ctx) # class rpc else: cls = ctx.descriptor.parent_class if cls.__orig__ is not None: cls = cls.__orig__ inst = cls.__respawn__(ctx) if inst is None: raise RespawnError('{%s}%s' % (cls.get_namespace(), cls.get_type_name())) in_cls = ctx.descriptor.in_message args = 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:] if ctx.descriptor.service_class is not None: ctx.in_object = [inst, ctx] ctx.in_object.extend(args) # hack to make sure inst goes first ctx.descriptor.no_ctx = True retval = ctx.descriptor.service_class.call_wrapper(ctx) elif ctx.function is not None: if ctx.descriptor.no_ctx: retval = ctx.function(inst, *args) else: retval = ctx.function(inst, ctx, *args) return retval def _has_callbacks(self): return self.interface._has_callbacks() def reinitialize(self): from spyne.server import ServerBase server = ServerBase(self) aux_memo = set() for d in self.interface.method_id_map.values(): if d.aux is not None and not id(d.aux) in aux_memo: d.aux.initialize(server) aux_memo.add(id(d.aux)) def __hash__(self): return hash(tuple((id(s) for s in self.services))) spyne-2.11.0/spyne/decorator.py0000644000175000001440000003513012345433230016377 0ustar plqusers00000000000000# # 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 # """The ``spyne.decorator`` module contains the the @srpc decorator and its helper methods. The @srpc decorator is responsible for tagging methods as remote procedure calls extracting method's input and output types. It's possible to create custom decorators that wrap the @srpc decorator in order to have a more elegant way of passing frequently-used parameter values. The @rpc decorator is a simple example of this. """ import spyne.const.xml_ns from copy import copy from spyne import MethodDescriptor # Empty means empty input, bare output. Doesn't say anything about response # being empty from spyne._base import BODY_STYLE_EMPTY from spyne._base import BODY_STYLE_WRAPPED from spyne._base import BODY_STYLE_BARE from spyne.model.complex import ComplexModel from spyne.model.complex import TypeInfo from spyne.const import add_request_suffix def _produce_input_message(f, params, kparams, in_message_name, in_variable_names, no_ctx, no_self, args): _body_style = _validate_body_style(kparams) arg_start = 0 if no_ctx is False: arg_start += 1 if no_self is False: arg_start += 1 if args is None: try: argcount = f.__code__.co_argcount args = f.__code__.co_varnames[arg_start:argcount] except AttributeError: raise TypeError( "It's not possible to instrospect builtins. You must pass a " "sequence of argument names as the '_args' argument to the " "rpc decorator to manually denote the arguments that this " "function accepts." ) if len(params) != len(args): raise Exception("%r function has %d argument(s) but its decorator " "has %d." % (f.__name__, len(args), len(params))) else: args = copy(args) if len(params) != len(args): raise Exception("%r function has %d argument(s) but the _args " "argument has %d." % ( f.__name__, len(args), len(params))) in_params = TypeInfo() for k, v in zip(args, params): k = in_variable_names.get(k, k) in_params[k] = v ns = spyne.const.xml_ns.DEFAULT_NS if in_message_name.startswith("{"): ns, _, in_message_name = in_message_name[1:].partition("}") message = None if _body_style == 'bare': if len(in_params) > 1: raise Exception("body_style='bare' can handle at most one function " "argument.") if len(in_params) == 0: message = ComplexModel.produce(type_name=in_message_name, namespace=ns, members=in_params) else: message, = in_params.values() message = message.customize(sub_name=in_message_name, sub_ns=ns) assert message.Attributes.sub_name is not None else: message = ComplexModel.produce(type_name=in_message_name, namespace=ns, members=in_params) message.__namespace__ = ns return message def _validate_body_style(kparams): _body_style = kparams.get('_body_style') _soap_body_style = kparams.get('_soap_body_style') if _body_style is None: _body_style = 'wrapped' elif not (_body_style in ('wrapped', 'bare')): raise ValueError("body_style must be one of ('wrapped', 'bare')") elif _soap_body_style == 'document': _body_style = 'wrapped' elif _soap_body_style == 'rpc': _body_style = 'bare' elif _soap_body_style is None: pass else: raise ValueError("soap_body_style must be one of ('rpc', 'document')") assert _body_style in ('wrapped', 'bare') return _body_style def _produce_output_message(func_name, kparams): """Generate an output message for "rpc"-style API methods. This message is a wrapper to the declared return type. """ _returns = kparams.get('_returns') _body_style = _validate_body_style(kparams) _out_message_name = kparams.get('_out_message_name', '%s%s' % (func_name, spyne.const.RESPONSE_SUFFIX)) out_params = TypeInfo() if _returns and _body_style == 'wrapped': if isinstance(_returns, (list, tuple)): default_names = ['%s%s%d'% (func_name, spyne.const.RESULT_SUFFIX, i) for i in range(len(_returns))] _out_variable_names = kparams.get('_out_variable_names', default_names) assert (len(_returns) == len(_out_variable_names)) var_pair = zip(_out_variable_names, _returns) out_params = TypeInfo(var_pair) else: _out_variable_name = kparams.get('_out_variable_name', '%s%s' % (func_name, spyne.const.RESULT_SUFFIX)) out_params[_out_variable_name] = _returns ns = spyne.const.xml_ns.DEFAULT_NS if _out_message_name.startswith("{"): ns = _out_message_name[1:].partition("}")[0] if _body_style == 'bare' and _returns is not None: message = _returns.customize(sub_name=_out_message_name, sub_ns=ns) else: message = ComplexModel.produce(type_name=_out_message_name, namespace=ns, members=out_params) message.Attributes._wrapper = True message.__namespace__ = ns # FIXME: is this necessary? return message def rpc(*params, **kparams): """Method decorator to tag a method as a remote procedure call in a :class:`spyne.service.ServiceBase` subclass. You should use the :class:`spyne.server.null.NullServer` transport if you want to call the methods directly. You can also use the 'function' attribute of the returned object to call the function itself. ``_operation_name`` vs ``_in_message_name``: Soap clients(SoapUI, Savon, suds) will use the operation name as the function name. The name of the input message(_in_message_name) is irrelevant when interfacing in this manner; this is because the clients mostly wrap around it. However, the soap xml request only uses the input message when posting with the soap server; the other protocols only use the input message as well. ``_operation_name`` cannot be used with ``_in_message_name``. :param _returns: Denotes The return type of the function. It can be a type or a sequence of types for functions that have multiple return values. :param _in_header: A type or an iterable of types that that this method accepts as incoming header. :param _out_header: A type or an iterable of types that that this method sends as outgoing header. :param _operation_name: The function's soap operation name. The operation and SoapAction names will be equal to the value of ``_operation_name``. Default is the function name. :param _in_message_name: The public name of the function's input message. Default is: ``_operation_name + REQUEST_SUFFIX``. :param _out_message_name: The public name of the function's output message. Default is: ``_operation_name + RESPONSE_SUFFIX``. :param _in_variable_names: The public names of the function arguments. It's a dict that maps argument names in the code to public ones. :param _out_variable_name: The public name of the function response object. It's a string. Ignored when ``_body_style != 'wrapped'`` or ``_returns`` is a sequence. :param _out_variable_names: The public name of the function response object. It's a sequence of strings. Ignored when ``_body_style != 'wrapped'`` or or ``_returns`` is not a sequence. Must be the same length as ``_returns``. :param _body_style: One of ``('bare', 'wrapped')``. Default: ``'wrapped'``. In wrapped mode, wraps response objects in an additional class. :param _soap_body_style: One of ('rpc', 'document'). Default ``'document'``. ``_soap_body_style='document'`` is an alias for ``_body_style='wrapped'``. ``_soap_body_style='rpc'`` is an alias for ``_body_style='bare'``. :param _port_type: Soap port type string. :param _no_ctx: Don't pass implicit ctx object to the user method. :param _no_self: This method does not get an implicit 'self' argument (before any other argument, including ctx). :param _udp: Short for UserDefinedProperties, you can use this to mark the method with arbitrary metadata. :param _aux: The auxiliary backend to run this method. ``None`` if primary. :param _throws: A sequence of exceptions that this function can throw. This has no real functionality besides publishing this information in interface documents. :param _args: the name of the arguments to expose. :param _service_class: A :class:`ServiceBase` subclass, if you feel like overriding it. """ def explain(f): def explain_method(*args, **kwargs): function_name = kwargs['_default_function_name'] _is_callback = kparams.get('_is_callback', False) _is_async = kparams.get('_is_async', False) _mtom = kparams.get('_mtom', False) _in_header = kparams.get('_in_header', None) _out_header = kparams.get('_out_header', None) _port_type = kparams.get('_soap_port_type', None) _no_ctx = kparams.get('_no_ctx', False) _no_self = kparams.get('_no_self', True) _udp = kparams.get('_udp', None) _aux = kparams.get('_aux', None) _pattern = kparams.get("_pattern", None) _patterns = kparams.get("_patterns", []) _args = kparams.get("_args", None) _translations = kparams.get("_translations", None) _when = kparams.get("_when", None) _service_class = kparams.get("_service_class", None) _href = kparams.get("_href", None) if _no_self: from spyne.model import SelfReference if SelfReference in params: raise ValueError("SelfReference can't be used in @rpc") if SelfReference in kparams.values(): raise ValueError("SelfReference can't be used in @rpc") _faults = None if ('_faults' in kparams) and ('_throws' in kparams): raise ValueError("only one of '_throws ' or '_faults' arguments" "should be given, as they're synonyms.") elif '_faults' in kparams: _faults = kparams.get('_faults', None) elif '_throws' in kparams: _faults = kparams.get('_throws', None) _in_message_name_override = not ('_in_message_name' in kparams) _in_message_name = kparams.get('_in_message_name', function_name) _operation_name = kparams.get('_operation_name', function_name) if _operation_name != function_name and _in_message_name != function_name: raise ValueError( "only one of '_operation_name' and '_in_message_name' " "arguments should be given") if _in_message_name == function_name: _in_message_name = add_request_suffix(_operation_name) _in_variable_names = kparams.get('_in_variable_names', {}) in_message = _produce_input_message(f, params, kparams, _in_message_name, _in_variable_names, _no_ctx, _no_self, _args) _out_message_name_override = not ('_out_message_name' in kparams) out_message = _produce_output_message(function_name, kparams) doc = getattr(f, '__doc__') if _pattern is not None and _patterns != []: raise ValueError("only one of '_pattern' and '_patterns' " "arguments should be given") if _pattern is not None: _patterns = [_pattern] body_style = BODY_STYLE_WRAPPED if _validate_body_style(kparams) == 'bare': body_style = BODY_STYLE_BARE t = in_message from spyne.model import ComplexModelBase if issubclass(t, ComplexModelBase) and len(t._type_info) == 0: body_style = BODY_STYLE_EMPTY retval = MethodDescriptor(f, in_message, out_message, doc, _is_callback, _is_async, _mtom, _in_header, _out_header, _faults, port_type=_port_type, no_ctx=_no_ctx, udp=_udp, class_key=function_name, aux=_aux, patterns=_patterns, body_style=body_style, args=_args, operation_name=_operation_name, no_self=_no_self, translations=_translations, when=_when, in_message_name_override=_in_message_name_override, out_message_name_override=_out_message_name_override, service_class=_service_class, href=_href, ) if _patterns is not None: for p in _patterns: p.hello(retval) return retval explain_method.__doc__ = f.__doc__ explain_method._is_rpc = True return explain_method return explain def srpc(*params, **kparams): """Method decorator to tag a method as a remote procedure call. See :func:`spyne.decorator.rpc` for detailed information. The initial "s" stands for "static". In Spyne terms, that means no implicit first argument is passed to the user callable, which really means the method is "stateless" rather than static. It's meant to be used for existing functions that can't be changed. """ kparams["_no_ctx"] = True return rpc(*params, **kparams) def mrpc(*params, **kparams): kparams["_no_self"] = False return rpc(*params, **kparams) spyne-2.11.0/spyne/error.py0000644000175000001440000000723712345433230015555 0ustar plqusers00000000000000 # # 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 # """The ``spyne.error`` module contains various common exceptions that the user code can throw. """ from spyne.model.fault import Fault from spyne.const import MAX_STRING_FIELD_LENGTH class InvalidCredentialsError(Fault): """Raised when requested resource is forbidden.""" STR = "You do not have permission to access this resource" def __init__(self, fault_string=STR, fault_object=None): super(InvalidCredentialsError, self).__init__( 'Client.InvalidCredentialsError', fault_string, detail=fault_object) class RequestTooLongError(Fault): """Raised when request is too long.""" def __init__(self, faultstring="Request too long"): super(RequestTooLongError, self).__init__('Client.RequestTooLong', faultstring) class RequestNotAllowed(Fault): """Raised when request is incomplete.""" def __init__(self, faultstring=""): super(RequestNotAllowed, self).__init__('Client.RequestNotAllowed', faultstring) class ArgumentError(Fault): """Raised when there is a general problem with input data.""" def __init__(self, faultstring=""): super(ArgumentError, self).__init__('Client.ArgumentError', faultstring) class InvalidInputError(Fault): """Raised when there is a general problem with input data.""" def __init__(self, faultstring="", data=""): super(InvalidInputError, self).__init__('Client.InvalidInput', repr((faultstring, data))) InvalidRequestError = InvalidInputError class ValidationError(Fault): """Raised when the input stream does not adhere to type constraints.""" def __init__(self, obj, custom_msg='The value %r could not be validated.'): s = repr(obj) if len(s) > MAX_STRING_FIELD_LENGTH: s = s[:MAX_STRING_FIELD_LENGTH] + "(...)" try: msg = custom_msg % s except TypeError: msg = custom_msg super(ValidationError, self).__init__('Client.ValidationError', msg) class InternalError(Fault): """Raised to communicate server-side errors.""" def __init__(self, error): super(InternalError, self).__init__('Server', "InternalError: An unknown error has occured.") class ResourceNotFoundError(Fault): """Raised when requested resource is not found.""" def __init__(self, fault_object, fault_string="Requested resource %r not found"): super(ResourceNotFoundError, self).__init__( 'Client.ResourceNotFound', fault_string % (fault_object,)) class RespawnError(ResourceNotFoundError): pass class ResourceAlreadyExistsError(Fault): """Raised when requested resource already exists on server side.""" def __init__(self, fault_object, fault_string="Resource %r already exists"): super(ResourceAlreadyExistsError, self).__init__('Client.ResourceAlreadyExists', fault_string % fault_object) spyne-2.11.0/spyne/service.py0000644000175000001440000001562112345433230016060 0ustar plqusers00000000000000 # # 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 # """ This module contains the :class:`ServiceBase` class and its helper objects. """ import logging logger = logging.getLogger(__name__) from spyne.util.six import add_metaclass, string_types from spyne import EventManager from spyne.util.oset import oset class ServiceBaseMeta(type): """Creates the :class:`spyne.MethodDescriptor` objects by iterating over tagged methods. """ def __init__(self, cls_name, cls_bases, cls_dict): super(ServiceBaseMeta, self).__init__(cls_name, cls_bases, cls_dict) self.__has_aux_methods = self.__aux__ is not None self.public_methods = {} self.event_manager = EventManager(self, self.__get_base_event_handlers(cls_bases)) for k, v in cls_dict.items(): if hasattr(v, '_is_rpc'): descriptor = v(_default_function_name=k) # these two lines are needed for staticmethod wrapping to work setattr(self, k, staticmethod(descriptor.function)) descriptor.reset_function(getattr(self, k)) getattr(self, k).descriptor = descriptor descriptor.service_class = self self.public_methods[k] = descriptor if descriptor.aux is None: if self.__has_aux_methods and self.__aux__ is None: raise Exception("You can't mix primary and " "auxiliary methods in a single service definition.") else: self.__has_aux_methods = True def __get_base_event_handlers(self, cls_bases): handlers = {} for base in cls_bases: evmgr = getattr(base, 'event_manager', None) if evmgr is None: continue for k, v in evmgr.handlers.items(): handler=handlers.get(k, oset()) for h in v: handler.add(h) handlers[k]=handler return handlers def is_auxiliary(self): return self.__has_aux_methods @add_metaclass(ServiceBaseMeta) class ServiceBase(object): """The ``ServiceBase`` class is the base class for all service definitions. The convention is to have public methods defined under a subclass of this class along with common properties of public methods like header classes or auxiliary processors. The :func:`spyne.decorator.srpc` decorator or its wrappers should be used to flag public methods. It is a natural abstract base class, because it's of no use without any method definitions, hence the 'Base' suffix in the name. This class supports the following 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_accept_document`` Called by the transport right after the incoming stream is parsed to the incoming protocol's document type. * ``method_return_document`` Called by the transport right after the outgoing object is serialized to the outgoing protocol's document type. * ``method_exception_document`` Called by the transport right before the outgoing exception object is serialized to the outgoing protocol's document type. * ``method_return_string`` Called by the transport right before passing the return string to the client. * ``method_exception_string`` Called by the transport right before passing the exception string to the client. """ __tns__ = None """For internal use only. You should use the ``tns`` argument to the :class:`spyne.application.Application` constructor to define the target namespace.""" __in_header__ = None """The incoming header object that the methods under this service definition accept.""" __out_header__ = None """The outgoing header object that the methods under this service definition accept.""" __service_name__ = None """The name of this service definition as exposed in the interface document. Defaults to the class name.""" __port_types__ = () """WSDL-Specific portType mappings""" __aux__ = None """The auxiliary method type. When set, the ``aux`` property of every method defined under this service is set to this value. The _aux flag in the @srpc decorator overrides this.""" @classmethod def get_service_class_name(cls): return cls.__name__ @classmethod def get_service_key(cls): return '{%s}%s' % (cls.get_tns(), cls.get_service_name()) @classmethod def get_service_name(cls): if cls.__service_name__ is None: return cls.__name__ else: return cls.__service_name__ @classmethod def get_port_types(cls): return cls.__port_types__ @classmethod def get_tns(cls): if not (cls.__tns__ is None): return cls.__tns__ retval = cls.__module__ if cls.__module__ == '__main__': service_name = cls.get_service_class_name().split('.')[-1] retval = '.'.join((service_name, service_name)) return retval @classmethod def _has_callbacks(cls): """Determines if this service definition has callback methods or not.""" for method in cls.public_methods.values(): if method.is_callback: return True return False @classmethod def call_wrapper(cls, ctx): """Called in place of the original method call. You can override this to do your own exception handling. :param ctx: The method context. The overriding function must call this function by convention. """ if ctx.function is not None: if ctx.descriptor.no_ctx: return ctx.function(*ctx.in_object) else: return ctx.function(ctx, *ctx.in_object) spyne-2.11.0/spyne.egg-info/0000755000175000001440000000000012352131452015532 5ustar plqusers00000000000000spyne-2.11.0/spyne.egg-info/PKG-INFO0000644000175000001440000010050712352131446016635 0ustar plqusers00000000000000Metadata-Version: 1.1 Name: spyne Version: 2.11.0 Summary: A transport and architecture agnostic rpc library that focuses on exposing public services with a well-defined API. Home-page: http://spyne.io Author: Burak Arslan Author-email: burak+spyne@arskom.com.tr License: LGPL-2.1 Description: Spyne aims to save the protocol implementers the hassle of implementing their own remote procedure call api and the application programmers the hassle of jumping through hoops just to expose their services using multiple protocols and transports. Changelog ========= spyne-2.11.0 ------------ * Experimental Python 3 Support for all of the Xml-related (non-Html) components. * Add support for altering output protocol by setting ``ctx.out_protocol``. * Add returning ctx.out_string support to null server (The ``ostr`` argument). * Add support for XmlData modifier. It lets mapping the data in the xml body to an object field via xsd:simpleContent. * Remove deprecated ``JsonObject`` identifier. Just do a gentle ``s/JsonObject/JsonDocument/g`` if you're still using it. * SQLAlchemy: Implement storing arrays of simple types in a table. * SQLAlchemy: Make it work with multiple foreign keys from one table to another. * SQLAlchemy: Implement a hybrid file container that puts file metadata in a json column in database and and file data in file system. Fully supported by all protocols as a binary File format. * Implement an Xml Schema parser. * Import all model markers to ``spyne.model``. * Import ``@rpc``\, ``@srpc``\, ``ServiceBase`` and ``Application`` inside the ``spyne`` module. * Implement JsonP protocol. * Implement SpyneJsonRpc 1.0 protocol -- it supports request headers. Sample request: ``{"ver":1, "body": {"div": [4,2]}, "head": {"id": 1234}}`` Sample response: ``{"ver":1, "body": 2}`` Sample request: ``{"ver":1, "body": {"div": {"dividend":4,"divisor":0]}}`` Sample response: ``{"ver":1, "fault": {"faultcode": "Server", "faultstring": "Internal Error"}}}`` * Steal and integrate the experimental WebSocket tranport for Twisted. * Support Django natively using :class:`spyne.server.django.DjangoView` and :class:`spyne.server.django.DjangoServer`. * It's now possible to override the ``JsonEncoder`` class ``JsonDocument`` uses. * Remove hard-coded utf-8 defaults from almost everywhere. * Remove hard-coded pytz.utc defaults from everywhere. Use spyne.LOCAL_TZ to configure the default time zone. * As a result of the above change, datetime objects deserialized by Spyne are are forced to the above time zone during soft validation (nothing should have changed from the user code perspective). * Add default_factory to ModelBase customizer. It's a callable that produces default values on demand. Suitable to be used with e.g. lambdas that return mutable defaults. * New ``spyne.util.AttrDict`` can be used for passing various dynamic configuration data. * ``child_attrs`` can now be passed to the ComplexModelBase customizer in order to make object-specific in-place customizations to child types. * Add mapper between Django models and :class:`spyne.util.django.DjangoComplexModel` types. * Removed ``@nillable_string`` and ``@nillable_string_iterable`` decorators from type (de)serializers from/to string. These methods are meant to be used via the wrapper functions {from,to}_string in ``ProtocolBase``\. * Spyne now tracks subclasses and adds them to the interface if they have the same namespace as their parent. * Simple dictionary protocol's ``hier_delim`` default value is now '.' * Fixes support for > 1 XmlAttribute referencing the same 'attribute_of' element in a ComplexModel subclass. * Render ``spyne.model.File`` as ``twisted.web.static.File`` when using HttpRpc over ``TwistedWebResource``. * Many, many, many bugs fixed. spyne-2.10.10 ------------- * Fix wsdl rendering in TwistedWebResource. * Fix http response header propagation in TwistedWebResource. * Fix handling of fractions in microsecond values. * Fix spyne.util.get_validation_schema() spyne-2.10.9 ------------ * Fix total_seconds quirk for Python 2.6. * Turn off Xml features like entity resolution by default. This mitigates an information disclosure attack risk in services whose response contain some fragments or all of the request. Also prevents DoS attacks that make use of entity expansion. See https://bitbucket.org/tiran/defusedxml for more info. * Drop Python 2.5 support (It wasn't working anyway). spyne-2.10.8 ------------ * Fix Unicode losing pattern on re-customization * Fix Duration serialization, add a ton of test cases. * Fix binary urlsafe_base64 encoding. * Fix arbitrary exception serialization. * Fix some doc errors. spyne-2.10.7 ------------ * Fix logic error in wsdl caching that prevented the url in Wsdl document from being customized. * Fix dictdoc not playing well with functions with empty return values. spyne-2.10.6 ------------ * Fix exception serialization regression in DictDocument family. * Fix xml utils (and its example). spyne-2.10.5 ------------ * Fix default value handling in ``HttpRpc``. * Fix invalid document type raising ``InternalError`` in DictDocument family. It now raises ``ValidationError``. * HttpRpc: Fix ``ByteArray`` deserialization. * HttpRpc: Fix many corner cases with ``Array``\s. * Fix Csv serializer. * Fix Mandatory variants of ``Double`` and ``Float`` inheriting from decimal. spyne-2.10.4 ------------ * Fix handling of ``spyne.model.binary.File.Value`` with just path name. * Fix decimal restrictions (some more). * Make user code that doesn't return anything work with twisted server transport. spyne-2.10.3 ------------ * Add validation tests for HierDictDocument and fix seen issues. * Add validation tests for FlatDictDocument and fix seen issues. * Clarify Json and Http behavior in relevant docstrings. * Fix Python2.6 generating max_occurs="inf" instead of "unbounded" sometimes. spyne-2.10.2 ------------ * Fix ByteArray support accross all protocols. * Fix namespaces of customized simple types inside ``XmlAttribute`` not being imported. spyne-2.10.1 ------------ * Fix confusion in Decimal restriction assignment. * Fix classmethod calls to ProtocolBase. * Fix schema generation error in namespaced xml attribute case. spyne-2.10.0 ------------ * Returning twisted's Deferred from user code is now supported. * You can now set Http response headers via ctx.out_header when out_protocol is HttpRpc. https://github.com/arskom/spyne/pull/201 * lxml is not a hard requirement anymore. * XmlDocument and friends: cleanup_namespaces is now True by default. * XmlDocument and friends: Added ``encoding`` and ``pretty_print`` flags that are directly passed to ``lxml.etree.tostring()``. * XmlDocument and friends:'attribute_of' added to ModelBase to add attribute support for primitives. This is currently ignored by (and mostly irrelevant to) other protocols. * XmlDocument and friends: Attribute serialization is working for arrays. * Add support for exposing existing whose source code via the _args argument to the srpc decorator. See the existing_api example for usage examples. * Add Streaming versions of Pyramid and Django bridge objects. * Remove destructor from ``MethodContext``. Now transports need to call ``.close()`` explicitly to close object and fire relevant events. * Application event 'method_context_constructed' was renamed to ``'method_context_created'``. * Application event 'method_context_destroyed' was removed. The ``'method_context_closed'`` event can be used instead. * SQLAlchemy integration now supports advanced features like specifying indexing methods. * The object composition graph can now be cyclic. * Integers were overhauled. Now boundary values of limited-size types are accessible via ``Attributes._{min,max}_bounds``. * We now have six spatial types, ``Point``, ``LineString`` and ``Polygon`` along with their ``Multi*`` variants. * The deprecated ``ProtocolBase.set_method_descriptor`` function was removed. * It's now possible to override serialization in service implementations. You can set ``ctx.out_document`` to have the return value from user funtion ignored. You can also set ``ctx.out_string`` to have the ``ctx.out_document`` ignored as well. * Added as_time_zone support to DateTime. It calls ``.astimezone(as_time_zone).replace(tzinfo=None)`` on native values. * Added YAML support via PyYaml. * Split dict logic in DictDocument as ``HierDictDocument`` and ``FlatDictDocument``. * Complete revamp of how DictDocument family work. skip_depth is replaced by richer functionalty that is enabled by two flags: ``ignore_wrappers`` and ``complex_as``. * Added cookie parsing support to HttpRpc via ``Cookie.SimpleCookie``. * Moved ``{to,from}_string`` logic from data models to ProtocolBase. This gives us the ability to have more complex fault messages with other fault subelements that are namespace-qualified without circular dependency problems - Stefan Andersson * DictDocument and friends: ``ignore_wrappers`` and ``complex_as`` options added as a way to customize protocol output without hindering other parts of the interface. spyne-2.9.5 ----------- * Fix restriction bases of simple types not being imported. * Fix for customized subclasses forgetting about their empty base classes. * Fix Attributes.nullable not surviving customization. spyne-2.9.4 ----------- * Fix for Python 2.6 quirk where any ``decimal.Decimal()`` is always less than any ``float()``. Where did that come from?! * Fix missing '/' in WsgiMounter. * Fix confusion in ``spyne.model.primitive.Decimal``'s parameter order. * Add forgotten ``HttpBase`` parameters to ``WsgiApplication``. spyne-2.9.3 ----------- * Fix WsgiApplication choking on empty string return value. * Fix TwistedWebResource choking on generators as return values. * Fix Csv serializer. spyne-2.9.2 ----------- * Fix Array serialization for Html Microformats * Fix deserialization of Fault objects for Soap11 * Fix Uuid not playing well with soft validation. * Fix Uuid not playing well with Xml Schema document. spyne-2.9.0 ----------- * Spyne is now stable! * Fix document_built events by adding a ``doc`` attribute to the ServerBase class. You can now do ``some_server.doc.wsdl11.event_manager.add_listener`` to add events to interface documents. * Add wsdl_document_built and xml_document_built events to relevant classes. * Behavioral change for TableModel's relationship handling: It's now an array by default. The TableModel is deprecated, long live __metadata__ on ComplexModel! * First-class integration with Pyramid. * First geospatial types: Point and Polygon. * Initial revision of the http request pattern matching support via ``werkzeug.routing``. * ``XmlObject`` -> ``XmlDocument``, ``JsonObject`` -> ``JsonDocument``, ``MessagePackObject`` -> ``MessagePackDocument``, ``DictObject`` -> ``DictDocument``. spyne-2.8.2-rc -------------- * travis-ci.org integration! See for yourself: http://travis-ci.org/arskom/spyne * Python 2.4 compatibility claim was dropped, because this particular Python version is nowhere to be found. * Many issues with Python 2.5 compatibility are fixed. spyne-2.8.1-rc -------------- * Misc fixes regarding the spyne.model.binary.File api. rpclib-2.8.0-rc -> spyne-2.8.0-rc --------------------------------- * Rpclib is dead. Long live Spyne! * Add support for JsonObject protocol. This initial version is expremental. * Add support for MessagePackObject and MessagePackRpc protocols. These initial versions are expremental. * Make DateTime string format customizable. * Implement TwistedWebResource that exposes an ``Application`` instance as a ``twisted.web.resource.Resource`` child. * Remove Deprecated ``XMLAttribute`` and ``XMLAttributeRef``. Use ``XmlAttribute`` and ``XmlAttributeRef`` instead. * Xml Schema: Add support for the tag. * Add a chapter about Validation to the manual. Thanks Alex! * Interface documents are no longer subclasses of InterfaceBase. It's up to the transport to expose the application using a given interface document standard now. The ``interface`` argument to the ``Application`` constructor is now ignored. * Html: Added a very simple lxml-based templating scheme: ``HtmlPage``. * Html: Added row-based tables: They show fields in rows. It's good for showing one object per table. * Html: Added ImageUri support. They render as tags in Html output. * Html: Added support for locales. You can now render field names as human- readable strings. * Add support for async methods, which execute after the primary user code returns. Currently, the only async execution method is via threads. * Xml & friends: Start tags are now in the same namespace as the definitions themselves. Intermediate tags are in the parent's namespace, just as before. * Xml & friends: Make the 'bare' mode work. * spyne.util.xml: ``get_object_as_xml`` can also get class suggestion. * spyne.util.xml: ``get_xml_as_object`` has argument order swapped: cls, elt -> elt, cls. See ab91a3e2ad4756b71d1a2752e5b0d2af8551e061. * There's a final argument order change in Application ctor: in_protocol, out_protocol, interface, name becomes: name, in_protocol, out_protocol, interface * Relevant pull requests with new features and notable changes: * https://github.com/arskom/spyne/pull/128 * https://github.com/arskom/spyne/pull/129 * https://github.com/arskom/spyne/pull/139 * https://github.com/arskom/spyne/pull/142 * https://github.com/arskom/spyne/pull/148 * https://github.com/arskom/spyne/pull/157 * https://github.com/arskom/spyne/pull/173 rpclib-2.7.0-beta ----------------- * Add support for non-chunked encoding to Wsgi transport. * Add support for Html Microformats. * Add ``function`` property to MethodContext that is re-initialized from ``descriptor.function`` for each new request. Stay away from ``descriptor.function`` unless you understand the consequences!.. * String and Unicode models are now separate objects with well-defined (de)serialization behaviour. * Argument order change in Application ctor: :: interface, in_protocol, out_protocol becomes: :: in_protocol, out_protocol, interface See here: https://github.com/arskom/spyne/commit/45f5af70aa826640008222bda96299d51c9df980#diff-1 * Full changelog: * https://github.com/arskom/spyne/pull/123 * https://github.com/arskom/spyne/pull/124 * https://github.com/arskom/spyne/pull/125 rpclib-2.6.1-beta ----------------- * Fix (for real this time) the race condition in wsgi server's wsdl handler. rpclib-2.6.0-beta ----------------- * HttpRpc now parses POST/PUT/PATCH bodies, can accept file uploads. Uses werkzeug to do that, which is now a soft dependency. * ByteArray now child of SimpleModel. It's now possible to customize it simply by calling it. * Fix race condition in wsgi server wsdl request. * Full change log: https://github.com/arskom/spyne/pull/122 rpclib-2.5.2-beta ----------------- * Misc. fixes. * Full change log: https://github.com/arskom/spyne/pull/118 rpclib-2.5.1-beta ----------------- * Switched to magic cookie constants instead of strings in protocol logic. * check_validator -> set_validator in ProtocolBase * Started parsing Http headers in HttpRpc protocol. * HttpRpc now properly validates nested value frequencies. * HttpRpc now works with arrays of simple types as well. * Full change log: https://github.com/arskom/spyne/pull/117 https://github.com/arskom/spyne/pull/116 rpclib-2.5.0-beta ----------------- * Implemented fanout support for transports and protocols that can handle that. * Implemented a helper module that generates a Soap/Wsdl 1.1 application in ``rpclib.util.simple`` * Some work towards supporting Python3 using ``2to3``. See issue #113. * ``ctx.descriptor.reset_function`` implemented. It's now safe to fiddle with that value in event handlers. * Added a cleaned-up version of the Django wrapper: https://gist.github.com/1316025 * Fix most of the tests that fail due to api changes. * Fix Http soap client. * Full change log: https://github.com/arskom/spyne/pull/115 rpclib-2.4.7-beta ----------------- * Made color in logs optional * Fixed ByteArray serializer rpclib-2.4.5-beta ----------------- * Time primitive was implemented. * Fix for multiple ports was integrated. * Added http cookie authentication example with suds. * Full change log: https://github.com/arskom/spyne/pull/109 rpclib-2.4.3-beta ----------------- * Many issues with 'soft' validation was fixed. * ``MethodDescriptor.udp`` added. Short for "User-Defined Properties", you can use it to store arbitrary metadata about the decorated method. * Fix HttpRpc response serialization. * Documentation updates. rpclib-2.4.1-beta ----------------- * Fixed import errors in Python<=2.5. * A problem with rpclib's String and unicode objects was fixed. rpclib-2.4.0-beta ----------------- * Fixed Fault publishing in Wsdl. * Implemented 'soft' validation. * Documentation improvements. It's mostly ready! * A bug with min/max_occurs logic was fixed. This causes rpclib not to send null values for elements with min_occurs=0 (the default value). * Native value for ``rpclib.model.primitive.String`` was changed to ``unicode``. To exchange raw data, you should use ``rpclib.model.binary.ByteArray``. * Full change log: https://github.com/arskom/spyne/pull/90 rpclib-2.3.3-beta ----------------- * Added MAX_CONTENT_LENGTH = 2 * 1024 * 1024 and BLOCK_LENGTH = 8 * 1024 constants to rpclib.server.wsgi module. * rpclib.model.binary.Attachment is deprecated, and is replaced by ByteArray. The native format of ByteArray is an iterable of strings. * Exception handling was formalized. HTTP return codes can be set by exception classes from rpclib.error or custom exceptions. * Full change log: https://github.com/arskom/spyne/pull/88 rpclib-2.3.2-beta ----------------- * Limited support for sqlalchemy.orm.relationship (no string arguments) * Added missing event firings. * Documented event api and fundamental data structures (rpclib._base) * Full change log: https://github.com/arskom/spyne/pull/87 rpclib-2.3.1-beta ----------------- * HttpRpc protocol now returns 404 when a requested resource was not found. * New tests added for HttpRpc protocol. * Miscellanous other fixes. See: https://github.com/arskom/spyne/pull/86 rpclib-2.3.0-beta ----------------- * Documentation improvements. * rpclib.protocol.xml.XmlObject is now working as out_protocol. * Many fixes. rpclib-2.2.3-beta ------------------ * Documentation improvements. * rpclib.client.http.Client -> rpclib.client.http.HttpClient * rpclib.client.zeromq.Client -> rpclib.client.zeromq.ZeroMQClient * rpclib.server.zeromq.Server -> rpclib.server.zeromq.ZeroMQServer * rpclib.model.table.TableSerializer -> rpclib.model.table.TableModel rpclib-2.2.2-beta ----------------- * Fixed call to rpclib.application.Application.call_wrapper * Fixed HttpRpc server transport instantiation. * Documentation improvements. rpclib-2.2.1-beta ----------------- * rpclib.application.Application.call_wrapper introduced * Documentation improvements. rpclib-2.2.0-beta ----------------- * The serialization / deserialization logic was redesigned. Now most of the serialization-related logic is under the responsibility of the ProtocolBase children. * Interface generation logic was redesigned. The WSDL logic is separated to XmlSchema and Wsdl11 classes. 'add_to_schema' calls were renamed to just 'add' and were moved inside rpclib.interface.xml_schema package. * Interface and Protocol assignment of an rpclib application is now more explicit. Both are also configurable during instantion. This doesn't mean there's much to configure :) * WS-I Conformance is back!. See https://github.com/arskom/spyne/blob/master/src/rpclib/test/interop/wsi-report-rpclib.xml for the latest conformance report. * Numeric types now support range restrictions. e.g. Integer(ge=0) will only accept positive integers. * Any -> AnyXml, AnyAsDict -> AnyDict. AnyAsDict is not the child of the AnyXml anymore. * rpclib.model.exception -> rpclib.model.fault. rpclib-2.1.0-alpha ------------------ * The method dispatch logic was rewritten: It's now possible for the protocols to override how method request strings are matched to methods definitions. * Unsigned integer primitives were added. * ZeroMQ client was fixed. * Header confusion in native http soap client was fixed. * Grouped transport-specific context information under ctx.transport attribute. * Added a self reference mechanism. rpclib-2.0.10-alpha ------------------- * The inclusion of base xml schemas were made optional. * WSDL: Fix out header being the same as in header. * Added type checking to outgoing Integer types. it's not handled as nicely as it should be. * Fixed the case where changing the _in_message tag name of the method prevented it from being called. * SOAP/WSDL: Added support for multiple {in,out}_header objects. * Fix some XMLAttribute bugs. rpclib-2.0.9-alpha ------------------ * Added inheritance support to rpclib.model.table.TableSerializer. rpclib-2.0.8-alpha ------------------ * The NullServer now also returns context with the return object to have it survive past user-defined method return. rpclib-2.0.7-alpha ------------------ * More tests are migrated to the new api. * Function identifier strings are no more created directly from the function object itself. Function's key in the class definition is used as default instead. * Base xml schemas are no longer imported. rpclib-2.0.6-alpha ------------------ * Added rpclib.server.null.NullServer, which is a server class with a client interface that attempts to do no (de)serialization at all. It's intended to be used in tests. rpclib-2.0.5-alpha ------------------ * Add late mapping support to sqlalchemy table serializer. rpclib-2.0.4-alpha ------------------ * Add preliminary support for a sqlalchemy-0.7-compatible serializer. rpclib-2.0.3-alpha ------------------ * Migrate the HttpRpc serializer to the new internal api. rpclib-2.0.2-alpha ------------------ * SimpleType -> SimpleModel * Small bugfixes. rpclib-2.0.1-alpha ------------------ * EventManager now uses ordered sets instead of normal sets to store event handlers. * Implemented sort_wsdl, a small hack to sort wsdl output in order to ease debugging. rpclib-2.0.0-alpha ------------------ * Implemented EventManager and replaced hook calls with events. * The rpc decorator now produces static methods. The methods still get an implicit first argument that holds the service contexts. It's an instance of the MethodContext class, and not the ServiceBase (formerly DefinitionBase) class. * The new srpc decorator doesn't force the methods to have an implicit first argument. * Fixed fault namespace resolution. * Moved xml constants to rpclib.const.xml_ns * The following changes to soaplib were ported to rpclib's SOAP/WSDL parts: * duration object is now compatible with Python's native timedelta. * WSDL: Support for multiple tags in the wsdl (one for each class in the application) * WSDL: Support for multiple tags and multiple ports. * WSDL: Support for enumerating exceptions a method can throw was added. * SOAP: Exceptions got some love to be more standards-compliant. * SOAP: Xml attribute support * Moved all modules with packagename.base to packagename._base. * Renamed classes to have module name as a prefix: * rpclib.client._base.Base -> rpclib.client._base.ClientBase * rpclib.model._base.Base -> rpclib.model._base.ModelBase * rpclib.protocol._base.Base -> rpclib.protocol._base.ProtocolBase * rpclib.server._base.Base -> rpclib.server._base.ServerBase * rpclib.service.DefinitionBase -> rpclib.service.ServiceBase * rpclib.server.wsgi.Application -> rpclib.server.wsgi.WsgiApplication * Moved some classes and modules around: * rpclib.model.clazz -> rpclib.model.complex * rpclib.model.complex.ClassSerializer -> rpclib.model.complex.ComplexModel * rpclib.Application -> rpclib.application.Application * rpclib.service.rpc, srpc -> rpclib.decorator.rpc, srpc soaplib-3.x -> rpclib-1.1.1-alpha --------------------------------- * Soaplib is now also protocol agnostic. As it now supports protocols other than soap (like Rest-minus-the-verbs HttpRpc), it's renamed to rpclib. This also means soaplib can now support multiple versions of soap and wsdl standards. * Mention of xml and soap removed from public api where it's not directly related to soap or xml. (e.g. a hook rename: on_method_exception_xml -> on_method_exception_doc) * Protocol serializers now return iterables instead of complete messages. This is a first step towards eliminating the need to have the whole message in memory during processing. soaplib-2.x ----------- * This release transformed soaplib from a soap server that exclusively supported http to a soap serialization/deserialization library that is architecture and transport agnostic. * Hard dependency on WSGI removed. * Sphinx docs with working examples: http://arskom.github.com/rpclib/ * Serializers renamed to Models. * Standalone xsd generation for ClassSerializer objects has been added. This allows soaplib to be used to define generic XML schemas, without SOAP artifacts. * Annotation Tags for primitive Models has been added. * The soaplib client has been re-written after having been dropped from recent releases. It follows the suds API but is based on lxml for better performance. WARNING: the soaplib client is not well-tested and future support is tentative and dependent on community response. * 0mq support added. * Twisted supported via WSGI wrappers. * Increased test coverage for soaplib and supported servers soaplib-1.0 ----------- * Standards-compliant Soap Faults * Allow multiple return values and return types soaplib-0.9.4 ------------- * pritimitive.Array -> clazz.Array * Support for SimpleType restrictions (pattern, length, etc.) soaplib-0.9.3 ------------- * Soap header support * Tried the WS-I Test first time. Many bug fixes. soaplib-0.9.2 ------------- * Support for inheritance. soaplib-0.9.1 ------------- * Support for publishing multiple service classes. soaplib-0.9 ----------- * Soap server logic almost completely rewritten. * Soap client removed in favor of suds. * Object definition api no longer needs a class types: under class definition. * XML Schema validation is supported. * Support for publishing multiple namespaces. (multiple tags in the wsdl) * Support for enumerations. * Application and Service Definition are separated. Application is instantiated on server start, and Service Definition is instantiated for each new request. * @soapmethod -> @rpc soaplib-0.8.1 ------------- * Switched to lxml for proper xml namespace support. soaplib-0.8.0 ------------- * First public stable release. Keywords: soap,wsdl,wsgi,zeromq,rest,rpc,json,http,msgpack,xml,django,pyramid,postgresql,sqlalchemy,werkzeug,twisted,yaml Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Operating System :: OS Independent Classifier: Natural Language :: English Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content spyne-2.11.0/spyne.egg-info/SOURCES.txt0000644000175000001440000001167212352131446017430 0ustar plqusers00000000000000README.rst setup.py spyne/__init__.py spyne/_base.py spyne/application.py spyne/decorator.py spyne/error.py spyne/service.py spyne.egg-info/PKG-INFO spyne.egg-info/SOURCES.txt spyne.egg-info/dependency_links.txt spyne.egg-info/entry_points.txt spyne.egg-info/not-zip-safe spyne.egg-info/requires.txt spyne.egg-info/top_level.txt spyne/auxproc/__init__.py spyne/auxproc/_base.py spyne/auxproc/sync.py spyne/auxproc/thread.py spyne/client/__init__.py spyne/client/_base.py spyne/client/django.py spyne/client/http.py spyne/client/zeromq.py spyne/client/twisted/__init__.py spyne/const/__init__.py spyne/const/ansi_color.py spyne/const/http.py spyne/const/xml_ns.py spyne/interface/__init__.py spyne/interface/_base.py spyne/interface/wsdl/__init__.py spyne/interface/wsdl/defn.py spyne/interface/wsdl/wsdl11.py spyne/interface/xml_schema/__init__.py spyne/interface/xml_schema/_base.py spyne/interface/xml_schema/defn.py spyne/interface/xml_schema/genpy.py spyne/interface/xml_schema/model.py spyne/interface/xml_schema/parser.py spyne/model/__init__.py spyne/model/_base.py spyne/model/binary.py spyne/model/complex.py spyne/model/enum.py spyne/model/fault.py spyne/model/primitive.py spyne/model/table.py spyne/protocol/__init__.py spyne/protocol/_base.py spyne/protocol/csv.py spyne/protocol/dictdoc.py spyne/protocol/http.py spyne/protocol/json.py spyne/protocol/msgpack.py spyne/protocol/xml.py spyne/protocol/yaml.py spyne/protocol/cloth/__init__.py spyne/protocol/cloth/_base.py spyne/protocol/cloth/to_cloth.py spyne/protocol/cloth/to_parent.py spyne/protocol/html/__init__.py spyne/protocol/html/_base.py spyne/protocol/html/microformat.py spyne/protocol/html/table.py spyne/protocol/html/template.py spyne/protocol/soap/__init__.py spyne/protocol/soap/mime.py spyne/protocol/soap/soap11.py spyne/server/__init__.py spyne/server/_base.py spyne/server/django.py spyne/server/http.py spyne/server/msgpack.py spyne/server/null.py spyne/server/pyramid.py spyne/server/wsgi.py spyne/server/zeromq.py spyne/server/twisted/__init__.py spyne/server/twisted/_base.py spyne/server/twisted/http.py spyne/server/twisted/msgpack.py spyne/server/twisted/websocket.py spyne/test/__init__.py spyne/test/regen_wsdl.py spyne/test/sort_wsdl.py spyne/test/test_null_server.py spyne/test/test_service.py spyne/test/test_soft_validation.py spyne/test/test_sqlalchemy.py spyne/test/test_sqlalchemy_deprecated.py spyne/test/test_util.py spyne/test/interface/__init__.py spyne/test/interface/test_interface.py spyne/test/interface/test_wsgi.py spyne/test/interface/test_xml_schema.py spyne/test/interface/wsdl/__init__.py spyne/test/interface/wsdl/defult_services.py spyne/test/interface/wsdl/port_service_services.py spyne/test/interface/wsdl/test_bindings.py spyne/test/interface/wsdl/test_default_wsdl.py spyne/test/interface/wsdl/test_op_req_suffix.py spyne/test/interface/wsdl/test_wsdl_ports_services.py spyne/test/interop/__init__.py spyne/test/interop/_test_soap_client_base.py spyne/test/interop/test_django.py spyne/test/interop/test_httprpc.py spyne/test/interop/test_pyramid.py spyne/test/interop/test_soap_client_http.py spyne/test/interop/test_soap_client_http_twisted.py spyne/test/interop/test_soap_client_zeromq.py spyne/test/interop/test_suds.py spyne/test/interop/test_wsi.py spyne/test/interop/server/__init__.py spyne/test/interop/server/_service.py spyne/test/interop/server/httprpc_csv_basic.py spyne/test/interop/server/httprpc_pod_basic.py spyne/test/interop/server/httprpc_pod_basic_twisted.py spyne/test/interop/server/httprpc_soap_basic.py spyne/test/interop/server/soap_http_basic.py spyne/test/interop/server/soap_http_basic_twisted.py spyne/test/interop/server/soap_http_static.py spyne/test/interop/server/soap_zeromq.py spyne/test/model/__init__.py spyne/test/model/test_binary.py spyne/test/model/test_complex.py spyne/test/model/test_enum.py spyne/test/model/test_exception.py spyne/test/model/test_include.py spyne/test/model/test_primitive.py spyne/test/multipython/__init__.py spyne/test/multipython/model/__init__.py spyne/test/multipython/model/test_complex.py spyne/test/protocol/__init__.py spyne/test/protocol/_test_dictdoc.py spyne/test/protocol/test_html_microformat.py spyne/test/protocol/test_html_table.py spyne/test/protocol/test_http.py spyne/test/protocol/test_json.py spyne/test/protocol/test_msgpack.py spyne/test/protocol/test_soap.py spyne/test/protocol/test_xml.py spyne/test/protocol/test_yaml.py spyne/test/transport/__init__.py spyne/test/transport/test_msgpack.py spyne/util/__init__.py spyne/util/_twisted_ws.py spyne/util/appreg.py spyne/util/cdict.py spyne/util/color.py spyne/util/dictdoc.py spyne/util/django.py spyne/util/email.py spyne/util/etreeconv.py spyne/util/http.py spyne/util/invregexp.py spyne/util/meta.py spyne/util/odict.py spyne/util/protocol.py spyne/util/simple.py spyne/util/six.py spyne/util/sqlalchemy.py spyne/util/test.py spyne/util/toposort.py spyne/util/web.py spyne/util/wsgi_wrapper.py spyne/util/xml.py spyne/util/oset/__init__.py spyne/util/oset/new.py spyne/util/oset/old.pyspyne-2.11.0/spyne.egg-info/dependency_links.txt0000644000175000001440000000000112352131446021603 0ustar plqusers00000000000000 spyne-2.11.0/spyne.egg-info/entry_points.txt0000644000175000001440000000007112352131446021031 0ustar plqusers00000000000000[console_scripts] sort_wsdl = spyne.test.sort_wsdl:main spyne-2.11.0/spyne.egg-info/not-zip-safe0000644000175000001440000000000112345431673017772 0ustar plqusers00000000000000 spyne-2.11.0/spyne.egg-info/requires.txt0000644000175000001440000000000412352131446020127 0ustar plqusers00000000000000pytzspyne-2.11.0/spyne.egg-info/top_level.txt0000644000175000001440000000000612352131446020263 0ustar plqusers00000000000000spyne spyne-2.11.0/README.rst0000644000175000001440000001342212352131303014366 0ustar plqusers00000000000000.. image:: https://travis-ci.org/arskom/spyne.png?branch=master :target: http://travis-ci.org/arskom/spyne About ===== Spyne aims to save the protocol implementers the hassle of implementing their own remote procedure call api and the application programmers the hassle of jumping through hoops just to expose their services using multiple protocols and transports. In other words, Spyne is a framework for building distributed solutions that strictly follow the MVC pattern, where Model = `spyne.model`, View = `spyne.protocol` and Controller = `user code`. Spyne comes with the implementations of popular transport, protocol and interface document standards along with a well-defined API that lets you build on existing functionality. Spyne currently supports the WSDL 1.1 interface description standard, along with SOAP 1.1 and the so-called HttpRpc, XmlDocument, JsonDocument, YamlDocument, MessagePackDocument and MessagePackRpc protocols which can be transported via Http or ZeroMQ. The transports can be used in both a client or server setting. The following are the primary sources of information about spyne: * Spyne's home page is: http://spyne.io/ * The latest documentation for all releases of Spyne can be found at: http://spyne.io/docs * The official source code repository is at: https://github.com/arskom/spyne * The official spyne discussion forum is at: people@spyne.io. Subscribe either via http://lists.spyne.io/listinfo/people or by sending an empty message to: people-subscribe at spyne dot io. * You can download Spyne releases from `github `_ or `pypi `_. Requirements ============ Spyne source distribution is a collection of highly decoupled components, which makes it a bit difficult to put a simple list of requirements, as literally everything except ``pytz`` is optional. Python version -------------- First things first: Spyne is known to fully work on Python versions 2.6 and 2.7. However Spyne's Soap (and all of its subcomponents like XmlDocument, Wsdl, etc.) subsystem also works on Python 3.3 and up. You can track the Python 3 porting progress from our jenkins deployment, here: https://spyne.ci.cloudbees.com/job/spyne/PYFLAV=3.3/ The only hard requirement is `pytz `_ which is available via pypi. Libraries --------- Additionally the following software packages are needed for various subsystems of Spyne: * A Wsgi server of your choice is needed to wrap ``spyne.server.wsgi.WsgiApplication`` * `lxml>=3.2.5 `_ is needed for any xml-related protocol. * `lxml>=3.3.0 `_ is needed for any html-related protocol. * `SQLAlchemy `_ is needed for ``spyne.model.complex.TTableModel``. * `pyzmq `_ is needed for ``spyne.client.zeromq.ZeroMQClient`` and ``spyne.server.zeromq.ZeroMQServer``. * `Werkzeug `_ is needed for using ``spyne.protocol.http.HttpRpc`` under a wsgi transport. * `PyParsing `_ is needed for using ``HttpPattern``'s with ``spyne.protocol.http.HttpRpc``\. Use PyParsing<2.0 on Python 2 as PyParsing>=2.x is Python 3 only. * `Twisted `_ is needed for anything in ``spyne.server.twisted`` and ``spyne.client.twisted``. * `Django `_ (tested with 1.4 and up) is needed for anything in ``spyne.server.django``. * `Pyramid `_ is needed for ``spyne.server.pyramid.PyramidApplication``. * `msgpack-python `_ is needed for ``spyne.protocol.msgpack``. * `PyYaml `_ is needed for ``spyne.protocol.yaml``. * `simplejson `_ is used when found for ``spyne.protocol.json``. You are advised to add these as requirements to your own projects, as these are only optional dependencies of Spyne, thus not handled in its setup script. Installing ========== You can get spyne via pypi: :: easy_install spyne or you can clone the latest master tree from github: :: git clone git://github.com/arskom/spyne.git To install from source distribution, you can run the setup script as usual: :: python setup.py install [--user] If you want to make any changes to the Spyne code, just use :: python setup.py develop [--user] so that you can painlessly test your patches. Finally, to run the tests use: :: pyhon setup.py test The test script should first install every single library that Spyne integrates with to the current directory, along with additional packages like pytest or tox that are only needed when running Spyne testsuite. Getting Support =============== The official mailing list for both users and developers alike can be found at http://lists.spyne.io/listinfo/people. You can also use the 'spyne' tag to ask questions on `Stack Overflow `_. Please don't use the issue tracker for asking questions. It's the database that holds the most important information for the project, so we must avoid cluttering it as much as possible. Contributing ============ Please see the CONTRIBUTING.rst file in the Spyne source distribution for information about how you can help Spyne get more awesome. Acknowledgments =============== .. image:: http://www.jetbrains.com/img/logos/pycharm_logo142x29.gif :target: http://www.jetbrains.com/pycharm/ Spyne committers get a free license for PyCharm Professional Edition, courtesy of JetBrains. .. image:: http://www.cloudbees.com/sites/default/files/Button-Built-on-CB-1.png :target: https://spyne.ci.cloudbees.com/ CloudBees generously hosts our Jenkins installation and gives us a ton of compute time for free. Thanks a lot guys!.. spyne-2.11.0/setup.py0000755000175000001440000003135712352126507014435 0ustar plqusers00000000000000#!/usr/bin/env python #encoding: utf8 from __future__ import print_function import os import re import sys from setuptools import setup from setuptools import find_packages from setuptools.command.test import test as TestCommand try: import colorama colorama.init() from colorama import Fore RESET = Fore.RESET GREEN = Fore.GREEN RED = Fore.RED except ImportError: RESET = '' GREEN = '' RED = '' import inspect from os.path import join, dirname, abspath OWN_PATH = abspath(inspect.getfile(inspect.currentframe())) EXAMPLES_DIR = join(dirname(OWN_PATH), 'examples') v = open(os.path.join(os.path.dirname(__file__), 'spyne', '__init__.py'), 'r') VERSION = re.match(r".*__version__ = '(.*?)'", v.read(), re.S).group(1) SHORT_DESC="""A transport and architecture agnostic rpc library that focuses on exposing public services with a well-defined API.""" LONG_DESC = """Spyne aims to save the protocol implementers the hassle of implementing their own remote procedure call api and the application programmers the hassle of jumping through hoops just to expose their services using multiple protocols and transports. """ try: os.stat('CHANGELOG.rst') LONG_DESC += "\n\n" + open('CHANGELOG.rst', 'r').read() except OSError: pass ############################### # Testing stuff def call_test(f, a, tests): import spyne.test from glob import glob from itertools import chain from multiprocessing import Process, Queue tests_dir = os.path.dirname(spyne.test.__file__) a.extend(chain(*[glob(join(tests_dir, test)) for test in tests])) queue = Queue() p = Process(target=_wrapper(f), args=[a, queue]) p.start() p.join() ret = queue.get() if ret == 0: print(tests, "OK") else: print(tests, "FAIL") return ret def _wrapper(f): def _(args, queue): try: retval = f(args) except TypeError: # it's a pain to call trial. sys.argv = ['trial'] sys.argv.extend(args) retval = f() queue.put(retval) return _ def run_tests_and_create_report(report_name, *tests, **kwargs): import spyne.test import pytest from glob import glob from itertools import chain if os.path.isfile(report_name): os.unlink(report_name) tests_dir = os.path.dirname(spyne.test.__file__) args = ['--tb=short', '--junitxml=%s' % report_name] args.extend('--{0}={1}'.format(k, v) for k, v in kwargs.items()) args.extend(chain(*[glob("%s/%s" % (tests_dir, test)) for test in tests])) return pytest.main(args) _ctr = 0 def call_pytest(*tests, **kwargs): global _ctr _ctr += 1 file_name = 'test_result.%d.xml' % _ctr return run_tests_and_create_report(file_name, *tests, **kwargs) def call_pytest_subprocess(*tests, **kwargs): global _ctr import pytest _ctr += 1 file_name = 'test_result.%d.xml' % _ctr if os.path.isfile(file_name): os.unlink(file_name) args = ['--tb=line', '--junitxml=%s' % file_name] args.extend('--{0}={1}'.format(k, v) for k, v in kwargs.items()) return call_test(pytest.main, args, tests) def call_trial(*tests, **kwargs): import spyne.test from glob import glob from itertools import chain global _ctr _ctr += 1 file_name = 'test_result.%d.subunit' % _ctr with SubUnitTee(file_name): tests_dir = os.path.dirname(spyne.test.__file__) sys.argv = ['trial', '--reporter=subunit'] sys.argv.extend(chain(*[glob(join(tests_dir, test)) for test in tests])) from twisted.scripts.trial import Options from twisted.scripts.trial import _makeRunner from twisted.scripts.trial import _getSuite config = Options() config.parseOptions() trialRunner = _makeRunner(config) suite = _getSuite(config) test_result = trialRunner.run(suite) try: subunit2junitxml(_ctr) except Exception as e: # this is not super important. print(e) return int(not test_result.wasSuccessful()) class InstallTestDeps(TestCommand): pass def subunit2junitxml(ctr): from testtools import ExtendedToStreamDecorator from testtools import StreamToExtendedDecorator from subunit import StreamResultToBytes from subunit.filters import filter_by_result from subunit.filters import run_tests_from_stream from spyne.util.six import BytesIO from junitxml import JUnitXmlResult sys.argv = ['subunit-1to2'] subunit1_file_name = 'test_result.%d.subunit' % ctr subunit2 = BytesIO() run_tests_from_stream(open(subunit1_file_name, 'rb'), ExtendedToStreamDecorator(StreamResultToBytes(subunit2))) subunit2.seek(0) sys.argv = ['subunit2junitxml'] sys.stdin = subunit2 def f(output): return StreamToExtendedDecorator(JUnitXmlResult(output)) junit_file_name = 'test_result.%d.xml' % ctr filter_by_result(f, junit_file_name, True, False, protocol_version=2, passthrough_subunit=True, input_stream=subunit2) class ExtendedTestCommand(TestCommand): """TestCommand customized to project needs.""" user_options = TestCommand.user_options + [ ('capture=', 'k', "py.test output capture control (see py.test " "--capture)"), ] def initialize_options(self): TestCommand.initialize_options(self) self.capture = 'fd' def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True class RunTests(ExtendedTestCommand): def run_tests(self): print("running tests") sys.path.append(join(EXAMPLES_DIR, 'django')) os.environ['DJANGO_SETTINGS_MODULE'] = 'rpctest.settings' ret = 0 ret = call_pytest('interface', 'model', 'multipython', 'protocol', 'test_null_server.py', 'test_service.py', 'test_soft_validation.py', 'test_util.py', 'test_sqlalchemy.py', 'test_sqlalchemy_deprecated.py', # here we run django tests in the same process # for coverage reason 'interop/test_django.py', 'interop/test_pyramid.py', capture=self.capture) or ret # test different versions of Django # FIXME: better to use tox in CI script # For now we run it here from tox._config import parseconfig from tox._cmdline import Session tox_args = ['-ctox.django.ini'] config = parseconfig(tox_args, 'tox') ret = Session(config).runcommand() or ret ret = call_pytest_subprocess('interop/test_httprpc.py', capture=self.capture) or ret ret = call_pytest_subprocess('interop/test_soap_client_http.py', capture=self.capture) or ret ret = call_pytest_subprocess('interop/test_soap_client_zeromq.py', capture=self.capture) or ret ret = call_pytest_subprocess('interop/test_suds.py', capture=self.capture) or ret ret = call_trial('interop/test_soap_client_http_twisted.py', 'transport/test_msgpack.py', capture=self.capture) or ret if ret == 0: print(GREEN + "All that glisters is not gold." + RESET) else: print(RED + "Something is rotten in the state of Denmark." + RESET) raise SystemExit(ret) class RunDjangoTests(ExtendedTestCommand): """Run django interoperability tests. Useful for Tox. """ def run_tests(self): import django print("running django tests") sys.path.append(join(EXAMPLES_DIR, 'django')) os.environ['DJANGO_SETTINGS_MODULE'] = 'rpctest.settings' file_name = 'test_result_django_{0}.xml'.format(django.get_version()) ret = run_tests_and_create_report(file_name, 'interop/test_django.py', capture=self.capture) if ret == 0: print(GREEN + "All Django tests passed." + RESET) else: print(RED + "At least one Django test failed." + RESET) raise SystemExit(ret) class RunMultiPythonTests(TestCommand): """Run tests compatible with different python implementations. """ def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True def get_python_flavour(self): try: # CPython2, PyPy return sys.subversion[0] except AttributeError: pass try: # CPython3 return sys.implementation.cache_tag except AttributeError: pass try: # Jython return sys.JYTHON_JAR except AttributeError: pass raise NotImplementedError def run_tests(self): flavour = self.get_python_flavour() file_name = 'test_result.multi_python_{0}.xml'.format(flavour) ret = run_tests_and_create_report(file_name, 'multipython') if ret == 0: print(GREEN + "All multi Python tests passed." + RESET) else: print(RED + "At least one multi Python test failed." + RESET) raise SystemExit(ret) multi_python_test_reqs = ['pytest', 'coverage', 'junitxml'] if 'test_multi_python' in sys.argv: test_reqs = multi_python_test_reqs else: test_reqs = multi_python_test_reqs + [ 'pytest', 'werkzeug', 'sqlalchemy', 'lxml>=2.3', 'pyyaml', 'pyzmq', 'twisted', 'colorama', 'msgpack-python', 'webtest', 'django', 'pytest_django', 'python-subunit', 'pyramid', 'tox' ] if sys.version_info < (3,0): test_reqs.extend(['pyparsing<1.99', 'suds']) else: test_reqs.extend(['pyparsing', 'suds-jurko']) class SubUnitTee(object): def __init__(self, name): self.name = name def __enter__(self): self.file = open(self.name, 'wb') self.stdout = sys.stdout self.stderr = sys.stderr sys.stdout = sys.stderr = self def __exit__(self, *args): sys.stdout = self.stdout sys.stderr = self.stderr print("CLOSED") self.file.close() def writelines(self, data): for d in data: self.write(data) self.write('\n') def write(self, data): if data.startswith("test:") \ or data.startswith("successful:") \ or data.startswith("error:") \ or data.startswith("failure:") \ or data.startswith("skip:") \ or data.startswith("notsupported:"): self.file.write(data) if not data.endswith("\n"): self.file.write("\n") self.stdout.write(data) def read(self,d=0): return '' def flush(self): self.stdout.flush() self.stderr.flush() # Testing stuff ends here. ############################### setup( name='spyne', packages=find_packages(), version=VERSION, description=SHORT_DESC, long_description=LONG_DESC, classifiers=[ 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: Implementation :: CPython', #'Programming Language :: Python :: Implementation :: Jython', 'Programming Language :: Python :: Implementation :: PyPy', 'Operating System :: OS Independent', 'Natural Language :: English', 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], keywords=('soap', 'wsdl', 'wsgi', 'zeromq', 'rest', 'rpc', 'json', 'http', 'msgpack', 'xml', 'django', 'pyramid', 'postgresql', 'sqlalchemy', 'werkzeug', 'twisted', 'yaml'), author='Burak Arslan', author_email='burak+spyne@arskom.com.tr', maintainer='Burak Arslan', maintainer_email='burak+spyne@arskom.com.tr', url='http://spyne.io', license='LGPL-2.1', zip_safe=False, install_requires=[ 'pytz', ], entry_points={ 'console_scripts': [ 'sort_wsdl=spyne.test.sort_wsdl:main', ] }, tests_require = test_reqs, cmdclass = {'test': RunTests, 'install_test_deps': InstallTestDeps, 'test_django': RunDjangoTests, 'test_multi_python': RunMultiPythonTests }, ) spyne-2.11.0/PKG-INFO0000644000175000001440000010050712352131452014002 0ustar plqusers00000000000000Metadata-Version: 1.1 Name: spyne Version: 2.11.0 Summary: A transport and architecture agnostic rpc library that focuses on exposing public services with a well-defined API. Home-page: http://spyne.io Author: Burak Arslan Author-email: burak+spyne@arskom.com.tr License: LGPL-2.1 Description: Spyne aims to save the protocol implementers the hassle of implementing their own remote procedure call api and the application programmers the hassle of jumping through hoops just to expose their services using multiple protocols and transports. Changelog ========= spyne-2.11.0 ------------ * Experimental Python 3 Support for all of the Xml-related (non-Html) components. * Add support for altering output protocol by setting ``ctx.out_protocol``. * Add returning ctx.out_string support to null server (The ``ostr`` argument). * Add support for XmlData modifier. It lets mapping the data in the xml body to an object field via xsd:simpleContent. * Remove deprecated ``JsonObject`` identifier. Just do a gentle ``s/JsonObject/JsonDocument/g`` if you're still using it. * SQLAlchemy: Implement storing arrays of simple types in a table. * SQLAlchemy: Make it work with multiple foreign keys from one table to another. * SQLAlchemy: Implement a hybrid file container that puts file metadata in a json column in database and and file data in file system. Fully supported by all protocols as a binary File format. * Implement an Xml Schema parser. * Import all model markers to ``spyne.model``. * Import ``@rpc``\, ``@srpc``\, ``ServiceBase`` and ``Application`` inside the ``spyne`` module. * Implement JsonP protocol. * Implement SpyneJsonRpc 1.0 protocol -- it supports request headers. Sample request: ``{"ver":1, "body": {"div": [4,2]}, "head": {"id": 1234}}`` Sample response: ``{"ver":1, "body": 2}`` Sample request: ``{"ver":1, "body": {"div": {"dividend":4,"divisor":0]}}`` Sample response: ``{"ver":1, "fault": {"faultcode": "Server", "faultstring": "Internal Error"}}}`` * Steal and integrate the experimental WebSocket tranport for Twisted. * Support Django natively using :class:`spyne.server.django.DjangoView` and :class:`spyne.server.django.DjangoServer`. * It's now possible to override the ``JsonEncoder`` class ``JsonDocument`` uses. * Remove hard-coded utf-8 defaults from almost everywhere. * Remove hard-coded pytz.utc defaults from everywhere. Use spyne.LOCAL_TZ to configure the default time zone. * As a result of the above change, datetime objects deserialized by Spyne are are forced to the above time zone during soft validation (nothing should have changed from the user code perspective). * Add default_factory to ModelBase customizer. It's a callable that produces default values on demand. Suitable to be used with e.g. lambdas that return mutable defaults. * New ``spyne.util.AttrDict`` can be used for passing various dynamic configuration data. * ``child_attrs`` can now be passed to the ComplexModelBase customizer in order to make object-specific in-place customizations to child types. * Add mapper between Django models and :class:`spyne.util.django.DjangoComplexModel` types. * Removed ``@nillable_string`` and ``@nillable_string_iterable`` decorators from type (de)serializers from/to string. These methods are meant to be used via the wrapper functions {from,to}_string in ``ProtocolBase``\. * Spyne now tracks subclasses and adds them to the interface if they have the same namespace as their parent. * Simple dictionary protocol's ``hier_delim`` default value is now '.' * Fixes support for > 1 XmlAttribute referencing the same 'attribute_of' element in a ComplexModel subclass. * Render ``spyne.model.File`` as ``twisted.web.static.File`` when using HttpRpc over ``TwistedWebResource``. * Many, many, many bugs fixed. spyne-2.10.10 ------------- * Fix wsdl rendering in TwistedWebResource. * Fix http response header propagation in TwistedWebResource. * Fix handling of fractions in microsecond values. * Fix spyne.util.get_validation_schema() spyne-2.10.9 ------------ * Fix total_seconds quirk for Python 2.6. * Turn off Xml features like entity resolution by default. This mitigates an information disclosure attack risk in services whose response contain some fragments or all of the request. Also prevents DoS attacks that make use of entity expansion. See https://bitbucket.org/tiran/defusedxml for more info. * Drop Python 2.5 support (It wasn't working anyway). spyne-2.10.8 ------------ * Fix Unicode losing pattern on re-customization * Fix Duration serialization, add a ton of test cases. * Fix binary urlsafe_base64 encoding. * Fix arbitrary exception serialization. * Fix some doc errors. spyne-2.10.7 ------------ * Fix logic error in wsdl caching that prevented the url in Wsdl document from being customized. * Fix dictdoc not playing well with functions with empty return values. spyne-2.10.6 ------------ * Fix exception serialization regression in DictDocument family. * Fix xml utils (and its example). spyne-2.10.5 ------------ * Fix default value handling in ``HttpRpc``. * Fix invalid document type raising ``InternalError`` in DictDocument family. It now raises ``ValidationError``. * HttpRpc: Fix ``ByteArray`` deserialization. * HttpRpc: Fix many corner cases with ``Array``\s. * Fix Csv serializer. * Fix Mandatory variants of ``Double`` and ``Float`` inheriting from decimal. spyne-2.10.4 ------------ * Fix handling of ``spyne.model.binary.File.Value`` with just path name. * Fix decimal restrictions (some more). * Make user code that doesn't return anything work with twisted server transport. spyne-2.10.3 ------------ * Add validation tests for HierDictDocument and fix seen issues. * Add validation tests for FlatDictDocument and fix seen issues. * Clarify Json and Http behavior in relevant docstrings. * Fix Python2.6 generating max_occurs="inf" instead of "unbounded" sometimes. spyne-2.10.2 ------------ * Fix ByteArray support accross all protocols. * Fix namespaces of customized simple types inside ``XmlAttribute`` not being imported. spyne-2.10.1 ------------ * Fix confusion in Decimal restriction assignment. * Fix classmethod calls to ProtocolBase. * Fix schema generation error in namespaced xml attribute case. spyne-2.10.0 ------------ * Returning twisted's Deferred from user code is now supported. * You can now set Http response headers via ctx.out_header when out_protocol is HttpRpc. https://github.com/arskom/spyne/pull/201 * lxml is not a hard requirement anymore. * XmlDocument and friends: cleanup_namespaces is now True by default. * XmlDocument and friends: Added ``encoding`` and ``pretty_print`` flags that are directly passed to ``lxml.etree.tostring()``. * XmlDocument and friends:'attribute_of' added to ModelBase to add attribute support for primitives. This is currently ignored by (and mostly irrelevant to) other protocols. * XmlDocument and friends: Attribute serialization is working for arrays. * Add support for exposing existing whose source code via the _args argument to the srpc decorator. See the existing_api example for usage examples. * Add Streaming versions of Pyramid and Django bridge objects. * Remove destructor from ``MethodContext``. Now transports need to call ``.close()`` explicitly to close object and fire relevant events. * Application event 'method_context_constructed' was renamed to ``'method_context_created'``. * Application event 'method_context_destroyed' was removed. The ``'method_context_closed'`` event can be used instead. * SQLAlchemy integration now supports advanced features like specifying indexing methods. * The object composition graph can now be cyclic. * Integers were overhauled. Now boundary values of limited-size types are accessible via ``Attributes._{min,max}_bounds``. * We now have six spatial types, ``Point``, ``LineString`` and ``Polygon`` along with their ``Multi*`` variants. * The deprecated ``ProtocolBase.set_method_descriptor`` function was removed. * It's now possible to override serialization in service implementations. You can set ``ctx.out_document`` to have the return value from user funtion ignored. You can also set ``ctx.out_string`` to have the ``ctx.out_document`` ignored as well. * Added as_time_zone support to DateTime. It calls ``.astimezone(as_time_zone).replace(tzinfo=None)`` on native values. * Added YAML support via PyYaml. * Split dict logic in DictDocument as ``HierDictDocument`` and ``FlatDictDocument``. * Complete revamp of how DictDocument family work. skip_depth is replaced by richer functionalty that is enabled by two flags: ``ignore_wrappers`` and ``complex_as``. * Added cookie parsing support to HttpRpc via ``Cookie.SimpleCookie``. * Moved ``{to,from}_string`` logic from data models to ProtocolBase. This gives us the ability to have more complex fault messages with other fault subelements that are namespace-qualified without circular dependency problems - Stefan Andersson * DictDocument and friends: ``ignore_wrappers`` and ``complex_as`` options added as a way to customize protocol output without hindering other parts of the interface. spyne-2.9.5 ----------- * Fix restriction bases of simple types not being imported. * Fix for customized subclasses forgetting about their empty base classes. * Fix Attributes.nullable not surviving customization. spyne-2.9.4 ----------- * Fix for Python 2.6 quirk where any ``decimal.Decimal()`` is always less than any ``float()``. Where did that come from?! * Fix missing '/' in WsgiMounter. * Fix confusion in ``spyne.model.primitive.Decimal``'s parameter order. * Add forgotten ``HttpBase`` parameters to ``WsgiApplication``. spyne-2.9.3 ----------- * Fix WsgiApplication choking on empty string return value. * Fix TwistedWebResource choking on generators as return values. * Fix Csv serializer. spyne-2.9.2 ----------- * Fix Array serialization for Html Microformats * Fix deserialization of Fault objects for Soap11 * Fix Uuid not playing well with soft validation. * Fix Uuid not playing well with Xml Schema document. spyne-2.9.0 ----------- * Spyne is now stable! * Fix document_built events by adding a ``doc`` attribute to the ServerBase class. You can now do ``some_server.doc.wsdl11.event_manager.add_listener`` to add events to interface documents. * Add wsdl_document_built and xml_document_built events to relevant classes. * Behavioral change for TableModel's relationship handling: It's now an array by default. The TableModel is deprecated, long live __metadata__ on ComplexModel! * First-class integration with Pyramid. * First geospatial types: Point and Polygon. * Initial revision of the http request pattern matching support via ``werkzeug.routing``. * ``XmlObject`` -> ``XmlDocument``, ``JsonObject`` -> ``JsonDocument``, ``MessagePackObject`` -> ``MessagePackDocument``, ``DictObject`` -> ``DictDocument``. spyne-2.8.2-rc -------------- * travis-ci.org integration! See for yourself: http://travis-ci.org/arskom/spyne * Python 2.4 compatibility claim was dropped, because this particular Python version is nowhere to be found. * Many issues with Python 2.5 compatibility are fixed. spyne-2.8.1-rc -------------- * Misc fixes regarding the spyne.model.binary.File api. rpclib-2.8.0-rc -> spyne-2.8.0-rc --------------------------------- * Rpclib is dead. Long live Spyne! * Add support for JsonObject protocol. This initial version is expremental. * Add support for MessagePackObject and MessagePackRpc protocols. These initial versions are expremental. * Make DateTime string format customizable. * Implement TwistedWebResource that exposes an ``Application`` instance as a ``twisted.web.resource.Resource`` child. * Remove Deprecated ``XMLAttribute`` and ``XMLAttributeRef``. Use ``XmlAttribute`` and ``XmlAttributeRef`` instead. * Xml Schema: Add support for the tag. * Add a chapter about Validation to the manual. Thanks Alex! * Interface documents are no longer subclasses of InterfaceBase. It's up to the transport to expose the application using a given interface document standard now. The ``interface`` argument to the ``Application`` constructor is now ignored. * Html: Added a very simple lxml-based templating scheme: ``HtmlPage``. * Html: Added row-based tables: They show fields in rows. It's good for showing one object per table. * Html: Added ImageUri support. They render as tags in Html output. * Html: Added support for locales. You can now render field names as human- readable strings. * Add support for async methods, which execute after the primary user code returns. Currently, the only async execution method is via threads. * Xml & friends: Start tags are now in the same namespace as the definitions themselves. Intermediate tags are in the parent's namespace, just as before. * Xml & friends: Make the 'bare' mode work. * spyne.util.xml: ``get_object_as_xml`` can also get class suggestion. * spyne.util.xml: ``get_xml_as_object`` has argument order swapped: cls, elt -> elt, cls. See ab91a3e2ad4756b71d1a2752e5b0d2af8551e061. * There's a final argument order change in Application ctor: in_protocol, out_protocol, interface, name becomes: name, in_protocol, out_protocol, interface * Relevant pull requests with new features and notable changes: * https://github.com/arskom/spyne/pull/128 * https://github.com/arskom/spyne/pull/129 * https://github.com/arskom/spyne/pull/139 * https://github.com/arskom/spyne/pull/142 * https://github.com/arskom/spyne/pull/148 * https://github.com/arskom/spyne/pull/157 * https://github.com/arskom/spyne/pull/173 rpclib-2.7.0-beta ----------------- * Add support for non-chunked encoding to Wsgi transport. * Add support for Html Microformats. * Add ``function`` property to MethodContext that is re-initialized from ``descriptor.function`` for each new request. Stay away from ``descriptor.function`` unless you understand the consequences!.. * String and Unicode models are now separate objects with well-defined (de)serialization behaviour. * Argument order change in Application ctor: :: interface, in_protocol, out_protocol becomes: :: in_protocol, out_protocol, interface See here: https://github.com/arskom/spyne/commit/45f5af70aa826640008222bda96299d51c9df980#diff-1 * Full changelog: * https://github.com/arskom/spyne/pull/123 * https://github.com/arskom/spyne/pull/124 * https://github.com/arskom/spyne/pull/125 rpclib-2.6.1-beta ----------------- * Fix (for real this time) the race condition in wsgi server's wsdl handler. rpclib-2.6.0-beta ----------------- * HttpRpc now parses POST/PUT/PATCH bodies, can accept file uploads. Uses werkzeug to do that, which is now a soft dependency. * ByteArray now child of SimpleModel. It's now possible to customize it simply by calling it. * Fix race condition in wsgi server wsdl request. * Full change log: https://github.com/arskom/spyne/pull/122 rpclib-2.5.2-beta ----------------- * Misc. fixes. * Full change log: https://github.com/arskom/spyne/pull/118 rpclib-2.5.1-beta ----------------- * Switched to magic cookie constants instead of strings in protocol logic. * check_validator -> set_validator in ProtocolBase * Started parsing Http headers in HttpRpc protocol. * HttpRpc now properly validates nested value frequencies. * HttpRpc now works with arrays of simple types as well. * Full change log: https://github.com/arskom/spyne/pull/117 https://github.com/arskom/spyne/pull/116 rpclib-2.5.0-beta ----------------- * Implemented fanout support for transports and protocols that can handle that. * Implemented a helper module that generates a Soap/Wsdl 1.1 application in ``rpclib.util.simple`` * Some work towards supporting Python3 using ``2to3``. See issue #113. * ``ctx.descriptor.reset_function`` implemented. It's now safe to fiddle with that value in event handlers. * Added a cleaned-up version of the Django wrapper: https://gist.github.com/1316025 * Fix most of the tests that fail due to api changes. * Fix Http soap client. * Full change log: https://github.com/arskom/spyne/pull/115 rpclib-2.4.7-beta ----------------- * Made color in logs optional * Fixed ByteArray serializer rpclib-2.4.5-beta ----------------- * Time primitive was implemented. * Fix for multiple ports was integrated. * Added http cookie authentication example with suds. * Full change log: https://github.com/arskom/spyne/pull/109 rpclib-2.4.3-beta ----------------- * Many issues with 'soft' validation was fixed. * ``MethodDescriptor.udp`` added. Short for "User-Defined Properties", you can use it to store arbitrary metadata about the decorated method. * Fix HttpRpc response serialization. * Documentation updates. rpclib-2.4.1-beta ----------------- * Fixed import errors in Python<=2.5. * A problem with rpclib's String and unicode objects was fixed. rpclib-2.4.0-beta ----------------- * Fixed Fault publishing in Wsdl. * Implemented 'soft' validation. * Documentation improvements. It's mostly ready! * A bug with min/max_occurs logic was fixed. This causes rpclib not to send null values for elements with min_occurs=0 (the default value). * Native value for ``rpclib.model.primitive.String`` was changed to ``unicode``. To exchange raw data, you should use ``rpclib.model.binary.ByteArray``. * Full change log: https://github.com/arskom/spyne/pull/90 rpclib-2.3.3-beta ----------------- * Added MAX_CONTENT_LENGTH = 2 * 1024 * 1024 and BLOCK_LENGTH = 8 * 1024 constants to rpclib.server.wsgi module. * rpclib.model.binary.Attachment is deprecated, and is replaced by ByteArray. The native format of ByteArray is an iterable of strings. * Exception handling was formalized. HTTP return codes can be set by exception classes from rpclib.error or custom exceptions. * Full change log: https://github.com/arskom/spyne/pull/88 rpclib-2.3.2-beta ----------------- * Limited support for sqlalchemy.orm.relationship (no string arguments) * Added missing event firings. * Documented event api and fundamental data structures (rpclib._base) * Full change log: https://github.com/arskom/spyne/pull/87 rpclib-2.3.1-beta ----------------- * HttpRpc protocol now returns 404 when a requested resource was not found. * New tests added for HttpRpc protocol. * Miscellanous other fixes. See: https://github.com/arskom/spyne/pull/86 rpclib-2.3.0-beta ----------------- * Documentation improvements. * rpclib.protocol.xml.XmlObject is now working as out_protocol. * Many fixes. rpclib-2.2.3-beta ------------------ * Documentation improvements. * rpclib.client.http.Client -> rpclib.client.http.HttpClient * rpclib.client.zeromq.Client -> rpclib.client.zeromq.ZeroMQClient * rpclib.server.zeromq.Server -> rpclib.server.zeromq.ZeroMQServer * rpclib.model.table.TableSerializer -> rpclib.model.table.TableModel rpclib-2.2.2-beta ----------------- * Fixed call to rpclib.application.Application.call_wrapper * Fixed HttpRpc server transport instantiation. * Documentation improvements. rpclib-2.2.1-beta ----------------- * rpclib.application.Application.call_wrapper introduced * Documentation improvements. rpclib-2.2.0-beta ----------------- * The serialization / deserialization logic was redesigned. Now most of the serialization-related logic is under the responsibility of the ProtocolBase children. * Interface generation logic was redesigned. The WSDL logic is separated to XmlSchema and Wsdl11 classes. 'add_to_schema' calls were renamed to just 'add' and were moved inside rpclib.interface.xml_schema package. * Interface and Protocol assignment of an rpclib application is now more explicit. Both are also configurable during instantion. This doesn't mean there's much to configure :) * WS-I Conformance is back!. See https://github.com/arskom/spyne/blob/master/src/rpclib/test/interop/wsi-report-rpclib.xml for the latest conformance report. * Numeric types now support range restrictions. e.g. Integer(ge=0) will only accept positive integers. * Any -> AnyXml, AnyAsDict -> AnyDict. AnyAsDict is not the child of the AnyXml anymore. * rpclib.model.exception -> rpclib.model.fault. rpclib-2.1.0-alpha ------------------ * The method dispatch logic was rewritten: It's now possible for the protocols to override how method request strings are matched to methods definitions. * Unsigned integer primitives were added. * ZeroMQ client was fixed. * Header confusion in native http soap client was fixed. * Grouped transport-specific context information under ctx.transport attribute. * Added a self reference mechanism. rpclib-2.0.10-alpha ------------------- * The inclusion of base xml schemas were made optional. * WSDL: Fix out header being the same as in header. * Added type checking to outgoing Integer types. it's not handled as nicely as it should be. * Fixed the case where changing the _in_message tag name of the method prevented it from being called. * SOAP/WSDL: Added support for multiple {in,out}_header objects. * Fix some XMLAttribute bugs. rpclib-2.0.9-alpha ------------------ * Added inheritance support to rpclib.model.table.TableSerializer. rpclib-2.0.8-alpha ------------------ * The NullServer now also returns context with the return object to have it survive past user-defined method return. rpclib-2.0.7-alpha ------------------ * More tests are migrated to the new api. * Function identifier strings are no more created directly from the function object itself. Function's key in the class definition is used as default instead. * Base xml schemas are no longer imported. rpclib-2.0.6-alpha ------------------ * Added rpclib.server.null.NullServer, which is a server class with a client interface that attempts to do no (de)serialization at all. It's intended to be used in tests. rpclib-2.0.5-alpha ------------------ * Add late mapping support to sqlalchemy table serializer. rpclib-2.0.4-alpha ------------------ * Add preliminary support for a sqlalchemy-0.7-compatible serializer. rpclib-2.0.3-alpha ------------------ * Migrate the HttpRpc serializer to the new internal api. rpclib-2.0.2-alpha ------------------ * SimpleType -> SimpleModel * Small bugfixes. rpclib-2.0.1-alpha ------------------ * EventManager now uses ordered sets instead of normal sets to store event handlers. * Implemented sort_wsdl, a small hack to sort wsdl output in order to ease debugging. rpclib-2.0.0-alpha ------------------ * Implemented EventManager and replaced hook calls with events. * The rpc decorator now produces static methods. The methods still get an implicit first argument that holds the service contexts. It's an instance of the MethodContext class, and not the ServiceBase (formerly DefinitionBase) class. * The new srpc decorator doesn't force the methods to have an implicit first argument. * Fixed fault namespace resolution. * Moved xml constants to rpclib.const.xml_ns * The following changes to soaplib were ported to rpclib's SOAP/WSDL parts: * duration object is now compatible with Python's native timedelta. * WSDL: Support for multiple tags in the wsdl (one for each class in the application) * WSDL: Support for multiple tags and multiple ports. * WSDL: Support for enumerating exceptions a method can throw was added. * SOAP: Exceptions got some love to be more standards-compliant. * SOAP: Xml attribute support * Moved all modules with packagename.base to packagename._base. * Renamed classes to have module name as a prefix: * rpclib.client._base.Base -> rpclib.client._base.ClientBase * rpclib.model._base.Base -> rpclib.model._base.ModelBase * rpclib.protocol._base.Base -> rpclib.protocol._base.ProtocolBase * rpclib.server._base.Base -> rpclib.server._base.ServerBase * rpclib.service.DefinitionBase -> rpclib.service.ServiceBase * rpclib.server.wsgi.Application -> rpclib.server.wsgi.WsgiApplication * Moved some classes and modules around: * rpclib.model.clazz -> rpclib.model.complex * rpclib.model.complex.ClassSerializer -> rpclib.model.complex.ComplexModel * rpclib.Application -> rpclib.application.Application * rpclib.service.rpc, srpc -> rpclib.decorator.rpc, srpc soaplib-3.x -> rpclib-1.1.1-alpha --------------------------------- * Soaplib is now also protocol agnostic. As it now supports protocols other than soap (like Rest-minus-the-verbs HttpRpc), it's renamed to rpclib. This also means soaplib can now support multiple versions of soap and wsdl standards. * Mention of xml and soap removed from public api where it's not directly related to soap or xml. (e.g. a hook rename: on_method_exception_xml -> on_method_exception_doc) * Protocol serializers now return iterables instead of complete messages. This is a first step towards eliminating the need to have the whole message in memory during processing. soaplib-2.x ----------- * This release transformed soaplib from a soap server that exclusively supported http to a soap serialization/deserialization library that is architecture and transport agnostic. * Hard dependency on WSGI removed. * Sphinx docs with working examples: http://arskom.github.com/rpclib/ * Serializers renamed to Models. * Standalone xsd generation for ClassSerializer objects has been added. This allows soaplib to be used to define generic XML schemas, without SOAP artifacts. * Annotation Tags for primitive Models has been added. * The soaplib client has been re-written after having been dropped from recent releases. It follows the suds API but is based on lxml for better performance. WARNING: the soaplib client is not well-tested and future support is tentative and dependent on community response. * 0mq support added. * Twisted supported via WSGI wrappers. * Increased test coverage for soaplib and supported servers soaplib-1.0 ----------- * Standards-compliant Soap Faults * Allow multiple return values and return types soaplib-0.9.4 ------------- * pritimitive.Array -> clazz.Array * Support for SimpleType restrictions (pattern, length, etc.) soaplib-0.9.3 ------------- * Soap header support * Tried the WS-I Test first time. Many bug fixes. soaplib-0.9.2 ------------- * Support for inheritance. soaplib-0.9.1 ------------- * Support for publishing multiple service classes. soaplib-0.9 ----------- * Soap server logic almost completely rewritten. * Soap client removed in favor of suds. * Object definition api no longer needs a class types: under class definition. * XML Schema validation is supported. * Support for publishing multiple namespaces. (multiple tags in the wsdl) * Support for enumerations. * Application and Service Definition are separated. Application is instantiated on server start, and Service Definition is instantiated for each new request. * @soapmethod -> @rpc soaplib-0.8.1 ------------- * Switched to lxml for proper xml namespace support. soaplib-0.8.0 ------------- * First public stable release. Keywords: soap,wsdl,wsgi,zeromq,rest,rpc,json,http,msgpack,xml,django,pyramid,postgresql,sqlalchemy,werkzeug,twisted,yaml Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Operating System :: OS Independent Classifier: Natural Language :: English Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content spyne-2.11.0/setup.cfg0000644000175000001440000000007312352131452014523 0ustar plqusers00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0