spyne-2.12.11/0000755000175000001440000000000012615200103012755 5ustar plqusers00000000000000spyne-2.12.11/spyne/0000755000175000001440000000000012615200103014113 5ustar plqusers00000000000000spyne-2.12.11/spyne/auxproc/0000755000175000001440000000000012615200103015574 5ustar plqusers00000000000000spyne-2.12.11/spyne/auxproc/__init__.py0000644000175000001440000000322512572316312017723 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.12.11/spyne/auxproc/_base.py0000644000175000001440000000560512572316312017241 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.12.11/spyne/auxproc/sync.py0000644000175000001440000000207712572316312017144 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.12.11/spyne/auxproc/thread.py0000644000175000001440000000340412572316312017432 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.12.11/spyne/client/0000755000175000001440000000000012615200103015371 5ustar plqusers00000000000000spyne-2.12.11/spyne/client/twisted/0000755000175000001440000000000012615200103017054 5ustar plqusers00000000000000spyne-2.12.11/spyne/client/twisted/__init__.py0000644000175000001440000001055212572316312021204 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 RemoteService 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 = RemoteService(_RemoteProcedure, url, app) spyne-2.12.11/spyne/client/__init__.py0000644000175000001440000000177412572316312017527 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 RemoteService from spyne.client._base import ClientBase from spyne.client._base import RemoteProcedureBase spyne-2.12.11/spyne/client/_base.py0000644000175000001440000001501712572316312017034 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 RemoteService(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, MethodContext.CLIENT) 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.12.11/spyne/client/django.py0000644000175000001440000000554712572316312017234 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 spyne import RemoteService, ClientBase, RemoteProcedureBase from django.test.client import Client class _RemoteProcedure(RemoteProcedureBase): def __init__(self, url, app, name, out_header=None, *args, **kwargs): super(_RemoteProcedure, self).__init__(url, app, name, out_header=out_header) self.secure = kwargs.get('secure', False) 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 = b''.join(self.ctx.out_string) # Hack client = Client() return client.post(self.url, content_type='text/xml', data=out_string, secure=self.secure) class DjangoTestClient(ClientBase): """The Django test client transport.""" def __init__(self, url, app, secure=False): super(DjangoTestClient, self).__init__(url, app) self.service = RemoteService(_RemoteProcedure, url, app, secure=secure) spyne-2.12.11/spyne/client/http.py0000644000175000001440000000474712572316312016752 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.""" from spyne import RemoteService, ClientBase, RemoteProcedureBase from spyne.util.six.moves.urllib.request import Request, urlopen from spyne.util.six.moves.urllib.error import HTTPError 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 = RemoteService(_RemoteProcedure, url, app) spyne-2.12.11/spyne/client/zeromq.py0000644000175000001440000000324312572316312017276 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 import RemoteService, ClientBase, RemoteProcedureBase 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 = RemoteService(_RemoteProcedure, url, app) spyne-2.12.11/spyne/const/0000755000175000001440000000000012615200103015241 5ustar plqusers00000000000000spyne-2.12.11/spyne/const/__init__.py0000644000175000001440000000434212572316312017371 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.12.11/spyne/const/ansi_color.py0000644000175000001440000000411712572316312017762 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.12.11/spyne/const/http.py0000644000175000001440000001035412572316312016611 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.12.11/spyne/const/xml.py0000644000175000001440000002006012572316312016425 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. """ NS_XML = 'http://www.w3.org/XML/1998/namespace' NS_XSD = 'http://www.w3.org/2001/XMLSchema' NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance' NS_WSA = 'http://schemas.xmlsoap.org/ws/2003/03/addressing' NS_XOP = 'http://www.w3.org/2004/08/xop/include' NS_XHTML = 'http://www.w3.org/1999/xhtml' NS_PLINK = 'http://schemas.xmlsoap.org/ws/2003/05/partner-link/' NS_SOAP11_ENC = 'http://schemas.xmlsoap.org/soap/encoding/' NS_SOAP11_ENV = 'http://schemas.xmlsoap.org/soap/envelope/' NS_SOAP12_ENC = 'http://www.w3.org/2003/05/soap-encoding' NS_SOAP12_ENV = 'http://www.w3.org/2003/05/soap-envelope' NS_WSDL11 = 'http://schemas.xmlsoap.org/wsdl/' NS_WSDL11_SOAP = 'http://schemas.xmlsoap.org/wsdl/soap/' NSMAP = { 'xml': NS_XML, 'xs': NS_XSD, 'xsi': NS_XSI, 'plink': NS_PLINK, 'wsdlsoap11': NS_WSDL11_SOAP, 'wsdl': NS_WSDL11, 'soap11enc': NS_SOAP11_ENC, 'soap11env': NS_SOAP11_ENV, 'soap12env': NS_SOAP12_ENV, 'soap12enc': NS_SOAP12_ENC, 'wsa': NS_WSA, 'xop': NS_XOP, } PREFMAP = None def regen_prefmap(): global PREFMAP PREFMAP = dict([(b, a) for a, b in NSMAP.items()]) regen_prefmap() schema_location = { NS_XSD: 'http://www.w3.org/2001/XMLSchema.xsd', } class DEFAULT_NS(object): pass def Tnswrap(ns): return lambda s: "{%s}%s" % (ns, s) XML = Tnswrap(NS_XML) XSD = Tnswrap(NS_XSD) XSI = Tnswrap(NS_XSI) WSA = Tnswrap(NS_WSA) XOP = Tnswrap(NS_XOP) XHTML = Tnswrap(NS_XHTML) PLINK = Tnswrap(NS_PLINK) SOAP11_ENC = Tnswrap(NS_SOAP11_ENC) SOAP11_ENV = Tnswrap(NS_SOAP11_ENV) SOAP12_ENC = Tnswrap(NS_SOAP12_ENC) SOAP12_ENV = Tnswrap(NS_SOAP12_ENV) WSDL11 = Tnswrap(NS_WSDL11) WSDL11_SOAP = Tnswrap(NS_WSDL11_SOAP) # names starting with underscore need () around to be used as proper regexps _PATT_BASE_CHAR = \ u"[\u0041-\u005A]|[\u0061-\u007A]|[\u00C0-\u00D6]|[\u00D8-\u00F6]" \ u"|[\u00F8-\u00FF]|[\u0100-\u0131]|[\u0134-\u013E]|[\u0141-\u0148]" \ u"|[\u014A-\u017E]|[\u0180-\u01C3]|[\u01CD-\u01F0]|[\u01F4-\u01F5]" \ u"|[\u01FA-\u0217]|[\u0250-\u02A8]|[\u02BB-\u02C1]|\u0386|[\u0388-\u038A]" \ u"|\u038C|[\u038E-\u03A1]|[\u03A3-\u03CE]|[\u03D0-\u03D6]" \ u"|\u03DA|\u03DC|\u03DE|\u03E0|[\u03E2-\u03F3]|[\u0401-\u040C]" \ u"|[\u040E-\u044F]|[\u0451-\u045C]|[\u045E-\u0481]|[\u0490-\u04C4]" \ u"|[\u04C7-\u04C8]|[\u04CB-\u04CC]|[\u04D0-\u04EB]|[\u04EE-\u04F5]" \ u"|[\u04F8-\u04F9]|[\u0531-\u0556]|\u0559|[\u0561-\u0586]|[\u05D0-\u05EA]" \ u"|[\u05F0-\u05F2]|[\u0621-\u063A]|[\u0641-\u064A]|[\u0671-\u06B7]" \ u"|[\u06BA-\u06BE]|[\u06C0-\u06CE]|[\u06D0-\u06D3]|\u06D5|[\u06E5-\u06E6]" \ u"|[\u0905-\u0939]|\u093D|[\u0958-\u0961]|[\u0985-\u098C]|[\u098F-\u0990]" \ u"|[\u0993-\u09A8]|[\u09AA-\u09B0]|\u09B2|[\u09B6-\u09B9]|[\u09DC-\u09DD]" \ u"|[\u09DF-\u09E1]|[\u09F0-\u09F1]|[\u0A05-\u0A0A]|[\u0A0F-\u0A10]" \ u"|[\u0A13-\u0A28]|[\u0A2A-\u0A30]|[\u0A32-\u0A33]|[\u0A35-\u0A36]" \ u"|[\u0A38-\u0A39]|[\u0A59-\u0A5C]|\u0A5E|[\u0A72-\u0A74]|[\u0A85-\u0A8B]" \ u"|\u0A8D|[\u0A8F-\u0A91]|[\u0A93-\u0AA8]|[\u0AAA-\u0AB0]|[\u0AB2-\u0AB3]" \ u"|[\u0AB5-\u0AB9]|\u0ABD|\u0AE0|[\u0B05-\u0B0C]|[\u0B0F-\u0B10]" \ u"|[\u0B13-\u0B28]|[\u0B2A-\u0B30]|[\u0B32-\u0B33]|[\u0B36-\u0B39]|\u0B3D" \ u"|[\u0B5C-\u0B5D]|[\u0B5F-\u0B61]|[\u0B85-\u0B8A]|[\u0B8E-\u0B90]" \ u"|[\u0B92-\u0B95]|[\u0B99-\u0B9A]|\u0B9C|[\u0B9E-\u0B9F]|[\u0BA3-\u0BA4]" \ u"|[\u0BA8-\u0BAA]|[\u0BAE-\u0BB5]|[\u0BB7-\u0BB9]|[\u0C05-\u0C0C]" \ u"|[\u0C0E-\u0C10]|[\u0C12-\u0C28]|[\u0C2A-\u0C33]|[\u0C35-\u0C39]" \ u"|[\u0C60-\u0C61]|[\u0C85-\u0C8C]|[\u0C8E-\u0C90]|[\u0C92-\u0CA8]" \ u"|[\u0CAA-\u0CB3]|[\u0CB5-\u0CB9]|\u0CDE|[\u0CE0-\u0CE1]|[\u0D05-\u0D0C]" \ u"|[\u0D0E-\u0D10]|[\u0D12-\u0D28]|[\u0D2A-\u0D39]|[\u0D60-\u0D61]" \ u"|[\u0E01-\u0E2E]|\u0E30|[\u0E32-\u0E33]|[\u0E40-\u0E45]|[\u0E81-\u0E82]" \ u"|\u0E84|[\u0E87-\u0E88]|\u0E8A|\u0E8D|[\u0E94-\u0E97]|[\u0E99-\u0E9F]" \ u"|[\u0EA1-\u0EA3]|\u0EA5|\u0EA7|[\u0EAA-\u0EAB]|[\u0EAD-\u0EAE]|\u0EB0" \ u"|[\u0EB2-\u0EB3]|\u0EBD|[\u0EC0-\u0EC4]|[\u0F40-\u0F47]|[\u0F49-\u0F69]" \ u"|[\u10A0-\u10C5]|[\u10D0-\u10F6]|\u1100|[\u1102-\u1103]|[\u1105-\u1107]" \ u"|\u1109|[\u110B-\u110C]|[\u110E-\u1112]|\u113C|\u113E|\u1140|\u114C" \ u"|\u114E|\u1150|[\u1154-\u1155]|\u1159|[\u115F-\u1161]|\u1163|\u1165" \ u"|\u1167|\u1169|[\u116D-\u116E]|[\u1172-\u1173]|\u1175|\u119E|\u11A8" \ u"|\u11AB|[\u11AE-\u11AF]|[\u11B7-\u11B8]|\u11BA|[\u11BC-\u11C2]|\u11EB" \ u"|\u11F0|\u11F9|[\u1E00-\u1E9B]|[\u1EA0-\u1EF9]|[\u1F00-\u1F15]" \ u"|[\u1F18-\u1F1D]|[\u1F20-\u1F45]|[\u1F48-\u1F4D]|[\u1F50-\u1F57]|\u1F59" \ u"|\u1F5B|\u1F5D|[\u1F5F-\u1F7D]|[\u1F80-\u1FB4]|[\u1FB6-\u1FBC]|\u1FBE" \ u"|[\u1FC2-\u1FC4]|[\u1FC6-\u1FCC]|[\u1FD0-\u1FD3]|[\u1FD6-\u1FDB]" \ u"|[\u1FE0-\u1FEC]|[\u1FF2-\u1FF4]|[\u1FF6-\u1FFC]|\u2126|[\u212A-\u212B]" \ u"|\u212E|[\u2180-\u2182]|[\u3041-\u3094]|[\u30A1-\u30FA]|[\u3105-\u312C]" \ u"|[\uAC00-\uD7A3]" _PATT_IDEOGRAPHIC = u"[\u4E00-\u9FA5]|\u3007|[\u3021-\u3029]" _PATT_COMBINING_CHAR = u"[\u0300-\u0345]|[\u0360-\u0361]|[\u0483-\u0486]" \ u"|[\u0591-\u05A1]|[\u05A3-\u05B9]|[\u05BB-\u05BD]|\u05BF|[\u05C1-\u05C2]" \ u"|\u05C4|[\u064B-\u0652]|\u0670|[\u06D6-\u06DC]|[\u06DD-\u06DF]" \ u"|[\u06E0-\u06E4]|[\u06E7-\u06E8]|[\u06EA-\u06ED]|[\u0901-\u0903]|\u093C" \ u"|[\u093E-\u094C]|\u094D|[\u0951-\u0954]|[\u0962-\u0963]|[\u0981-\u0983]" \ u"|\u09BC|\u09BE|\u09BF|[\u09C0-\u09C4]|[\u09C7-\u09C8]|[\u09CB-\u09CD]" \ u"|\u09D7|[\u09E2-\u09E3]|\u0A02|\u0A3C|\u0A3E|\u0A3F|[\u0A40-\u0A42]" \ u"|[\u0A47-\u0A48]|[\u0A4B-\u0A4D]|[\u0A70-\u0A71]|[\u0A81-\u0A83]|\u0ABC" \ u"|[\u0ABE-\u0AC5]|[\u0AC7-\u0AC9]|[\u0ACB-\u0ACD]|[\u0B01-\u0B03]|\u0B3C" \ u"|[\u0B3E-\u0B43]|[\u0B47-\u0B48]|[\u0B4B-\u0B4D]|[\u0B56-\u0B57]" \ u"|[\u0B82-\u0B83]|[\u0BBE-\u0BC2]|[\u0BC6-\u0BC8]|[\u0BCA-\u0BCD]|\u0BD7" \ u"|[\u0C01-\u0C03]|[\u0C3E-\u0C44]|[\u0C46-\u0C48]|[\u0C4A-\u0C4D]" \ u"|[\u0C55-\u0C56]|[\u0C82-\u0C83]|[\u0CBE-\u0CC4]|[\u0CC6-\u0CC8]" \ u"|[\u0CCA-\u0CCD]|[\u0CD5-\u0CD6]|[\u0D02-\u0D03]|[\u0D3E-\u0D43]" \ u"|[\u0D46-\u0D48]|[\u0D4A-\u0D4D]|\u0D57|\u0E31|[\u0E34-\u0E3A]" \ u"|[\u0E47-\u0E4E]|\u0EB1|[\u0EB4-\u0EB9]|[\u0EBB-\u0EBC]|[\u0EC8-\u0ECD]" \ u"|[\u0F18-\u0F19]|\u0F35|\u0F37|\u0F39|\u0F3E|\u0F3F|[\u0F71-\u0F84]" \ u"|[\u0F86-\u0F8B]|[\u0F90-\u0F95]|\u0F97|[\u0F99-\u0FAD]|[\u0FB1-\u0FB7]" \ u"|\u0FB9|[\u20D0-\u20DC]|\u20E1|[\u302A-\u302F]|\u3099|\u309A" _PATT_DIGIT = u"[\u0030-\u0039]|[\u0660-\u0669]|[\u06F0-\u06F9]|[\u0966-\u096F]" \ u"|[\u09E6-\u09EF]|[\u0A66-\u0A6F]|[\u0AE6-\u0AEF]|[\u0B66-\u0B6F]" \ u"|[\u0BE7-\u0BEF]|[\u0C66-\u0C6F]|[\u0CE6-\u0CEF]|[\u0D66-\u0D6F]" \ u"|[\u0E50-\u0E59]|[\u0ED0-\u0ED9]|[\u0F20-\u0F29]" _PATT_EXTENDER = u"\u00B7|\u02D0|\u02D1|\u0387|\u0640|\u0E46|\u0EC6|\u3005" \ u"|[\u3031-\u3035]|[\u309D-\u309E]|[\u30FC-\u30FE]" PATT_LETTER = u"(%s)" % u'|'.join([_PATT_BASE_CHAR, _PATT_IDEOGRAPHIC]) PATT_NAMECHAR = u"(%s)" % u'|'.join([PATT_LETTER, _PATT_DIGIT, u'.', u'-', u'_', u':', _PATT_COMBINING_CHAR, _PATT_EXTENDER]) PATT_NAME = u"(%s)(%s)+" % (u'|'.join([PATT_LETTER, u'_', u':']), u"(%s)*" % PATT_NAMECHAR) PATT_NMTOKEN = u"(%s)+" % PATT_NAMECHAR spyne-2.12.11/spyne/const/xml_ns.py0000644000175000001440000000403612572316312017132 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. Use ``spyne.const.xml``. # FIXME: 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/' xhtml = 'http://www.w3.org/1999/xhtml' plink = 'http://schemas.xmlsoap.org/ws/2003/05/partner-link/' soap11_enc = 'http://schemas.xmlsoap.org/soap/encoding/' soap11_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, 'soap11enc': soap11_enc, 'soap11env': soap11_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.12.11/spyne/interface/0000755000175000001440000000000012615200103016053 5ustar plqusers00000000000000spyne-2.12.11/spyne/interface/wsdl/0000755000175000001440000000000012615200103017024 5ustar plqusers00000000000000spyne-2.12.11/spyne/interface/wsdl/__init__.py0000644000175000001440000000175312572316312021157 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.12.11/spyne/interface/wsdl/defn.py0000644000175000001440000000540612572316312020333 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.12.11/spyne/interface/wsdl/wsdl11.py0000644000175000001440000005255512572316472020550 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 lxml.builder import E from lxml.etree import SubElement from spyne.const.xml import WSDL11, XSD, NS_WSA, WSDL11_SOAP, PLINK from spyne.interface.xml_schema import XmlSchema PREF_WSA = spyne.const.xml.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 """) 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 = SubElement(self.root_elt, WSDL11("portType")) 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 = SubElement(self.root_elt, WSDL11("service")) 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(WSDL11("definitions"), nsmap=self.interface.nsmap) root.set('targetNamespace', self.interface.tns) root.set('name', service_name) # create types node types = SubElement(root, WSDL11("types")) 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 = SubElement(root, PLINK("partnerLinkType")) 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 = SubElement(plink, PLINK("role")) role.set('name', service_name) plink_port_type = SubElement(role, PLINK("portType")) plink_port_type.set('name', '%s:%s' % (pref_tns, service_name)) if self._has_callbacks(): role = SubElement(plink, PLINK("role")) role.set('name', '%sCallback' % service_name) plink_port_type = SubElement(role, PLINK("portType")) 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 = SubElement(service, WSDL11("port")) wsdl_port.set('name', port_name) wsdl_port.set('binding', '%s:%s' % (pref_tns, binding_name)) addr = SubElement(wsdl_port, WSDL11_SOAP("address")) 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 = self._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 = SubElement(cb_port_type, WSDL11("operation")) else: operation = SubElement(port_type, WSDL11("operation")) operation.set('name', method.operation_name) if method.doc is not None: operation.append(E(WSDL11("documentation"), method.doc)) operation.set('parameterOrder', method.in_message.get_element_name()) op_input = SubElement(operation, WSDL11("input")) 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 = SubElement(operation, WSDL11("output")) 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 = SubElement(operation, WSDL11("fault")) 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 = SubElement(root, WSDL11("message")) message.set('name', message_name) if isinstance(obj, (list, tuple)): objs = obj else: objs = (obj,) for obj in objs: part = SubElement(message, WSDL11("part")) 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(self.interface.get_tns()) def inner(method, binding): operation = etree.Element(WSDL11("operation")) operation.set('name', method.operation_name) soap_operation = SubElement(operation, WSDL11_SOAP("operation")) soap_operation.set('soapAction', method.operation_name) soap_operation.set('style', 'document') # get input input = SubElement(operation, WSDL11("input")) input.set('name', method.in_message.get_element_name()) soap_body = SubElement(input, WSDL11_SOAP("body")) 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 = SubElement(input, WSDL11_SOAP('header')) 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 = SubElement(operation, WSDL11("output")) output.set('name', method.out_message.get_element_name()) soap_body = SubElement(output, WSDL11_SOAP("body")) 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 = SubElement(output, WSDL11_SOAP("header")) 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 = SubElement(operation, WSDL11("fault")) wsdl_fault.set('name', f.get_type_name()) soap_fault = SubElement(wsdl_fault, WSDL11_SOAP("fault")) soap_fault.set('name', f.get_type_name()) soap_fault.set('use', 'literal') if method.is_callback: relates_to = SubElement(input, WSDL11_SOAP("header")) 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 = SubElement(input, WSDL11_SOAP("header")) rt_header.set('message', '%s:ReplyToHeader' % pref_tns) rt_header.set('part', 'ReplyTo') rt_header.set('use', 'literal') mid_header = SubElement(input, WSDL11_SOAP("header")) 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 = SubElement(root, WSDL11("binding")) binding.set('name', port_type_name) binding.set('type', '%s:%s'% (pref_tns, port_type_name)) transport = SubElement(binding, WSDL11_SOAP("binding")) 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 = SubElement(root, WSDL11("binding")) cb_binding.set('name', service_name) cb_binding.set('type', '%s:%s'% (pref_tns, service_name)) transport = SubElement(cb_binding, WSDL11_SOAP("binding")) 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 # FIXME: I don't think this is working. def _add_callbacks(self, service, root, types, service_name, url): ns_tns = self.interface.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 = SubElement(types, XSD("schema")) wsa_schema.set("targetNamespace", '%sCallback' % ns_tns) wsa_schema.set("elementFormDefault", "qualified") import_ = SubElement(wsa_schema, XSD("import")) import_.set("namespace", NS_WSA) import_.set("schemaLocation", NS_WSA) relt_message = SubElement(root, WSDL11("message")) relt_message.set('name', 'RelatesToHeader') relt_part = SubElement(relt_message, WSDL11("part")) relt_part.set('name', 'RelatesTo') relt_part.set('element', '%s:RelatesTo' % PREF_WSA) reply_message = SubElement(root, WSDL11("message")) reply_message.set('name', 'ReplyToHeader') reply_part = SubElement(reply_message, WSDL11("part")) reply_part.set('name', 'ReplyTo') reply_part.set('element', '%s:ReplyTo' % PREF_WSA) id_header = SubElement(root, WSDL11("message")) id_header.set('name', 'MessageIDHeader') id_part = SubElement(id_header, WSDL11("part")) id_part.set('name', 'MessageID') id_part.set('element', '%s:MessageID' % PREF_WSA) # make portTypes cb_port_type = SubElement(root, WSDL11("portType")) cb_port_type.set('name', '%sCallback' % service_name) cb_service_name = '%sCallback' % service_name cb_service = SubElement(root, WSDL11("service")) cb_service.set('name', cb_service_name) cb_wsdl_port = SubElement(cb_service, WSDL11("port")) cb_wsdl_port.set('name', cb_service_name) cb_wsdl_port.set('binding', '%s:%s' % (pref_tns, cb_service_name)) cb_address = SubElement(cb_wsdl_port, WSDL11_SOAP("address")) cb_address.set('location', url) return cb_port_typespyne-2.12.11/spyne/interface/xml_schema/0000755000175000001440000000000012615200103020173 5ustar plqusers00000000000000spyne-2.12.11/spyne/interface/xml_schema/__init__.py0000644000175000001440000000205512572316312022322 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.12.11/spyne/interface/xml_schema/_base.py0000644000175000001440000002470312572316312021640 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('.'.join(__name__.split(".")[:-1])) import os 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 = {} tags = set() for cls in chain.from_iterable(toposort2(self.interface.deps)): self.add(cls, tags) 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 = os.path.join(tmp_dir_name, "%s.xsd" % 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(os.path.join(tmp_dir_name, "%s.xsd" % pref_tns), 'r') as f: try: self.validation_schema = etree.XMLSchema(etree.parse(f)) except Exception: f.seek(0) logger.error("This could be a Spyne error. Unless you're " "sure the reason for this error is outside " "Spyne, please open a new issue with a " "minimal test case that reproduces it.") 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.12.11/spyne/interface/xml_schema/defn.py0000644000175000001440000001436512572316312021506 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 List(SchemaBase): _type_info = [ ('item_type', XmlAttribute(Unicode(sub_name='itemType'))), ] class SimpleType(SchemaBase): _type_info = [ ('name', XmlAttribute(Unicode)), ('list', List), ('union', 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.12.11/spyne/interface/xml_schema/genpy.py0000644000175000001440000001023212572316312021701 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__) import spyne from datetime import datetime from itertools import chain from collections import defaultdict 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 %s at %s. # Modify at your own risk. from spyne.model import * """ % (spyne.__version__, datetime.now().replace(microsecond=0).isoformat(' ')), "", # 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.12.11/spyne/interface/xml_schema/model.py0000644000175000001440000003571312572316312021672 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() XSD = lambda s: '{%s}%s' % (_ns_xsd, s) # In Xml Schema, some customizations do not need a class to be extended -- they # are specified in-line in the parent class definition, like nullable or # min_occurs. The dict below contains list of parameters that do warrant a # proper subclass definition for each type. This must be updated as the Xml # Schema implementation makes progress. 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): """Make sure only customizations that need a restriction tag generate one""" 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('_')]) # get the attributes different from the parent class diff = set() for k in (ckeys | ekeys): if getattr(eattrs, k, None) != getattr(cattrs, k, None): diff.add(k) # compare them with what comes from ATTR_NAMES 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(XSD('simpleType')) simple_type.set('name', cls.get_type_name()) document.add_simple_type(cls, simple_type) restriction = etree.SubElement(simple_type, XSD('restriction')) restriction.set('base', extends.get_type_name_ns(document.interface)) for v in cls.Attributes.values: enumeration = etree.SubElement(restriction, XSD('enumeration')) enumeration.set('value', XmlDocument().to_unicode(cls, 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(XSD('complexType')) complex_type.set('name', cls.get_type_name()) doc_text = cls.get_documentation() if doc_text or cls.Annotations.appinfo is not None: annotation = etree.SubElement(complex_type, XSD('annotation')) if doc_text: doc = etree.SubElement(annotation, XSD('documentation')) doc.text = doc_text _ai = cls.Annotations.appinfo if _ai is not None: appinfo = etree.SubElement(annotation, XSD('appinfo')) if isinstance(_ai, dict): dict_to_etree(_ai, appinfo) elif isinstance(_ai, string_types): 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, XSD('complexContent')) extension = etree.SubElement(complex_content, XSD('extension')) extension.set('base', extends.get_type_name_ns(document.interface)) sequence_parent = extension if cls.Attributes._xml_tag_body_as is not None: for xtba_key, xtba_type in cls.Attributes._xml_tag_body_as: _sc = etree.SubElement(sequence_parent, XSD('simpleContent')) xtba_ext = etree.SubElement(_sc, XSD('extension')) xtba_ext.attrib['base'] = xtba_type.type.get_type_name_ns( document.interface) sequence = etree.Element(XSD('sequence')) deferred = deque() choice_tags = defaultdict(lambda: etree.Element(XSD('choice'))) 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 == XSD('element'): member.set('name', name) member.set('type', type_name_ns) elif a.schema_tag == XSD('any') 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') v_doc_text = v.get_documentation() if v_doc_text: # Doesn't support multi-language documentation annotation = etree.SubElement(member, XSD('annotation')) doc = etree.SubElement(annotation, XSD('documentation')) doc.text = doc_text 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(XSD('attribute')) xml_attribute_add(v, k, attribute, document) if cls.Attributes._xml_tag_body_as 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, XSD('complexType')) _sc = etree.SubElement(_ct, XSD('simpleContent')) _ext = etree.SubElement(_sc, XSD('extension')) _ext_elements[ao] = _ext _ext.attrib['base'] = elt.attrib['type'] del elt.attrib['type'] attribute = etree.SubElement(_ext, XSD('attribute')) 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(XSD('element')) 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(XSD('simpleType')) simple_type.set('name', cls.get_type_name()) restriction = etree.SubElement(simple_type, XSD('restriction')) restriction.set('base', '%s:string' % document.interface.get_namespace_prefix(_ns_xsd)) for v in cls.__values__: enumeration = etree.SubElement(restriction, XSD('enumeration')) 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, XSD('length')) length.set('value', str(cls.Attributes.min_len)) else: if cls.Attributes.min_len != Unicode.Attributes.min_len: min_l = etree.SubElement(restriction, XSD('minLength')) min_l.set('value', str(cls.Attributes.min_len)) if cls.Attributes.max_len != Unicode.Attributes.max_len: max_l = etree.SubElement(restriction, XSD('maxLength')) max_l.set('value', str(cls.Attributes.max_len)) # pattern if cls.Attributes.pattern != Unicode.Attributes.pattern: pattern = etree.SubElement(restriction, XSD('pattern')) pattern.set('value', cls.Attributes.pattern) return restriction prot = XmlDocument() @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, XSD('fractionDigits')) 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, XSD('totalDigits')) 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): restriction = simple_get_restriction_tag(document, cls) if restriction is None: return if cls.Attributes.gt != T.Attributes.gt: elt = etree.SubElement(restriction, XSD('minExclusive')) elt.set('value', prot.to_string(cls, cls.Attributes.gt)) if cls.Attributes.ge != T.Attributes.ge: elt = etree.SubElement(restriction, XSD('minInclusive')) elt.set('value', prot.to_string(cls, cls.Attributes.ge)) if cls.Attributes.lt != T.Attributes.lt: elt = etree.SubElement(restriction, XSD('maxExclusive')) elt.set('value', prot.to_string(cls, cls.Attributes.lt)) if cls.Attributes.le != T.Attributes.le: elt = etree.SubElement(restriction, XSD('maxInclusive')) elt.set('value', prot.to_string(cls, cls.Attributes.le)) if cls.Attributes.pattern != T.Attributes.pattern: elt = etree.SubElement(restriction, XSD('pattern')) elt.set('value', cls.Attributes.pattern) _get_additional_restrictions(prot, restriction, cls) return restriction return _get_range_restriction_tag spyne-2.12.11/spyne/interface/xml_schema/parser.py0000644000175000001440000005601612572316312022065 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 # # To see the list of xml schema builtins recognized by this parser, run defn.py # in this package. # This module is EXPERIMENTAL. Only a subset of Xml schema standard is # implemented. from collections import defaultdict import logging logger = logging.getLogger(__name__) import os 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.model.complex import XmlModifier 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, MAG, YEL 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 = [clsid, '('] xtba = cls.Attributes._xml_tag_body_as if xtba is not None: xtba_key, xtba_type = next(xtba) 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() hier_repr = Thier_repr() hier_repr_ns = Thier_repr(with_ns=True) class XmlSchemaParser(object): def __init__(self, files, base_dir=None, repr_=Thier_repr(with_ns=False), skip_errors=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.nsmap = None self.schema = None self.prefmap = None self.tns = None self.pending_elements = None self.pending_types = None self.skip_errors = skip_errors self.pending_simple_types = defaultdict(set) 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, 'rb').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, 'rb').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(None, 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_list(self, s, name=None): item_type = s.list.item_type if item_type is None: self.debug1("skipping simple type: %s because its list itemType " "could not be found", name) return base = self.get_type(item_type) if base is None: self.pending_simple_types[self.get_name(item_type)].add((s, name)) self.debug1("pending simple type list: %s " "because of unseen base %s", name, item_type) return self.debug1("adding simple type list: %s", name) retval = Array(base, serialize_as='sd-list') # FIXME: to be implemented retval.__type_name__ = name retval.__namespace__ = self.tns assert not retval.get_type_name() is retval.Empty return retval def process_simple_type_restriction(self, s, name=None): base_name = s.restriction.base if base_name is None: self.debug1("skipping simple type: %s because its restriction base " "could not be found", name) return base = self.get_type(base_name) if base is None: self.pending_simple_types[self.get_name(base_name)].add((s, name)) self.debug1("pending simple type: %s because of unseen base %s", name, base_name) return self.debug1("adding simple type: %s", name) 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 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_simple_type_union(self, s, name=None): self.debug1("skipping simple type: %s because its union is not " "implemented", name) def process_simple_type(self, s, name=None): """Returns the simple Spyne type from `` tag.""" retval = None if name is None: name = s.name if s.list is not None: retval = self.process_simple_type_list(s, name) elif s.union is not None: retval = self.process_simple_type_union(s, name) elif s.restriction is not None: retval = self.process_simple_type_restriction(s, name) if retval is None: self.debug1("skipping simple type: %s", name) return self.retval[self.tns].types[s.name] = retval key = self.get_name(name) dependents = self.pending_simple_types[key] for s, name in set(dependents): st = self.process_simple_type(s, name) if st is not None: self.retval[self.tns].types[s.name] = st self.debug2("added back simple type: %s", s.name) dependents.remove((s, name)) if len(dependents) == 0: del self.pending_simple_types[key] 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) if t: if e.name in self.pending_elements: del self.pending_elements[e.name] self.retval[self.tns].elements[e.name] = e else: self.pending_elements[e.name] = 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 and a.simple_type is not None: raise ValueError(a, "Both type and simple_type are defined.") elif a.type is not None: t = self.get_type(a.type) if t is None: raise ValueError(a, 'type %r not found' % a.type) elif a.simple_type is not None: t = self.process_simple_type(a.simple_type, a.name) if t is None: raise ValueError(a, 'simple type %r not found' % a.simple_type) else: raise Exception("dunno attr") 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=None, element=None, attribute=None): if wrapper is None: wrapper = lambda x: x else: assert issubclass(wrapper, XmlModifier), wrapper 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 if tn is None: # According to http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-element # this means this element is now considered to be a # http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#ur-type-itself self.debug2(" skipped: %s ur-type", e.name) return else: raise Exception("dunno") process_type(tn, name, element=e) ti = [] 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: sc = c.simple_content ext = sc.extension restr = sc.restriction 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)) elif 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)) else: raise Exception("Invalid simpleContent tag: %r", sc) if issubclass(b, ComplexModelBase): base = b else: process_type(base_name, "_data", XmlData) if c.name in self.retval[self.tns].types: r = self.retval[self.tns].types[c.name] r._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 return r def get_name(self, tn): 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 return ns, qn def get_type(self, tn): if tn is None: return Null ns, qn = self.get_name(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 list(self.pending_types.items()): self.process_complex_type(_v) self.debug0("7 %s processing pending elements", YEL(self.tns)) for _k, _v in self.pending_elements.items(): self.process_schema_element(_v) def print_pending(self, fail=False): ptt_pending = sum((len(v) for v in self.pending_simple_types.values())) > 0 if len(self.pending_elements) > 0 or len(self.pending_types) > 0 or \ ptt_pending: 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("simple types") self.debug0(pformat(self.pending_simple_types)) self.debug0("%" * 50) self.debug0("complex 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 = dict(elt.nsmap.items()) self.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", MAG(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) fname = self.files[imp.namespace] self.clone(2, dirname(fname)).parse_schema_file(fname) self.retval[tns].imports.add(imp.namespace) self.debug0("3 %s processing simple_types", G(tns)) if schema.simple_types: for s in schema.simple_types.values(): self.process_simple_type(s) # no simple types should have been left behind. assert sum((len(v) for v in self.pending_simple_types.values())) == 0, \ self.pending_simple_types.values() self.debug0("4 %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("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", YEL(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: should put this in a while loop that loops until no # changes occur 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=(not self.skip_errors)) return self.retval spyne-2.12.11/spyne/interface/__init__.py0000644000175000001440000000241512572316312020202 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.12.11/spyne/interface/_base.py0000644000175000001440000004626112572316472017532 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 from spyne.util.six import get_function_name 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.method_descriptor_id_to_key = {} self.service_attrs = defaultdict(dict) 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. 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 in_header_ns = in_header.get_namespace() if in_header_ns != self.get_tns() and \ self.is_valid_import(in_header_ns): self.imports[self.get_tns()].add(in_header_ns) 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 out_header_ns = out_header.get_namespace() if out_header_ns != self.get_tns() and \ self.is_valid_import(out_header_ns): self.imports[self.get_tns()].add(out_header_ns) 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()) in_message_ns = method.in_message.get_namespace() if in_message_ns != self.get_tns() and \ self.is_valid_import(in_message_ns): self.imports[self.get_tns()].add(method.in_message.get_namespace()) if method.aux is None: yield method.in_message method.out_message.resolve_namespace(method.out_message, self.get_tns()) assert not method.out_message.get_type_name() is method.out_message.Empty out_message_ns = method.out_message.get_namespace() if out_message_ns != self.get_tns() and \ self.is_valid_import(out_message_ns): self.imports[self.get_tns()].add(out_message_ns) 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) key = _generate_method_id(s, method) if key in self.method_id_map: c = self.method_id_map[key].parent_class if c is s: pass elif 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 logger.debug(' adding method %s.%s to match %r tag.', s.__name__, get_function_name(method.function), method_key) 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 check_method(self, method): """Override this if you need to cherry-pick methods added to the interface document.""" return True 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(self.app)) 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)) if not self.check_method(method): logger.debug("method %s' discarded by check_method", method.class_key) continue logger.debug(" enumerating classes for method '%s'", method.class_key) for cls in self.add_method(method): self.add_class(cls) # populate call routes for service methods for s in self.services: self.service_attrs[s]['tns'] = self.get_tns() logger.debug("populating '%s.%s' routes...", 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.__orig__ or cls, descriptor) # populate method descriptor id to method key map self.method_descriptor_id_to_key = dict(((id(v[0]), k) for k,v in self.service_method_map.items())) 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) and self.is_valid_import(ns): self.imports[ns] = set() class_key = '{%s}%s' % (ns, tn) logger.debug(' adding 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), cls self.deps[cls] # despite the appearances, this is not totally useless. self.classes[class_key] = cls if ns == self.get_tns(): self.classes[tn] = cls # add parent class extends = getattr(cls, '__extends__', None) while extends is not None and \ (extends.get_type_name() is ModelBase.Empty): extends = getattr(extends, '__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(" importing %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(" adding %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(" importing %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(" importing %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(" populating 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(" adding 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(" not 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.12.11/spyne/model/0000755000175000001440000000000012615200103015213 5ustar plqusers00000000000000spyne-2.12.11/spyne/model/primitive/0000755000175000001440000000000012615200103017223 5ustar plqusers00000000000000spyne-2.12.11/spyne/model/primitive/__init__.py0000644000175000001440000001551512603166541021361 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 # NATIVE_MAP = {} string_encoding = 'UTF-8' # ??? from spyne.model.primitive._base import Any from spyne.model.primitive._base import AnyDict from spyne.model.primitive._base import AnyHtml from spyne.model.primitive._base import AnyXml from spyne.model.primitive._base import Boolean from spyne.model.primitive.string import Unicode from spyne.model.primitive.string import String from spyne.model.primitive.string import AnyUri from spyne.model.primitive.string import Uuid from spyne.model.primitive.string import ImageUri from spyne.model.primitive.string import Ltree from spyne.model.primitive.xml import ID from spyne.model.primitive.xml import Token from spyne.model.primitive.xml import NMToken from spyne.model.primitive.xml import Name from spyne.model.primitive.xml import NCName from spyne.model.primitive.xml import Language from spyne.model.primitive.xml import NormalizedString from spyne.model.primitive.spatial import Point from spyne.model.primitive.spatial import Line from spyne.model.primitive.spatial import LineString from spyne.model.primitive.spatial import Polygon from spyne.model.primitive.spatial import MultiPoint from spyne.model.primitive.spatial import MultiLine from spyne.model.primitive.spatial import MultiLineString from spyne.model.primitive.spatial import MultiPolygon # Date/Time types from spyne.model.primitive.datetime import Date from spyne.model.primitive.datetime import DateTime from spyne.model.primitive.datetime import Duration from spyne.model.primitive.datetime import Time # Numbers from spyne.model.primitive.number import Decimal from spyne.model.primitive.number import Double from spyne.model.primitive.number import Float from spyne.model.primitive.number import Integer8 from spyne.model.primitive.number import Byte from spyne.model.primitive.number import Integer16 from spyne.model.primitive.number import Short from spyne.model.primitive.number import Integer32 from spyne.model.primitive.number import Int from spyne.model.primitive.number import Integer64 from spyne.model.primitive.number import Long from spyne.model.primitive.number import Integer from spyne.model.primitive.number import UnsignedInteger8 from spyne.model.primitive.number import UnsignedByte from spyne.model.primitive.number import UnsignedInteger16 from spyne.model.primitive.number import UnsignedShort from spyne.model.primitive.number import UnsignedInteger32 from spyne.model.primitive.number import UnsignedInt from spyne.model.primitive.number import UnsignedInteger64 from spyne.model.primitive.number import UnsignedLong from spyne.model.primitive.number import NonNegativeInteger # Xml Schema calls it so from spyne.model.primitive.number import UnsignedInteger from spyne.model.primitive.network import MacAddress from spyne.model.primitive.network import IpAddress from spyne.model.primitive.network import Ipv4Address from spyne.model.primitive.network import Ipv6Address # 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) assert Mandatory.Long == Mandatory.Integer64 spyne-2.12.11/spyne/model/primitive/_base.py0000644000175000001440000000556412572316312020674 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 from spyne.model import SimpleModel from spyne.model.primitive import NATIVE_MAP from spyne.model._base import apply_pssm, msgpack, xml, json 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 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 Any(SimpleModel): pass # 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' Value = dict 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 Boolean(SimpleModel): """Life is simple here. Just true or false.""" __type_name__ = 'boolean' NATIVE_MAP.update({ bool: Boolean, }) spyne-2.12.11/spyne/model/primitive/datetime.py0000644000175000001440000002310112615200042021370 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 re import spyne import datetime from spyne.model import SimpleModel from spyne.model.primitive import NATIVE_MAP 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 class Time(SimpleModel): """Just that, Time. No time zone support. Native type is :class:`datetime.time`. """ __type_name__ = 'time' Value = datetime.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 )) _min_dt = datetime.datetime.min.replace(tzinfo=spyne.LOCAL_TZ) _max_dt = datetime.datetime.max.replace(tzinfo=spyne.LOCAL_TZ) 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' Value = datetime.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 = _min_dt # minExclusive """The datetime should be greater than this datetime. It must always have a timezone.""" ge = _min_dt # minInclusive """The datetime should be greater than or equal to this datetime. It must always have a timezone.""" lt = _max_dt # maxExclusive """The datetime should be lower than this datetime. It must always have a timezone.""" le = _max_dt # 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.""" out_format = None """DateTime format fed to the ``strftime`` function only when serializing. 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 ( # min_dt is also a valid value if gt is intact. (cls.Attributes.gt is _min_dt or value > cls.Attributes.gt) and value >= cls.Attributes.ge # max_dt is also a valid value if lt is intact. and (cls.Attributes.lt is _max_dt or 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)') Value = datetime.date 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' Value = datetime.timedelta NATIVE_MAP.update({ datetime.datetime: DateTime, datetime.time: Time, datetime.date: Date, datetime.timedelta: Duration, }) spyne-2.12.11/spyne/model/primitive/network.py0000644000175000001440000001221212603166541021302 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._base import SimpleModel from spyne.model.primitive._base import re_match_with_span from spyne.model.primitive.string import Unicode _PATT_MAC = "([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" 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) ))) _mac_validate = { None: _validate_string, # TODO: add int serialization } _MacBase = Unicode(max_len=17, min_len=17, pattern=_PATT_MAC) class MacAddress(_MacBase): """Unicode subclass for Universially-Unique Identifiers.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'addr_mac' class Attributes(_MacBase.Attributes): serialize_as = None @staticmethod def validate_string(cls, value): return _mac_validate[cls.Attributes.serialize_as](cls, value) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) _PATT_IPV4_FRAG = "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])" _PATT_IPV4 = "(%(P4)s\.){3,3}%(P4)s" % {'P4': _PATT_IPV4_FRAG} _ipv4_validate = { None: _validate_string, # TODO: add int serialization } _Ipv4Base = Unicode(15, pattern=_PATT_IPV4) class Ipv4Address(_Ipv4Base): """Unicode subclass for Universially-Unique Identifiers.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'addr_ipv4' class Attributes(_Ipv4Base.Attributes): serialize_as = None @staticmethod def validate_string(cls, value): return _ipv4_validate[cls.Attributes.serialize_as](cls, value) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) # http://stackoverflow.com/a/1934546 _PATT_IPV6_FRAG = "[0-9a-fA-F]{1,4}" _PATT_IPV6 = ("(" "(%(P6)s:){7,7}%(P6)s|" # 1:2:3:4:5:6:7:8 "(%(P6)s:){1,7}:|" # 1:: 1:2:3:4:5:6:7:: "(%(P6)s:){1,6}:%(P6)s|" # 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 "(%(P6)s:){1,5}(:%(P6)s){1,2}|" # 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 "(%(P6)s:){1,4}(:%(P6)s){1,3}|" # 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 "(%(P6)s:){1,3}(:%(P6)s){1,4}|" # 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 "(%(P6)s:){1,2}(:%(P6)s){1,5}|" # 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 "%(P6)s:((:%(P6)s){1,6})|" # 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 ":((:%(P6)s){1,7}|:)|" # ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: "fe80:(:%(P6)s){0,4}%%[0-9a-zA-Z]{1,}|" # fe80::7:8%eth0 fe80::7:8%1 (link-local IPv6 addresses with zone index) "::(ffff(:0{1,4}){0,1}:){0,1}%(A4)s|" # ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses) "(%(P6)s:){1,4}:%(A4)s" # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 (IPv4-Embedded IPv6 Address) ")") % {'P6': _PATT_IPV6_FRAG, 'A4': _PATT_IPV4} _ipv6_validate = { None: _validate_string, # TODO: add int serialization } _Ipv6Base = Unicode(45, pattern=_PATT_IPV6) class Ipv6Address(_Ipv6Base): """Unicode subclass for Universially-Unique Identifiers.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'addr_ipv6' class Attributes(_Ipv6Base.Attributes): serialize_as = None @staticmethod def validate_string(cls, value): return _ipv6_validate[cls.Attributes.serialize_as](cls, value) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) _PATT_IPV4V6 = "(%s|%s)" % (_PATT_IPV4, _PATT_IPV6) _ip_validate = { None: _validate_string, # TODO: add int serialization } _IpAddressBase = Unicode(45, pattern=_PATT_IPV4V6) class IpAddress(_IpAddressBase): """Unicode subclass for Universially-Unique Identifiers.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'addr_ip' class Attributes(_Ipv6Base.Attributes): serialize_as = None @staticmethod def validate_string(cls, value): return _ip_validate[cls.Attributes.serialize_as](cls, value) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) spyne-2.12.11/spyne/model/primitive/number.py0000644000175000001440000002714412572316312021111 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 math import decimal import platform from spyne.model import SimpleModel from spyne.model.primitive import NATIVE_MAP from spyne.util import six from spyne.util import memoize 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' Value = decimal.Decimal # contrary to popular belief, Decimal hates float. 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. .. NOTE:: This class is not compatible with :class:`spyne.model.Decimal`. You can get strange results if you're using a `decimal.Decimal` instance for a field denoted as `Double` or `Float` and vice versa. Make sure you only return instances of types compatible with designated types. """ __type_name__ = 'double' Value = float 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' Value = int @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`.""" NATIVE_MAP.update({ float: Double, decimal.Decimal: Decimal, }) if six.PY3: NATIVE_MAP.update({ int: Integer, }) else: NATIVE_MAP.update({ 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 spyne-2.12.11/spyne/model/primitive/spatial.py0000644000175000001440000001753212572316312021256 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 # # # FIXME: Supports e.g. # MULTIPOINT (10 40, 40 30, 20 20, 30 10) # # but not: # MULTIPOINT ((10 40), (40 30), (20 20), (30 10)) # from spyne.model import SimpleModel from spyne.model.primitive.string import Unicode FLOAT_PATTERN = r'-?[0-9]+\.?[0-9]*(e-?[0-9]+)?' _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) 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 spyne-2.12.11/spyne/model/primitive/string.py0000644000175000001440000001457112572316312021127 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 sys import decimal import uuid from spyne.model.primitive import NATIVE_MAP from spyne.util import six from spyne.model._base import SimpleModel from spyne.model.primitive._base import re_match_with_span UUID_PATTERN = "%(x)s{8}-%(x)s{4}-%(x)s{4}-%(x)s{4}-%(x)s{12}" % \ {'x': '[a-fA-F0-9]'} LTREE_PATTERN = u"\w+(\\.\w+)*" # Actual ltree max size is 65536 but it's advised to keep it under 2048. LTREE_OPTIMAL_SIZE = 2048 class Unicode(SimpleModel): """The type to represent human-readable data. Its native format is `unicode` or `str` with given encoding. """ __type_name__ = 'string' Value = six.text_type 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""" unicode_pattern = None """Same as ``pattern``, but, will be compiled with ``re.UNICODE``. See: https://docs.python.org/2/library/re.html#re.UNICODE""" 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 ))) @staticmethod def validate_native(cls, value): return (SimpleModel.validate_native(cls, value) and (value is None or ( re_match_with_span(cls.Attributes, 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.""" 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'), six.binary_type: _Tuuid_validate('bytes'), 'bytes': _Tuuid_validate('bytes'), 'bytes_le': _Tuuid_validate('bytes_le'), 'fields': _Tuuid_validate('fields'), int: _Tuuid_validate('int'), 'int': _Tuuid_validate('int'), } class Uuid(Unicode(pattern=UUID_PATTERN)): """Unicode subclass for Universially-Unique Identifiers.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'uuid' Value = uuid.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) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) class Ltree(Unicode(LTREE_OPTIMAL_SIZE, unicode_pattern=LTREE_PATTERN)): """A special kind of String type designed to hold the Ltree type from Postgresql.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'ltreeString' if six.PY3: NATIVE_MAP.update({ str: Unicode, }) else: NATIVE_MAP.update({ str: String, unicode: Unicode, }) spyne-2.12.11/spyne/model/primitive/xml.py0000644000175000001440000000343712572316312020420 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.const.xml import PATT_NMTOKEN from spyne.model.primitive.string import Unicode 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 NMToken(Unicode): __type_name__ = 'NMTOKEN' class Attributes(Unicode.Attributes): unicode_pattern = PATT_NMTOKEN 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})*' spyne-2.12.11/spyne/model/__init__.py0000644000175000001440000000417712572316312017351 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 # Primitives from spyne.model.primitive import * # store_as values # it's sad that xml the pssm and xml the module conflict. that's why we need # this after import of primitive package from spyne.model._base import xml from spyne.model._base import json from spyne.model._base import table from spyne.model._base import msgpack # 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.12.11/spyne/model/_base.py0000644000175000001440000007014112615200042016643 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 logging logger = logging.getLogger(__name__) import re import spyne.const.xml_ns from decimal import Decimal from spyne import const from spyne.util import Break, six from spyne.util.cdict import cdict from spyne.util.odict import odict from spyne.const.xml_ns import DEFAULT_NS def _decode_pa_dict(d): """Decodes dict passed to prot_attrs. >>> _decode_pa_dict({}) cdict({}) >>> _decode_pa_dict({1: 2)}) cdict({1: 2}) >>> _decode_pa_dict({(1,2): 3)}) cdict({1: 3, 2: 3}) """ retval = cdict() for k, v in d.items(): if isinstance(k, tuple): for subk in k: retval[subk] = v for k, v in d.items(): if not isinstance(k, tuple): retval[k] = v return retval 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): # you will probably want to look at ModelBase._s_customize as well. 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 not hasattr(self, '_nullable'): self._nullable = None if not hasattr(self, '_default_factory'): self._default_factory = None if not hasattr(self, '_html_cloth'): self._html_cloth = None if not hasattr(self, '_html_root_cloth'): self._html_root_cloth = None if 'html_cloth' in cls_dict: self.set_html_cloth(cls_dict.pop('html_cloth')) if 'html_root_cloth' in cls_dict: self.set_html_cloth(cls_dict.pop('html_root_cloth')) if not hasattr(self, '_xml_cloth'): self._xml_cloth = None if not hasattr(self, '_xml_root_cloth'): self._xml_root_cloth = None if 'xml_cloth' in cls_dict: self.set_xml_cloth(cls_dict.pop('xml_cloth')) if 'xml_root_cloth' in cls_dict: self.set_xml_cloth(cls_dict.pop('xml_root_cloth')) 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) def get_default_factory(self): return self._default_factory def set_default_factory(self, what): self._default_factory = staticmethod(what) default_factory = property(get_default_factory, set_default_factory) def get_html_cloth(self): return self._html_cloth def set_html_cloth(self, what): from spyne.protocol.cloth.to_cloth import ClothParserMixin cm = ClothParserMixin.from_html_cloth(what) if cm._root_cloth is not None: self._html_root_cloth = cm._root_cloth elif cm._cloth is not None: self._html_cloth = cm._cloth else: raise Exception("%r is not a suitable cloth", what) html_cloth = property(get_html_cloth, set_html_cloth) def get_html_root_cloth(self): return self._html_root_cloth html_root_cloth = property(get_html_root_cloth) def get_xml_cloth(self): return self._xml_cloth def set_xml_cloth(self, what): from spyne.protocol.cloth.to_cloth import ClothParserMixin cm = ClothParserMixin.from_xml_cloth(what) if cm._root_cloth is not None: self._xml_root_cloth = cm._root_cloth elif cm._cloth is not None: self._xml_cloth = cm._cloth else: raise Exception("%r is not a suitable cloth", what) xml_cloth = property(get_xml_cloth, set_xml_cloth) def get_xml_root_cloth(self): return self._xml_root_cloth xml_root_cloth = property(get_xml_root_cloth) class ModelBaseMeta(type(object)): def __getitem__(self, item): return self.customize(**item) def customize(self, **kwargs): """Duplicates cls and overwrites the values in ``cls.Attributes`` with ``**kwargs`` and returns the new class.""" cls_name, cls_bases, cls_dict = ModelBase._s_customize(self, **kwargs) return type(cls_name, cls_bases, cls_dict) @six.add_metaclass(ModelBaseMeta) 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.""" Value = type(None) """The value of this type is an instance of this class""" # 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. @six.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 callable that produces a 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 value 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. Set this to ``True`` for e.g. read-only properties.""" prot_attrs = None """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.""" pa = None """Alias for prot_attrs.""" 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.""" polymap = {} """A dict of classes that override polymorphic substitions for classes given as keys to classes given as values.""" class Annotations(object): """The class that holds the annotations for the given type.""" __use_parent_doc__ = False """If equal to True and doc is empty, Annotations will use __doc__ from parent. Set it to False to avoid this mechanism. 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__ @classmethod def _fill_empty_type_name(cls, parent_ns, parent_tn, k): cls.__namespace__ = parent_ns cls.__type_name__ = "%s_%s%s" % (parent_tn, k, const.TYPE_SUFFIX) extends = cls.__extends__ while extends is not None and extends.__type_name__ is ModelBase.Empty: cls.__extends__._fill_empty_type_name(cls.get_namespace(), cls.get_type_name(), k + const.PARENT_SUFFIX) extends = extends.__extends__ # 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) # too slow # logger.debug(" resolve ns for %r to %r", cls, cls.__namespace__) 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 # FIXME: Rename this to get_type_name_with_ns_pref @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 to_unicode(cls, value): """ Returns unicode(value). This should be overridden if this is not enough. """ return six.text_type(value) @classmethod def get_documentation(cls): if cls.Annotations.doc: return cls.Annotations.doc elif cls.Annotations.__use_parent_doc__: return cls.__doc__ else: return '' @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__, '__doc__': cls.__doc__}) if getattr(cls, '__orig__', None) is None: cls_dict['__orig__'] = cls else: cls_dict['__orig__'] = cls.__orig__ 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 # properties get reset everytime a new class is defined. So we need # to reinitialize them explicitly. for k in ('nillable', '_xml_cloth', '_xml_root_cloth', '_html_cloth', '_html_root_cloth'): v = getattr(cls.Attributes, k) if v is not None: setattr(Attributes, k, v) class Annotations(cls.Annotations): pass cls_dict['Annotations'] = Annotations # get protocol attrs prot = kwargs.get('protocol', None) if prot is None: prot = kwargs.get('prot', None) if prot is None: prot = kwargs.get('p', None) if prot is not None and len(prot.type_attrs) > 0: # if there is a class customization from protocol, do it type_attrs = prot.type_attrs.copy() type_attrs.update(kwargs) logger.debug("%r: kwargs %r => %r from prot typeattr %r", cls, kwargs, type_attrs, prot.type_attrs) kwargs = type_attrs for k, v in kwargs.items(): if k.startswith('_'): continue if k in ('protocol', 'prot', 'p'): setattr(Attributes, 'prot', v) elif k in ("doc", "appinfo"): setattr(Annotations, k, v) elif k in ('primary_key', 'pk'): setattr(Attributes, 'primary_key', v) Attributes.sqla_column_args[-1]['primary_key'] = v elif k in ('protocol_attrs', 'prot_attrs', 'pa'): setattr(Attributes, 'prot_attrs', _decode_pa_dict(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 == 'values_dict': assert not 'values' in v, "`values` and `values_dict` can't be" \ "specified at the same time" Attributes.values = v.keys() Attributes.values_dict = 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) def get_unicode_pattern(self): return self._pattern def set_unicode_pattern(self, pattern): self._pattern = pattern if pattern is not None: self._pattern_re = re.compile(pattern, re.UNICODE) unicode_pattern = property(get_unicode_pattern, set_unicode_pattern) upattern = property(get_unicode_pattern, set_unicode_pattern) class SimpleModel(ModelBase): """The base class for primitives.""" __namespace__ = "http://www.w3.org/2001/XMLSchema" @six.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.""" # some hacks are done in _s_customize to make `values_dict` # behave like `values` values_dict = dict() """The dict of possible values for this type. Dict keys are values and dict values are either a single string or a translation dict.""" _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, pretty_print=False): self.root_tag = root_tag self.no_ns = no_ns self.pretty_print = pretty_print 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, fk_left_deferrable=None, fk_left_initially=None, fk_right_deferrable=None, fk_right_initially=None, explicit_join=False, order_by=False): 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 self.fk_left_deferrable = fk_left_deferrable self.fk_left_initially = fk_left_initially self.fk_right_deferrable = fk_right_deferrable self.fk_right_initially = fk_right_initially self.explicit_join = explicit_join self.order_by = order_by 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.12.11/spyne/model/binary.py0000644000175000001440000003003512572316312017066 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 logging logger = logging.getLogger(__name__) import os import base64 import tempfile from mmap import mmap, ACCESS_READ 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, isfile, basename 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(b''.join(value)) @classmethod def from_base64(cls, value): joiner = type(value)() try: return [b64decode(joiner.join(value))] except TypeError: 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', type=None): """Marker to be passed to File's store_as to denote a hybrid Sql/Filesystem storage scheme. :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 self.type = type if not isdir(self.store): os.makedirs(self.store) assert isdir(self.store) _BINARY = type('FileTypeBinary', (object,), {}) _TEXT = type('FileTypeText', (object,), {}) 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='len')), ] 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. """ if self.data is not None: if self.path is None: self.handle = tempfile.NamedTemporaryFile() self.name = self.path = self.handle.name else: self.handle = open(self.path, 'wb') # data is a ByteArray, so a sequence of str/bytes objects for d in self.data: self.handle.write(d) elif self.handle is not None: self.data = mmap(self.handle.fileno(), 0) # 0 = whole file elif self.path is not None: if not isfile(self.path): logger.error("File path in %r not found", self) self.handle = open(self.path, 'rb') self.data = mmap(self.handle.fileno(), 0, access=ACCESS_READ) self.abspath = abspath(self.path) self.name = self.path = basename(self.path) else: raise ValueError("Invalid file object passed in. All of " ".data, .handle and .path are None.") 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" BINARY = _BINARY TEXT = _BINARY Value = _Value 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') """ type = _Value """The native type used to serialize the information in the file object. """ contents = _BINARY """Set this to type=File.TEXT if you're sure you're handling unicode data. This lets serializers like HtmlCloth avoid base64 encoding. Do note that you still need to set encoding attribute explicitly to None!.. One of (File.BINARY, File.TEXT) """ @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.12.11/spyne/model/complex.py0000644000175000001440000014417412615200042017251 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. """ from __future__ import print_function import logging logger = logging.getLogger(__name__) import decimal from copy import copy from weakref import WeakKeyDictionary from collections import deque from inspect import isclass from itertools import chain 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 _is_under_pydev_debugger(): import inspect for frame in inspect.stack(): if frame[1].endswith("pydevd.py"): return True return False def _get_flat_type_info(cls, retval): assert isinstance(retval, TypeInfo) parent = getattr(cls, '__extends__', None) if parent != None: _get_flat_type_info(parent, retval) retval.update(cls._type_info) retval.alt.update(cls._type_info_alt) # FIXME: move to cls._type_info.alt return retval class TypeInfo(odict): def __init__(self, *args, **kwargs): super(TypeInfo, self).__init__(*args, **kwargs) self.attributes = {} self.alt = {} 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', 'can_be_empty'] def __init__(self, path, parent, type_, is_array, can_be_empty): self.path = path self.parent = parent self.type = type_ self.is_array = is_array self.can_be_empty = can_be_empty 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 @classmethod def _fill_empty_type_name(cls, parent_ns, parent_tn, k): cls.__namespace__ = parent_ns tn = "%s_%s%s" % (parent_tn, k, const.TYPE_SUFFIX) child_v = cls.type child_v.__type_name__ = tn cls._type_info = TypeInfo({tn: child_v}) cls.__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 cls.Empty: extends._fill_empty_type_name(parent_ns, parent_tn, k + const.PARENT_SUFFIX) extends = extends.__extends__ 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`. """ customize_args = [] customize_kwargs = {} __orig__ = None def __init__(self): raise NotImplementedError() @classmethod def customize(cls, *args, **kwargs): args = chain(args, cls.customize_args) kwargs = dict(chain(kwargs.items(), cls.customize_kwargs.items())) if cls.__orig__ is None: cls.__orig__ = cls return type("SelfReference", (cls,), { 'customize_args': args, 'customize_kwargs': kwargs, }) 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 = TypeInfo() extends = cls_dict.get('__extends__', None) # user did not specify explicit base class so let's try to derive it from # the actual class hierarchy if extends is None: # we don't want origs end up as base classes orig = cls_dict.get("__orig__", None) if orig is None: orig = getattr(cls, '__orig__', None) if orig is not None: bases = orig.__bases__ logger.debug("Got bases for %s from orig: %r", cls_name, bases) else: bases = cls_bases logger.debug("Got bases for %s from meta: %r", cls_name, bases) for b in bases: base_types = getattr(b, "_type_info", None) # we don't care about non-ComplexModel bases if base_types is None: continue # mixins are simple if getattr(b, '__mixin__', False) == True: logger.debug("Adding fields from mixin %r to '%s'", b, cls_name) mixin.update(b.get_flat_type_info(b)) if '__mixin__' not in cls_dict: cls_dict['__mixin__'] = False continue 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.") if len(base_types) > 0 and issubclass(b, ModelBase): extends = cls_dict["__extends__"] = b assert extends.__orig__ is None, "You can't inherit from a " \ "customized class. You should first get your class " \ "hierarchy right, then start customizing classes." b.get_subclasses.memo.clear() logger.debug("Registering %r as base of '%s'", b, cls_name) 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 if issubclass(d.in_message, ComplexModelBase): 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. if issubclass(d.out_message, ComplexModelBase): for k, v in d.out_message._type_info.items(): if v is d.out_message: d.out_message._type_info[k] = cls if d.patterns is not None and not d.no_self: d.name = '.'.join((cls.get_type_name(), d.name)) for p in d.patterns: if p.address is None: p.address = d.name def _gen_methods(cls, 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, _self_ref_replacement=cls) setattr(cls, 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 def _process_child_attrs(cls, retval, kwargs): child_attrs_all = kwargs.get('child_attrs_all', None) if child_attrs_all is not None: ti = retval._type_info logger.debug("processing child_attrs_all for %r", cls) for k, v in ti.items(): logger.debug(" child_attrs_all set %r=%r", k, child_attrs_all) ti[k] = ti[k].customize(**child_attrs_all) if retval.__extends__ is not None: retval.__extends__ = retval.__extends__.customize( child_attrs_all=child_attrs_all) retval.Attributes._delayed_child_attrs_all = child_attrs_all child_attrs = copy(kwargs.get('child_attrs', None)) if child_attrs is not None: ti = retval._type_info logger.debug("processing child_attrs for %r", cls) for k, v in list(child_attrs.items()): if k in ti: logger.debug(" child_attr set %r=%r", k, v) ti[k] = ti[k].customize(**v) del child_attrs[k] base_fti = {} if retval.__extends__ is not None: retval.__extends__ = retval.__extends__.customize( child_attrs=child_attrs) base_fti = retval.__extends__.get_flat_type_info( retval.__extends__) for k, v in child_attrs.items(): if k not in base_fti: logger.debug(" child_attr delayed %r=%r", k, v) retval.Attributes._delayed_child_attrs[k] = v 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.""" attrs = _gen_attrs(cls_bases, cls_dict) assert issubclass(attrs, ComplexModelBase.Attributes), \ ("%r must be a ComplexModelBase.Attributes subclass" % attrs) 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) # 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): self._replace_field(k, self.customize(*v.customize_args, **v.customize_kwargs)) elif issubclass(v, XmlData): if self.Attributes._xml_tag_body_as is None: self.Attributes._xml_tag_body_as = [(k, v)] else: self.Attributes._xml_tag_body_as.append((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 = _gen_methods(self, cls_dict) if len(methods) > 0: self.Attributes.methods = methods 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.store.relational 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.store.relational 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() # # pydev debugger seems to secretly manipulate class dictionaries without # doing proper checks -- not always do a class with a metaclass with a # __prepare__ has to have it. this code has a very specific workaround to # make debugging under pydev for python 3.x work. # # see https://github.com/arskom/spyne/issues/432 for details # # this can be removed once pydev fixes secret calls to class dict's # __delitem__ if _is_under_pydev_debugger(): @classmethod def __prepare__(mcs, name, bases, **kwds): return odict((('__class__', mcs),)) print("ComplexModelMeta.__prepare__ substitution for PyDev successful") _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.""" child_attrs_all = None """Customize all child attributes. It's a dict. This is ignored unless used via explicit customization. `child_attrs` always take precedence. """ 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 _delayed_child_attrs = None _delayed_child_attrs_all = None _subclasses = None def __init__(self, *args, **kwargs): cls = self.__class__ fti = cls.get_flat_type_info(cls) if cls.__orig__ is not None: logger.warning("%r seems to be a customized class. It is not " "supposed to be instantiated. You have been warned.", cls) if cls.Attributes._xml_tag_body_as is not None: for arg, (xtba_key, xtba_type) in \ zip(args, cls.Attributes._xml_tag_body_as): if xtba_key is not None and len(args) == 1: self._safe_set(xtba_key, arg, 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: if six.PY2 and hasattr(def_fac, 'im_func'): # unbound-method error workaround. huh. def_fac = def_fac.im_func dval = def_fac() # should not check for read-only for default values setattr(self, k, dval) 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 hasattr(cls, '_sa_class_manager'): # except the attributes that sqlalchemy doesn't know about if v.Attributes.exc_table: try: setattr(self, k, None) except AttributeError: # it could be a read-only property pass 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: return False try: setattr(self, key, value) except AttributeError as e: logger.exception(e) raise AttributeError("can't set %r attribute %s to %r" % (self.__class__, key, value)) return True def as_dict(self): """Represent object as dict. Null values are omitted from dict representation to support optional not nullable attributes. """ return dict(( (k, getattr(self, k)) for k in self.get_flat_type_info(self) if getattr(self, k) is not None )) @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): cls_orig = cls if cls.__orig__ is not None: cls_orig = cls.__orig__ inst = cls_orig() 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() return cls.__orig__() @classmethod @memoize_id def get_subclasses(cls): retval = [] subca = cls.Attributes._subclasses if subca is not None: retval.extend(subca) for subc in subca: retval.extend(subc.get_subclasses()) return retval @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() key = hier_delim.join(prefix) if issubclass(v, ComplexModelBase): retval[key] = _SimpleTypeInfoElement(path=tuple(prefix), parent=parent, type_=v, is_array=tuple(is_array), can_be_empty=True) 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: 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), can_be_empty=False) 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: v._fill_empty_type_name(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. Because each class is registered as a variant of the original (__orig__) class, using this function to generate classes dynamically on-the-fly could cause memory leaks. You have been warned. """ 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__ if '__extends__' not in cls_dict: cls_dict['__extends__'] = cls.__extends__ 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()) 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 cls is not ComplexModel: cls._process_variants(retval) _process_child_attrs(cls, retval, kwargs) # we could be smarter, but customize is supposed to be called only # during daemon initialization, so it's not really necessary. ComplexModelBase.get_subclasses.memo.clear() ComplexModelBase.get_flat_type_info.memo.clear() ComplexModelBase.get_simple_type_info.memo.clear() return retval @classmethod def _process_variants(cls, retval): orig = getattr(retval, '__orig__', None) if orig is not 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_impl(cls, field_name, field_type): assert isinstance(field_name, string_types) dcaa = cls.Attributes._delayed_child_attrs_all if dcaa is not None: field_type = field_type.customize(**dcaa) 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 ComplexModelBase.get_flat_type_info.memo.clear() ComplexModelBase.get_simple_type_info.memo.clear() @classmethod def _append_to_variants(cls, field_name, field_type): if cls.Attributes._variants is not None: for c in cls.Attributes._variants: c.append_field(field_name, field_type) @classmethod def append_field(cls, field_name, field_type): cls._append_field_impl(field_name, field_type) cls._append_to_variants(field_name, field_type) @classmethod def _insert_to_variants(cls, 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) @classmethod def _insert_field_impl(cls, index, field_name, field_type): assert isinstance(index, int) assert isinstance(field_name, string_types) dcaa = cls.Attributes._delayed_child_attrs_all if dcaa is not None: field_type = field_type.customize(**dcaa) 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)) ComplexModelBase.get_flat_type_info.memo.clear() ComplexModelBase.get_simple_type_info.memo.clear() @classmethod def insert_field(cls, index, field_name, field_type): cls._insert_field_impl(index, field_name, field_type) cls._insert_to_variants(index, field_name, field_type) @classmethod def _replace_in_variants(cls, field_name, field_type): if cls.Attributes._variants is not None: for c in cls.Attributes._variants: c._replace_field(field_name, field_type) @classmethod def _replace_field_impl(cls, field_name, field_type): assert isinstance(field_name, string_types) cls._type_info[field_name] = field_type ComplexModelBase.get_flat_type_info.memo.clear() ComplexModelBase.get_simple_type_info.memo.clear() @classmethod def _replace_field(cls, field_name, field_type): cls._replace_field_impl(field_name, field_type) cls._replace_in_variants(field_name, field_type) @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, **kwargs): retval = (cls if cls.__orig__ is None else cls.__orig__)() for k, v in cls._type_info.items(): if v.Attributes.read_only: continue try: if k in kwargs: setattr(retval, k, kwargs[k]) elif hasattr(other, k): setattr(retval, k, getattr(other, k)) except AttributeError as e: logger.warning("Error setting %s: %r", k, e) 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, wrapped=True, **kwargs): if not wrapped: if serializer.Attributes.max_occurs == 1: kwargs['max_occurs'] = 'unbounded' return serializer.customize(**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 _fill_empty_type_name(cls, parent_ns, parent_tn, k): cls.__namespace__ = parent_ns tn = "%s_%s%s" % (parent_tn, k, const.TYPE_SUFFIX) child_v, = cls._type_info.values() child_v.__type_name__ = tn cls._type_info = TypeInfo({tn: child_v}) cls.__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 cls.Empty: extends._fill_empty_type_name(parent_ns, parent_tn, k + const.PARENT_SUFFIX) extends = extends.__extends__ @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 = TypeInfo({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.store.relational import add_column class TableModelBase(ComplexModelBase): @classmethod def append_field(cls, field_name, field_type): cls._append_field_impl(field_name, field_type) # There could have been changes to field_type in ComplexModel so we # should not use field_type directly from above if cls.__table__ is not None: add_column(cls, field_name, cls._type_info[field_name]) cls._append_to_variants(field_name, field_type) @classmethod def replace_field(cls, field_name, field_type): raise NotImplementedError() @classmethod def insert_field(cls, index, field_name, field_type): cls._insert_field_impl(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 if cls.__table__ is not None: add_column(cls, field_name, cls._type_info[field_name]) cls._insert_to_variants(index, field_name, field_type) 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.12.11/spyne/model/enum.py0000644000175000001440000000672612572316312016560 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.12.11/spyne/model/fault.py0000644000175000001440000000674512572316312016730 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 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. :param lang: Language code corresponding to the language of faultstring. """ __type_name__ = "Fault" def __init__(self, faultcode='Server', faultstring="", faultactor="", detail=None, lang=spyne.DEFAULT_LANGUAGE): self.faultcode = faultcode self.faultstring = faultstring or self.get_type_name() self.faultactor = faultactor self.detail = detail self.lang = lang 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.12.11/spyne/model/table.py0000644000175000001440000001717312572316312016701 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 warnings warnings.warn("%r module is deprecated. Please switch to " "spyne.model.complex.TTableModel.\nHere's where the import " "comes from:" % __name__) import traceback traceback.print_stack() 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.12.11/spyne/protocol/0000755000175000001440000000000012615200103015754 5ustar plqusers00000000000000spyne-2.12.11/spyne/protocol/cloth/0000755000175000001440000000000012615200103017065 5ustar plqusers00000000000000spyne-2.12.11/spyne/protocol/cloth/__init__.py0000644000175000001440000000202112572316312021205 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.12.11/spyne/protocol/cloth/_base.py0000644000175000001440000002026112572316312020525 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 ProtocolContext, 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 class XmlClothProtocolContext(ProtocolContext): def __init__(self, parent, transport, type=None): super(XmlClothProtocolContext, self).__init__(parent, transport, type) self.inst_stack = [] self.prot_stack = [] self.doctype_written = False self.close_until = None 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, cloth_parser=None, polymorphic=True): super(XmlCloth, self).__init__(app=app, mime_type=mime_type, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers, polymorphic=polymorphic) self._init_cloth(cloth, cloth_parser) def get_context(self, parent, transport): return XmlClothProtocolContext(parent, transport) 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() logger.debug("%r %d", 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 self.docfile(ctx.out_stream) as xf: # as XmlDocument is not push-ready yet, this is what we do. # this is an ugly 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 the output is a StringIO object, which means we're run by a sync framework. Async frameworks have the out_stream write directly to the output stream so out_string should not be used. """ 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 self.docfile(ctx.out_stream) as xf: ctx.protocol.doctype_written = False ctx.protocol.prot_stack = [] 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 docfile(self, *args, **kwargs): return etree.xmlfile(*args, **kwargs) def write_doctype(self, ctx, parent, cloth=None): pass # FIXME: write it @staticmethod def get_class_cloth(cls): return cls.Attributes._xml_cloth @staticmethod def get_class_root_cloth(cls): return cls.Attributes._xml_root_cloth def check_class_cloths(self, ctx, cls, inst, parent, name, **kwargs): c = self.get_class_root_cloth(cls) eltstack = getattr(ctx.protocol, 'eltstack', []) if c is not None and len(eltstack) == 0 and not (eltstack[-1] is c): if not ctx.protocol.doctype_written: self.write_doctype(ctx, parent, c) logger.debug("to object root cloth") return True, self.to_root_cloth(ctx, cls, inst, c, parent, name, **kwargs) c = self.get_class_cloth(cls) if c is not None: if not ctx.protocol.doctype_written: self.write_doctype(ctx, parent, c) logger.debug("to object cloth") return True, self.to_parent_cloth(ctx, cls, inst, c, parent, name, **kwargs) return False, None def subserialize(self, ctx, cls, inst, parent, name='', **kwargs): pstack = ctx.protocol.prot_stack pstack.append(self) logger.debug("push prot %r. newlen: %d", self, len(pstack)) if self._root_cloth is not None: logger.debug("to root cloth") retval = self.to_root_cloth(ctx, cls, inst, self._root_cloth, parent, name) elif self._cloth is not None: logger.debug("to parent cloth") retval = self.to_parent_cloth(ctx, cls, inst, self._cloth, parent, name) else: logger.debug("to parent") retval = self.start_to_parent(ctx, cls, inst, parent, name, **kwargs) # FIXME: if retval is a coroutine handle, this will be inconsistent pstack.pop() logger.debug("pop prot %r. newlen: %d", self, len(pstack)) return retval def decompose_incoming_envelope(self, ctx, message): raise NotImplementedError("This is an output-only protocol.") spyne-2.12.11/spyne/protocol/cloth/to_cloth.py0000644000175000001440000005265712615200042021273 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 # # TODO: strip comments without removing e.g. [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 extend(self, keys): for key in keys: 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, oset): return len(self) == len(other) and list(self) == list(other) return set(self) == set(other) @property def back(self): return self.end[1][0] if __name__ == '__main__': print((oset('abracadabra'))) stuff = oset() stuff.add(1) print(stuff) stuff.add(1) print(stuff) print((oset('simsalabim'))) o = oset('abcde') print(o) print(o.end) o = oset() print(o.back) o = oset([3]) print(o.back) spyne-2.12.11/spyne/util/oset/old.py0000644000175000001440000001441612572316312017214 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.12.11/spyne/util/__init__.py0000644000175000001440000001725212605464654017236 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 import functools logger = logging.getLogger(__name__) import sys import datetime from inspect import isgeneratorfunction from spyne.util.six import PY3 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): try: ret = func(*args, **kwargs) except TypeError as e: logger.error("Function %r at %s:%d got error %r", func.func_name, func.__module__, func.__code__.co_firstlineno, e) raise try: next(ret) except StopIteration as e: 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()]) class memoize_id_method(memoize_id): """A memoization decorator that keeps caching until reset for unhashable types on instance methods. It works on id()'s of objects instead.""" def __get__(self, obj, objtype): """Support instance methods.""" fn = functools.partial(self.__call__, obj) fn.reset = self.reset return fn 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 PY3: def _bytes_join(val, joiner=b''): return 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.12.11/spyne/util/_twisted_ws.py0000644000175000001440000004403712572316312020021 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.12.11/spyne/util/appreg.py0000644000175000001440000000521112572316312016733 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, appinit=False), ostr=NullServer(app, appinit=False, ostr=True) ) logger.debug("Registering %r as %r" % (app, key)) def get_application(tns, name='Application'): return applications.get((tns, name), None) spyne-2.12.11/spyne/util/cdict.py0000644000175000001440000000467112572316312016554 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: if not hasattr(cls, '__bases__'): cls = cls.__class__ for b in cls.__bases__: try: retval = self[b] self[cls] = retval return retval except KeyError: pass raise e def get(self, k, d=None): try: return self[k] except KeyError: return d spyne-2.12.11/spyne/util/cherry.py0000644000175000001440000000246312572316312016757 0ustar plqusers00000000000000# Use Cherrypy as wsgi server. # Source: https://www.digitalocean.com/community/tutorials/how-to-deploy-python-wsgi-applications-using-a-cherrypy-web-server-behind-nginx import logging import cherrypy def cherry_graft_and_start(wsgi_application, host="0.0.0.0", port=8080, num_threads=30, ssl_module=None, cert=None, key=None, cacert=None): logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) # Mount the application cherrypy.tree.graft(wsgi_application, "/") # Unsubscribe the default server cherrypy.server.unsubscribe() # Instantiate a new server object server = cherrypy._cpserver.Server() # Configure the server object server.socket_host = host server.socket_port = port server.thread_pool = num_threads # For SSL Support if ssl_module is not None: server.ssl_module = ssl_module # eg. 'pyopenssl' server.ssl_certificate = cert # eg. 'ssl/certificate.crt' server.ssl_private_key = key # eg. 'ssl/private.key' server.ssl_certificate_chain = cacert # eg. 'ssl/bundle.crt' # Subscribe this server server.subscribe() # Start the server engine (Option 1 *and* 2) cherrypy.engine.start() return cherrypy.engine.block() spyne-2.12.11/spyne/util/color.py0000644000175000001440000000403312615200042016562 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 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)) YEL = lambda s: ''.join((colorama.Fore.YELLOW, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) MAG = lambda s: ''.join((colorama.Fore.MAGENTA, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) CYA = lambda s: ''.join((colorama.Fore.CYAN, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) except ImportError: R = lambda s: s G = lambda s: s B = lambda s: s YEL = lambda s: s MAG = lambda s: s CYA = lambda s: s if __name__ == '__main__': print(R("RED")) print(G("GREEN")) print(B("BLUE")) print(YEL("YELLOW")) print(MAG("MAGENTA")) print(CYA("CYAN")) spyne-2.12.11/spyne/util/dictdoc.py0000644000175000001440000001307712572316312017077 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 try: from spyne.protocol.msgpack import MessagePackDocument except ImportError as e: def MessagePackDocument(*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_unicode_handlers[Double] = lambda cls, val: val self._from_unicode_handlers[Boolean] = lambda cls, val: val self._from_unicode_handlers[Decimal] = lambda cls, val: val self._from_unicode_handlers[Integer] = lambda cls, val: val self._to_unicode_handlers[Double] = lambda cls, val: val self._to_unicode_handlers[Boolean] = lambda cls, val: val self._to_unicode_handlers[Decimal] = lambda cls, val: val self._to_unicode_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=None, ignore_wrappers=True, complex_as=dict, protocol=_UtilProtocol): if cls is None: cls = o.__class__ retval = protocol(ignore_wrappers=ignore_wrappers, complex_as=complex_as)._object_to_doc(cls, o) if not ignore_wrappers: return {cls.get_type_name(): retval} return retval def get_object_as_simple_dict(o, cls=None, hier_delim='_'): if cls is None: cls = o.__class__ return SimpleDictDocument(hier_delim=hier_delim) \ .object_to_simple_dict(cls, o) def get_object_as_json(o, cls=None, ignore_wrappers=True, complex_as=list, encoding='utf8', polymorphic=False): if cls is None: cls = o.__class__ prot = JsonDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as, polymorphic=polymorphic) 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=None, ignore_wrappers=False, complex_as=dict, encoding='utf8', polymorphic=False): if cls is None: cls = o.__class__ prot = YamlDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as, polymorphic=polymorphic) 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_msgpack(o, cls=None, ignore_wrappers=False, complex_as=dict, encoding='utf8', polymorphic=False): if cls is None: cls = o.__class__ prot = MessagePackDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as, polymorphic=polymorphic) ctx = FakeContext(out_document=[prot._object_to_doc(cls,o)]) prot.create_out_string(ctx, encoding) return ''.join(ctx.out_string) def json_loads(s, cls, protocol=JsonDocument, **kwargs): prot = protocol(**kwargs) ctx = FakeContext(in_string=[s]) prot.create_in_document(ctx) return prot._doc_to_object(cls, ctx.in_document, validator=prot.validator) get_json_as_object = json_loads def yaml_loads(s, cls, protocol=YamlDocument, ignore_wrappers=False, **kwargs): prot = protocol(ignore_wrappers=ignore_wrappers, **kwargs) ctx = FakeContext(in_string=[s]) prot.create_in_document(ctx) return prot._doc_to_object(cls, ctx.in_document, validator=prot.validator) get_yaml_as_object = yaml_loads spyne-2.12.11/spyne/util/django.py0000644000175000001440000004200512572316312016721 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 # """Useful stuff to integrate Spyne with Django. * Django model <-> spyne type mapping * Service for common exception handling 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, ObjectDoesNotExist, ValidationError as DjValidationError) from django.core.validators import (slug_re, comma_separated_int_list_re, MinLengthValidator, MaxLengthValidator) from spyne.error import (ResourceNotFoundError, ValidationError as BaseValidationError, Fault) from spyne.model import primitive from spyne.model.complex import ComplexModelMeta, ComplexModelBase from spyne.service import ServiceBase from spyne.util.cdict import cdict from spyne.util.odict import odict from spyne.util.six import add_metaclass # regex is based on http://www.w3.org/TR/xforms20/#xforms:email email_re = re.compile( r"[A-Za-z0-9!#-'\*\+\-/=\?\^_`\{-~]+" r"(\.[A-Za-z0-9!#-'\*\+\-/=\?\^_`\{-~]+)*@" # domain part is either a single symbol r"(([a-zA-Z0-9]|" # or have at least two symbols # hyphen can't be at the beginning or end of domain part # domain should contain at least 2 parts, the last one is TLD r"([a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])+)\.)+" # TLD should contain only letters, at least 2 r"[A-Za-z]{2,}", re.IGNORECASE) def _handle_minlength(validator, params): new_min = validator.limit_value old_min = params.setdefault('min_len', new_min) params['min_len'] = max(old_min, new_min) def _handle_maxlength(validator, params): new_max = validator.limit_value old_max = params.setdefault('max_len', new_max) params['max_len'] = min(old_max, new_max) class BaseDjangoFieldMapper(object): """Abstrace base class for field mappers.""" _VALIDATOR_HANDLERS = cdict({ MinLengthValidator: _handle_minlength, MaxLengthValidator: _handle_maxlength, }) @staticmethod def is_field_nullable(field, **kwargs): """Return True if django field is nullable.""" return field.null @staticmethod def is_field_blank(field, **kwargs): """Return True if django field is blank.""" return field.blank 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() self._process_validators(field.validators, params) nullable = self.is_field_nullable(field, **kwargs) blank = self.is_field_blank(field, **kwargs) required = not (field.has_default() or blank 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 def _process_validators(self, validators, params): for v in validators: handler = self._VALIDATOR_HANDLERS.get(type(v)) if handler: handler(v, params) 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_blank(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_blank(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.__class__.__name__ 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.__class__.__name__ 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] # django's own slug_re.pattern is invalid according to xml schema -- it doesn't # like the location of the dash character. using the equivalent pattern accepted # by xml schema here. SLUG_RE_PATTERN = '[a-zA-Z0-9_-]+' 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), ('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__ abstract = bool(attrs.get('__abstract__', False)) if abstract: # skip processing of abstract models 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 getattr(attributes, 'django_model', None) 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. """ __abstract__ = True class ObjectNotFoundError(ResourceNotFoundError): """Fault constructed from `model.DoesNotExist` exception.""" def __init__(self, does_not_exist_exc): """Construct fault with code Client.NotFound.""" message = str(does_not_exist_exc) object_name = message.split()[0] # we do not want to reuse initialization of ResourceNotFoundError Fault.__init__( self, faultcode='Client.{0}NotFound'.format(object_name), faultstring=message) class ValidationError(BaseValidationError): """Fault constructed from `ValidationError` exception.""" def __init__(self, validation_error_exc): """Construct fault with code Client..""" message = str(validation_error_exc) # we do not want to reuse initialization of BaseValidationError Fault.__init__( self, faultcode='Client.{0}'.format( type(validation_error_exc).__name__), faultstring=message) class DjangoServiceBase(ServiceBase): """Service with common Django exception handling.""" @classmethod def call_wrapper(cls, ctx): """Handle common Django exceptions.""" try: out_object = super(DjangoServiceBase, cls).call_wrapper(ctx) except ObjectDoesNotExist as e: raise ObjectNotFoundError(e) except DjValidationError as e: raise ValidationError(e) return out_object spyne-2.12.11/spyne/util/dynint.py0000644000175000001440000000104312572316312016761 0ustar plqusers00000000000000 from spyne import Integer, ModelBase from spyne.util.cdict import cdict MAP = cdict({ ModelBase: cdict({object: lambda _:_, basestring: lambda _:_}), Integer: cdict({ int: lambda _:_, basestring: lambda s: None if s == '' else int(s), }) }) def dynamic_init(cls, **kwargs): fti = cls.get_flat_type_info(cls) retval = cls() for k, v in fti.items(): if k in kwargs: subval = kwargs[k] t = MAP[v] setattr(retval, k, t[type(subval)](subval)) return retval spyne-2.12.11/spyne/util/email.py0000644000175000001440000000523412572316312016551 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 COMMASPACE, 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 ' exc = traceback.format_exc() if exc is not None: message = (u"%s\n\n%s" % (message, exc)) msg = MIMEText(message.encode('utf8'), 'plain', 'utf8') msg['To'] = COMMASPACE.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.12.11/spyne/util/etreeconv.py0000644000175000001440000000771212572316312017457 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.12.11/spyne/util/fileproxy.py0000644000175000001440000001371512572316312017506 0ustar plqusers00000000000000# # Copyright (C) 2013-2014 by Hong Minhee # # 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 collections import os __all__ = 'FileProxy', 'ReusableFileProxy', 'SeekableFileProxy' class FileProxy(collections.Iterator): """The complete proxy for ``wrapped`` file-like object. :param wrapped: the file object to wrap :type wrapped: :class:`file`, file-like object """ def __init__(self, wrapped): self.wrapped = wrapped self.mmap = None def __iter__(self): f = self.wrapped it = getattr(f, '__iter__', None) if callable(it): return it() return self def __next__(self): """Implementation of :class:`collections.Iterator` protocol.""" line = self.readline() if not line: raise StopIteration('hit eof') return line next = __next__ def read(self, size=-1): """Reads at the most ``size`` bytes from the file. It maybe less if the read hits EOF before obtaining ``size`` bytes. :param size: bytes to read. if it is negative or omitted, read all data until EOF is reached. default is -1 :returns: read bytes. an empty string when EOF is encountered immediately :rtype: :class:`str` """ return self.wrapped.read(size) def readline(self, size=None): r"""Reads an entire line from the file. A trailing newline character is kept in the string (but maybe absent when a file ends with an incomplete line). :param size: if it's present and non-negative, it is maximum byte count (including trailing newline) and an incomplete line maybe returned :type size: :class:`numbers.Integral` :returns: read bytes :rtype: :class:`str` .. note:: Unlike ``stdio``'s :c:func:`fgets()`, the returned string contains null characters (``'\0'``) if they occurred in the input. """ return self.wrapped.readline(size) def readlines(self, sizehint=None): """Reads until EOF using :meth:`readline()`. :param sizehint: if it's present, instead of reading up to EOF, whole lines totalling approximately ``sizehint`` bytes (or more to accommodate a final whole line) :type sizehint: :class:`numbers.Integral` :returns: a list containing the lines read :rtype: :class:`list` """ wrapped = self.wrapped try: readlines = wrapped.readlines except AttributeError: lines = [] while 1: line = wrapped.readline() if line: lines.append(line) else: break return lines return readlines() if sizehint is None else readlines(sizehint) def xreadlines(self): """The same to ``iter(file)``. Use that. .. deprecated:: long time ago Use :func:`iter()` instead. """ return iter(self) def close(self): """Closes the file. It's a context manager as well, so prefer :keyword:`with` statement than direct call of this:: with FileProxy(file_) as f: print f.read() """ try: close = self.wrapped.close except AttributeError: pass else: close() def __enter__(self): return self.wrapped def __exit__(self, exc_type, value, traceback): self.close() def __del__(self): if self.mmap is not None: self.mmap.close() self.wrapped.close() def fileno(self): return self.wrapped.fileno() class SeekableFileProxy(FileProxy): """The almost same to :class:`FileProxy` except it has :meth:`seek()` and :meth:`tell()` methods in addition. """ def seek(self, offset, whence=os.SEEK_SET): """Sets the file's current position. :param offset: the offset to set :type offset: :class:`numbers.Integral` :param whence: see the docs of :meth:`file.seek()`. default is :const:`os.SEEK_SET` """ self.wrapped.seek(offset, whence) def tell(self): """Gets the file's current position. :returns: the file's current position :rtype: :class:`numbers.Integral` """ return self.wrapped.tell() class ReusableFileProxy(SeekableFileProxy): """It memorizes the current position (:meth:`tell()`) when the context enters and then rewinds (:meth:`seek()`) back to the memorized :attr:`initial_offset` when the context exits. """ def __enter__(self): self.initial_offset = self.tell() self.seek(0) return super(ReusableFileProxy, self).__enter__() def __exit__(self, exc_type, value, traceback): self.seek(self.initial_offset) spyne-2.12.11/spyne/util/http.py0000644000175000001440000000423212572316312016436 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 sys 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) assert time.time() < sys.maxint expires = time.time() + max_age expires = min(2<<30, expires) - 1 # FIXME 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.12.11/spyne/util/invregexp.py0000644000175000001440000001714012572316312017470 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.12.11/spyne/util/meta.py0000644000175000001440000001217012572316312016405 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 warnings import warn 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 try: attributes["__new__"] = wraps(constructor)(preparing_constructor) except: warn("Wrapping class initializer failed. This is normal " "when runnign under Nuitka") return type.__new__(cls, name, bases, attributes) spyne-2.12.11/spyne/util/odict.py0000644000175000001440000000733112573332574016575 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 an ordered dictionary implementation. We need this in Python 2.7 because collections.OrderedDict does not support reordering by assignment to keys(). We need this in Python 3.x because keys() returns KeyView which which doesn't support `__getitem__` -- i.e. getting nth variable from the ordered dict. """ 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.12.11/spyne/util/protocol.py0000644000175000001440000000243312572316312017321 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, 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.12.11/spyne/util/simple.py0000644000175000001440000000413612572316312016753 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.12.11/spyne/util/six.py0000644000175000001440000007211512572316312016267 0ustar plqusers00000000000000"""Utilities for writing code that runs on Python 2 and 3""" # Copyright (c) 2010-2015 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. from __future__ import absolute_import import functools import itertools import operator import sys import types __author__ = "Benjamin Peterson " __version__ = "1.9.0" # 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): result = self._resolve() setattr(obj, self.name, result) # Invokes __set__. try: # This is a bit ugly, but it avoids running this again by # removing this descriptor. delattr(obj.__class__, self.name) except AttributeError: pass 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): _module = self._resolve() 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 _SixMetaPathImporter(object): """ A meta path importer to import six.moves and its submodules. This class implements a PEP302 finder and loader. It should be compatible with Python 2.5 and all existing versions of Python3 """ def __init__(self, six_module_name): self.name = six_module_name self.known_modules = {} def _add_module(self, mod, *fullnames): for fullname in fullnames: self.known_modules[self.name + "." + fullname] = mod def _get_module(self, fullname): return self.known_modules[self.name + "." + fullname] def find_module(self, fullname, path=None): if fullname in self.known_modules: return self return None def __get_module(self, fullname): try: return self.known_modules[fullname] except KeyError: raise ImportError("This loader does not know module " + fullname) def load_module(self, fullname): try: # in case of a reload return sys.modules[fullname] except KeyError: pass mod = self.__get_module(fullname) if isinstance(mod, MovedModule): mod = mod._resolve() else: mod.__loader__ = self sys.modules[fullname] = mod return mod def is_package(self, fullname): """ Return true, if the named module is a package. We need this method to get correct spec objects with Python 3.4 (see PEP451) """ return hasattr(self.__get_module(fullname), "__path__") def get_code(self, fullname): """Return None Required, if is_package is implemented""" self.__get_module(fullname) # eventually raises ImportError return None get_source = get_code # same as get_code _importer = _SixMetaPathImporter(__name__) class _MovedItems(_LazyModule): """Lazy loading of moved objects""" __path__ = [] # mark as package _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("intern", "__builtin__", "sys"), MovedAttribute("map", "itertools", "builtins", "imap", "map"), MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), MovedAttribute("reload_module", "__builtin__", "imp", "reload"), MovedAttribute("reduce", "__builtin__", "functools"), MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), MovedAttribute("StringIO", "StringIO", "io"), MovedAttribute("UserDict", "UserDict", "collections"), MovedAttribute("UserList", "UserList", "collections"), 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("_dummy_thread", "dummy_thread", "_dummy_thread"), 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_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), 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", "SimpleXMLRPCServer", "xmlrpc.server"), MovedModule("winreg", "_winreg"), ] for attr in _moved_attributes: setattr(_MovedItems, attr.name, attr) if isinstance(attr, MovedModule): _importer._add_module(attr, "moves." + attr.name) del attr _MovedItems._moved_attributes = _moved_attributes moves = _MovedItems(__name__ + ".moves") _importer._add_module(moves, "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("splittag", "urllib", "urllib.parse"), MovedAttribute("splituser", "urllib", "urllib.parse"), MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), MovedAttribute("uses_params", "urlparse", "urllib.parse"), MovedAttribute("uses_query", "urlparse", "urllib.parse"), MovedAttribute("uses_relative", "urlparse", "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 _importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), "moves.urllib_parse", "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 _importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), "moves.urllib_error", "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 _importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), "moves.urllib_request", "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 _importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), "moves.urllib_response", "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 _importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), "moves.urllib_robotparser", "moves.urllib.robotparser") class Module_six_moves_urllib(types.ModuleType): """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" __path__ = [] # mark as package parse = _importer._get_module("moves.urllib_parse") error = _importer._get_module("moves.urllib_error") request = _importer._get_module("moves.urllib_request") response = _importer._get_module("moves.urllib_response") robotparser = _importer._get_module("moves.urllib_robotparser") def __dir__(self): return ['parse', 'error', 'request', 'response', 'robotparser'] _importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), "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__" _func_name = "__name__" 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" _func_name = "func_name" 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) get_function_name = operator.attrgetter(_func_name) if PY3: def iterkeys(d, **kw): return iter(d.keys(**kw)) def itervalues(d, **kw): return iter(d.values(**kw)) def iteritems(d, **kw): return iter(d.items(**kw)) def iterlists(d, **kw): return iter(d.lists(**kw)) viewkeys = operator.methodcaller("keys") viewvalues = operator.methodcaller("values") viewitems = operator.methodcaller("items") else: def iterkeys(d, **kw): return iter(d.iterkeys(**kw)) def itervalues(d, **kw): return iter(d.itervalues(**kw)) def iteritems(d, **kw): return iter(d.iteritems(**kw)) def iterlists(d, **kw): return iter(d.iterlists(**kw)) viewkeys = operator.methodcaller("viewkeys") viewvalues = operator.methodcaller("viewvalues") viewitems = operator.methodcaller("viewitems") _add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") _add_doc(itervalues, "Return an iterator over the values of a dictionary.") _add_doc(iteritems, "Return an iterator over the (key, value) pairs of a dictionary.") _add_doc(iterlists, "Return an iterator over the (key, [values]) pairs of a dictionary.") 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 _assertCountEqual = "assertCountEqual" _assertRaisesRegex = "assertRaisesRegex" _assertRegex = "assertRegex" 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]) iterbytes = functools.partial(itertools.imap, ord) import StringIO StringIO = BytesIO = StringIO.StringIO _assertCountEqual = "assertItemsEqual" _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" _add_doc(b, """Byte literal""") _add_doc(u, """Text literal""") def assertCountEqual(self, *args, **kwargs): return getattr(self, _assertCountEqual)(*args, **kwargs) def assertRaisesRegex(self, *args, **kwargs): return getattr(self, _assertRaisesRegex)(*args, **kwargs) def assertRegex(self, *args, **kwargs): return getattr(self, _assertRegex)(*args, **kwargs) if PY3: exec_ = getattr(moves.builtins, "exec") def reraise(tp, value, tb=None): if value is None: value = tp() 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 """) if sys.version_info[:2] == (3, 2): exec_("""def raise_from(value, from_value): if from_value is None: raise value raise value from from_value """) elif sys.version_info[:2] > (3, 2): exec_("""def raise_from(value, from_value): raise value from from_value """) else: def raise_from(value, from_value): raise value 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) if sys.version_info[:2] < (3, 3): _print = print_ def print_(*args, **kwargs): fp = kwargs.get("file", sys.stdout) flush = kwargs.pop("flush", False) _print(*args, **kwargs) if flush and fp is not None: fp.flush() _add_doc(reraise, """Reraise an exception.""") if sys.version_info[0:2] < (3, 4): def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES): def wrapper(f): f = functools.wraps(wrapped, assigned, updated)(f) f.__wrapped__ = wrapped return f return wrapper else: wraps = functools.wraps def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" # This requires a bit of explanation: the basic idea is to make a dummy # metaclass for one level of class instantiation that replaces itself with # the actual metaclass. class metaclass(meta): def __new__(cls, name, this_bases, d): return meta(name, bases, d) return type.__new__(metaclass, 'temporary_class', (), {}) def add_metaclass(metaclass): """Class decorator for creating a class with a metaclass.""" def wrapper(cls): orig_vars = cls.__dict__.copy() 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) orig_vars.pop('__dict__', None) orig_vars.pop('__weakref__', None) return metaclass(cls.__name__, cls.__bases__, orig_vars) return wrapper def python_2_unicode_compatible(klass): """ A decorator that defines __unicode__ and __str__ methods under Python 2. Under Python 3 it does nothing. To support Python 2 and 3 with a single code base, define a __str__ method returning text and apply this decorator to the class. """ if PY2: if '__str__' not in klass.__dict__: raise ValueError("@python_2_unicode_compatible cannot be applied " "to %s because it doesn't define __str__()." % klass.__name__) klass.__unicode__ = klass.__str__ klass.__str__ = lambda self: self.__unicode__().encode('utf-8') return klass # Complete the moves implementation. # This code is at the end of this module to speed up module loading. # Turn this module into a package. __path__ = [] # required for PEP 302 and PEP 451 __package__ = __name__ # see PEP 366 @ReservedAssignment if globals().get("__spec__") is not None: __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable # Remove other six meta path importers, since they cause problems. This can # happen if six is removed from sys.modules and then reloaded. (Setuptools does # this for some reason.) if sys.meta_path: for i, importer in enumerate(sys.meta_path): # Here's some real nastiness: Another "instance" of the six module might # be floating around. Therefore, we can't use isinstance() to check for # the six meta path importer, since the other six instance will have # inserted an importer with different class. if (type(importer).__name__ == "_SixMetaPathImporter" and importer.name == __name__): del sys.meta_path[i] break del i, importer # Finally, add the importer to the meta path import hook. sys.meta_path.append(_importer) spyne-2.12.11/spyne/util/tdict.py0000644000175000001440000000545512572316312016576 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 typed dict module""" from itertools import chain class tdict(dict): def __init__(self, kt=None, vt=None, data=None): """This is a typed dict implementation that optionally enforces given types on contained values on assignment.""" self._kt = kt self._vt = vt if kt is None and vt is None: self.check = self._check_noop elif kt is None: self.check = self._check_v elif vt is None: self.check = self._check_k else: self.check = self._check_kv if data is not None: self.update(data) def _check_noop(self, *_): pass def _check_k(self, key, _): if not isinstance(key, self._kt): raise TypeError(repr(key)) def _check_v(self, _, value): if not isinstance(value, self._vt): raise TypeError(repr(value)) def _check_kv(self, key, value): if not isinstance(key, self._kt): raise TypeError(repr(key)) if not isinstance(value, self._vt): raise TypeError(repr(value)) def __setitem__(self, key, value): self.check(key, value) super(tdict, self).__setitem__(key, value) def update(self, E=None, **F): try: it = chain(E.items(), F.items()) except AttributeError: it = chain(E, F) for k, v in it: self[k] = v def setdefault(self, k, d=None): self._check_k(k, d) if self._kt is None else None self._check_v(k, d) if self._vt is None else None super(tdict, self).setdefault(k, d) @classmethod def fromkeys(cls, S, v=None): kt = vt = None if len(S) > 0: kt, = set((type(s) for s in S)) if v is not None: vt = type(v) retval = tdict(kt, vt) for s in S: retval[s] = v return retval def repr(self): return "tdict(kt=%s, vt=%s, data=%s)" % \ (self._kt, self._vt, super(tdict, self).__repr__()) spyne-2.12.11/spyne/util/test.py0000644000175000001440000000505512574047543016453 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, getcwd from os.path import join, basename def show(elt, tn=None, stdout=True): if tn is None: import inspect for frame in inspect.stack(): if frame[3].startswith("test_"): cn = frame[0].f_locals['self'].__class__.__name__ tn = "%s.%s" % (cn, frame[3]) break else: raise Exception("don't be lazy and pass test name.") from lxml import html, etree out_string = etree.tostring(elt, pretty_print=True) if stdout: print(out_string) fn = '%s.html' % tn if basename(getcwd()) != 'test_html': try: mkdir('test_html') except OSError: pass f = open(join("test_html", fn), 'w') else: f = open(fn, 'w') f.write(html.tostring(elt, pretty_print=True, doctype="")) spyne-2.12.11/spyne/util/toposort.py0000644000175000001440000000403012572316312017344 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. from pprint import pformat try: from functools import reduce except: pass def toposort2(data): if len(data) == 0: raise StopIteration() 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\n%s" % pformat(data) spyne-2.12.11/spyne/util/web.py0000644000175000001440000001777412600455063016252 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 # """ Some code dump from some time ago. If you're using this for anything serious, you're insane. """ from __future__ import absolute_import from inspect import isclass from sqlalchemy.orm import sessionmaker from sqlalchemy.orm.exc import NoResultFound from spyne import BODY_STYLE_WRAPPED, rpc, Any, AnyDict, NATIVE_MAP from spyne.util import six 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.store.relational import PGFileJson EXCEPTION_ADDRESS = None try: 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 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 cls in (list, tuple): cls = Array(Any) if cls is dict: cls = AnyDict if cls in NATIVE_MAP: cls = NATIVE_MAP[cls] 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, File) and isinstance(obj, File.Value): cls = obj.__class__ if cls.Attributes.logged == 'len': l = '?' try: if isinstance(obj, (list, tuple)): l = str(sum([len(o) for o in obj])) else: l = str(len(obj)) except TypeError: if given_len is not None: l = str(given_len) return "" % l if issubclass(cls, Array): cls, = cls._type_info.values() if (cls.Attributes.max_occurs > 1) and not from_array: if id(obj) in tags: return "%s(...)" % obj.__class__.__name__ tags.add(id(obj)) retval = [] subcls = cls if issubclass(cls, Array): subcls, = cls._type_info.values() if isinstance(obj, PushBase): retval = '[]' else: for i, o in enumerate(obj): if i >= MAX_ARRAY_ELEMENT_NUM: retval.append("(...)") break retval.append(log_repr(o, subcls, from_array=True, tags=tags)) 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 not t.Attributes.logged: continue try: v = getattr(obj, k, None) except (AttributeError, KeyError): v = None # HACK!: sometimes non-db attributes restored from database don't # get properly reinitialized. if isclass(v) and issubclass(v, ModelBase): continue polymap = t.Attributes.polymap if polymap is not None: t = polymap.get(v.__class__, t) 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.12.11/spyne/util/wsgi_wrapper.py0000644000175000001440000001026012572316312020166 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.12.11/spyne/util/xml.py0000644000175000001440000001512512572316312016262 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), skip_errors=False): """Parses a schema string and returns a _Schema object. :param s: The string or bytes object that contains the schema document. :param files: A dict that maps namespaces to path to schema files that contain the schema document for those namespaces. :param repr_: A callable that functions as `repr`. :param skip_errors: Skip parsing errors and return a partial schema. See debug log for details. :return: :class:`spyne.interface.xml_schema.parser._Schema` instance. """ elt = etree.fromstring(s, parser=PARSER) return XmlSchemaParser(files, repr_=repr_, skip_errors=skip_errors).parse_schema(elt) def parse_schema_element(elt, files={}, repr_=Thier_repr(with_ns=False), skip_errors=False): """Parses a `` element and returns a _Schema object. :param elt: The `` element, an lxml.etree._Element instance. :param files: A dict that maps namespaces to path to schema files that contain the schema document for those namespaces. :param repr_: A callable that functions as `repr`. :param skip_errors: Skip parsing errors and return a partial schema. See debug log for details. :return: :class:`spyne.interface.xml_schema.parser._Schema` instance. """ return XmlSchemaParser(files, repr_=repr_, skip_errors=skip_errors).parse_schema(elt) def parse_schema_file(file_name, files={}, repr_=Thier_repr(with_ns=False), skip_errors=False): """Parses a schema file and returns a _Schema object. Schema files typically have the `*.xsd` extension. :param file_name: The path to the file that contains the schema document to be parsed. :param files: A dict that maps namespaces to path to schema files that contain the schema document for those namespaces. :param repr_: A callable that functions as `repr`. :param skip_errors: Skip parsing errors and return a partial schema. See debug log for details. :return: :class:`spyne.interface.xml_schema.parser._Schema` instance. """ elt = etree.fromstring(open(file_name, 'rb').read(), parser=PARSER) wd = abspath(dirname(file_name)) return XmlSchemaParser(files, wd, repr_=repr_, skip_errors=skip_errors).parse_schema(elt) spyne-2.12.11/spyne/__init__.py0000644000175000001440000000452212606426413016245 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.12.11' from pytz import utc as LOCAL_TZ from decimal import Decimal as D DEFAULT_LANGUAGE = 'en' from spyne._base import BODY_STYLE_WRAPPED from spyne._base import BODY_STYLE_BARE from spyne._base import BODY_STYLE_OUT_BARE from spyne._base import BODY_STYLE_EMPTY from spyne._base import AuxMethodContext from spyne._base import TransportContext from spyne._base import ProtocolContext from spyne._base import EventContext from spyne._base import MethodContext from spyne._base import MethodDescriptor from spyne._base import EventManager from spyne._base import Address 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 * from spyne.model import Mandatory as M from spyne.error import InvalidCredentialsError from spyne.error import RequestTooLongError from spyne.error import RequestNotAllowed from spyne.error import ArgumentError from spyne.error import InvalidInputError from spyne.error import ValidationError from spyne.error import InternalError from spyne.error import ResourceNotFoundError from spyne.error import RespawnError from spyne.error import ResourceAlreadyExistsError from spyne.error import Redirect from spyne.client import ClientBase, RemoteProcedureBase, RemoteService from spyne.server import ServerBase, NullServer 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.12.11/spyne/_base.py0000644000175000001440000005541512615177750015575 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 gc, logging logger = logging.getLogger(__name__) from time import time from copy import copy from collections import deque, namedtuple, defaultdict 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 BODY_STYLE_OUT_BARE: pass # When spyne.server.twisted gets imported, this type gets a static method named # `from_twisted_address`. Dark magic. Address = namedtuple("Address", ["type", "host", "port"]) class _add_address_types(): Address.TCP4 = 'TCP4' Address.TCP6 = 'TCP6' Address.UDP4 = 'UDP4' Address.UDP6 = 'UDP6' def address_str(self): return ":".join((self.type, self.host, str(self.port))) Address.__str__ = address_str 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""" self.remote_addr = None """The address of the other end of the connection.""" self.sessid = '' """The session id.""" class ProtocolContext(object): """Generic object that holds protocol-specific context information""" def __init__(self, parent, transport, type=None): self.parent = parent """The MethodContext this object belongs to""" self.itself = transport """The protocol itself as passed to the `Application` init. This is a `ProtocolBase` instance.""" self.type = type """The protocol the transport uses.""" self._subctx = defaultdict( lambda: self.__class__(parent, transport, type)) def __getitem__(self, item): return self._subctx[item] 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. """ SERVER = type("SERVER", (object,), {}) CLIENT = type("CLIENT", (object,), {}) frozen = False def copy(self): retval = copy(self) if retval.transport is not None: retval.transport.parent = retval if retval.inprot_ctx is not None: retval.inprot_ctx.parent = retval if retval.outprot_ctx is not None: retval.outprot_ctx.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, way): # 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.is_closed = False 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.outprot_ctx = None """The output-protocol-specific context. Protocol implementors can use this to their liking.""" if self.app.out_protocol is not None: self.outprot_ctx = self.app.out_protocol.get_context(self, transport) self.inprot_ctx = None """The input-protocol-specific context. Protocol implementors can use this to their liking.""" if self.app.in_protocol is not None: self.inprot_ctx = self.app.in_protocol.get_context(self, transport) self.protocol = None """The protocol-specific context. This points to the in_protocol when an incoming message is being processed and out_protocol when an outgoing message is being processed.""" if way is MethodContext.SERVER: self.protocol = self.inprot_ctx elif way is MethodContext.CLIENT: self.protocol = self.outprot_ctx else: raise ValueError(way) 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.files = [] """List of stuff to be closed when closing this context. Anything that has a close() callable can go in.""" 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) for f in self.files: f.close() self.is_closed = True # this is important to have file descriptors returned in a timely manner gc.collect() 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 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, *args, **kwargs): """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, *args, **kwargs) class FakeContext(object): def __init__(self, app=None, descriptor=None, in_object=None, in_error=None, in_document=None, in_string=None, out_object=None, out_error=None, out_document=None, out_string=None, in_protocol=None, out_protocol=None): self.app = app self.descriptor = descriptor self.in_object = in_object self.in_error = in_error self.in_document = in_document self.in_string = in_string self.out_error = out_error self.out_object = out_object self.out_document = out_document self.out_string = out_string self.in_protocol = in_protocol self.out_protocol = out_protocol if self.in_protocol is not None: self.inprot_ctx = self.in_protocol.get_context(self, None) else: self.inprot_ctx = type("ProtocolContext", (object,), {})() from spyne.protocol.html._base import HtmlClothProtocolContext if self.out_protocol is not None: self.outprot_ctx = self.out_protocol.get_context(self, None) else: # The outprot_ctx here must contain properties from ALL tested # protocols' context objects. That's why we use # HtmlClothProtocolContext here, it's just the one with most # attributes. self.outprot_ctx = HtmlClothProtocolContext(self, None) self.protocol = self.outprot_ctx self.transport = type("ProtocolContext", (object,), {})() spyne-2.12.11/spyne/application.py0000644000175000001440000002637412615200042017006 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'])) logger_server = logging.getLogger('.'.join([__name__, 'server'])) from spyne import BODY_STYLE_EMPTY from spyne import BODY_STYLE_BARE from spyne import BODY_STYLE_WRAPPED from spyne.error import Fault, Redirect 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 config: An arbitrary python object to store random global data. Supported events: * ``method_call``: Called right before the service method is executed * ``method_return_object``: Called right after the service method is executed * ``method_exception_object``: Called when an exception occurred in a service method, before the exception is serialized. * ``method_context_created``: Called from the constructor of the MethodContext instance. * ``method_context_closed``: Called from the ``close()`` function of the MethodContext instance, which in turn is called by the transport when the response is fully sent to the client (or in the client case, the response is fully received from server). """ transport = None def __init__(self, services, tns, name=None, in_protocol=None, out_protocol=None, config=None): self.services = tuple(services) self.tns = tns self.name = name self.config = config 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) 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) # in object is always a sequence of incoming values. We need to fix # that for bare mode. if ctx.descriptor.body_style is BODY_STYLE_BARE: ctx.in_object = [ctx.in_object] elif ctx.descriptor.body_style is BODY_STYLE_EMPTY: ctx.in_object = [] # call user method ctx.out_object = self.call_wrapper(ctx) # out object is always a sequence of return values. see # MethodContext docstrings for more info if ctx.descriptor.body_style is not BODY_STYLE_WRAPPED or \ len(ctx.descriptor.out_message._type_info) <= 1: # if it's not a wrapped method, OR there's just one return type # we wrap it ourselves ctx.out_object = [ctx.out_object] # Now that the processing is switched to the outgoing message, # point ctx.protocol to ctx.out_protocol ctx.protocol = ctx.outprot_ctx # 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 Redirect as e: try: e.do_redirect() ctx.out_object = [None] # Now that the processing is switched to the outgoing message, # point ctx.protocol to ctx.out_protocol ctx.protocol = ctx.outprot_ctx # fire events self.event_manager.fire_event('method_redirect', ctx) if ctx.service_class is not None: ctx.service_class.event_manager.fire_event( 'method_redirect', ctx) except Exception as e: logger_server.exception(e) ctx.out_error = Fault('Server', get_fault_string_from_exception(e)) # fire events self.event_manager.fire_event('method_redirect_exception', ctx) if ctx.service_class is not None: ctx.service_class.event_manager.fire_event( 'method_redirect_exception', 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) # we don't catch BaseException because we're not interested in # "system-exiting" exceptions. See: # https://docs.python.org/2/library/exceptions.html#exceptions.Exception except Exception as e: logger_server.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. """ 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, server): """This is normally called on transport instantiation by ServerBase""" seen = set() from spyne import MethodDescriptor for d in self.interface.method_id_map.values(): assert isinstance(d, MethodDescriptor) if d.aux is not None and not id(d.aux) in seen: d.aux.initialize(server) seen.add(id(d.aux)) if d.service_class is not None and not id(d.service_class) in seen: d.service_class.initialize(server) seen.add(id(d.service_class)) def __hash__(self): return hash(tuple((id(s) for s in self.services))) spyne-2.12.11/spyne/decorator.py0000644000175000001440000004047612572316312016476 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 inspect import isclass 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._base import BODY_STYLE_OUT_BARE from spyne.model import ModelBase, ComplexModel from spyne.model.complex import TypeInfo from spyne.const import add_request_suffix def _produce_input_message(f, params, in_message_name, in_variable_names, no_ctx, no_self, args, body_style_str): 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_str == '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) from spyne.model import ComplexModelBase if issubclass(message, ComplexModelBase) and not message._type_info: raise Exception("body_style='bare' does not allow empty " "model as param") # there can't be multiple arguments here. if message.__type_name__ is ModelBase.Empty: message._fill_empty_type_name(ns, in_message_name, "%s_arg0" % in_message_name) 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.pop('_body_style', None) _soap_body_style = kparams.pop('_soap_body_style', None) allowed_body_styles = ('wrapped', 'bare', 'out_bare') if _body_style is None: _body_style = 'wrapped' elif not (_body_style in allowed_body_styles): raise ValueError("body_style must be one of %r" % allowed_body_styles) 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', 'out_bare') return _body_style def _produce_output_message(func_name, body_style_str, kparams): """Generate an output message for "rpc"-style API methods. This message is a wrapper to the declared return type. """ _returns = kparams.pop('_returns', None) _out_message_name = kparams.pop('_out_message_name', '%s%s' % (func_name, spyne.const.RESPONSE_SUFFIX)) out_params = TypeInfo() if _returns and body_style_str == '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.pop('_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.pop('_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_str.endswith('bare') and _returns is not None: message = _returns.customize(sub_name=_out_message_name, sub_ns=ns) if message.__type_name__ is ModelBase.Empty: message.__type_name__ = _out_message_name 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 _substitute_self_reference(params, kparams, kwargs, _no_self): from spyne.model import SelfReference for i, v in enumerate(params): if isclass(v) and issubclass(v, SelfReference): if _no_self: raise ValueError("SelfReference can't be used in @rpc") else: params[i] = kwargs['_self_ref_replacement'] else: params[i] = v for k, v in kparams.items(): if isclass(v) and issubclass(v, SelfReference): if _no_self: raise ValueError("SelfReference can't be used in @rpc") else: kparams[k] = kwargs['_self_ref_replacement'] else: kparams[k] = v 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. """ params = list(params) def explain(f): def explain_method(**kwargs): function_name = kwargs['_default_function_name'] # this block is passed straight to the descriptor _is_callback = kparams.pop('_is_callback', False) _is_async = kparams.pop('_is_async', False) _mtom = kparams.pop('_mtom', False) _in_header = kparams.pop('_in_header', None) _out_header = kparams.pop('_out_header', None) _port_type = kparams.pop('_soap_port_type', None) _no_ctx = kparams.pop('_no_ctx', False) _no_self = kparams.pop('_no_self', True) _udp = kparams.pop('_udp', None) _aux = kparams.pop('_aux', None) _pattern = kparams.pop("_pattern", None) _patterns = kparams.pop("_patterns", []) _args = kparams.pop("_args", None) _translations = kparams.pop("_translations", None) _when = kparams.pop("_when", None) _service_class = kparams.pop("_service_class", None) _href = kparams.pop("_href", None) _substitute_self_reference(params, kparams, kwargs, _no_self) _faults = None if ('_faults' in kparams) and ('_throws' in kparams): raise ValueError("only one of '_throws ' or '_faults' arguments" "must be given -- they're synonyms.") elif '_faults' in kparams: _faults = kparams.pop('_faults') elif '_throws' in kparams: _faults = kparams.pop('_throws') _in_message_name_override = not ('_in_message_name' in kparams) _in_message_name = kparams.pop('_in_message_name', function_name) _operation_name = kparams.pop('_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.pop('_in_variable_names', {}) body_style = BODY_STYLE_WRAPPED body_style_str = _validate_body_style(kparams) if body_style_str.endswith('bare'): if body_style_str == 'out_bare': body_style = BODY_STYLE_OUT_BARE else: body_style = BODY_STYLE_BARE in_message = _produce_input_message(f, params, _in_message_name, _in_variable_names, _no_ctx, _no_self, _args, body_style_str) _out_message_name_override = not ('_out_message_name' in kparams) out_message = _produce_output_message(function_name, body_style_str, 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] if body_style_str.endswith('bare'): from spyne.model import ComplexModelBase t = in_message 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 and _no_self: for p in _patterns: p.hello(retval) if len(kparams) > 0: raise Exception("Unknown kwarg(s) %r passed.", kparams) 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.12.11/spyne/error.py0000644000175000001440000001003312572316312015627 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) class Redirect(Fault): def __init__(self, ctx, location, orig_exc=None): super(Redirect, self).__init__('Client.MustBeRedirected', faultstring=location) self.ctx = ctx self.location= location self.orig_exc = orig_exc def do_redirect(self): raise NotImplementedError() spyne-2.12.11/spyne/service.py0000644000175000001440000001606012572316472016153 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__) import collections from spyne import EventManager from spyne.util import six 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)) try: getattr(self, k).descriptor = descriptor except AttributeError as e: pass # FIXME: this fails with builtins. Temporary hack while we # investigate whether we really need this or not 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 @six.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. This class is designed to be subclassed just once. You're supposed to combine ServiceBase subclasses in order to get the public method mix you want. 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. """ __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, app): return '{%s}%s' % (app.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 _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: args = ctx.in_object # python3 wants a proper sequence as *args assert not isinstance(args, six.string_types) if not isinstance(args, collections.Sequence): args = tuple(args) if ctx.descriptor.no_ctx: return ctx.function(*args) else: return ctx.function(ctx, *args) @classmethod def initialize(cls, app): pass spyne-2.12.11/spyne.egg-info/0000755000175000001440000000000012615200103015605 5ustar plqusers00000000000000spyne-2.12.11/spyne.egg-info/PKG-INFO0000644000175000001440000003753512615200065016726 0ustar plqusers00000000000000Metadata-Version: 1.1 Name: spyne Version: 2.12.11 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: Homepage: http://spyne.io 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.12.11 ------------- * Fix self-referential relationships pointing the wrong way * Fix wrong use of string interpolation operator in logging call slowing Spyne down for no visible reason * Detect and prevent name clashes between the foreign key column name and the object itself. * Silence a lot of (wrong) customized class instantiation warnings. spyne-2.12.10 ------------- * IpAddress types now support PostgreSQL's PGInet. * Drop trial for twisted tests and switch to pytest-twisted. * ``_safe_set`` now returns True on success so that protocols can react accordingly. * \*DictDoc now logs properly whether a value is discarded or passed to the deserialized instance. * Minor bug fixes here and there. spyne-2.12.9 ------------ * Make ``DateTime`` handle unicode date format strings for Python 2. * Fix idle timer not starting on connectionMade for ``MessagePackTransportBase`` spyne-2.12.7 ------------ * Not beta anymore. Woo! * Made ServiceBase subclasses reusable * Implemented class customization via ``__getitem__``\. * Fixed an ``ImportError`` running Python 3.4 under Pydev using PyCharm. (Eclipse still has issues, see `issue #432 `_. Any help would be much appreciated) * Fixed DateTime corner case with μs values between 999995 and 999999. * Help misguided user code that returns an int for a string type by implicitly yet not-so-silently converting the ``int``/``long`` to ``str``\. * Fixed \*Cloth sometimes getting stuck ``repr()``\'ing passed instance. * Fixed ``SimpleDictDocument`` confusing a missing value and an empty value for array types. When the client wants to denote an empty array, it should pass ``array_field=empty``\. Normally it passes something along the lines of: ``array_field[0]=Something&array_field[1]=SomethingElse``\. * Split ``MessagePackServerBase`` to ``MessagePackTransportBase`` and ``MessagePackServerBase``\. No API was harmed during this change. * Implement optional idle timeout for ``MessagePackTransportBase``\. * Add support for PGObjectJson, PGObjectXml and PGFileJson to sql table reflection. * ``log_repr`` now consults ``NATIVE_MAP`` as a last resort before freaking out. * Removed some dead code. spyne-2.12.6-beta ----------------- * Thanks to `issue #446 `_ we noticed that in some cases, SOAP messages inside HTTP requests got processed even when the request method != 'POST'. This got resolved, but you should check whether this is the case in your setup and take the necessary precautions before deploying Spyne. spyne-2.12.[12345]-beta ----------------------- * Many bugs fixed very quick. spyne-2.12.0-beta ----------------- * XmlObject: Support for ``attribute_of`` is removed. * NullServer now supports async. * XmlCloth was rewritten while less sleep-deprived. * ProtocolBase now also implements serializing primitives to unicode. * Add initial support for input polymorphism to XmlDocument (parsing xsi:type). It's an experimental feature. * Add output polymorphism for all protocols. It's off-by-default for XmlDocument and friends, on-by-default for others. * Add stub implementation for SOAP 1.2 * Add initial implementation for SOAP 1.2 Faults. * Remove the deprecated ``interface`` argument to ``Application``\. * HierDictDocument's broken wrapped dict support was fixed. Even though this is supposed to break compatibility with 2.11, virtually no one seems to use this feature. Only now it's mature enough to be set on stone. Let us know! * We now validate kwargs passed to ``@rpc``\. Be sure to test your daemons before deploying for production, because if you got leftovers, the server will refuse to boot! * It's now forbidden (by assert) to inherit from a customized class. * It's also forbidden (by convention) to instantiate a customized class. Don't do it! The warning will be converted to an assert in the future. 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.Value instance. * Implement an Xml Schema parser. * Import all model markers as well as the ``@rpc``\, ``@srpc``\, ``@mrpc``, ``ServiceBase`` and ``Application`` to the root ``spyne`` package. * 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 from Twisted. * Support Django natively using `spyne.server.django.DjangoView` and `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 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 `spyne.util.django.DjangoComplexModel` types. * Spyne now tracks subclasses and adds them to the interface if they are in the same namespace as their parent. * Simple dictionary protocol's ``hier_delim`` default value is now '.' * Fixes support for XmlAttributes with max_occurs>1 referencing the same 'attribute_of' element in a ComplexModel subclass. * Renders ``spyne.model.File`` as ``twisted.web.static.File`` when using HttpRpc over ``TwistedWebResource``. This lets twisted handle Http 1.1-specific functionality like range requests. * 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_timezone 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. Check the documentation at http://spyne.io/docs for changelogs of the older versions 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.12.11/spyne.egg-info/SOURCES.txt0000644000175000001440000001434212615200065017504 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.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/table.py spyne/model/primitive/__init__.py spyne/model/primitive/_base.py spyne/model/primitive/datetime.py spyne/model/primitive/network.py spyne/model/primitive/number.py spyne/model/primitive/spatial.py spyne/model/primitive/string.py spyne/model/primitive/xml.py spyne/protocol/__init__.py spyne/protocol/_base.py spyne/protocol/_inbase.py spyne/protocol/_outbase.py spyne/protocol/csv.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/dictdoc/__init__.py spyne/protocol/dictdoc/_base.py spyne/protocol/dictdoc/hier.py spyne/protocol/dictdoc/simple.py spyne/protocol/html/__init__.py spyne/protocol/html/_base.py spyne/protocol/html/microformat.py spyne/protocol/html/pretty.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/protocol/soap/soap12.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/store/__init__.py spyne/store/relational/__init__.py spyne/store/relational/_base.py spyne/store/relational/document.py spyne/store/relational/simple.py spyne/store/relational/spatial.py spyne/store/relational/util.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_msgpackrpc_client_http.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/msgpackrpc_http_basic.py spyne/test/interop/server/soap11/__init__.py spyne/test/interop/server/soap11/httprpc_soap_basic.py spyne/test/interop/server/soap11/soap_http_basic.py spyne/test/interop/server/soap11/soap_http_basic_twisted.py spyne/test/interop/server/soap11/soap_http_static.py spyne/test/interop/server/soap11/soap_zeromq.py spyne/test/interop/server/soap12/__init__.py spyne/test/interop/server/soap12/httprpc_soap_basic.py spyne/test/interop/server/soap12/soap_http_basic.py spyne/test/interop/server/soap12/soap_http_basic_twisted.py spyne/test/interop/server/soap12/soap_http_static.py spyne/test/interop/server/soap12/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_cloth.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_soap11.py spyne/test/protocol/test_soap12.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/cherry.py spyne/util/color.py spyne/util/dictdoc.py spyne/util/django.py spyne/util/dynint.py spyne/util/email.py spyne/util/etreeconv.py spyne/util/fileproxy.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/tdict.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.12.11/spyne.egg-info/dependency_links.txt0000644000175000001440000000000112615200065021662 0ustar plqusers00000000000000 spyne-2.12.11/spyne.egg-info/entry_points.txt0000644000175000001440000000007112615200065021110 0ustar plqusers00000000000000[console_scripts] sort_wsdl = spyne.test.sort_wsdl:main spyne-2.12.11/spyne.egg-info/not-zip-safe0000644000175000001440000000000112600264204020041 0ustar plqusers00000000000000 spyne-2.12.11/spyne.egg-info/requires.txt0000644000175000001440000000000512615200065020207 0ustar plqusers00000000000000pytz spyne-2.12.11/spyne.egg-info/top_level.txt0000644000175000001440000000000612615200065020342 0ustar plqusers00000000000000spyne spyne-2.12.11/README.rst0000644000175000001440000001364412615200042014456 0ustar plqusers00000000000000.. image:: https://travis-ci.org/arskom/spyne.png?branch=master :target: http://travis-ci.org/arskom/spyne .. image:: https://landscape.io/github/arskom/spyne/master/landscape.svg :target: https://landscape.io/github/arskom/spyne/master :alt: Code Health If you like and use Spyne, star it on `Github `_! 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. 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 at spyne dot 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.4.1 `_ 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``\. * `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 first need to have package manager (pip, easy_install) installed. Spyne ships with a setuptools bootstrapper, so if setup.py refuses to run because it can't find setuptools, do: bin/distribute_setup.py You can add append --user to get it installed with $HOME/.local prefix. 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/pycharm/docs/logo_pycharm.png :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.12.11/setup.py0000755000175000001440000002335512605464654014530 0ustar plqusers00000000000000#!/usr/bin/env python #encoding: utf8 from __future__ import print_function import os import re import sys import inspect from glob import glob from itertools import chain from os.path import join, dirname, abspath 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 = '' IS_PYPY = '__pypy__' in sys.builtin_module_names 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 = """Homepage: http://spyne.io 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 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 if os.path.isfile(report_name): os.unlink(report_name) tests_dir = os.path.dirname(spyne.test.__file__) args = ['--twisted', '--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) 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() 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") ret = 0 tests = ['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', 'interop/test_pyramid.py', 'interop/test_soap_client_http_twisted.py', 'transport/test_msgpack.py'] ret = call_pytest(*tests,capture=self.capture) 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 # excluding PyPy as it brokes here on LXML if not IS_PYPY: ret = call_pytest_subprocess('interop/test_suds.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 RunPython3Tests(TestCommand): """Run tests compatible with different python implementations. """ def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): file_name = 'test_result_py3.xml' ret = run_tests_and_create_report(file_name, 'multipython', 'model/test_enum.py', 'model/test_exception.py', 'model/test_include.py', ) if ret == 0: print(GREEN + "All Python 3 tests passed." + RESET) else: print(RED + "At one Python 3 test failed." + RESET) raise SystemExit(ret) 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', ] }, cmdclass = { 'test': RunTests, 'test_python3': RunPython3Tests }, ) spyne-2.12.11/PKG-INFO0000644000175000001440000003753512615200103014067 0ustar plqusers00000000000000Metadata-Version: 1.1 Name: spyne Version: 2.12.11 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: Homepage: http://spyne.io 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.12.11 ------------- * Fix self-referential relationships pointing the wrong way * Fix wrong use of string interpolation operator in logging call slowing Spyne down for no visible reason * Detect and prevent name clashes between the foreign key column name and the object itself. * Silence a lot of (wrong) customized class instantiation warnings. spyne-2.12.10 ------------- * IpAddress types now support PostgreSQL's PGInet. * Drop trial for twisted tests and switch to pytest-twisted. * ``_safe_set`` now returns True on success so that protocols can react accordingly. * \*DictDoc now logs properly whether a value is discarded or passed to the deserialized instance. * Minor bug fixes here and there. spyne-2.12.9 ------------ * Make ``DateTime`` handle unicode date format strings for Python 2. * Fix idle timer not starting on connectionMade for ``MessagePackTransportBase`` spyne-2.12.7 ------------ * Not beta anymore. Woo! * Made ServiceBase subclasses reusable * Implemented class customization via ``__getitem__``\. * Fixed an ``ImportError`` running Python 3.4 under Pydev using PyCharm. (Eclipse still has issues, see `issue #432 `_. Any help would be much appreciated) * Fixed DateTime corner case with μs values between 999995 and 999999. * Help misguided user code that returns an int for a string type by implicitly yet not-so-silently converting the ``int``/``long`` to ``str``\. * Fixed \*Cloth sometimes getting stuck ``repr()``\'ing passed instance. * Fixed ``SimpleDictDocument`` confusing a missing value and an empty value for array types. When the client wants to denote an empty array, it should pass ``array_field=empty``\. Normally it passes something along the lines of: ``array_field[0]=Something&array_field[1]=SomethingElse``\. * Split ``MessagePackServerBase`` to ``MessagePackTransportBase`` and ``MessagePackServerBase``\. No API was harmed during this change. * Implement optional idle timeout for ``MessagePackTransportBase``\. * Add support for PGObjectJson, PGObjectXml and PGFileJson to sql table reflection. * ``log_repr`` now consults ``NATIVE_MAP`` as a last resort before freaking out. * Removed some dead code. spyne-2.12.6-beta ----------------- * Thanks to `issue #446 `_ we noticed that in some cases, SOAP messages inside HTTP requests got processed even when the request method != 'POST'. This got resolved, but you should check whether this is the case in your setup and take the necessary precautions before deploying Spyne. spyne-2.12.[12345]-beta ----------------------- * Many bugs fixed very quick. spyne-2.12.0-beta ----------------- * XmlObject: Support for ``attribute_of`` is removed. * NullServer now supports async. * XmlCloth was rewritten while less sleep-deprived. * ProtocolBase now also implements serializing primitives to unicode. * Add initial support for input polymorphism to XmlDocument (parsing xsi:type). It's an experimental feature. * Add output polymorphism for all protocols. It's off-by-default for XmlDocument and friends, on-by-default for others. * Add stub implementation for SOAP 1.2 * Add initial implementation for SOAP 1.2 Faults. * Remove the deprecated ``interface`` argument to ``Application``\. * HierDictDocument's broken wrapped dict support was fixed. Even though this is supposed to break compatibility with 2.11, virtually no one seems to use this feature. Only now it's mature enough to be set on stone. Let us know! * We now validate kwargs passed to ``@rpc``\. Be sure to test your daemons before deploying for production, because if you got leftovers, the server will refuse to boot! * It's now forbidden (by assert) to inherit from a customized class. * It's also forbidden (by convention) to instantiate a customized class. Don't do it! The warning will be converted to an assert in the future. 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.Value instance. * Implement an Xml Schema parser. * Import all model markers as well as the ``@rpc``\, ``@srpc``\, ``@mrpc``, ``ServiceBase`` and ``Application`` to the root ``spyne`` package. * 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 from Twisted. * Support Django natively using `spyne.server.django.DjangoView` and `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 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 `spyne.util.django.DjangoComplexModel` types. * Spyne now tracks subclasses and adds them to the interface if they are in the same namespace as their parent. * Simple dictionary protocol's ``hier_delim`` default value is now '.' * Fixes support for XmlAttributes with max_occurs>1 referencing the same 'attribute_of' element in a ComplexModel subclass. * Renders ``spyne.model.File`` as ``twisted.web.static.File`` when using HttpRpc over ``TwistedWebResource``. This lets twisted handle Http 1.1-specific functionality like range requests. * 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_timezone 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. Check the documentation at http://spyne.io/docs for changelogs of the older versions 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.12.11/setup.cfg0000644000175000001440000000007312615200103014576 0ustar plqusers00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0