diff --git a/pym/calculate/contrib/spyne/__init__.py b/pym/calculate/contrib/spyne/__init__.py new file mode 100644 index 0000000..c38cecf --- /dev/null +++ b/pym/calculate/contrib/spyne/__init__.py @@ -0,0 +1,81 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + + +class LogicError(Exception): + pass + +__version__ = '2.13.16' + +from pytz import utc as LOCAL_TZ +from decimal import Decimal as D + +DEFAULT_LANGUAGE = 'en' + +from spyne._base import Address + +from spyne.context import AuxMethodContext +from spyne.context import TransportContext +from spyne.context import ProtocolContext +from spyne.context import EventContext +from spyne.context import MethodContext + +from spyne.evmgr import EventManager + +from spyne.descriptor import MethodDescriptor +from spyne.descriptor import BODY_STYLE_WRAPPED +from spyne.descriptor import BODY_STYLE_BARE +from spyne.descriptor import BODY_STYLE_OUT_BARE +from spyne.descriptor import BODY_STYLE_EMPTY +from spyne.descriptor import BODY_STYLE_EMPTY_OUT_BARE + +# decorator imports descriptor, so this needs to come after +from spyne.decorator import rpc +from spyne.decorator import srpc +from spyne.decorator import mrpc + +from spyne.service import ServiceBase as Service +from spyne.service import ServiceBase # DEPRECATED +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 MissingFieldError +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() diff --git a/pym/calculate/contrib/spyne/__init__.pyc b/pym/calculate/contrib/spyne/__init__.pyc new file mode 100644 index 0000000..bee0ac0 Binary files /dev/null and b/pym/calculate/contrib/spyne/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/_base.py b/pym/calculate/contrib/spyne/_base.py new file mode 100644 index 0000000..7f558fc --- /dev/null +++ b/pym/calculate/contrib/spyne/_base.py @@ -0,0 +1,47 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logger = logging.getLogger('spyne') + +from collections import namedtuple + +# 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 + + # this gets overwritten once spyne.server.twisted is imported + @staticmethod + def _fta(*a, **kw): + from spyne.server.twisted._base import _address_from_twisted_address + return _address_from_twisted_address(*a, **kw) + + Address.from_twisted_address = _fta diff --git a/pym/calculate/contrib/spyne/_base.pyc b/pym/calculate/contrib/spyne/_base.pyc new file mode 100644 index 0000000..e5d71dd Binary files /dev/null and b/pym/calculate/contrib/spyne/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/application.py b/pym/calculate/contrib/spyne/application.py new file mode 100644 index 0000000..1a4c051 --- /dev/null +++ b/pym/calculate/contrib/spyne/application.py @@ -0,0 +1,321 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logger = logging.getLogger(__name__) +logger_client = logging.getLogger('.'.join([__name__, 'client'])) +logger_server = logging.getLogger('.'.join([__name__, 'server'])) + +from pprint import pformat + +from spyne import BODY_STYLE_EMPTY, BODY_STYLE_BARE, BODY_STYLE_WRAPPED, \ + EventManager +from spyne.error import Fault, Redirect, RespawnError, InvalidRequestError +from spyne.interface import Interface +from spyne.util import six +from spyne.util.appreg import register_application + + +class MethodAlreadyExistsError(Exception): + def __init__(self, what): + super(MethodAlreadyExistsError, self) \ + .__init__("Method key %r already exists", what) + + +def get_fault_string_from_exception(e): + # haha. + return "Internal Error" + + +def return_traceback_in_unhandled_exceptions(): + """Call this function first thing in your main function to return original + python errors to your clients in case of unhandled exceptions. + """ + + global get_fault_string_from_exception + + import traceback + def _get_fault_string_from_exception(e): + return traceback.format_exc() + get_fault_string_from_exception = _get_fault_string_from_exception + + +class Application(object): + """The Application class is the glue between one or more service + definitions, input and output protocols. + + :param services: An iterable of Service subclasses that defines + the exposed services. + :param tns: The targetNamespace attribute of the exposed + service. + :param name: The optional name attribute of the exposed service. + The default is the name of the application class + which is by default 'Application'. + :param in_protocol: A ProtocolBase instance that denotes the input + protocol. It's only optional for NullServer transport. + :param out_protocol: A ProtocolBase instance that denotes the output + protocol. It's only optional for NullServer transport. + :param config: An arbitrary python object to store random global data. + + Supported events: + * ``method_call``: + Called right before the service method is executed + + * ``method_return_object``: + Called right after the service method is executed + + * ``method_exception_object``: + Called when an exception occurred in a service method, before the + exception is serialized. + + * ``method_context_created``: + Called from the constructor of the MethodContext instance. + + * ``method_context_closed``: + Called from the ``close()`` function of the MethodContext instance, + which in turn is called by the transport when the response is fully + sent to the client (or in the client case, the response is fully + received from server). + """ + + transport = None + + def __init__(self, services, tns, name=None, + in_protocol=None, out_protocol=None, config=None, classes=()): + self.services = tuple(services) + self.tns = tns + self.name = name + self.config = config + self.classes = classes + print("SPYNE DEBUG") + print(__name__) + if self.name is None: + self.name = self.__class__.__name__.split('.')[-1] + + logger.info("Initializing application {%s}%s...", self.tns, self.name) + + self.event_manager = EventManager(self) + self.error_handler = None + + self.in_protocol = in_protocol + self.out_protocol = out_protocol + + if self.in_protocol is None: + from spyne.protocol import ProtocolBase + self.in_protocol = ProtocolBase() + + if self.out_protocol is None: + from spyne.protocol import ProtocolBase + self.out_protocol = ProtocolBase() + + self.check_unique_method_keys() # is this really necessary nowadays? + + # this needs to be after protocol assignments to give _static_when + # functions as much info as possible about the application + self.interface = Interface(self) + + # set_app needs to be after interface init because the protocols use it. + self.in_protocol.set_app(self) + # FIXME: this normally is another parameter to set_app but it's kept + # separate for backwards compatibility reasons. + self.in_protocol.message = self.in_protocol.REQUEST + + self.out_protocol.set_app(self) + # FIXME: this normally is another parameter to set_app but it's kept + # separate for backwards compatibility reasons. + self.out_protocol.message = self.out_protocol.RESPONSE + + register_application(self) + + def process_request(self, ctx): + """Takes a MethodContext instance. Returns the response to the request + as a native python object. If the function throws an exception, it + returns None and sets the exception object to ctx.out_error. + + Overriding this method would break event management. So this is not + meant to be overridden unless you know what you're doing. + """ + + try: + ctx.fire_event('method_call') + + # in object is always a sequence of incoming values. We need to fix + # that for bare mode. + if ctx.descriptor.body_style is BODY_STYLE_BARE: + ctx.in_object = [ctx.in_object] + elif ctx.descriptor.body_style is BODY_STYLE_EMPTY: + ctx.in_object = [] + + # call user method + ctx.out_object = self.call_wrapper(ctx) + + # out object is always a sequence of return values. see + # MethodContext docstrings for more info + if ctx.descriptor.body_style is not BODY_STYLE_WRAPPED or \ + len(ctx.descriptor.out_message._type_info) <= 1: + # if it's not a wrapped method, OR there's just one return type + # we wrap it ourselves + ctx.out_object = [ctx.out_object] + + # Now that the processing is switched to the outgoing message, + # point ctx.protocol to ctx.out_protocol + ctx.protocol = ctx.outprot_ctx + + ctx.fire_event('method_return_object') + + except Redirect as e: + try: + e.do_redirect() + + ctx.out_object = [None] + + # Now that the processing is switched to the outgoing message, + # point ctx.protocol to ctx.out_protocol + # point ctx.protocol to ctx.out_protocol + ctx.protocol = ctx.outprot_ctx + + ctx.fire_event('method_redirect') + + except Exception as e: + logger_server.exception(e) + ctx.out_error = Fault('Server', + get_fault_string_from_exception(e)) + + ctx.fire_event('method_redirect_exception') + + except Fault as e: + if e.faultcode == 'Client' or e.faultcode.startswith('Client.'): + logger_client.exception(e) + else: + logger.exception(e) + + ctx.out_error = e + + ctx.fire_event('method_exception_object') + + # we don't catch BaseException because we actually don't want to catch + # "system-exiting" exceptions. See: + # https://docs.python.org/2/library/exceptions.html#exceptions.Exception + except Exception as e: + logger_server.critical(e, **{'exc_info': 1}) + + ctx.out_error = Fault('Server', get_fault_string_from_exception(e)) + + ctx.fire_event('method_exception_object') + + def call_wrapper(self, ctx): + """This method calls the call_wrapper method in the service definition. + This can be overridden to make an application-wide custom exception + management. + """ + + # no function + if ctx.function is None: + logger.debug("Skipping user code call as ctx.function is None.") + return None + + # @rpc inside service class + if ctx.descriptor.no_self: + assert ctx.descriptor.service_class is not None + return ctx.descriptor.service_class.call_wrapper(ctx) + + # from here on it's @mrpc in a (parent) class + cls = ctx.descriptor.parent_class + if cls.__orig__ is not None: + cls = cls.__orig__ + + filters = {} + inst = cls.__respawn__(ctx, filters) + if inst is None: + raise RespawnError('{%s}%s with params %r' % + (cls.get_namespace(), cls.get_type_name(), filters)) + + in_cls = ctx.descriptor.in_message + + args = tuple(ctx.in_object) + if args is None: + args = () + + elif ctx.descriptor.body_style is BODY_STYLE_WRAPPED and \ + len(in_cls.get_flat_type_info(in_cls)) <= 1: + args = () + + else: + args = args[1:] + + # check whether this is a valid request according to the prerequisite + # function (the callable that was passed in the _when argument to @mrpc) + if ctx.descriptor.when is not None: + if not ctx.descriptor.when(inst, ctx): + raise InvalidRequestError("Invalid object state for request") + + if ctx.descriptor.no_ctx: + args = (inst,) + args + else: + args = (inst, ctx,) + args + + if ctx.descriptor.service_class is None: + retval = ctx.function(*args) + + else: + retval = ctx.descriptor.service_class.call_wrapper(ctx, args=args) + + return retval + + def _has_callbacks(self): + return self.interface._has_callbacks() + + def reinitialize(self, server): + """This is normally called on transport instantiation by ServerBase""" + + seen = set() + + from spyne import MethodDescriptor + for d in self.interface.method_id_map.values(): + assert isinstance(d, MethodDescriptor) + + if d.aux is not None and not id(d.aux) in seen: + d.aux.initialize(server) + seen.add(id(d.aux)) + + if d.service_class is not None and not id(d.service_class) in seen: + d.service_class.initialize(server) + seen.add(id(d.service_class)) + + def __hash__(self): + return hash(tuple((id(s) for s in self.services))) + + def check_unique_method_keys(self): + keys = {} + for s in self.services: + #DEBUG + logger.debug("service s: %s", s) + logger.debug("service public methods: %s", s.public_methods.values()) + for mdesc in s.public_methods.values(): + other_mdesc = keys.get(mdesc.internal_key, None) + if other_mdesc is not None: + logger.error( + 'Methods keys for "%s.%s" and "%s.%s" conflict', + mdesc.function.__module__, + six.get_function_name(mdesc.function), + other_mdesc.function.__module__, + six.get_function_name(other_mdesc.function)) + raise MethodAlreadyExistsError(mdesc.internal_key) + + keys[mdesc.internal_key] = mdesc diff --git a/pym/calculate/contrib/spyne/application.pyc b/pym/calculate/contrib/spyne/application.pyc new file mode 100644 index 0000000..ec85894 Binary files /dev/null and b/pym/calculate/contrib/spyne/application.pyc differ diff --git a/pym/calculate/contrib/spyne/auxproc/__init__.py b/pym/calculate/contrib/spyne/auxproc/__init__.py new file mode 100644 index 0000000..19e90b3 --- /dev/null +++ b/pym/calculate/contrib/spyne/auxproc/__init__.py @@ -0,0 +1,42 @@ + +# +# 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:`Service` 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 DEPRECATED. Get rid of this ASAP. +""" + +from spyne.auxproc._base import process_contexts +from spyne.auxproc._base import AuxProcBase diff --git a/pym/calculate/contrib/spyne/auxproc/__init__.pyc b/pym/calculate/contrib/spyne/auxproc/__init__.pyc new file mode 100644 index 0000000..82fa72b Binary files /dev/null and b/pym/calculate/contrib/spyne/auxproc/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/auxproc/_base.py b/pym/calculate/contrib/spyne/auxproc/_base.py new file mode 100644 index 0000000..565e6ef --- /dev/null +++ b/pym/calculate/contrib/spyne/auxproc/_base.py @@ -0,0 +1,88 @@ + +# +# 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) diff --git a/pym/calculate/contrib/spyne/auxproc/_base.pyc b/pym/calculate/contrib/spyne/auxproc/_base.pyc new file mode 100644 index 0000000..92214dd Binary files /dev/null and b/pym/calculate/contrib/spyne/auxproc/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/auxproc/sync.py b/pym/calculate/contrib/spyne/auxproc/sync.py new file mode 100644 index 0000000..4299fa7 --- /dev/null +++ b/pym/calculate/contrib/spyne/auxproc/sync.py @@ -0,0 +1,32 @@ + +# +# 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) diff --git a/pym/calculate/contrib/spyne/auxproc/sync.pyc b/pym/calculate/contrib/spyne/auxproc/sync.pyc new file mode 100644 index 0000000..4311725 Binary files /dev/null and b/pym/calculate/contrib/spyne/auxproc/sync.pyc differ diff --git a/pym/calculate/contrib/spyne/auxproc/thread.py b/pym/calculate/contrib/spyne/auxproc/thread.py new file mode 100644 index 0000000..dd076e2 --- /dev/null +++ b/pym/calculate/contrib/spyne/auxproc/thread.py @@ -0,0 +1,52 @@ + +# +# 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) diff --git a/pym/calculate/contrib/spyne/auxproc/thread.pyc b/pym/calculate/contrib/spyne/auxproc/thread.pyc new file mode 100644 index 0000000..bd1d559 Binary files /dev/null and b/pym/calculate/contrib/spyne/auxproc/thread.pyc differ diff --git a/pym/calculate/contrib/spyne/client/__init__.py b/pym/calculate/contrib/spyne/client/__init__.py new file mode 100644 index 0000000..21025e9 --- /dev/null +++ b/pym/calculate/contrib/spyne/client/__init__.py @@ -0,0 +1,25 @@ + +# +# 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 diff --git a/pym/calculate/contrib/spyne/client/__init__.pyc b/pym/calculate/contrib/spyne/client/__init__.pyc new file mode 100644 index 0000000..103f40d Binary files /dev/null and b/pym/calculate/contrib/spyne/client/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/client/_base.py b/pym/calculate/contrib/spyne/client/_base.py new file mode 100644 index 0000000..a68c0b1 --- /dev/null +++ b/pym/calculate/contrib/spyne/client/_base.py @@ -0,0 +1,176 @@ + +# +# 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.context 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.out_error is None: + ctx.fire_event('method_return_document') + else: + ctx.fire_event('method_exception_document') + + ctx.out_protocol.create_out_string(ctx, string_encoding) + + if ctx.out_error is None: + ctx.fire_event('method_return_string') + else: + ctx.fire_event('method_exception_string') + + 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) + ctx.fire_event('method_accept_document') + + # 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) diff --git a/pym/calculate/contrib/spyne/client/_base.pyc b/pym/calculate/contrib/spyne/client/_base.pyc new file mode 100644 index 0000000..e07e66a Binary files /dev/null and b/pym/calculate/contrib/spyne/client/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/client/django.py b/pym/calculate/contrib/spyne/client/django.py new file mode 100644 index 0000000..35e1eed --- /dev/null +++ b/pym/calculate/contrib/spyne/client/django.py @@ -0,0 +1,81 @@ + +# +# 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) diff --git a/pym/calculate/contrib/spyne/client/django.pyc b/pym/calculate/contrib/spyne/client/django.pyc new file mode 100644 index 0000000..9519dd6 Binary files /dev/null and b/pym/calculate/contrib/spyne/client/django.pyc differ diff --git a/pym/calculate/contrib/spyne/client/http.py b/pym/calculate/contrib/spyne/client/http.py new file mode 100644 index 0000000..2101c3a --- /dev/null +++ b/pym/calculate/contrib/spyne/client/http.py @@ -0,0 +1,70 @@ + +# +# 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 = b''.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) diff --git a/pym/calculate/contrib/spyne/client/http.pyc b/pym/calculate/contrib/spyne/client/http.pyc new file mode 100644 index 0000000..6ae766b Binary files /dev/null and b/pym/calculate/contrib/spyne/client/http.pyc differ diff --git a/pym/calculate/contrib/spyne/client/twisted/__init__.py b/pym/calculate/contrib/spyne/client/twisted/__init__.py new file mode 100644 index 0000000..6609e82 --- /dev/null +++ b/pym/calculate/contrib/spyne/client/twisted/__init__.py @@ -0,0 +1,148 @@ + +# +# 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.util import six + +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): + if six.PY2: + 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( + b'POST', self.url, + Headers({b'User-Agent': + [b'Spyne Twisted Http Client %s' % VERSION.encode()]}), + _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) diff --git a/pym/calculate/contrib/spyne/client/twisted/__init__.pyc b/pym/calculate/contrib/spyne/client/twisted/__init__.pyc new file mode 100644 index 0000000..af98c43 Binary files /dev/null and b/pym/calculate/contrib/spyne/client/twisted/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/client/zeromq.py b/pym/calculate/contrib/spyne/client/zeromq.py new file mode 100644 index 0000000..70c9796 --- /dev/null +++ b/pym/calculate/contrib/spyne/client/zeromq.py @@ -0,0 +1,54 @@ + +# +# 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) diff --git a/pym/calculate/contrib/spyne/client/zeromq.pyc b/pym/calculate/contrib/spyne/client/zeromq.pyc new file mode 100644 index 0000000..c300b8f Binary files /dev/null and b/pym/calculate/contrib/spyne/client/zeromq.pyc differ diff --git a/pym/calculate/contrib/spyne/const/__init__.py b/pym/calculate/contrib/spyne/const/__init__.py new file mode 100644 index 0000000..eb5ca90 --- /dev/null +++ b/pym/calculate/contrib/spyne/const/__init__.py @@ -0,0 +1,82 @@ + + +# +# 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_DICT_ELEMENT_NUM = 2 +"""Maximum number of dict 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`.""" + +MIN_GC_INTERVAL = 1.0 +"""Minimum time in seconds between gc.collect() calls.""" + +DEFAULT_LOCALE = 'en_US' +"""Locale code to use for the translation subsystem when locale information is +missing in an incoming request.""" + +WARN_ON_DUPLICATE_FAULTCODE = True +"""Warn about duplicate faultcodes in all Fault subclasses globally. Only works +when CODE class attribute is set for every Fault subclass.""" + + +def add_request_suffix(string): + """Concatenates REQUEST_SUFFIX to end of string""" + return string + REQUEST_SUFFIX diff --git a/pym/calculate/contrib/spyne/const/__init__.pyc b/pym/calculate/contrib/spyne/const/__init__.pyc new file mode 100644 index 0000000..b01cfc2 Binary files /dev/null and b/pym/calculate/contrib/spyne/const/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/const/ansi_color.py b/pym/calculate/contrib/spyne/const/ansi_color.py new file mode 100644 index 0000000..98935ba --- /dev/null +++ b/pym/calculate/contrib/spyne/const/ansi_color.py @@ -0,0 +1,81 @@ + +# +# 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() diff --git a/pym/calculate/contrib/spyne/const/ansi_color.pyc b/pym/calculate/contrib/spyne/const/ansi_color.pyc new file mode 100644 index 0000000..c33897d Binary files /dev/null and b/pym/calculate/contrib/spyne/const/ansi_color.pyc differ diff --git a/pym/calculate/contrib/spyne/const/http.py b/pym/calculate/contrib/spyne/const/http.py new file mode 100644 index 0000000..e6a0025 --- /dev/null +++ b/pym/calculate/contrib/spyne/const/http.py @@ -0,0 +1,116 @@ + +# +# 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), + ".", + ) + )) diff --git a/pym/calculate/contrib/spyne/const/http.pyc b/pym/calculate/contrib/spyne/const/http.pyc new file mode 100644 index 0000000..e42cbc3 Binary files /dev/null and b/pym/calculate/contrib/spyne/const/http.pyc differ diff --git a/pym/calculate/contrib/spyne/const/xml.py b/pym/calculate/contrib/spyne/const/xml.py new file mode 100644 index 0000000..e320e0b --- /dev/null +++ b/pym/calculate/contrib/spyne/const/xml.py @@ -0,0 +1,187 @@ + +# +# 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/' +NS_WSDL11_SOAP12 = 'http://schemas.xmlsoap.org/wsdl/soap12/' +NS_WSDL11_HTTP = 'http://schemas.xmlsoap.org/wsdl/http/' + +NSMAP = { + 'xml': NS_XML, + 'xs': NS_XSD, + 'xsi': NS_XSI, + 'plink': NS_PLINK, + 'wsdlsoap11': NS_WSDL11_SOAP, + 'wsdlsoap12': NS_WSDL11_SOAP12, + '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, + 'http': NS_WSDL11_HTTP, +} + +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 get_binding_ns(protocol_type): + "Returns the wsdl binding namespace based on the protocol type" + if 'soap12' in protocol_type: + return WSDL11_SOAP12 + elif 'http' in protocol_type: + return WSDL11_HTTP + else: + # Bind to Soap1.1 namespace by default for backwards compatibility + return WSDL11_SOAP + + +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) +WSDL11_SOAP12 = Tnswrap(NS_WSDL11_SOAP12) +WSDL11_HTTP = Tnswrap(NS_WSDL11_HTTP) + +# 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 diff --git a/pym/calculate/contrib/spyne/const/xml.pyc b/pym/calculate/contrib/spyne/const/xml.pyc new file mode 100644 index 0000000..25aaffe Binary files /dev/null and b/pym/calculate/contrib/spyne/const/xml.pyc differ diff --git a/pym/calculate/contrib/spyne/const/xml_ns.py b/pym/calculate/contrib/spyne/const/xml_ns.py new file mode 100644 index 0000000..f3949b2 --- /dev/null +++ b/pym/calculate/contrib/spyne/const/xml_ns.py @@ -0,0 +1,63 @@ + +# +# 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``. + +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 diff --git a/pym/calculate/contrib/spyne/const/xml_ns.pyc b/pym/calculate/contrib/spyne/const/xml_ns.pyc new file mode 100644 index 0000000..d9a4297 Binary files /dev/null and b/pym/calculate/contrib/spyne/const/xml_ns.pyc differ diff --git a/pym/calculate/contrib/spyne/context.py b/pym/calculate/contrib/spyne/context.py new file mode 100644 index 0000000..bdcebc5 --- /dev/null +++ b/pym/calculate/contrib/spyne/context.py @@ -0,0 +1,463 @@ + +# +# 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('spyne') + +from time import time +from copy import copy +from collections import deque, defaultdict + +from spyne import const + + +_LAST_GC_RUN = 0.0 + + +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.""" + + def get_peer(self): + """Returns None when not applicable, otherwise returns + :class:`spyne.Address`""" + + return None + + +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,), {}) + TransportContext = TransportContext + + 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 + + def fire_event(self, event, *args, **kwargs): + self.app.event_manager.fire_event(event, self, *args, **kwargs) + + desc = self.descriptor + if desc is not None: + for evmgr in desc.event_managers: + evmgr.fire_event(event, self, *args, **kwargs) + + @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 + """`True` means response is fully sent and request finalized.""" + + self.app = transport.app + """The parent application.""" + + self.udc = None + """The user defined context. Use it to your liking.""" + + self.transport = None + """The transport-specific context. Transport implementors can use this + to their liking.""" + + if self.TransportContext is not None: + self.transport = self.TransportContext(self, transport) + + 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.active = False + """Transports may choose to delay incoming requests. When a context + is queued but waiting, this is False.""" + + 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.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.pusher_stack = [] + """Last one is the current PushBase instance writing to the stream.""" + + self.frozen = True + """When this is set, no new attribute can be added to this class + instance. This is mostly for internal use. + """ + + self.fire_event("method_context_created") + + 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. + """ + + # FIXME: 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(['{']) + + for k2, v2 in sorted(v.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): + global _LAST_GC_RUN + + 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 + t = time() + if (t - _LAST_GC_RUN) > const.MIN_GC_INTERVAL: + gc.collect() + + dt = (time() - t) + _LAST_GC_RUN = t + + logger.debug("gc.collect() took around %dms.", round(dt, 2) * 1000) + + def set_out_protocol(self, what): + self._out_protocol = what + if self._out_protocol.app is None: + self._out_protocol.set_app(self.app) + + def get_out_protocol(self): + return self._out_protocol + + out_protocol = property(get_out_protocol, set_out_protocol) + + def set_in_protocol(self, what): + self._in_protocol = what + self._in_protocol.app = self.app + + def get_in_protocol(self): + return self._in_protocol + + in_protocol = property(get_in_protocol, set_in_protocol) + + +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,), {})() diff --git a/pym/calculate/contrib/spyne/context.pyc b/pym/calculate/contrib/spyne/context.pyc new file mode 100644 index 0000000..4be9555 Binary files /dev/null and b/pym/calculate/contrib/spyne/context.pyc differ diff --git a/pym/calculate/contrib/spyne/decorator.py b/pym/calculate/contrib/spyne/decorator.py new file mode 100644 index 0000000..38ad8cc --- /dev/null +++ b/pym/calculate/contrib/spyne/decorator.py @@ -0,0 +1,572 @@ + +# +# 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 + +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 import LogicError +from spyne import BODY_STYLE_EMPTY +from spyne import BODY_STYLE_WRAPPED +from spyne import BODY_STYLE_BARE +from spyne import BODY_STYLE_OUT_BARE +from spyne import BODY_STYLE_EMPTY_OUT_BARE + +from spyne.model import ModelBase, ComplexModel, ComplexModelBase +from spyne.model.complex import TypeInfo, recust_selfref, SelfReference + +from spyne.const import add_request_suffix + + +def _produce_input_message(f, params, in_message_name, in_variable_names, + no_ctx, no_self, argnames, body_style_str, self_ref_cls, + in_wsdl_part_name): + arg_start = 0 + if no_ctx is False: + arg_start += 1 + if no_self is False: + arg_start += 1 + + if argnames is None: + try: + argcount = f.__code__.co_argcount + argnames = 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 no_self is False: + params = [self_ref_cls.novalidate_freq()] + params + argnames = ('self',) + argnames + + if len(params) != len(argnames): + raise LogicError("%r function has %d argument(s) but its decorator " + "has %d." % (f.__name__, len(argnames), len(params))) + + else: + argnames = copy(argnames) + if len(params) != len(argnames): + raise LogicError("%r function has %d argument(s) but the _args " + "argument has %d." % ( + f.__name__, len(argnames), len(params))) + + in_params = TypeInfo() + from spyne import SelfReference + for k, v in zip(argnames, params): + try: + is_self_ref = issubclass(v, SelfReference) + except TypeError: + is_self_ref = False + + if is_self_ref: + if no_self is False: + raise LogicError("SelfReference can't be used in @rpc") + v = recust_selfref(v, self_ref_cls) + + k = in_variable_names.get(k, k) + in_params[k] = v + + ns = spyne.const.xml.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: + # The soap Body elt contains 1 elt (called "body entry" in the soap + # standard) per method call. If bare methods were allowed to have >1 + # argument, it would have to be serialized as multiple body entries, + # which would violate the standard. It's easy to work around this + # restriction by creating a ComplexModel that contains all the + # required parameters. + raise LogicError("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) + + if issubclass(message, ComplexModelBase) and not message._type_info: + raise LogicError("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 + + if in_wsdl_part_name: + message = message.customize(wsdl_part_name=in_wsdl_part_name) + + 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, self_ref_cls, + no_self, 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) + + try: + is_self_ref = issubclass(_returns, SelfReference) + except TypeError: + is_self_ref = False + + if is_self_ref: + if no_self is False: + raise LogicError("SelfReference can't be used in @rpc") + + _returns = recust_selfref(_returns, self_ref_cls) + + _is_out_message_name_overridden = not ('_out_message_name' in kparams) + _out_message_name = kparams.pop('_out_message_name', '%s%s' % + (func_name, spyne.const.RESPONSE_SUFFIX)) + + if no_self is False and \ + (body_style_str == 'wrapped' or _is_out_message_name_overridden): + _out_message_name = '%s.%s' % \ + (self_ref_cls.get_type_name(), _out_message_name) + + _out_wsdl_part_name = kparams.pop('_wsdl_part_name', None) + + 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.DEFAULT_NS + if _out_message_name.startswith("{"): + _out_message_name_parts = _out_message_name[1:].partition("}") + ns = _out_message_name_parts[0] # skip index 1, it is the closing '}' + _out_message_name = _out_message_name_parts[2] + + 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? + + if _out_wsdl_part_name: + message = message.customize(wsdl_part_name=_out_wsdl_part_name) + + return message + + +def _substitute_self_reference(params, kparams, self_ref_replacement, _no_self): + from spyne.model import SelfReference + + for i, v in enumerate(params): + if isclass(v) and issubclass(v, SelfReference): + if _no_self: + raise LogicError("SelfReference can't be used in @rpc") + params[i] = recust_selfref(v, self_ref_replacement) + else: + params[i] = v + + for k, v in kparams.items(): + if isclass(v) and issubclass(v, SelfReference): + if _no_self: + raise LogicError("SelfReference can't be used in @rpc") + kparams[k] = recust_selfref(v, self_ref_replacement) + else: + kparams[k] = v + + +def _get_event_managers(kparams): + _evmgr = kparams.pop("_evmgr", None) + _evmgrs = kparams.pop("_evmgrs", None) + + if _evmgr is not None and _evmgrs is not None: + raise LogicError("Pass one of _evmgr or _evmgrs but not both") + + if _evmgr is not None: + _evmgrs = [_evmgr] + + _event_manager = kparams.pop("_event_manager", None) + _event_managers = kparams.pop("_event_managers", None) + + if _event_manager is not None and _event_managers is not None: + raise LogicError("Pass one of _event_manager or " + "_event_managers but not both") + + if _event_manager is not None: + _event_managers = [_event_manager] + + if _evmgrs is not None and _event_managers is not None: + raise LogicError("You must pass at most one of _evmgr* " + "arguments or _event_manager* arguments") + elif _evmgrs is not None: + _event_managers = _evmgrs + + return _event_managers if _event_managers is not None else [] + + +def rpc(*params, **kparams): + """Method decorator to tag a method as a remote procedure call in a + :class:`spyne.service.Service` 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_arg_names: The public names of the function arguments. It's + a dict that maps argument names in the code to public ones. + :param _in_variable_names: **DEPRECATED** Same as _in_arg_names, kept for + backwards compatibility. + :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 _udd: Short for User Defined Data, you can use this to mark the + method with arbitrary metadata. + :param _udp: **DEPRECATED** synonym of ``_udd``. + :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 _event_managers: An iterable of :class:`spyne.EventManager` + instances. This is useful for adding additional event handlers to + individual functions. + :param _event_manager: An instance of :class:`spyne.EventManager` class. + :param _logged: May be the string '...' to denote that the rpc arguments + will not be logged. + :param _evmgrs: Same as ``_event_managers``. + :param _evmgr: Same as ``_event_manager``. + :param _service_class: A :class:`Service` subclass. It's generally not a + good idea to override it for ``@rpc`` methods. It could be necessary to + override it for ``@mrpc`` methods to add events and other goodies. + :param _service: Same as ``_service``. + :param _wsdl_part_name: Overrides the part name attribute within wsdl + input/output messages eg "parameters" + """ + + params = list(params) + + def explain(f): + def explain_method(**kwargs): + # params and kparams are passed by the user to the @rpc family + # of decorators. + + # kwargs is passed by spyne while sanitizing methods. it mainly + # contains information about the method context like the service + # class that contains the method at hand. + + function_name = kwargs['_default_function_name'] + _service_class = kwargs.pop("_service_class", None) + _self_ref_replacement = None + + # 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('_port_type', None) + _no_ctx = kparams.pop('_no_ctx', False) + _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) + _static_when = kparams.pop("_static_when", None) + _href = kparams.pop("_href", None) + _logged = kparams.pop("_logged", True) + _internal_key_suffix = kparams.pop('_internal_key_suffix', '') + if '_service' in kparams and '_service_class' in kparams: + raise LogicError("Please pass only one of '_service' and " + "'_service_class'") + if '_service' in kparams: + _service_class = kparams.pop("_service") + if '_service_class' in kparams: + _service_class = kparams.pop("_service_class") + + _no_self = kparams.pop('_no_self', True) + _event_managers = _get_event_managers(kparams) + + # mrpc-specific + _self_ref_replacement = kwargs.pop('_self_ref_replacement', None) + _default_on_null = kparams.pop('_default_on_null', False) + _substitute_self_reference(params, kparams, _self_ref_replacement, + _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') + + _is_in_message_name_overridden = not ('_in_message_name' in kparams) + _in_message_name = kparams.pop('_in_message_name', function_name) + + if _no_self is False and _is_in_message_name_overridden: + _in_message_name = '%s.%s' % \ + (_self_ref_replacement.get_type_name(), _in_message_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) + + if '_in_arg_names' in kparams and '_in_variable_names' in kparams: + raise LogicError("Use either '_in_arg_names' or " + "'_in_variable_names', not both.") + elif '_in_arg_names' in kparams: + _in_arg_names = kparams.pop('_in_arg_names') + + elif '_in_variable_names' in kparams: + _in_arg_names = kparams.pop('_in_variable_names') + + else: + _in_arg_names = {} + + if '_udd' in kparams and '_udp' in kparams: + raise LogicError("Use either '_udd' or '_udp', not both.") + elif '_udd' in kparams: + _udd = kparams.pop('_udd') + + elif '_udp' in kparams: + _udd = kparams.pop('_udp') + + else: + _udd = {} + + _wsdl_part_name = kparams.get('_wsdl_part_name', None) + + 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_arg_names, _no_ctx, _no_self, + _args, body_style_str, _self_ref_replacement, + _wsdl_part_name) + + out_message = _produce_output_message(function_name, + body_style_str, _self_ref_replacement, _no_self, kparams) + + if _logged != True: + in_message.Attributes.logged = _logged + out_message.Attributes.logged = _logged + + 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 + + ti = in_message + to = out_message + if issubclass(ti, ComplexModelBase) and len(ti._type_info) == 0: + if not issubclass(to, ComplexModelBase) or \ + len(to._type_info) > 0: + body_style = BODY_STYLE_EMPTY_OUT_BARE + else: + body_style = BODY_STYLE_EMPTY + + assert _in_header is None or isinstance(_in_header, tuple) + retval = MethodDescriptor(f, + in_message, out_message, doc, + is_callback=_is_callback, is_async=_is_async, mtom=_mtom, + in_header=_in_header, out_header=_out_header, faults=_faults, + parent_class=_self_ref_replacement, + port_type=_port_type, no_ctx=_no_ctx, udd=_udd, + 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, static_when=_static_when, + service_class=_service_class, href=_href, + internal_key_suffix=_internal_key_suffix, + default_on_null=_default_on_null, + event_managers=_event_managers, + logged=_logged, + ) + + if _patterns is not None and _no_self: + for p in _patterns: + p.hello(retval) + + if len(kparams) > 0: + raise ValueError("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) diff --git a/pym/calculate/contrib/spyne/decorator.pyc b/pym/calculate/contrib/spyne/decorator.pyc new file mode 100644 index 0000000..42f8e72 Binary files /dev/null and b/pym/calculate/contrib/spyne/decorator.pyc differ diff --git a/pym/calculate/contrib/spyne/descriptor.py b/pym/calculate/contrib/spyne/descriptor.py new file mode 100644 index 0000000..41804e9 --- /dev/null +++ b/pym/calculate/contrib/spyne/descriptor.py @@ -0,0 +1,309 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logger = logging.getLogger('spyne') + +from spyne import LogicError +from spyne.util import six +from spyne.util import DefaultAttrDict +from spyne.service import Service, ServiceBaseBase +from spyne.const.xml import DEFAULT_NS + + +class BODY_STYLE_WRAPPED: pass +class BODY_STYLE_EMPTY: pass +class BODY_STYLE_BARE: pass +class BODY_STYLE_OUT_BARE: pass +class BODY_STYLE_EMPTY_OUT_BARE: pass + + +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, is_async, mtom, in_header, out_header, faults, + parent_class, port_type, no_ctx, udd, class_key, aux, patterns, + body_style, args, operation_name, no_self, translations, + when, static_when, service_class, href, internal_key_suffix, + default_on_null, event_managers, logged): + + 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.internal_key_suffix = internal_key_suffix + """A string that is appended to the internal key string. Helpful when + generating services programmatically.""" + + 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.""" + + # FIXME: Remove UDP assingment in Spyne 3 + self.udp = self.udd = DefaultAttrDict(**udd) + """Short for "User Defined Data", this is an empty DefaultAttrDict that + can be updated by the user to pass arbitrary metadata via the ``@rpc`` + decorator.""" + + self.class_key = class_key + """ The identifier of this method in its parent + :class:`spyne.service.Service` 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.""" + + self.no_self = no_self + """When False, this passes self as first argument (before ctx) to the + decorated function. This is what separates ``@rpc`` and ``@mrpc``.""" + + self.service_class = service_class + """The Service subclass the method belongs to. If not None for + ``@mrpc`` methods, a Service subclass for anything else.""" + + self.parent_class = parent_class + """The ComplexModel subclass the method belongs to. Only set for + ``@mrpc`` methods.""" + + self.default_on_null = default_on_null + if parent_class is None and not (default_on_null is False): + raise LogicError("default_on_null is only to be used inside @mrpc") + + # 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 an object instance and a + :class:`MethodContext` and returns a boolean value. If this callable + returns ``True``, the object can process that action. + """ + + self.static_when = static_when + """None or a callable that takes an :class:`Application` instance and + returns a boolean value. If true, the object can have that action + registered in the interface document. + """ + + self.event_managers = event_managers + """Event managers registered with this method.""" + + self.logged = logged + """Denotes the logging style for this method.""" + + if self.service_class is not None: + self.event_managers.append(self.service_class.event_manager) + + 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()) + + @property + def internal_key(self): + """The internal function identifier in '{namespace}name' form.""" + + pc = self.parent_class + if pc is not None: + mn = pc.__module__ + on = pc.__name__ + + dn = self.name + # prevent duplicate class name. this happens when the class is a + # direct subclass of ComplexModel + if dn.split('.', 1)[0] != on: + return "{%s}%s.%s" % (mn, on, dn) + + return "{%s}%s" % (mn, dn) + + sc = self.service_class + if sc is not None: + return '{%s}%s%s' % (sc.get_internal_key(), + six.get_function_name(self.function), + self.internal_key_suffix) + + @staticmethod + def get_owner_name(cls): + if issubclass(cls, Service): + return cls.get_service_name() + return cls.__name__ + + def gen_interface_key(self, cls): + # this is a regular service method decorated by @rpc + if issubclass(cls, ServiceBaseBase): + return u'{}.{}.{}'.format(cls.__module__, + self.get_owner_name(cls), self.name) + + # this is a member method decorated by @mrpc + else: + mn = cls.get_namespace() or '__none__' + on = cls.get_type_name() + + dn = self.name + # prevent duplicate class name. this happens when the class is a + # direct subclass of ComplexModel + if dn.split(u'.', 1)[0] != on: + return u'.'.join( (mn, on, dn) ) + + return u'.'.join( (mn, dn) ) + + @staticmethod + def _get_class_module_name(cls): + return '.'.join([frag for frag in cls.__module__.split('.') + if not frag.startswith('_')]) + + def is_out_bare(self): + return self.body_style in (BODY_STYLE_EMPTY_OUT_BARE, + BODY_STYLE_EMPTY, + BODY_STYLE_BARE, + BODY_STYLE_OUT_BARE) + + def reset_function(self, val=None): + if val != None: + self.__real_function = val + self.function = self.__real_function + + @property + def in_header(self): + return self.__in_header + + @in_header.setter + def in_header(self, in_header): + from spyne.model._base import ModelBase + try: + is_model = issubclass(in_header, ModelBase) + except TypeError: + is_model = False + + if is_model: + in_header = (in_header,) + + assert in_header is None or isinstance(in_header, tuple) + self.__in_header = in_header + + @property + def out_header(self): + return self.__out_header + + @out_header.setter + def out_header(self, out_header): + from spyne.model._base import ModelBase + try: + is_model = issubclass(out_header, ModelBase) + except TypeError: + is_model = False + + if is_model: + out_header = (out_header,) + + assert out_header is None or isinstance(out_header, tuple) + self.__out_header = out_header diff --git a/pym/calculate/contrib/spyne/descriptor.pyc b/pym/calculate/contrib/spyne/descriptor.pyc new file mode 100644 index 0000000..c1e6c6c Binary files /dev/null and b/pym/calculate/contrib/spyne/descriptor.pyc differ diff --git a/pym/calculate/contrib/spyne/error.py b/pym/calculate/contrib/spyne/error.py new file mode 100644 index 0000000..20cfd79 --- /dev/null +++ b/pym/calculate/contrib/spyne/error.py @@ -0,0 +1,154 @@ + +# +# 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 + + +class InvalidCredentialsError(Fault): + """Raised when requested resource is forbidden.""" + + CODE = 'Client.InvalidCredentialsError' + STR = "You do not have permission to access this resource." + + def __init__(self, fault_string=STR, params=None): + super(InvalidCredentialsError, self) \ + .__init__(self.CODE, fault_string, detail=params) + + +class RequestTooLongError(Fault): + """Raised when request is too long.""" + + CODE = 'Client.RequestTooLong' + + def __init__(self, faultstring="Request too long"): + super(RequestTooLongError, self).__init__(self.CODE, faultstring) + + +class RequestNotAllowed(Fault): + """Raised when request is incomplete.""" + + CODE = 'Client.RequestNotAllowed' + + def __init__(self, faultstring=""): + super(RequestNotAllowed, self).__init__(self.CODE, faultstring) + + +class ArgumentError(Fault): + """Raised when there is a general problem with input data.""" + + CODE = 'Client.ArgumentError' + + def __init__(self, faultstring=""): + super(ArgumentError, self).__init__(self.CODE, 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 MissingFieldError(InvalidInputError): + """Raised when a mandatory value is missing.""" + + CODE = 'Client.InvalidInput' + + def __init__(self, field_name, message="Field '%s' is missing."): + try: + message = message % (field_name,) + except TypeError: + pass + + super(MissingFieldError, self).__init__(self.CODE, message) + + +class ValidationError(Fault): + """Raised when the input stream does not adhere to type constraints.""" + + CODE = 'Client.ValidationError' + + def __init__(self, obj, custom_msg='The value %r could not be validated.'): + try: + msg = custom_msg % (obj,) + except TypeError: + msg = custom_msg + + super(ValidationError, self).__init__(self.CODE, msg) + + +class InternalError(Fault): + """Raised to communicate server-side errors.""" + + CODE = 'Server' + + def __init__(self, error): + super(InternalError, self) \ + .__init__(self.CODE, "InternalError: An unknown error has occured.") + + +class ResourceNotFoundError(Fault): + """Raised when requested resource is not found.""" + + CODE = 'Client.ResourceNotFound' + + def __init__(self, fault_object, + fault_string="Requested resource %r not found"): + super(ResourceNotFoundError, self) \ + .__init__(self.CODE, fault_string % (fault_object,)) + + +class RespawnError(ResourceNotFoundError): + pass + + +class ResourceAlreadyExistsError(Fault): + """Raised when requested resource already exists on server side.""" + + CODE = 'Client.ResourceAlreadyExists' + + def __init__(self, fault_object, fault_string="Resource %r already exists"): + super(ResourceAlreadyExistsError, self) \ + .__init__(self.CODE, fault_string % fault_object) + + +class Redirect(Fault): + """Raised when client needs to make another request for the same + resource.""" + + CODE = 'Client.Redirect' + + def __init__(self, ctx, location, orig_exc=None): + super(Redirect, self).__init__(self.CODE, faultstring=location) + + self.ctx = ctx + self.location = location + self.orig_exc = orig_exc + + def do_redirect(self): + raise NotImplementedError() diff --git a/pym/calculate/contrib/spyne/error.pyc b/pym/calculate/contrib/spyne/error.pyc new file mode 100644 index 0000000..03bb796 Binary files /dev/null and b/pym/calculate/contrib/spyne/error.pyc differ diff --git a/pym/calculate/contrib/spyne/evmgr.py b/pym/calculate/contrib/spyne/evmgr.py new file mode 100644 index 0000000..6b2250d --- /dev/null +++ b/pym/calculate/contrib/spyne/evmgr.py @@ -0,0 +1,83 @@ + +# +# 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.oset import oset + + +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 performance measurements. + + Various Spyne components support firing events at various stages during the + request handling process, which are documented in the relevant classes. + + 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={}): + """Initializer for the ``EventManager`` instance. + + :param parent: The owner of this event manager. As of Spyne 2.13, event + managers can be owned by multiple objects, in which case this property + will be none. + + :param handlers: A dict of event name (string)/callable pairs. The dict + shallow-copied to the ``EventManager`` instance. + """ + + 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 del_listener(self, event_name, handler=None): + if handler is None: + del self.handlers[event_name] + else: + self.handlers[event_name].remove(handler) + + + 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) diff --git a/pym/calculate/contrib/spyne/evmgr.pyc b/pym/calculate/contrib/spyne/evmgr.pyc new file mode 100644 index 0000000..ed0bcd4 Binary files /dev/null and b/pym/calculate/contrib/spyne/evmgr.pyc differ diff --git a/pym/calculate/contrib/spyne/interface/__init__.py b/pym/calculate/contrib/spyne/interface/__init__.py new file mode 100644 index 0000000..e0044a2 --- /dev/null +++ b/pym/calculate/contrib/spyne/interface/__init__.py @@ -0,0 +1,43 @@ + +# +# 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 __future__ import print_function + +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 as e: + if 'No module named lxml' in e.args or "No module named 'lxml'" in e.args: + HAS_WSDL = False + else: + raise diff --git a/pym/calculate/contrib/spyne/interface/__init__.pyc b/pym/calculate/contrib/spyne/interface/__init__.pyc new file mode 100644 index 0000000..5cd3e98 Binary files /dev/null and b/pym/calculate/contrib/spyne/interface/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/interface/_base.py b/pym/calculate/contrib/spyne/interface/_base.py new file mode 100644 index 0000000..9bc58ef --- /dev/null +++ b/pym/calculate/contrib/spyne/interface/_base.py @@ -0,0 +1,550 @@ + +# +# 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.util import six +from spyne.model import ModelBase, Array, Iterable, ComplexModelBase +from spyne.model.complex import XmlModifier +from spyne.const import xml as namespace + + +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.NSMAP) + self.prefmap = dict(namespace.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() + + key = '{%s}%s' % (ns, tn) + c = self.classes.get(key) + 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: '%s'" % + (cls, c, key)) + 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 = u'{%s}%s' % (self.app.tns, method.name) + + if issubclass(s, ComplexModelBase): + method_object_name = method.name.split('.', 1)[0] + if s.get_type_name() != method_object_name: + method_key = u'{%s}%s.%s' % (self.app.tns, s.get_type_name(), + method.name) + + key = method.gen_interface_key(s) + if key in self.method_id_map: + c = self.method_id_map[key].parent_class + if c is None: + pass + + elif 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.', + method.get_owner_name(s), six.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 types...", s.get_internal_key()) + + for method in s.public_methods.values(): + if method.in_header is None: + method.in_header = s.__in_header__ + + if method.out_header is None: + method.out_header = s.__out_header__ + + if method.aux is None: + method.aux = s.__aux__ + + if method.aux is not None: + method.aux.methods.append(method.gen_interface_key(s)) + + 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 additional types + for c in self.app.classes: + self.add_class(c) + + # 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, method in self.member_methods: + should_we = True + if method.static_when is not None: + should_we = method.static_when(self.app) + logger.debug("static_when returned %r for %s " + "while populating methods", should_we, method.internal_key) + + if should_we: + s = method.service_class + if s is not None: + if method.in_header is None: + method.in_header = s.__in_header__ + + if method.out_header is None: + method.out_header = s.__out_header__ + + # FIXME: There's no need to process aux info here as it's + # not currently known how to write aux member methods in the + # first place. + + self.process_method(cls.__orig__ or cls, method) + + # 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, six.text_type)): + 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) + + should_we = True + if descriptor.static_when is not None: + should_we = descriptor.static_when(self.app) + logger.debug("static_when returned %r for %s " + "while populating classes", + should_we, descriptor.internal_key) + + if should_we: + 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.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.') diff --git a/pym/calculate/contrib/spyne/interface/_base.pyc b/pym/calculate/contrib/spyne/interface/_base.pyc new file mode 100644 index 0000000..3e30e5e Binary files /dev/null and b/pym/calculate/contrib/spyne/interface/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/interface/wsdl/__init__.py b/pym/calculate/contrib/spyne/interface/wsdl/__init__.py new file mode 100644 index 0000000..c7868e5 --- /dev/null +++ b/pym/calculate/contrib/spyne/interface/wsdl/__init__.py @@ -0,0 +1,25 @@ + +# +# 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 diff --git a/pym/calculate/contrib/spyne/interface/wsdl/__init__.pyc b/pym/calculate/contrib/spyne/interface/wsdl/__init__.pyc new file mode 100644 index 0000000..cfa8580 Binary files /dev/null and b/pym/calculate/contrib/spyne/interface/wsdl/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/interface/wsdl/defn.py b/pym/calculate/contrib/spyne/interface/wsdl/defn.py new file mode 100644 index 0000000..e072b28 --- /dev/null +++ b/pym/calculate/contrib/spyne/interface/wsdl/defn.py @@ -0,0 +1,108 @@ + +from spyne.util.six import add_metaclass + +from spyne.const import xml + +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_WSDL11 + + +@add_metaclass(ComplexModelMeta) +class Soap11Base(ComplexModelBase): + __namespace__ = xml.NS_WSDL11_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_WSDL11_SOAP, + sub_name="body") + soap_header = SoapHeaderDefinition.customize(sub_ns=xml.NS_WSDL11_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_WSDL11_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_WSDL11_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_WSDL11_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), + ] diff --git a/pym/calculate/contrib/spyne/interface/wsdl/defn.pyc b/pym/calculate/contrib/spyne/interface/wsdl/defn.pyc new file mode 100644 index 0000000..b22a54a Binary files /dev/null and b/pym/calculate/contrib/spyne/interface/wsdl/defn.pyc differ diff --git a/pym/calculate/contrib/spyne/interface/wsdl/wsdl11.py b/pym/calculate/contrib/spyne/interface/wsdl/wsdl11.py new file mode 100644 index 0000000..8a3124b --- /dev/null +++ b/pym/calculate/contrib/spyne/interface/wsdl/wsdl11.py @@ -0,0 +1,576 @@ + +# +# 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 + +import spyne.const.xml as ns + +from spyne.util import six + +from lxml import etree +from lxml.builder import E +from lxml.etree import SubElement + +from spyne.const.xml import WSDL11, XSD, NS_WSA, PLINK +from spyne.interface.xml_schema import XmlSchema + +REGEX_WSDL = re.compile('[.?]wsdl$') +PREF_WSA = ns.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, xsl_href=None, _with_partnerlink=False): + super(Wsdl11, self).__init__(interface) + + self._with_plink = _with_partnerlink + self.xsl_href = xsl_href + + 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() + #DEBUG + logger.debug("service name: %s", service_name) + # create wsdl root node + self.root_elt = root = etree.Element(WSDL11("definitions"), + nsmap=self.interface.nsmap) + if self.xsl_href is not None: + # example: + # " + + # pi.attrib.__setitem__ is ignored, so we get a proper list of + # attributes to pass with the following hack. + pitext = etree.tostring(etree.Element("dummy", + dict(type='text/xsl', href=self.xsl_href)), encoding='unicode') \ + .split(" ", 1)[-1][:-2] + + pi = etree.ProcessingInstruction("xml-stylesheet", pitext) + self.root_elt.addprevious(pi) + + self.root_tree = root.getroottree() + + 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(self.root_tree, 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, + ns.get_binding_ns(self.interface.app.in_protocol.type)("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_wsdl_part_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()) + input_binding_ns = ns.get_binding_ns(self.interface.app.in_protocol.type) + output_binding_ns = ns.get_binding_ns(self.interface.app.out_protocol.type) + + def inner(method, binding): + operation = etree.Element(WSDL11("operation")) + operation.set('name', method.operation_name) + + soap_operation = SubElement(operation, input_binding_ns("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, input_binding_ns("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, input_binding_ns('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, output_binding_ns("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, output_binding_ns("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, input_binding_ns("fault")) + soap_fault.set('name', f.get_type_name()) + soap_fault.set('use', 'literal') + + if method.is_callback: + relates_to = SubElement(input, input_binding_ns("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, input_binding_ns("header")) + rt_header.set('message', '%s:ReplyToHeader' % pref_tns) + rt_header.set('part', 'ReplyTo') + rt_header.set('use', 'literal') + + mid_header = SubElement(input, input_binding_ns("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', self._get_binding_name(port_type_name)) + binding.set('type', '%s:%s'% (pref_tns, port_type_name)) + + transport = SubElement(binding, input_binding_ns("binding")) + transport.set('style', 'document') + transport.set('transport', self.interface.app.transport) + + 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, input_binding_ns("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' + input_binding_ns = ns.get_binding_ns(self.interface.app.in_protocol.type) + + 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, input_binding_ns("address")) + cb_address.set('location', url) + + return cb_port_type diff --git a/pym/calculate/contrib/spyne/interface/wsdl/wsdl11.pyc b/pym/calculate/contrib/spyne/interface/wsdl/wsdl11.pyc new file mode 100644 index 0000000..882e3af Binary files /dev/null and b/pym/calculate/contrib/spyne/interface/wsdl/wsdl11.pyc differ diff --git a/pym/calculate/contrib/spyne/interface/xml_schema/__init__.py b/pym/calculate/contrib/spyne/interface/xml_schema/__init__.py new file mode 100644 index 0000000..3e157f5 --- /dev/null +++ b/pym/calculate/contrib/spyne/interface/xml_schema/__init__.py @@ -0,0 +1,25 @@ + +# +# 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 diff --git a/pym/calculate/contrib/spyne/interface/xml_schema/__init__.pyc b/pym/calculate/contrib/spyne/interface/xml_schema/__init__.pyc new file mode 100644 index 0000000..6876e92 Binary files /dev/null and b/pym/calculate/contrib/spyne/interface/xml_schema/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/interface/xml_schema/_base.py b/pym/calculate/contrib/spyne/interface/xml_schema/_base.py new file mode 100644 index 0000000..4cdfa70 --- /dev/null +++ b/pym/calculate/contrib/spyne/interface/xml_schema/_base.py @@ -0,0 +1,297 @@ + +# +# 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 as 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 = ns.NS_XSD +_ns_wsa = ns.NS_WSA +_ns_wsdl = ns.NS_WSDL11 +_ns_soap = ns.NS_WSDL11_SOAP +_pref_wsa = ns.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, ns.XSD('import')) + + 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 = 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(ns.XSD('element')) + 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(ns.XSD('element')) + 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(ns.XSD('schema'), + 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 diff --git a/pym/calculate/contrib/spyne/interface/xml_schema/_base.pyc b/pym/calculate/contrib/spyne/interface/xml_schema/_base.pyc new file mode 100644 index 0000000..b114e58 Binary files /dev/null and b/pym/calculate/contrib/spyne/interface/xml_schema/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/interface/xml_schema/defn.py b/pym/calculate/contrib/spyne/interface/xml_schema/defn.py new file mode 100644 index 0000000..a22fb7c --- /dev/null +++ b/pym/calculate/contrib/spyne/interface/xml_schema/defn.py @@ -0,0 +1,188 @@ + +# +# 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 +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) diff --git a/pym/calculate/contrib/spyne/interface/xml_schema/defn.pyc b/pym/calculate/contrib/spyne/interface/xml_schema/defn.pyc new file mode 100644 index 0000000..a9618fa Binary files /dev/null and b/pym/calculate/contrib/spyne/interface/xml_schema/defn.pyc differ diff --git a/pym/calculate/contrib/spyne/interface/xml_schema/genpy.py b/pym/calculate/contrib/spyne/interface/xml_schema/genpy.py new file mode 100644 index 0000000..21784f8 --- /dev/null +++ b/pym/calculate/contrib/spyne/interface/xml_schema/genpy.py @@ -0,0 +1,144 @@ + +# +# 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) diff --git a/pym/calculate/contrib/spyne/interface/xml_schema/genpy.pyc b/pym/calculate/contrib/spyne/interface/xml_schema/genpy.pyc new file mode 100644 index 0000000..2c55b98 Binary files /dev/null and b/pym/calculate/contrib/spyne/interface/xml_schema/genpy.pyc differ diff --git a/pym/calculate/contrib/spyne/interface/xml_schema/model.py b/pym/calculate/contrib/spyne/interface/xml_schema/model.py new file mode 100644 index 0000000..6b491ba --- /dev/null +++ b/pym/calculate/contrib/spyne/interface/xml_schema/model.py @@ -0,0 +1,397 @@ + +# +# 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 import NS_XSD, 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() + +# 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_unicode(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_unicode(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 = v_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: + attribute = etree.Element(XSD('attribute')) + xml_attribute_add(v, k, attribute, document) + + if cls.Attributes._xml_tag_body_as is None: + sequence_parent.append(attribute) + else: + xtba_ext.append(attribute) + + 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() + + +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_unicode(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_unicode(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_unicode(cls, cls.Attributes.gt)) + + if cls.Attributes.ge != T.Attributes.ge: + elt = etree.SubElement(restriction, XSD('minInclusive')) + elt.set('value', prot.to_unicode(cls, cls.Attributes.ge)) + + if cls.Attributes.lt != T.Attributes.lt: + elt = etree.SubElement(restriction, XSD('maxExclusive')) + elt.set('value', prot.to_unicode(cls, cls.Attributes.lt)) + + if cls.Attributes.le != T.Attributes.le: + elt = etree.SubElement(restriction, XSD('maxInclusive')) + elt.set('value', prot.to_unicode(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 diff --git a/pym/calculate/contrib/spyne/interface/xml_schema/model.pyc b/pym/calculate/contrib/spyne/interface/xml_schema/model.pyc new file mode 100644 index 0000000..be762fe Binary files /dev/null and b/pym/calculate/contrib/spyne/interface/xml_schema/model.pyc differ diff --git a/pym/calculate/contrib/spyne/interface/xml_schema/parser.py b/pym/calculate/contrib/spyne/interface/xml_schema/parser.py new file mode 100644 index 0000000..010c912 --- /dev/null +++ b/pym/calculate/contrib/spyne/interface/xml_schema/parser.py @@ -0,0 +1,705 @@ + +# +# 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. +# + +import logging +logger = logging.getLogger(__name__) + +import os + +from copy import copy +from pprint import pformat +from itertools import chain +from collections import defaultdict +from os.path import dirname, abspath, join + +from lxml import etree + +from spyne.util import memoize +from spyne.util.odict import odict + +from spyne.model import Null, XmlData, XmlAttribute, Array, ComplexModelBase, \ + 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. +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 == 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 = iter(xtba) + 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') + 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 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_unicode(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_unicode(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_unicode(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 e.type and ":" 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 diff --git a/pym/calculate/contrib/spyne/interface/xml_schema/parser.pyc b/pym/calculate/contrib/spyne/interface/xml_schema/parser.pyc new file mode 100644 index 0000000..6599423 Binary files /dev/null and b/pym/calculate/contrib/spyne/interface/xml_schema/parser.pyc differ diff --git a/pym/calculate/contrib/spyne/model/__init__.py b/pym/calculate/contrib/spyne/model/__init__.py new file mode 100644 index 0000000..2426d2f --- /dev/null +++ b/pym/calculate/contrib/spyne/model/__init__.py @@ -0,0 +1,70 @@ + +# +# 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 jsonb +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 diff --git a/pym/calculate/contrib/spyne/model/__init__.pyc b/pym/calculate/contrib/spyne/model/__init__.pyc new file mode 100644 index 0000000..58a2ba7 Binary files /dev/null and b/pym/calculate/contrib/spyne/model/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/model/_base.py b/pym/calculate/contrib/spyne/model/_base.py new file mode 100644 index 0000000..dd92936 --- /dev/null +++ b/pym/calculate/contrib/spyne/model/_base.py @@ -0,0 +1,1070 @@ + +# +# 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. +""" + +from __future__ import print_function + +import logging +logger = logging.getLogger(__name__) + +import re +import decimal +import threading + +import spyne.const.xml + +from copy import deepcopy +from collections import OrderedDict + +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 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, (frozenset, tuple)): + for subk in k: + retval[subk] = v + + for k, v in d.items(): + if not isinstance(k, (frozenset, 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 + + rd = {} + for k in list(cls_dict.keys()): + if k in ('parser', 'cast'): + rd['parser'] = cls_dict.pop(k) + continue + + if k in ('sanitize', 'sanitizer'): + rd['sanitizer'] = cls_dict.pop(k) + continue + + if k == 'logged': + rd['logged'] = cls_dict.pop(k) + continue + + retval = super(AttributesMeta, cls).__new__(cls, cls_name, cls_bases, + cls_dict) + + for k, v in rd.items(): + if v is None: + setattr(retval, k, None) + else: + setattr(retval, k, staticmethod(v)) + + return retval + + def __init__(self, cls_name, cls_bases, cls_dict): + # you will probably want to look at ModelBase._s_customize as well. + if not hasattr(self, '_method_config_do'): + self._method_config_do = None + + 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')) + + if 'method_config_do' in cls_dict and \ + cls_dict['method_config_do'] is not None: + cls_dict['method_config_do'] = \ + staticmethod(cls_dict['method_config_do']) + + 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) + + def get_method_config_do(self): + return self._method_config_do + def set_method_config_do(self, what): + if what is None: + self._method_config_do = None + else: + self._method_config_do = staticmethod(what) + method_config_do = property(get_method_config_do, set_method_config_do) + + +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 = self._s_customize(**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. + + _explicit_type_name = False + # set to true when type_name is passed to customize() call. + + out_type = None + """Override serialization type. Usually, this designates the return type + of the callable in the `sanitizer` attribute. If this is a two-way type, + it may be a good idea to also use the `parser` attribute to perform + reverse conversion.""" + + default = None + """The default value if the input is None. + + Please note that this default is UNCONDITIONALLY applied in class + initializer. It's recommended to at least make an effort to use this + only in customized classes and not in original models. + """ + + default_factory = None + """The callable that produces a default value if the value is None. + + The warnings in ``default`` apply here as well.""" + + db_default = None + """The default value used only when persisting the value if it is + ``None``. + + Only works for primitives. Unlike ``default`` this can also be set to a + callable that takes no arguments according to SQLAlchemy docs.""" + + 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 = spyne.const.xml.XSD('element') + """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. + """ + + wsdl_part_name = None + """This specifies which string should be used as wsdl message part name when this + type is serialized under a ComplexModel ie."parameters". + """ + + 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 + """DEPRECATED !!! Use ``exc_db`` instead.""" + + exc_db = False + """If ``True``, this field will not be persisted to the database. This + attribute only makes sense in a subfield of a ``ComplexModel`` subclass. + """ + + exc_interface = False + """If `True`, this field will be excluded from the interface + document.""" + + exc = False + """If `True`, this field will be excluded from all serialization or + deserialization operations. See `prot_attrs` to make this only apply to + a specific protocol class or instance.""" + + logged = True + """If `False`, this object will be ignored in ``log_repr``, mostly used + for logging purposes. + + * Primitives can have logger=``'...'`` which will + always log the value as ``(...)``. + + * ``AnyDict`` can have one of + ``('keys', 'keys-full', 'values', 'values-full, 'full')`` as logger + value where for ``'keys'`` and ``'values'`` the output of ``keys()`` + and ``values()`` will be logged up to MAX_DICT_ELEMENT_NUM number of + elements and for ``'full'`` variants, all of the contents of the dict + will be logged will be logged + + * ``Array`` can also have ``logger='full'`` where all of the value + will be logged where as for simple ``logger=True`` only + MAX_ARRAY_ELEMENT_NUM elements will be logged. + + * For ``ComplexModel`` subclasses sent as first value to log_repr, + ``logger=False`` means a string of form ``ClassName(...)`` will be + logged. + """ + + sanitizer = None + """A callable that takes the associated native type and returns the + parsed value. Only called during serialization.""" + + parser = None + """A callable that takes the associated native type and returns the + parsed value. Only called during deserialization.""" + + 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. Should be one of: + + ('btree', 'gin', 'gist', 'hash', 'spgist') + + 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.""" + + validate_on_assignment = False + """Perform validation on assignment (i.e. all the time) instead of on + just serialization""" + + 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 + + @classmethod + def ancestors(cls): + """Returns a list of parent classes in child-to-parent order.""" + + retval = [] + + extends = cls.__extends__ + while extends is not None: + retval.append(extends) + extends = extends.__extends__ + + return retval + + @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.DEFAULT_NS: + cls.__namespace__ = default_ns + + if (cls.__namespace__ in spyne.const.xml.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) + + # print(" 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_wsdl_part_name(cls): + return cls.Attributes.wsdl_part_name or cls.get_element_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_bytes(cls, value): + """ + Returns str(value). This should be overridden if this is not enough. + """ + return six.binary_type(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 '' + + @classmethod + def _s_customize(cls, **kwargs): + """Sanitizes customization parameters of the class it belongs to. + Doesn't perform any actual customization. + """ + + def _log_debug(s, *args): + logger.debug("\t%s: %s" % (cls.get_type_name(), s), *args) + + 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): + _explicit_type_name = False + + if cls.Attributes.translations is None: + Attributes.translations = {} + + if cls.Attributes.sqla_column_args is None: + Attributes.sqla_column_args = (), {} + else: + Attributes.sqla_column_args = deepcopy( + cls.Attributes.sqla_column_args) + + cls_dict['Attributes'] = Attributes + + # properties get reset every time 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) + _log_debug("kwargs %r => %r from prot typeattr %r", + kwargs, type_attrs, prot.type_attrs) + kwargs = type_attrs + + # the ones that wrap values in staticmethod() should be added to + # AttributesMeta initializer + for k, v in kwargs.items(): + if k.startswith('_'): + _log_debug("ignoring '%s' because of leading underscore", k) + continue + + if k in ('protocol', 'prot', 'p'): + Attributes.prot = v + _log_debug("setting prot=%r", v) + + elif k in ('voa', 'validate_on_assignment'): + Attributes.validate_on_assignment = v + _log_debug("setting voa=%r", v) + + elif k in ('parser', 'in_cast'): + setattr(Attributes, 'parser', staticmethod(v)) + _log_debug("setting %s=%r", k, v) + + elif k in ('sanitize', 'sanitizer', 'out_cast'): + setattr(Attributes, 'sanitizer', staticmethod(v)) + _log_debug("setting %s=%r as sanitizer", k, v) + + elif k == 'logged': + setattr(Attributes, 'logged', staticmethod(v)) + _log_debug("setting %s=%r as log sanitizer", k, v) + + elif k in ("doc", "appinfo"): + setattr(Annotations, k, v) + _log_debug("setting Annotations.%s=%r", k, v) + + elif k in ('primary_key', 'pk'): + setattr(Attributes, 'primary_key', v) + Attributes.sqla_column_args[-1]['primary_key'] = v + _log_debug("setting primary_key=%r", v) + + elif k in ('protocol_attrs', 'prot_attrs', 'pa'): + setattr(Attributes, 'prot_attrs', _decode_pa_dict(v)) + _log_debug("setting prot_attrs=%r", v) + + elif k in ('foreign_key', 'fk'): + from sqlalchemy.schema import ForeignKey + t, d = Attributes.sqla_column_args + fkt = (ForeignKey(v),) + new_v = (t + fkt, d) + Attributes.sqla_column_args = new_v + _log_debug("setting sqla_column_args=%r", new_v) + + elif k in ('autoincrement', 'onupdate', 'server_default'): + Attributes.sqla_column_args[-1][k] = v + _log_debug("adding %s=%r to Attributes.sqla_column_args", k, v) + + elif k == 'values_dict': + assert not 'values' in v, "`values` and `values_dict` can't be" \ + "specified at the same time" + + if not isinstance(v, dict): + # our odict has one nasty implicit behaviour: setitem on + # int keys is treated as array indexes, not dict keys. so + # dicts with int indexes can't work with odict. so we use + # the one from stdlib + v = OrderedDict(v) + + Attributes.values = list(v.keys()) + Attributes.values_dict = v + _log_debug("setting values=%r, values_dict=%r", + Attributes.values, Attributes.values_dict) + + elif k == 'exc_table': + Attributes.exc_table = v + Attributes.exc_db = v + _log_debug("setting exc_table=%r, exc_db=%r", v, v) + + elif k == 'max_occurs' and v in ('unbounded', 'inf', float('inf')): + new_v = decimal.Decimal('inf') + setattr(Attributes, k, new_v) + _log_debug("setting max_occurs=%r", new_v) + + elif k == 'type_name': + Attributes._explicit_type_name = True + _log_debug("setting _explicit_type_name=True because " + "we have 'type_name'") + + else: + setattr(Attributes, k, v) + _log_debug("setting %s=%r", 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(**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) + if 'type_name' in kwargs: + logger.debug("Type name for %r was overridden as '%s'", + retval, retval.__type_name__) + + 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.orig_thread = threading.current_thread() + + self._cb = callback + self._eb = errback + + self.length = 0 + self.ctx = None + self.app = None + self.gen = None + self._cb_finish = None + self._eb_finish = None + self.interim = False + + def _init(self, ctx, gen, _cb_finish, _eb_finish, interim): + self.length = 0 + + self.ctx = ctx + self.app = ctx.app + + self.gen = gen + + self._cb_finish = _cb_finish + self._eb_finish = _eb_finish + + self.interim = interim + + def init(self, ctx, gen, _cb_finish, _eb_finish, interim): + self._init(ctx, gen, _cb_finish, _eb_finish, interim) + 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 extend(self, insts): + for inst in insts: + 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 https://docs.sqlalchemy.org/en/13/orm/relationship_api.html?highlight=lazy#sqlalchemy.orm.relationship.params.backref + :param cascade: https://docs.sqlalchemy.org/en/13/orm/relationship_api.html?highlight=lazy#sqlalchemy.orm.relationship.params.cascade + :param lazy: See https://docs.sqlalchemy.org/en/13/orm/relationship_api.html?highlight=lazy#sqlalchemy.orm.relationship.params.lazy + :param back_populates: See https://docs.sqlalchemy.org/en/13/orm/relationship_api.html?highlight=lazy#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, + fk_left_ondelete=None, fk_left_onupdate=None, + fk_right_ondelete=None, fk_right_onupdate=None, + explicit_join=False, order_by=False, single_parent=None): + self.multi = multi + self.left = left + self.right = right + self.backref = backref + self.id_backref = id_backref + self.cascade = cascade + self.lazy = lazy + self.back_populates = back_populates + 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.fk_left_ondelete = fk_left_ondelete + self.fk_left_onupdate = fk_left_onupdate + self.fk_right_ondelete = fk_right_ondelete + self.fk_right_onupdate = fk_right_onupdate + self.explicit_join = explicit_join + self.order_by = order_by + self.single_parent = single_parent + + +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 jsonb: + """Compound option object for jsonb serialization. It's meant to be passed + to :func:`ComplexModelBase.Attributes.store_as`. + """ + + 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 + + +PSSM_VALUES = {'json': json, 'jsonb': jsonb, 'xml': xml, + 'msgpack': msgpack, 'table': table} + + +def apply_pssm(val): + if val is not None: + val_c = PSSM_VALUES.get(val, None) + if val_c is None: + assert isinstance(val, tuple(PSSM_VALUES.values())), \ + "'store_as' should be one of: %r or an instance of %r not %r" \ + % (tuple(PSSM_VALUES.keys()), tuple(PSSM_VALUES.values()), val) + + return val + return val_c() diff --git a/pym/calculate/contrib/spyne/model/_base.pyc b/pym/calculate/contrib/spyne/model/_base.pyc new file mode 100644 index 0000000..f9cb01f Binary files /dev/null and b/pym/calculate/contrib/spyne/model/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/model/addtl.py b/pym/calculate/contrib/spyne/model/addtl.py new file mode 100644 index 0000000..6bc83cf --- /dev/null +++ b/pym/calculate/contrib/spyne/model/addtl.py @@ -0,0 +1,98 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import re +from spyne import M, Boolean, DateTime, Date, Time, ComplexModel, \ + ValidationError +from spyne.protocol import InProtocolBase + + +class SegmentBase(object): + @classmethod + def from_string(cls, s): + match = cls._SEGMENT_RE.match(s) + if match is None: + raise ValidationError(s) + start_incl, start_str, end_str, end_incl = match.groups() + + print() + print(start_incl, start_str, end_str, end_incl) + + start_incl = (start_incl == '[') + start = InProtocolBase().from_unicode( + cls._type_info['start'], start_str) + end = InProtocolBase().from_unicode(cls._type_info['start'], end_str) + end_incl = (end_incl == ']') + + print(start_incl, start, end, end_incl) + + return cls(start_inclusive=start_incl, start=start, end=end, + end_inclusive=end_incl) + + def to_string(self): + return '[%s,%s]' % (self.start.isoformat(), self.end.isoformat()) + + +class DateTimeSegment(ComplexModel, SegmentBase): + _SEGMENT_RE = re.compile( + u"([\\[\\]])" + u"([0-9:\\.T-]+)" + u"," + u"([0-9:\\.T-]+)" + u"([\\[\\]])", re.DEBUG | re.UNICODE) + + _type_info = [ + ('start_inclusive', M(Boolean(default=True))), + ('start', M(DateTime)), + ('end', M(DateTime)), + ('end_inclusive', M(Boolean(default=True))), + ] + + + +class DateSegment(ComplexModel, SegmentBase): + _SEGMENT_RE = re.compile( + u"([\\[\\]])" + u"([0-9-]+)" + u"," + u"([0-9-]+)" + u"([\\[\\]])", re.DEBUG | re.UNICODE) + + _type_info = [ + ('start_inclusive', M(Boolean(default=True))), + ('start', M(Date)), + ('end', M(Date)), + ('end_inclusive', M(Boolean(default=True))), + ] + + +class TimeSegment(ComplexModel, SegmentBase): + _SEGMENT_RE = re.compile( + u"([\\[\\]])" + u"([0-9:\\.]+)" + u"," + u"([0-9:\\.]+)" + u"([\\[\\]])", re.DEBUG | re.UNICODE) + + _type_info = [ + ('start_inclusive', M(Boolean(default=True))), + ('start', M(Time)), + ('end', M(Time)), + ('end_inclusive', M(Boolean(default=True))), + ] diff --git a/pym/calculate/contrib/spyne/model/addtl.pyc b/pym/calculate/contrib/spyne/model/addtl.pyc new file mode 100644 index 0000000..09c85f8 Binary files /dev/null and b/pym/calculate/contrib/spyne/model/addtl.pyc differ diff --git a/pym/calculate/contrib/spyne/model/binary.py b/pym/calculate/contrib/spyne/model/binary.py new file mode 100644 index 0000000..9c49470 --- /dev/null +++ b/pym/calculate/contrib/spyne/model/binary.py @@ -0,0 +1,386 @@ + +# +# 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 +import errno + +from mmap import mmap, ACCESS_READ, error as MmapError +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.error import ValidationError +from spyne.util import _bytes_join +from spyne.model import ComplexModel, Unicode +from spyne.model import SimpleModel +from spyne.util import six +from spyne.util.six import BytesIO, StringIO + +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): + if isinstance(value, (list, tuple)) and isinstance(value[0], mmap): + return b64encode(value[0]) + + if isinstance(value, (six.binary_type, memoryview, mmap)): + return b64encode(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') + try: + return (urlsafe_b64decode(_bytes_join(value)),) + + except TypeError as e: + logger.exception(e) + + if len(value) > 100: + raise ValidationError(value) + else: + raise ValidationError(value[:100] + "(...)") + + @classmethod + def to_hex(cls, value): + return hexlify(_bytes_join(value)) + + @classmethod + def from_hex(cls, value): + return (unhexlify(_bytes_join(value)),) + + +def _default_binary_encoding(b): + if isinstance(b, (six.binary_type, memoryview)): + return b + + if isinstance(b, tuple) and len(b) > 0 and isinstance(b[0], mmap): + return b[0] + + if isinstance(b, six.text_type): + raise ValueError(b) + + return b''.join(b) + + +binary_encoding_handlers = { + None: _default_binary_encoding, + 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 SanitizationError(ValidationError): + def __init__(self, obj): + super(SanitizationError, self).__init__( + obj, "%r was not sanitized before use") + + +class _FileValue(ComplexModel): + """The class for values marked as ``File``. + + :param name: Original name of 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. + """ + # ^ This is the public docstring. + + __type_name__ = "FileValue" + + _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, _sanitize=True): + + self.name = name + """The file basename, no directory information here.""" + + if self.name is not None and _sanitize: + if not os.path.basename(self.name) == self.name: + raise ValidationError(self.name, + "File name %r should not contain any directory information") + + self.sanitized = _sanitize + + self.path = path + """Relative path of the file.""" + + self.type = type + """Mime type of the file""" + + self.data = data + """The contents of the file. It's a sequence of str/bytes objects by + contract. It can contain the contents of a the file as a single + instance of `mmap.mmap()` object inside a tuple.""" + + self.handle = handle + """The file handle.""" + + self.move = move + """When True, Spyne can move the file (instead of copy) to its final + location where it will be persisted. Defaults to `False`. See PGFile* + objects to see how it's used.""" + + self.abspath = None + """The absolute path of the file. It can be None even when the data is + file-backed.""" + + 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 not self.sanitized: + raise SanitizationError(self) + + if self.data is not None: + if self.path is None: + self.handle = tempfile.NamedTemporaryFile() + self.abspath = self.path = self.handle.name + self.name = basename(self.abspath) + else: + self.handle = open(self.path, 'wb') + # FIXME: abspath could be None here, how do we make sure it's + # the right value? + + # 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: + try: + if isinstance(self.handle, (StringIO, BytesIO)): + self.data = (self.handle.getvalue(),) + else: + # 0 = whole file + self.data = (mmap(self.handle.fileno(), 0),) + + except MmapError as e: + if e.errno == errno.EACCES: + self.data = ( + mmap(self.handle.fileno(), 0, access=ACCESS_READ), + ) + else: + raise + + 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 = _TEXT + Value = _FileValue + + 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 = _FileValue + """The native type used to serialize the information in the file object. + """ + + mode = _BINARY + """Set this to mode=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: + return + + 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) diff --git a/pym/calculate/contrib/spyne/model/binary.pyc b/pym/calculate/contrib/spyne/model/binary.pyc new file mode 100644 index 0000000..be43674 Binary files /dev/null and b/pym/calculate/contrib/spyne/model/binary.pyc differ diff --git a/pym/calculate/contrib/spyne/model/complex.py b/pym/calculate/contrib/spyne/model/complex.py new file mode 100644 index 0000000..975418a --- /dev/null +++ b/pym/calculate/contrib/spyne/model/complex.py @@ -0,0 +1,1631 @@ + +# +# 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 +import traceback + +from copy import copy +from weakref import WeakKeyDictionary +from collections import deque, OrderedDict +from inspect import isclass +from itertools import chain + +from spyne import const +from spyne.const.xml import PREFMAP + +from spyne.model import Point, Unicode, PushBase, ModelBase +from spyne.model._base import PSSM_VALUES, apply_pssm +from spyne.model.primitive import NATIVE_MAP + +from spyne.util import six, memoize, memoize_id, sanitize_args, \ + memoize_ignore_none +from spyne.util.color import YEL +from spyne.util.meta import Prepareable +from spyne.util.odict import odict +from spyne.util.six import add_metaclass, with_metaclass, string_types + +# FIXME: for backwards compatibility, to be removed in Spyne 3 +# noinspection PyUnresolvedReferences +from spyne.model import json, jsonb, xml, msgpack, table + + +def _get_flat_type_info(cls, retval): + assert isinstance(retval, TypeInfo) + parent = getattr(cls, '__extends__', None) + if not (parent is 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 + retval.attrs.update({k: v for (k, v) in cls._type_info.items() + if issubclass(v, XmlAttribute)}) + return retval + + +class TypeInfo(odict): + def __init__(self, *args, **kwargs): + super(TypeInfo, self).__init__(*args, **kwargs) + + self.attributes = {} + self.alt = OrderedDict() + self.attrs = OrderedDict() + + 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 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_bytes(cls.type, value) + else: + parent_elt[-1].tail = prot.to_bytes(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.""" + + def __new__(cls, type_, use=None, ns=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' + 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 = list(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 k.startswith('_'): + continue + + if isinstance(v, tuple) and len(v) == 1 and \ + _get_spyne_type(cls_name, k, v[0]) is not None: + logger.warning(YEL("There seems to be a stray comma in the" + "definition of '%s.%s'.", cls_name, k)) + + v = _get_spyne_type(cls_name, k, v) + + if v is None: + continue + + 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) + + for k, v in reversed(mixin.items()): + _type_info.insert(0, (k, v)) + + return _type_info + + +class _MethodsDict(dict): + def __init__(self, *args, **kwargs): + super(_MethodsDict, self).__init__(*args, **kwargs) + + self._processed = False + + +def _gen_methods(cls, cls_dict): + methods = _MethodsDict() + for k, v in cls_dict.items(): + if not k.startswith('_') and hasattr(v, '_is_rpc'): + logger.debug("Registering %s as member method for %r", k, cls) + assert cls is not None + + # generate method descriptor from information in the decorator + descriptor = v(_default_function_name=k, _self_ref_replacement=cls) + + # strip the decorator and put the original function in the class + setattr(cls, k, descriptor.function) + + # modify the descriptor with user-supplied class + if cls.Attributes.method_config_do is not None: + descriptor = cls.Attributes.method_config_do(descriptor) + + 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 + + +D_EXC = dict(exc=True) + + +def _process_child_attrs(cls, retval, kwargs): + child_attrs = copy(kwargs.get('child_attrs', None)) + child_attrs_all = kwargs.get('child_attrs_all', None) + child_attrs_noexc = copy(kwargs.get('child_attrs_noexc', None)) + + # add exc=False to child_attrs_noexc + if child_attrs_noexc is not None: + # if there is _noexc, make sure that child_attrs_all is also used to + # exclude exclude everything else first + if child_attrs_all is None: + child_attrs_all = D_EXC + + else: + if 'exc' in child_attrs_all and child_attrs_all['exc'] != D_EXC: + logger.warning("Overriding child_attrs_all['exc'] to True " + "for %r", cls) + + child_attrs_all.update(D_EXC) + + # update child_attrs_noexc with exc=False + for k, v in child_attrs_noexc.items(): + if 'exc' in v: + logger.warning("Overriding 'exc' for %s.%s from " + "child_attrs_noexc with False", cls.get_type_name(), k) + + v['exc'] = False + + # update child_attrs with data from child_attrs_noexc + if child_attrs is None: + child_attrs = child_attrs_noexc + + else: + # update with child_attrs_noexc with exc=False + if child_attrs is None: + child_attrs = dict() + + for k, v in child_attrs_noexc.items(): + if k in child_attrs: + logger.warning("Overriding child_attrs for %s.%s from " + "child_attrs_noexc", cls.get_type_name(), k) + + child_attrs[k] = v + + 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 + + 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 + + +def recust_selfref(selfref, cls): + if len(selfref.customize_args) > 0 or len(selfref.customize_kwargs) > 0: + logger.debug("Replace self reference with %r with *%r and **%r", + cls, selfref.customize_args, selfref.customize_kwargs) + return cls.customize(*selfref.customize_args, + **selfref.customize_kwargs) + logger.debug("Replace self reference with %r", cls) + return cls + + +def _set_member_default(inst, key, cls, attr): + def_val = attr.default + def_fac = attr.default_factory + + if def_fac is None and def_val is None: + return False + + 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(inst, key, dval) + + return True + + if def_val is not None: + # should not check for read-only for default values + setattr(inst, key, def_val) + + return True + + assert False, "Invalid application state" + + +def _is_sqla_array(cls, attr): + # inner object is complex + ret1 = issubclass(cls, Array) and \ + hasattr(cls.get_inner_type(), '_sa_class_manager') + + # inner object is primitive + ret2 = issubclass(cls, Array) and attr.store_as is not None + + # object is a bare array + ret3 = attr.max_occurs > 1 and hasattr(cls, '_sa_class_manager') + + return ret1 or ret2 or ret3 + + +def _init_member(inst, key, cls, attr): + cls_getattr_ret = getattr(inst.__class__, key, None) + + if isinstance(cls_getattr_ret, property) and cls_getattr_ret.fset is None: + return # we skip read-only properties + + if _set_member_default(inst, key, cls, attr): + return + + # sqlalchemy objects do their own init. + if _is_sqla_array(cls, attr): + # except the attributes that sqlalchemy doesn't know about + if attr.exc_db: + setattr(inst, key, None) + + elif attr.store_as is None: + setattr(inst, key, None) + + return + + # sqlalchemy objects do their own init. + if hasattr(inst.__class__, '_sa_class_manager'): + # except the attributes that sqlalchemy doesn't know about + if attr.exc_db: + setattr(inst, key, None) + + elif issubclass(cls, ComplexModelBase) and attr.store_as is None: + setattr(inst, key, None) + + return + + setattr(inst, key, None) + + +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 + + # sanitize fields + for k, v in type_info.items(): + # replace bare SelfRerefence + if issubclass(v, SelfReference): + self._replace_field(k, recust_selfref(v, self)) + + # cache XmlData for easier access + 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)) + + # replace SelfRerefence in arrays + 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(recust_selfref(v2, self)) + + # apply field order + # 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 + + # install checkers for validation on assignment + for k, v in self._type_info.items(): + if not v.Attributes.validate_on_assignment: + continue + + def _get_prop(self): + return self.__dict__[k] + + def _set_prop(self, val): + if not (val is None or isinstance(val, v.Value)): + raise ValueError("Invalid value %r, " + "should be an instance of %r" % (val, v.Value)) + + self.__dict__[k] = val + + setattr(self, k, property(_get_prop, _set_prop)) + + # process member rpc methods + methods = _gen_methods(self, cls_dict) + if len(methods) > 0: + self.Attributes.methods = methods + + # finalize sql table mapping + tn = self.Attributes.table_name + meta = self.Attributes.sqla_metadata + t = self.Attributes.sqla_table + + # 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() + + +_is_array = lambda v: issubclass(v, Array) or (v.Attributes.max_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 subclass.""" % (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 + """A dict of member RPC methods (typically marked with @mrpc).""" + + method_config_do = None + """When not None, it's a callable that accepts a ``@mrpc`` method + descriptor and returns a modified version.""" + + not_wrapped = None + """When True, serializes to non-wrapped object, overriding the protocol + flag.""" + + wrapped = None + """When True, serializes to a wrapped object, overriding the protocol + flag. When a str/bytes/unicode value, uses that value as key wrapper + object name.""" + + _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__ + cls_attr = cls.Attributes + fti = cls.get_flat_type_info(cls) + + if cls.__orig__ is not None: + logger.warning("%r(0x%X) seems to be a customized class. It is not " + "supposed to be instantiated. You have been warned.", + cls, id(cls)) + logger.debug(traceback.format_stack()) + + if cls_attr._xml_tag_body_as is not None: + for arg, (xtba_key, xtba_type) in \ + zip(args, cls_attr._xml_tag_body_as): + + if xtba_key is not None and len(args) == 1: + attr = xtba_type.Attributes + _init_member(self, xtba_key, xtba_type, attr) + self._safe_set(xtba_key, arg, xtba_type, + xtba_type.Attributes) + 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(): + attr = v.Attributes + if not k in self.__dict__: + _init_member(self, k, v, attr) + + if k in kwargs: + self._safe_set(k, kwargs[k], v, attr) + + 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, attrs): + if attrs.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 + + @classmethod + def get_identifiers(cls): + for k, v in cls.get_flat_type_info(cls).items(): + if getattr(v.Attributes, 'primary_key', None): + yield k, v + + @classmethod + def get_primary_keys(cls): + return cls.get_identifiers() + + 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.__class__) + 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): + keys = cls.get_flat_type_info(cls).keys() + + if not len(value) <= len(keys): + logger.error("\n\tcls: %r" "\n\tvalue: %r" "\n\tkeys: %r", + cls, value, keys) + raise ValueError("Impossible sequence to instance conversion") + + cls_orig = cls + if cls.__orig__ is not None: + cls_orig = cls.__orig__ + + try: + inst = cls_orig() + + except Exception as e: + logger.error("Error instantiating %r: %r", cls_orig, e) + raise + + 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.get_flat_type_info(cls): + setattr(inst, k, value.get(k, None)) + + else: + inst = value + + return inst + + @classmethod + def get_deserialization_instance(cls, ctx): + """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_ignore_none + 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 + 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 ``'.'``. + """ + return ComplexModelBase.get_simple_type_info_with_prot( + cls, hier_delim=hier_delim) + + @staticmethod + @memoize + def get_simple_type_info_with_prot(cls, prot=None, hier_delim="."): + """See :func:ComplexModelBase.get_simple_type_info""" + fti = cls.get_flat_type_info(cls) + + retval = TypeInfo() + tags = set() + + queue = deque() + if prot is None: + for k, v in fti.items(): + sub_name = k + + queue.append(( + (k,), + v, + (sub_name,), + (_is_array(v),), + cls, + )) + + else: + for k, v in fti.items(): + cls_attrs = prot.get_cls_attrs(v) + sub_name = cls_attrs.sub_name + if sub_name is None: + sub_name = k + + queue.append(( + (k,), + v, + (sub_name,), + (_is_array(v),), + cls, + )) + + tags.add(cls) + + while len(queue) > 0: + keys, v, prefix, is_array, parent = queue.popleft() + k = keys[-1] + 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=keys, + parent=parent, + type_=v, + is_array=tuple(is_array), + can_be_empty=True, + ) + + if not (v in tags): + tags.add(v) + if prot is None: + for k2, v2 in v.get_flat_type_info(v).items(): + sub_name = k2 + queue.append(( + keys + (k2,), + v2, + prefix + (sub_name,), + is_array + (_is_array(v),), + v + )) + + else: + for k2, v2 in v.get_flat_type_info(v).items(): + cls_attrs = prot.get_cls_attrs(v2) + sub_name = cls_attrs.sub_name + if sub_name is None: + sub_name = k2 + + queue.append(( + keys + (k2,), + v2, + prefix + (sub_name,), + is_array + (_is_array(v),), + v, + )) + + 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=keys, + 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)) + if store_as is not None: + kwargs['store_as'] = store_as + + cls_name, cls_bases, cls_dict = cls._s_customize(**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_with_prot.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_with_prot.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_with_prot.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_with_prot.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.get_flat_type_info(cls).items(): + try: + if k in kwargs: + retval._safe_set(k, kwargs[k], v, v.Attributes) + + elif hasattr(other, k): + retval._safe_set(k, getattr(other, k), v, v.Attributes) + + except AttributeError as e: + logger.warning("Error setting %s: %r", k, e) + + return retval + + @classmethod + def __respawn__(cls, ctx=None, filters=None): + if ctx is not None and ctx.in_object is not None and \ + len(ctx.in_object) > 0: + retval = next(iter(ctx.in_object)) + if retval is not None: + return retval + + if ctx.descriptor.default_on_null: + return cls.get_deserialization_instance(ctx) + + +@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'] + + logger.debug('Pass serializer attrs %r', 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 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, ctx): + return [] + + @classmethod + def get_inner_type(cls): + return next(iter(cls._type_info.values())) + + +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): + """The push interface to the `Iterable`. + + Anything append()'ed to a `Push` instance is serialized and written to + outgoing stream immediately. + + When using Twisted, Push callbacks are called from the reactor thread if + the instantiation is done in a reactor thread. Otherwise, callbacks are + called by `deferToThread`. Make sure to avoid relying on thread-local + stuff as `deferToThread` is not guaranteed to restore original thread + context. + """ + pass + + +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 +def TTableModel(metadata=None, base=None, metaclass=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 + + if base is None: + base = TTableModelBase() + if metaclass is None: + metaclass = ComplexModelMeta + + @add_metaclass(metaclass) + class TableModel(base): + class Attributes(ComplexModelBase.Attributes): + sqla_metadata = metadata if metadata is not None else 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) diff --git a/pym/calculate/contrib/spyne/model/complex.pyc b/pym/calculate/contrib/spyne/model/complex.pyc new file mode 100644 index 0000000..a5e695b Binary files /dev/null and b/pym/calculate/contrib/spyne/model/complex.pyc differ diff --git a/pym/calculate/contrib/spyne/model/enum.py b/pym/calculate/contrib/spyne/model/enum.py new file mode 100644 index 0000000..ec0901d --- /dev/null +++ b/pym/calculate/contrib/spyne/model/enum.py @@ -0,0 +1,121 @@ + +# +# 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 diff --git a/pym/calculate/contrib/spyne/model/enum.pyc b/pym/calculate/contrib/spyne/model/enum.pyc new file mode 100644 index 0000000..2d9c03b Binary files /dev/null and b/pym/calculate/contrib/spyne/model/enum.pyc differ diff --git a/pym/calculate/contrib/spyne/model/fault.py b/pym/calculate/contrib/spyne/model/fault.py new file mode 100644 index 0000000..89c9f29 --- /dev/null +++ b/pym/calculate/contrib/spyne/model/fault.py @@ -0,0 +1,175 @@ + +# +# 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 warnings import warn +from collections import defaultdict + +import spyne.const + +from spyne.model.primitive import Any + +from spyne.util.six import add_metaclass + +from spyne.model.complex import ComplexModelMeta +from spyne.model.complex import ComplexModelBase + + +class FaultMeta(ComplexModelMeta): + def __init__(self, cls_name, cls_bases, cls_dict): + super(FaultMeta, self).__init__(cls_name, cls_bases, cls_dict) + + code = cls_dict.get('CODE', None) + + if code is not None: + target = Fault.REGISTERED[code] + target.add(self) + if spyne.const.WARN_ON_DUPLICATE_FAULTCODE and len(target) > 1: + warn("Duplicate faultcode {} detected for classes {}" + .format(code, target)) + + +@add_metaclass(FaultMeta) +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. + """ + + REGISTERED = defaultdict(set) + """Class-level variable that holds a multimap of all fault codes and the + associated classes.""" + + __type_name__ = "Fault" + + CODE = None + + 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): + if self.detail is None: + return "%s(%s: %r)" % (self.__class__.__name__, + self.faultcode, self.faultstring) + + return "%s(%s: %r detail: %r)" % (self.__class__.__name__, + self.faultcode, self.faultstring, self.detail) + + @staticmethod + def to_dict(cls, value, prot): + if not issubclass(cls, Fault): + return { + "faultcode": "Server.Unknown", + "faultstring": cls.__name__, + "detail": str(value), + } + + retval = { + "faultcode": value.faultcode, + "faultstring": value.faultstring, + } + + if value.faultactor is not None: + if len(value.faultactor) > 0 or (not prot.ignore_empty_faultactor): + retval["faultactor"] = value.faultactor + + if value.detail is not None: + retval["detail"] = value.detail_to_doc(prot) + + return retval + + # + # From http://schemas.xmlsoap.org/soap/envelope/ + # + # + # + # + # + # + @staticmethod + def to_list(cls, value, prot=None): + if not issubclass(cls, Fault): + return [ + "Server.Unknown", # faultcode + cls.__name__, # faultstring + "", # faultactor + str(value), # detail + ] + + retval = [ + value.faultcode, + value.faultstring, + ] + + if value.faultactor is not None: + retval.append(value.faultactor) + else: + retval.append("") + + if value.detail is not None: + retval.append(value.detail_to_doc(prot)) + else: + retval.append("") + + return retval + + @classmethod + def to_bytes_iterable(cls, value): + return [ + value.faultcode.encode('utf8'), + b'\n\n', + value.faultstring.encode('utf8'), + ] + + def detail_to_doc(self, prot): + return self.detail + + def detail_from_doc(self, prot, doc): + self.detail = doc diff --git a/pym/calculate/contrib/spyne/model/fault.pyc b/pym/calculate/contrib/spyne/model/fault.pyc new file mode 100644 index 0000000..ac265bd Binary files /dev/null and b/pym/calculate/contrib/spyne/model/fault.pyc differ diff --git a/pym/calculate/contrib/spyne/model/primitive/__init__.py b/pym/calculate/contrib/spyne/model/primitive/__init__.py new file mode 100644 index 0000000..64f1ba4 --- /dev/null +++ b/pym/calculate/contrib/spyne/model/primitive/__init__.py @@ -0,0 +1,155 @@ + +# +# 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.string import MimeType +from spyne.model.primitive.string import MimeTypeStrict +from spyne.model.primitive.string import MediaType +from spyne.model.primitive.string import MediaTypeStrict + +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 QName +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 NumberLimitsWarning + +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 diff --git a/pym/calculate/contrib/spyne/model/primitive/__init__.pyc b/pym/calculate/contrib/spyne/model/primitive/__init__.pyc new file mode 100644 index 0000000..8eb7052 Binary files /dev/null and b/pym/calculate/contrib/spyne/model/primitive/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/model/primitive/_base.py b/pym/calculate/contrib/spyne/model/primitive/_base.py new file mode 100644 index 0000000..d6ea85f --- /dev/null +++ b/pym/calculate/contrib/spyne/model/primitive/_base.py @@ -0,0 +1,130 @@ + +# +# 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) + # if m: + # print(m, m.span(), len(value)) + # else: + # print(m) + 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""" + + +class Any(SimpleModel): + @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)) + if store_as is not None: + kwargs['store_as'] = store_as + + return super(Any, cls).customize(**kwargs) + + +class AnyHtml(SimpleModel): + __type_name__ = 'string' + + @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)) + if store_as is not None: + kwargs['store_as'] = store_as + + return super(AnyHtml, cls).customize(**kwargs) + + +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', + 'jsonb', 'msgpack'. It makes sense to specify this only when this object + belongs to 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)) + 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.""" + + class Attributes(SimpleModel.Attributes): + store_as = bool + """Method for serializing to persistent storage. One of `bool` or `int` + builtins. It makes sense to specify this only when this object belongs + to a `ComplexModel` sublass.""" + + __type_name__ = 'boolean' + + +NATIVE_MAP.update({ + bool: Boolean, +}) diff --git a/pym/calculate/contrib/spyne/model/primitive/_base.pyc b/pym/calculate/contrib/spyne/model/primitive/_base.pyc new file mode 100644 index 0000000..20fdbde Binary files /dev/null and b/pym/calculate/contrib/spyne/model/primitive/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/model/primitive/datetime.py b/pym/calculate/contrib/spyne/model/primitive/datetime.py new file mode 100644 index 0000000..161b85e --- /dev/null +++ b/pym/calculate/contrib/spyne/model/primitive/datetime.py @@ -0,0 +1,273 @@ + +# +# 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 = None # 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 = None # 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""" + + time_format = None + """Time 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.""" + + @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 ( + (cls.Attributes.gt is None or value > cls.Attributes.gt) + and value >= cls.Attributes.ge + and (cls.Attributes.lt is None or 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 = None # 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 = None # 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""" + + dt_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')""" + + # TODO: Move this to ModelBase and make it work with all types in all + # protocols. + parser = None + """Callable for string parser. It must accept exactly four arguments: + `protocol, cls, string` and must return a `datetime.datetime` object. + If this is not None, all other parsing configurations (e.g. + `date_format`) are ignored. + """ + + @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 None 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 None 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 = None # 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 = None # 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.""" + + date_format = None + """Date 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, +}) diff --git a/pym/calculate/contrib/spyne/model/primitive/datetime.pyc b/pym/calculate/contrib/spyne/model/primitive/datetime.pyc new file mode 100644 index 0000000..07d65cd Binary files /dev/null and b/pym/calculate/contrib/spyne/model/primitive/datetime.pyc differ diff --git a/pym/calculate/contrib/spyne/model/primitive/network.py b/pym/calculate/contrib/spyne/model/primitive/network.py new file mode 100644 index 0000000..dfbc292 --- /dev/null +++ b/pym/calculate/contrib/spyne/model/primitive/network.py @@ -0,0 +1,157 @@ + +# +# 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 a MAC address.""" + + __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 = r"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])" +_PATT_IPV4 = r"(%(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 an IPv4 address.""" + + __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 an IPv6 address.""" + + __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 an IPv4 or IPv6 address.""" + + __namespace__ = 'http://spyne.io/schema' + __type_name__ = 'addr_ip' + + class Attributes(_IpAddressBase.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) diff --git a/pym/calculate/contrib/spyne/model/primitive/network.pyc b/pym/calculate/contrib/spyne/model/primitive/network.pyc new file mode 100644 index 0000000..595d0de Binary files /dev/null and b/pym/calculate/contrib/spyne/model/primitive/network.pyc differ diff --git a/pym/calculate/contrib/spyne/model/primitive/number.py b/pym/calculate/contrib/spyne/model/primitive/number.py new file mode 100644 index 0000000..e10c08b --- /dev/null +++ b/pym/calculate/contrib/spyne/model/primitive/number.py @@ -0,0 +1,417 @@ + +# +# 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 _warnings import warn + +from spyne.model import SimpleModel +from spyne.model.primitive import NATIVE_MAP +from spyne.util import six + + +class NumberLimitsWarning(Warning): + pass + + +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/2/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] + + retval = SimpleModel.__new__(cls, **kwargs) + + return retval + + @classmethod + def _s_customize(cls, **kwargs): + td = kwargs.get('total_digits', None) + fd = kwargs.get('fraction_digits', None) + if td is not None and fd is not None: + assert td > 0, "'total_digits' must be positive." + assert fd <= td, \ + "'total_digits' must be greater than" \ + " or equal to 'fraction_digits'." \ + " %r ! <= %r" % (fd, td) + + msl = kwargs.get('max_str_len', None) + if msl is None: + kwargs['max_str_len'] = cls.Attributes.total_digits + 2 + # + 1 for decimal separator + # + 1 for negative sign + + else: + kwargs['max_str_len'] = msl + + minb = cls.Attributes.min_bound + maxb = cls.Attributes.max_bound + ge = kwargs.get("ge", None) + gt = kwargs.get("gt", None) + le = kwargs.get("le", None) + lt = kwargs.get("lt", None) + + if minb is not None: + if ge is not None and ge < minb: + warn("'Greater than or equal value' %d smaller than min_bound %d" + % (ge, minb), NumberLimitsWarning) + + if gt is not None and gt < minb: + warn("'Greater than' value %d smaller than min_bound %d" + % (gt, minb), NumberLimitsWarning) + + if le is not None and le < minb: + raise ValueError( + "'Little than or equal' value %d smaller than min_bound %d" + % (le, minb)) + + if lt is not None and lt <= minb: + raise ValueError( + "'Little than' value %d smaller than min_bound %d" + % (lt, minb)) + + if maxb is not None: + if le is not None and le > maxb: + warn("'Little than or equal' value %d greater than max_bound %d" + % (le, maxb), NumberLimitsWarning) + + if lt is not None and lt > maxb: + warn("'Little than' value %d greater than max_bound %d" + % (lt, maxb), NumberLimitsWarning) + + if ge is not None and ge > maxb: + raise ValueError( + "'Greater than or equal' value %d greater than max_bound %d" + % (ge, maxb)) + + if gt is not None and gt >= maxb: + raise ValueError( + "'Greater than' value %d greater than max_bound %d" + % (gt, maxb)) + + return super(Decimal, cls)._s_customize(**kwargs) + + @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)) + + +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 + + +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 not six.PY2: + 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 diff --git a/pym/calculate/contrib/spyne/model/primitive/number.pyc b/pym/calculate/contrib/spyne/model/primitive/number.pyc new file mode 100644 index 0000000..8db8a36 Binary files /dev/null and b/pym/calculate/contrib/spyne/model/primitive/number.pyc differ diff --git a/pym/calculate/contrib/spyne/model/primitive/spatial.py b/pym/calculate/contrib/spyne/model/primitive/spatial.py new file mode 100644 index 0000000..cf3ada5 --- /dev/null +++ b/pym/calculate/contrib/spyne/model/primitive/spatial.py @@ -0,0 +1,263 @@ + +# +# 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' % _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' % _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' % _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' % _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' % _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): + if isinstance(x, str) or isinstance(y, str): + assert isinstance(x, str) + assert isinstance(y, str) + return 'POINT(%s %s)' % (x, y) + + 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 + diff --git a/pym/calculate/contrib/spyne/model/primitive/spatial.pyc b/pym/calculate/contrib/spyne/model/primitive/spatial.pyc new file mode 100644 index 0000000..b2119a2 Binary files /dev/null and b/pym/calculate/contrib/spyne/model/primitive/spatial.pyc differ diff --git a/pym/calculate/contrib/spyne/model/primitive/string.py b/pym/calculate/contrib/spyne/model/primitive/string.py new file mode 100644 index 0000000..bce51ae --- /dev/null +++ b/pym/calculate/contrib/spyne/model/primitive/string.py @@ -0,0 +1,307 @@ + +# +# 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 unicode_literals + +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 = r"\w+(\.\w+)*" + +# Actual ltree max size is 65536 but it's advised to keep it under 2048. +LTREE_OPTIMAL_SIZE = 2048 +LTREE_MAXIMUM_SIZE = 65536 + + +def _gen_mime_type_pattern(strict, with_params): + ows = "[ \\t]*" # Optional WhiteSpace + token = "[0-9A-Za-z!#$%&'*+.^_`|~-]+" + quotedString = "\"(?:[^\"\\\\]|\\.)*\"" + if strict: + main_type = "(" \ + "application|audio|font|example|image|message|model|multipart" \ + "|text|video|x-(?:" + token + ")" \ + ")" + + else: + main_type = token + + param = token + "=" + "(?:" + token + "|" + quotedString + ");?" + ows + params = ";" + ows + param + "(" + param + ")*" + + if not with_params: + return main_type + "/" + "(" + token + ")" + else: + return main_type + "/" + "(" + token + ")" + params + + +MIME_TYPE_PATTERN_STRICT = \ + _gen_mime_type_pattern(strict=True, with_params=False) +MIME_TYPE_PATTERN_PERMISSIVE = \ + _gen_mime_type_pattern(strict=False, with_params=False) + +MEDIA_TYPE_PATTERN_STRICT = \ + _gen_mime_type_pattern(strict=True, with_params=True) +MEDIA_TYPE_PATTERN_PERMISSIVE = \ + _gen_mime_type_pattern(strict=False, with_params=True) + + +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 binary data 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""" + + cast = None + """Type override callable for casting non-unicode input to unicode.""" + + 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 not six.PY2: + 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.""" + + anchor_class = None + """The class of the generated tag.""" + + 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 + + def __repr__(self): + return "Uri(href={0!r}, text={1!r}, content={2!r})" \ + .format(self.href, self.text, self.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' + + +class LtreeLarge(Unicode(LTREE_MAXIMUM_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__ = 'largeLtreeString' + + +class MimeTypeStrict(Unicode(unicode_pattern=MIME_TYPE_PATTERN_STRICT)): + """A special kind of String type designed to hold a mime type as defined + by IANA.""" + + __namespace__ = 'http://spyne.io/schema' + __type_name__ = 'strictMimeTypeString' + + +class MimeType(Unicode(unicode_pattern=MIME_TYPE_PATTERN_PERMISSIVE)): + """A special kind of String type designed to hold a forward-compatible + mime type that can have any string as main type.""" + + __namespace__ = 'http://spyne.io/schema' + __type_name__ = 'mimeTypeString' + + +class MediaTypeStrict(Unicode(unicode_pattern=MEDIA_TYPE_PATTERN_STRICT)): + """A special kind of String type designed to hold a mime type as defined + by IANA followed by arbitrary parameters. + + See: https://tools.ietf.org/html/rfc7231#section-3.1.1.1""" + + __namespace__ = 'http://spyne.io/schema' + __type_name__ = 'strictMediaTypeString' + + +class MediaType(Unicode(unicode_pattern=MEDIA_TYPE_PATTERN_PERMISSIVE)): + """A special kind of String type designed to hold a forward-compatible + media type that can have any string as main type. A media type is + essentially a mime type plus parameters. + + See: https://tools.ietf.org/html/rfc7231#section-3.1.1.1""" + + __namespace__ = 'http://spyne.io/schema' + __type_name__ = 'mediaTypeString' + + +if not six.PY2: + NATIVE_MAP.update({ + str: Unicode, + }) + +else: + NATIVE_MAP.update({ + str: String, + unicode: Unicode, + }) diff --git a/pym/calculate/contrib/spyne/model/primitive/string.pyc b/pym/calculate/contrib/spyne/model/primitive/string.pyc new file mode 100644 index 0000000..d3d5c3b Binary files /dev/null and b/pym/calculate/contrib/spyne/model/primitive/string.pyc differ diff --git a/pym/calculate/contrib/spyne/model/primitive/xml.py b/pym/calculate/contrib/spyne/model/primitive/xml.py new file mode 100644 index 0000000..d60494a --- /dev/null +++ b/pym/calculate/contrib/spyne/model/primitive/xml.py @@ -0,0 +1,195 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import re + +from spyne.const.xml import PATT_NMTOKEN +from spyne.model.primitive.string import Unicode + + +RE_BaseChar = re.compile( + 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]", flags=re.UNICODE) + + +RE_Ideographic = re.compile(u"[\u4E00-\u9FA5]|\u3007|[\u3021-\u3029]", + flags=re.UNICODE) + +RE_CombiningChar= re.compile( + u"[\u0300-\u0345]|[\u0360-\u0361]|[\u0483-\u0486]|[\u0591-\u05A1]|" + u"[\u05A3-\u05B9]|[\u05BB-\u05BD]|\u05BF|[\u05C1-\u05C2]|\u05C4|" + u"[\u064B-\u0652]|\u0670|[\u06D6-\u06DC]|[\u06DD-\u06DF]|[\u06E0-\u06E4]|" + u"[\u06E7-\u06E8]|[\u06EA-\u06ED]|[\u0901-\u0903]|\u093C|[\u093E-\u094C]|" + u"\u094D|[\u0951-\u0954]|[\u0962-\u0963]|[\u0981-\u0983]|\u09BC|\u09BE|" + u"\u09BF|[\u09C0-\u09C4]|[\u09C7-\u09C8]|[\u09CB-\u09CD]|\u09D7|" + u"[\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", + flags=re.UNICODE) + + +RE_Digit = re.compile( + 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]", flags=re.UNICODE) + + +RE_Extender = re.compile( + u"\u00B7|\u02D0|\u02D1|\u0387|\u0640|\u0E46|\u0EC6|\u3005|[\u3031-\u3035]|" + u"[\u309D-\u309E]|[\u30FC-\u30FE]", flags=re.UNICODE) + + +RE_Letter = re.compile(u'|'.join((RE_BaseChar.pattern, RE_Ideographic.pattern)), + flags=re.UNICODE) + + +RE_NameChar = re.compile(u'|'.join(( + RE_Letter.pattern, RE_Digit.pattern, '.', '-', '_', ':', + RE_CombiningChar.pattern, RE_Extender.pattern, + )), flags=re.UNICODE) + + +RE_NCNameChar = re.compile(u'|'.join(( + RE_Letter.pattern, RE_Digit.pattern, '.', '-', '_', # <= no column + RE_CombiningChar.pattern, RE_Extender.pattern, + )), flags=re.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" + +# https://www.w3.org/TR/2000/WD-xml-2e-20000814#NT-Name +class Name(Token): + __type_name__ = 'Name' + + class Attributes(Unicode.Attributes): + # https://www.w3.org/TR/2000/WD-xml-2e-20000814#NT-Name + pattern = '(%s)(%s)*' % ( + u'|'.join((RE_Letter.pattern, '_', ':')), + RE_NameChar.pattern + ) + + +# https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName +class NCName(Name): + __type_name__ = 'NCName' + class Attributes(Unicode.Attributes): + pattern = "(%s|_)%s*" % (RE_Letter.pattern, RE_NCNameChar.pattern) + + +# https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName +class QName(Token): + __type_name__ = "QName" + + class Attributes(Unicode.Attributes): + """ + QName = (PrefixedName | UnprefixedName) + PrefixedName ::= Prefix ':' LocalPart + UnprefixedName ::= LocalPart + Prefix ::= NCName + LocalPart ::= NCName + + i.e. + + QName = (NCName:)?NCName + """ + pattern = "(%s:)?(%s)" % ( + NCName.Attributes.pattern, + NCName.Attributes.pattern, + ) + + +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})*' diff --git a/pym/calculate/contrib/spyne/model/primitive/xml.pyc b/pym/calculate/contrib/spyne/model/primitive/xml.pyc new file mode 100644 index 0000000..b4fcdf4 Binary files /dev/null and b/pym/calculate/contrib/spyne/model/primitive/xml.pyc differ diff --git a/pym/calculate/contrib/spyne/model/relational.py b/pym/calculate/contrib/spyne/model/relational.py new file mode 100644 index 0000000..4f2ab67 --- /dev/null +++ b/pym/calculate/contrib/spyne/model/relational.py @@ -0,0 +1,46 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from spyne.model.complex import ComplexModel +from spyne.model.primitive import Unicode + + +class FileData(ComplexModel): + _type_info = [ + ('name', Unicode), + ('type', Unicode), + ('path', Unicode), + ] + + @property + def data(self): + return self._data + + @data.setter + def data(self, data): + self._data = data + + @property + def handle(self): + return self._handle + + @handle.setter + def handle(self, handle): + self._handle = handle + diff --git a/pym/calculate/contrib/spyne/model/relational.pyc b/pym/calculate/contrib/spyne/model/relational.pyc new file mode 100644 index 0000000..45f1d3e Binary files /dev/null and b/pym/calculate/contrib/spyne/model/relational.pyc differ diff --git a/pym/calculate/contrib/spyne/model/table.py b/pym/calculate/contrib/spyne/model/table.py new file mode 100644 index 0000000..82cdfe4 --- /dev/null +++ b/pym/calculate/contrib/spyne/model/table.py @@ -0,0 +1,227 @@ + +# +# 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 = {} diff --git a/pym/calculate/contrib/spyne/model/table.pyc b/pym/calculate/contrib/spyne/model/table.pyc new file mode 100644 index 0000000..1a04529 Binary files /dev/null and b/pym/calculate/contrib/spyne/model/table.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/__init__.py b/pym/calculate/contrib/spyne/protocol/__init__.py new file mode 100644 index 0000000..a98a9b6 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/__init__.py @@ -0,0 +1,44 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.protocol`` package contains the +:class:`spyne.protocol.ProtocolBase`` abstract base class. Every protocol +implementation is a subclass of ``ProtocolBase``. +""" + +from spyne.protocol._base import ProtocolMixin +from spyne.protocol._inbase import InProtocolBase +from spyne.protocol._outbase import OutProtocolBase + + +class ProtocolBase(InProtocolBase, OutProtocolBase): + def __init__(self, app=None, validator=None, mime_type=None, + ignore_uncap=False, ignore_wrappers=False, binary_encoding=None, + string_encoding='utf8'): + + InProtocolBase.__init__(self, app=app, validator=validator, + mime_type=mime_type, ignore_wrappers=ignore_wrappers, + binary_encoding=binary_encoding) + + OutProtocolBase.__init__(self, app=app, mime_type=mime_type, + ignore_wrappers=ignore_wrappers, ignore_uncap=ignore_uncap, + binary_encoding=binary_encoding) + + self.default_string_encoding = string_encoding + self.ignore_empty_faultactor = True diff --git a/pym/calculate/contrib/spyne/protocol/__init__.pyc b/pym/calculate/contrib/spyne/protocol/__init__.pyc new file mode 100644 index 0000000..0986794 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/_base.py b/pym/calculate/contrib/spyne/protocol/_base.py new file mode 100644 index 0000000..e593350 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/_base.py @@ -0,0 +1,422 @@ + +# +# 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 datetime import datetime +from weakref import WeakKeyDictionary + +from spyne import ProtocolContext, EventManager +from spyne.const import DEFAULT_LOCALE +from spyne.model import Array +from spyne.error import ResourceNotFoundError +from spyne.util import DefaultAttrDict +from spyne.util.six import string_types + + +_MISSING = type("_MISSING", (object,), {})() + + +class ProtocolMixin(object): + mime_type = 'application/octet-stream' + + SOFT_VALIDATION = type("Soft", (object,), {}) + REQUEST = type("Request", (object,), {}) + RESPONSE = type("Response", (object,), {}) + + type = set() + """Set that contains keywords about a protocol.""" + + default_binary_encoding = None + """Default encoding for binary data. It could be e.g. base64.""" + + default_string_encoding = None + """Default encoding for text content. It could be e.g. UTF-8.""" + + type_attrs = {} + """Default customizations to be passed to underlying classes.""" + + def __init__(self, app=None, mime_type=None, ignore_wrappers=None, + binary_encoding=None, string_encoding=None): + self.__app = None + self.set_app(app) + + self.ignore_wrappers = ignore_wrappers + self.event_manager = EventManager(self) + self.binary_encoding = binary_encoding + if self.binary_encoding is None: + self.binary_encoding = self.default_binary_encoding + + self.string_encoding = string_encoding + if self.string_encoding is None: + self.string_encoding = self.default_string_encoding + + if mime_type is not None: + self.mime_type = mime_type + + self._attrcache = WeakKeyDictionary() + self._sortcache = WeakKeyDictionary() + + def _cast(self, cls_attrs, inst): + if cls_attrs.parser is not None: + return cls_attrs.parser(inst) + return inst + + _parse = _cast + + def _sanitize(self, cls_attrs, inst): + if cls_attrs.sanitizer is not None: + return cls_attrs.sanitizer(inst) + return inst + + def _datetime_from_sec(self, cls, value): + try: + return datetime.fromtimestamp(value) + except TypeError: + logger.error("Invalid value %r", value) + raise + + def _datetime_from_sec_float(self, cls, value): + try: + return datetime.fromtimestamp(value) + except TypeError: + logger.error("Invalid value %r", value) + raise + + def _datetime_from_msec(self, cls, value): + try: + return datetime.fromtimestamp(value // 1000) + except TypeError: + logger.error("Invalid value %r", value) + raise + + def _datetime_from_msec_float(self, cls, value): + try: + return datetime.fromtimestamp(value / 1000) + except TypeError: + logger.error("Invalid value %r", value) + raise + + def _datetime_from_usec(self, cls, value): + try: + return datetime.fromtimestamp(value / 1e6) + except TypeError: + logger.error("Invalid value %r", value) + raise + + def _get_datetime_format(self, cls_attrs): + # FIXME: this should be dt_format, all other aliases are to be + # deprecated + dt_format = cls_attrs.datetime_format + if dt_format is None: + dt_format = cls_attrs.dt_format + if dt_format is None: + dt_format = cls_attrs.date_format + if dt_format is None: + dt_format = cls_attrs.out_format + if dt_format is None: + dt_format = cls_attrs.format + + return dt_format + + def _get_date_format(self, cls_attrs): + date_format = cls_attrs.date_format + if date_format is None: + date_format = cls_attrs.format + + return date_format + + def _get_time_format(self, cls_attrs): + time_format = cls_attrs.time_format + if time_format is None: + time_format = cls_attrs.format + + return time_format + + @property + def app(self): + return self.__app + + @staticmethod + def strip_wrappers(cls, inst): + ti = getattr(cls, '_type_info', {}) + + while len(ti) == 1 and cls.Attributes._wrapper: + # Wrappers are auto-generated objects that have exactly one + # child type. + key, = ti.keys() + if not issubclass(cls, Array): + inst = getattr(inst, key, None) + cls, = ti.values() + ti = getattr(cls, '_type_info', {}) + + return cls, inst + + def set_app(self, value): + assert self.__app is None, "One protocol instance should belong to one " \ + "application instance. It currently belongs " \ + "to: %r" % self.__app + self.__app = value + + @staticmethod + def issubclass(sub, cls): + suborig = getattr(sub, '__orig__', None) + clsorig = getattr(cls, '__orig__', None) + return issubclass(sub if suborig is None else suborig, + cls if clsorig is None else clsorig) + + def get_cls_attrs(self, cls): + #DEBUG + #this one is getting way to spammy + #logger.debug("%r attrcache size: %d", self, len(self._attrcache)) + attr = self._attrcache.get(cls, None) + if attr is not None: + return attr + + self._attrcache[cls] = attr = DefaultAttrDict([ + (k, getattr(cls.Attributes, k)) + for k in dir(cls.Attributes) + META_ATTR + if not k.startswith('__')]) + + if cls.Attributes.prot_attrs: + cls_attrs = cls.Attributes.prot_attrs.get(self.__class__, {}) + # logger.debug("%r cls attr %r", cls, cls_attrs) + attr.update(cls_attrs) + + inst_attrs = cls.Attributes.prot_attrs.get(self, {}) + # logger.debug("%r inst attr %r", cls, cls_attrs) + attr.update(inst_attrs) + + return attr + + def get_context(self, parent, transport): + return ProtocolContext(parent, transport) + + def generate_method_contexts(self, ctx): + """Generates MethodContext instances for every callable assigned to the + given method handle. + + The first element in the returned list is always the primary method + context whereas the rest are all auxiliary method contexts. + """ + + call_handles = self.get_call_handles(ctx) + if len(call_handles) == 0: + raise ResourceNotFoundError(ctx.method_request_string) + + retval = [] + for d in call_handles: + assert d is not None + + c = ctx.copy() + c.descriptor = d + + retval.append(c) + + return retval + + def get_call_handles(self, ctx): + """Method to be overriden to perform any sort of custom method mapping + using any data in the method context. Returns a list of contexts. + Can return multiple contexts if a method_request_string matches more + than one function. (This is called the fanout mode.) + """ + + name = ctx.method_request_string + if not name.startswith(u"{"): + name = u'{%s}%s' % (self.app.interface.get_tns(), name) + + call_handles = self.app.interface.service_method_map.get(name, []) + + return call_handles + + def get_polymorphic_target(self, cls, inst): + """If the protocol is polymorphic, extract what's returned by the user + code. + """ + + if not self.polymorphic: + #DEBUG + #way too spammy + #logger.debug("PMORPH Skipped: %r is NOT polymorphic", self) + return cls, False + + orig_cls = cls.__orig__ or cls + + if inst.__class__ is orig_cls: + logger.debug("PMORPH Skipped: Instance class %r is the same as " + "designated base class", inst.__class__) + return cls, False + + if not isinstance(inst, orig_cls): + logger.debug("PMORPH Skipped: Instance class %r is not a subclass " + "of designated base class %r", inst.__class__, orig_cls) + return cls, False + + cls_attr = self.get_cls_attrs(cls) + polymap_cls = cls_attr.polymap.get(inst.__class__, None) + + if polymap_cls is not None: + logger.debug("PMORPH OK: cls switch with polymap: %r => %r", + cls, polymap_cls) + return polymap_cls, True + + else: + logger.debug("PMORPH OK: cls switch without polymap: %r => %r", + cls, inst.__class__) + return inst.__class__, True + + @staticmethod + def trc_verbose(cls, locale, default): + """Translate a class. + + :param cls: class + :param locale: locale string + :param default: default string if no translation found + :returns: translated string + """ + + if locale is None: + locale = DEFAULT_LOCALE + _log_locale = "default locale '%s'" + else: + _log_locale = "given locale '%s'" + + if cls.Attributes.translations is None: + retval = default + _log_tr = "translated to '%s' without any translations at all with" + + else: + retval = cls.Attributes.translations.get(locale, _MISSING) + if retval is _MISSING: + retval = default + _log_tr = "translated to '%s': No translation for" + else: + _log_tr = "translated to '%s' with" + + logger.debug(' '.join(("%r ", _log_tr, _log_locale)), + cls, retval, locale) + + return retval + + @staticmethod + def trc(cls, locale, default): + """Translate a class. + + :param cls: class + :param locale: locale string + :param default: default string if no translation found + :returns: translated string + """ + + if locale is None: + locale = DEFAULT_LOCALE + if cls.Attributes.translations is not None: + return cls.Attributes.translations.get(locale, default) + return default + + @staticmethod + def trd_verbose(trdict, locale, default): + """Translate from a translations dict. + + :param trdict: translation dict + :param locale: locale string + :param default: default string if no translation found + :returns: translated string + """ + + if locale is None: + locale = DEFAULT_LOCALE + _log_locale = "default locale '%s'" + else: + _log_locale = "given locale '%s'" + + if trdict is None: + retval = default + _log_tr = "translated to '%s' without any translations at all with" + + elif isinstance(trdict, string_types): + retval = trdict + _log_tr = "translated to '%s' regardless of" + + else: + retval = trdict.get(locale, _MISSING) + if retval is _MISSING: + retval = default + _log_tr = "translated to '%s': No translation for" + else: + _log_tr = "translated to '%s' with" + + logger.debug(' '.join(("%r ", _log_tr, _log_locale)), + trdict, retval, locale) + + return retval + + @staticmethod + def trd(trdict, locale, default): + """Translate from a translations dict. + + :param trdict: translation dict + :param locale: locale string + :param default: default string if no translation found + :returns: translated string + """ + + if locale is None: + locale = DEFAULT_LOCALE + if trdict is None: + return default + if isinstance(trdict, string_types): + return trdict + + return trdict.get(locale, default) + + def sort_fields(self, cls=None, items=None): + logger.debug("%r sortcache size: %d", self, len(self._sortcache)) + retval = self._sortcache.get(cls, None) + if retval is not None: + return retval + + if items is None: + items = list(cls.get_flat_type_info(cls).items()) + + indexes = {} + for k, v in items: + order = self.get_cls_attrs(v).order + if order is not None: + if order < 0: + indexes[k] = len(items) + order + else: + indexes[k] = order + + for k, v in items: + order = self.get_cls_attrs(v).order + if order is None: + indexes[k] = len(indexes) + + items.sort(key=lambda x: indexes[x[0]]) + self._sortcache[cls] = items + + return items + + +META_ATTR = ['nullable', 'default_factory'] diff --git a/pym/calculate/contrib/spyne/protocol/_base.pyc b/pym/calculate/contrib/spyne/protocol/_base.pyc new file mode 100644 index 0000000..adfd928 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/_inbase.py b/pym/calculate/contrib/spyne/protocol/_inbase.py new file mode 100644 index 0000000..c124aaa --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/_inbase.py @@ -0,0 +1,716 @@ + +# +# 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__) + +import re +import pytz +import uuid + +from math import modf +from time import strptime, mktime +from datetime import timedelta, time, datetime, date +from decimal import Decimal as D, InvalidOperation + +from pytz import FixedOffset + +try: + from lxml import etree + from lxml import html +except ImportError: + etree = None + html = None + +from spyne.protocol._base import ProtocolMixin +from spyne.model import ModelBase, XmlAttribute, Array, Null, \ + ByteArray, File, ComplexModelBase, AnyXml, AnyHtml, Unicode, String, \ + Decimal, Double, Integer, Time, DateTime, Uuid, Date, Duration, Boolean, Any + +from spyne.error import ValidationError + +from spyne.model.binary import binary_decoding_handlers, BINARY_ENCODING_USE_DEFAULT + +from spyne.util import six +from spyne.model.enum import EnumBase +from spyne.model.primitive.datetime import TIME_PATTERN, DATE_PATTERN + +from spyne.util.cdict import cdict + + +_date_re = re.compile(DATE_PATTERN) +_time_re = re.compile(TIME_PATTERN) +_duration_re = re.compile( + r'(?P-?)' + r'P' + r'(?:(?P\d+)Y)?' + r'(?:(?P\d+)M)?' + r'(?:(?P\d+)D)?' + r'(?:T(?:(?P\d+)H)?' + r'(?:(?P\d+)M)?' + r'(?:(?P\d+(.\d+)?)S)?)?' + ) + + +class InProtocolBase(ProtocolMixin): + """This is the abstract base class for all input protocol implementations. + Child classes can implement only the required subset of the public methods. + + An output protocol must implement :func:`serialize` and + :func:`create_out_string`. + + An input protocol must implement :func:`create_in_document`, + :func:`decompose_incoming_envelope` and :func:`deserialize`. + + The ProtocolBase class supports the following events: + + * ``before_deserialize``: + Called before the deserialization operation is attempted. + + * ``after_deserialize``: + Called after the deserialization operation is finished. + + The arguments the constructor takes are as follows: + + :param app: The application this protocol belongs to. + :param mime_type: The mime_type this protocol should set for transports + that support this. This is a quick way to override the mime_type by + default instead of subclassing the releavant protocol implementation. + """ + + def __init__(self, app=None, validator=None, mime_type=None, + ignore_wrappers=False, binary_encoding=None, string_encoding=None): + + self.validator = None + + super(InProtocolBase, self).__init__(app=app, mime_type=mime_type, + ignore_wrappers=ignore_wrappers, + binary_encoding=binary_encoding, string_encoding=string_encoding) + + self.message = None + self.validator = None + self.set_validator(validator) + + if mime_type is not None: + self.mime_type = mime_type + + fsh = { + Any: self.any_from_bytes, + Null: self.null_from_bytes, + File: self.file_from_bytes, + Array: self.array_from_bytes, + Double: self.double_from_bytes, + String: self.string_from_bytes, + AnyXml: self.any_xml_from_bytes, + Boolean: self.boolean_from_bytes, + Integer: self.integer_from_bytes, + Unicode: self.unicode_from_bytes, + AnyHtml: self.any_html_from_bytes, + ByteArray: self.byte_array_from_bytes, + EnumBase: self.enum_base_from_bytes, + ModelBase: self.model_base_from_bytes, + XmlAttribute: self.xmlattribute_from_bytes, + ComplexModelBase: self.complex_model_base_from_bytes + } + + self._from_bytes_handlers = cdict(fsh) + self._from_unicode_handlers = cdict(fsh) + + self._from_bytes_handlers[Date] = self.date_from_bytes + self._from_bytes_handlers[Time] = self.time_from_bytes + self._from_bytes_handlers[Uuid] = self.uuid_from_bytes + self._from_bytes_handlers[Decimal] = self.decimal_from_bytes + self._from_bytes_handlers[DateTime] = self.datetime_from_bytes + self._from_bytes_handlers[Duration] = self.duration_from_bytes + + self._from_unicode_handlers[Date] = self.date_from_unicode + self._from_unicode_handlers[Uuid] = self.uuid_from_unicode + self._from_unicode_handlers[Time] = self.time_from_unicode + self._from_unicode_handlers[Decimal] = self.decimal_from_unicode + self._from_unicode_handlers[DateTime] = self.datetime_from_unicode + self._from_unicode_handlers[Duration] = self.duration_from_unicode + + + self._datetime_dsmap = { + None: self._datetime_from_unicode, + 'sec': self._datetime_from_sec, + 'sec_float': self._datetime_from_sec_float, + 'msec': self._datetime_from_msec, + 'msec_float': self._datetime_from_msec_float, + 'usec': self._datetime_from_usec, + } + + def _datetime_from_sec(self, cls, value): + try: + return datetime.fromtimestamp(value) + except TypeError: + logger.error("Invalid value %r", value) + raise + + def _datetime_from_sec_float(self, cls, value): + try: + return datetime.fromtimestamp(value) + except TypeError: + logger.error("Invalid value %r", value) + raise + + def _datetime_from_msec(self, cls, value): + try: + return datetime.fromtimestamp(value // 1000) + except TypeError: + logger.error("Invalid value %r", value) + raise + + def _datetime_from_msec_float(self, cls, value): + try: + return datetime.fromtimestamp(value / 1000) + except TypeError: + logger.error("Invalid value %r", value) + raise + + def _datetime_from_usec(self, cls, value): + try: + return datetime.fromtimestamp(value / 1e6) + except TypeError: + logger.error("Invalid value %r", value) + raise + + def create_in_document(self, ctx, in_string_encoding=None): + """Uses ``ctx.in_string`` to set ``ctx.in_document``.""" + + def decompose_incoming_envelope(self, ctx, message): + """Sets the ``ctx.method_request_string``, ``ctx.in_body_doc``, + ``ctx.in_header_doc`` and ``ctx.service`` properties of the ctx object, + if applicable. + """ + + def deserialize(self, ctx, message): + """Takes a MethodContext instance and a string containing ONE document + instance in the ``ctx.in_string`` attribute. + + Returns the corresponding native python object in the ctx.in_object + attribute. + """ + + def validate_document(self, payload): + """Method to be overriden to perform any sort of custom input + validation on the parsed input document. + """ + + def set_validator(self, validator): + """You must override this function if you want your protocol to support + validation.""" + + assert validator is None + + self.validator = None + + def from_bytes(self, class_, string, *args, **kwargs): + if string is None: + return None + + if isinstance(string, six.string_types) and \ + len(string) == 0 and class_.Attributes.empty_is_none: + return None + + handler = self._from_bytes_handlers[class_] + return handler(class_, string, *args, **kwargs) + + def from_unicode(self, class_, string, *args, **kwargs): + if string is None: + return None + #if not six.PY2: + # assert isinstance(string, str), \ + # "Invalid type passed to `from_unicode`: {}".format( + # (class_, type(string), string)) + + cls_attrs = self.get_cls_attrs(class_) + + if isinstance(string, six.string_types) and len(string) == 0 and \ + cls_attrs.empty_is_none: + return None + + handler = self._from_unicode_handlers[class_] + return handler(class_, string, *args, **kwargs) + + def null_from_bytes(self, cls, value): + return None + + def any_from_bytes(self, cls, value): + return value + + def any_xml_from_bytes(self, cls, string): + try: + return etree.fromstring(string) + except etree.XMLSyntaxError as e: + raise ValidationError(string, "%%r: %r" % e) + + def any_html_from_bytes(self, cls, string): + try: + return html.fromstring(string) + except etree.ParserError as e: + if e.args[0] == "Document is empty": + pass + else: + raise + + def uuid_from_unicode(self, cls, string, suggested_encoding=None): + attr = self.get_cls_attrs(cls) + ser_as = attr.serialize_as + encoding = attr.encoding + + if encoding is None: + encoding = suggested_encoding + + retval = string + + if ser_as in ('bytes', 'bytes_le'): + retval, = binary_decoding_handlers[encoding](string) + + try: + retval = _uuid_deserialize[ser_as](retval) + except ValueError as e: + raise ValidationError(e) + + return retval + + def uuid_from_bytes(self, cls, string, suggested_encoding=None, **_): + attr = self.get_cls_attrs(cls) + ser_as = attr.serialize_as + encoding = attr.encoding + + if encoding is None: + encoding = suggested_encoding + + retval = string + + if ser_as in ('bytes', 'bytes_le'): + retval, = binary_decoding_handlers[encoding](string) + elif isinstance(string, six.binary_type): + retval = string.decode('ascii') + + try: + retval = _uuid_deserialize[ser_as](retval) + except ValueError as e: + raise ValidationError(e) + + return retval + + def unicode_from_bytes(self, cls, value): + retval = value + + if isinstance(value, six.binary_type): + cls_attrs = self.get_cls_attrs(cls) + + if cls_attrs.encoding is not None: + retval = six.text_type(value, cls_attrs.encoding, + errors=cls_attrs.unicode_errors) + + elif self.string_encoding is not None: + retval = six.text_type(value, self.string_encoding, + errors=cls_attrs.unicode_errors) + + else: + retval = six.text_type(value, errors=cls_attrs.unicode_errors) + + return retval + + def string_from_bytes(self, cls, value): + retval = value + cls_attrs = self.get_cls_attrs(cls) + if isinstance(value, six.text_type): + if cls_attrs.encoding is None: + raise Exception("You need to define a source encoding for " + "decoding incoming unicode values.") + else: + retval = value.encode(cls_attrs.encoding) + + return retval + + def decimal_from_unicode(self, cls, string): + cls_attrs = self.get_cls_attrs(cls) + if cls_attrs.max_str_len is not None and len(string) > \ + cls_attrs.max_str_len: + raise ValidationError(string, "Decimal %%r longer than %d " + "characters" % cls_attrs.max_str_len) + + try: + return D(string) + except InvalidOperation as e: + raise ValidationError(string, "%%r: %r" % e) + + def decimal_from_bytes(self, cls, string): + return self.decimal_from_unicode(cls, + string.decode(self.default_string_encoding)) + + def double_from_bytes(self, cls, string): + try: + return float(string) + except (TypeError, ValueError) as e: + raise ValidationError(string, "%%r: %r" % e) + + def integer_from_bytes(self, cls, string): + cls_attrs = self.get_cls_attrs(cls) + + if isinstance(string, (six.text_type, six.binary_type)) and \ + cls_attrs.max_str_len is not None and \ + len(string) > cls_attrs.max_str_len: + raise ValidationError(string, + "Integer %%r longer than %d characters" + % cls_attrs.max_str_len) + + try: + return int(string) + except ValueError: + raise ValidationError(string, "Could not cast %r to integer") + + def time_from_unicode(self, cls, string): + """Expects ISO formatted times.""" + + match = _time_re.match(string) + if match is None: + raise ValidationError(string, "%%r does not match regex %r " % + _time_re.pattern) + + fields = match.groupdict(0) + microsec = fields.get('sec_frac') + if microsec is None or microsec == 0: + microsec = 0 + else: + microsec = min(999999, int(round(float(microsec) * 1e6))) + + return time(int(fields['hr']), int(fields['min']), + int(fields['sec']), microsec) + + def time_from_bytes(self, cls, string): + if isinstance(string, six.binary_type): + string = string.decode(self.default_string_encoding) + + return self.time_from_unicode(cls, string) + + def date_from_unicode_iso(self, cls, string): + """This is used by protocols like SOAP who need ISO8601-formatted dates + no matter what. + """ + + try: + return date(*(strptime(string, u'%Y-%m-%d')[0:3])) + + except ValueError: + match = cls._offset_re.match(string) + + if match: + year = int(match.group('year')) + month = int(match.group('month')) + day = int(match.group('day')) + + return date(year, month, day) + + raise ValidationError(string) + + def enum_base_from_bytes(self, cls, value): + if self.validator is self.SOFT_VALIDATION and not ( + cls.validate_string(cls, value)): + raise ValidationError(value) + return getattr(cls, value) + + def model_base_from_bytes(self, cls, value): + return cls.from_bytes(value) + + def datetime_from_unicode_iso(self, cls, string): + astz = self.get_cls_attrs(cls).as_timezone + + match = cls._utc_re.match(string) + if match: + tz = pytz.utc + retval = _parse_datetime_iso_match(match, tz=tz) + if astz is not None: + retval = retval.astimezone(astz) + return retval + + if match is None: + match = cls._offset_re.match(string) + if match: + tz_hr, tz_min = [int(match.group(x)) + for x in ("tz_hr", "tz_min")] + tz = FixedOffset(tz_hr * 60 + tz_min, {}) + retval = _parse_datetime_iso_match(match, tz=tz) + if astz is not None: + retval = retval.astimezone(astz) + return retval + + if match is None: + match = cls._local_re.match(string) + if match: + retval = _parse_datetime_iso_match(match) + if astz: + retval = retval.replace(tzinfo=astz) + return retval + + raise ValidationError(string) + + def datetime_from_unicode(self, cls, string): + serialize_as = self.get_cls_attrs(cls).serialize_as + return self._datetime_dsmap[serialize_as](cls, string) + + def datetime_from_bytes(self, cls, string): + if isinstance(string, six.binary_type): + string = string.decode(self.default_string_encoding) + + serialize_as = self.get_cls_attrs(cls).serialize_as + return self._datetime_dsmap[serialize_as](cls, string) + + def date_from_bytes(self, cls, string): + if isinstance(string, six.binary_type): + string = string.decode(self.default_string_encoding) + + date_format = self._get_date_format(self.get_cls_attrs(cls)) + try: + if date_format is not None: + dt = datetime.strptime(string, date_format) + return date(dt.year, dt.month, dt.day) + + return self.date_from_unicode_iso(cls, string) + + except ValueError as e: + match = cls._offset_re.match(string) + if match: + return date(int(match.group('year')), + int(match.group('month')), int(match.group('day'))) + else: + raise ValidationError(string, + "%%r: %s" % repr(e).replace("%", "%%")) + + def date_from_unicode(self, cls, string): + date_format = self._get_date_format(self.get_cls_attrs(cls)) + try: + if date_format is not None: + dt = datetime.strptime(string, date_format) + return date(dt.year, dt.month, dt.day) + + return self.date_from_unicode_iso(cls, string) + + except ValueError as e: + match = cls._offset_re.match(string) + if match: + return date(int(match.group('year')), + int(match.group('month')), int(match.group('day'))) + else: + # the message from ValueError is quite nice already + raise ValidationError(e.message, "%s") + + def duration_from_unicode(self, cls, string): + duration = _duration_re.match(string).groupdict(0) + if duration is None: + raise ValidationError(string, + "Time data '%%s' does not match regex '%s'" % + (_duration_re.pattern,)) + + days = int(duration['days']) + days += int(duration['months']) * 30 + days += int(duration['years']) * 365 + hours = int(duration['hours']) + minutes = int(duration['minutes']) + seconds = float(duration['seconds']) + f, i = modf(seconds) + seconds = i + microseconds = int(1e6 * f) + + delta = timedelta(days=days, hours=hours, minutes=minutes, + seconds=seconds, microseconds=microseconds) + + if duration['sign'] == "-": + delta *= -1 + + return delta + + def duration_from_bytes(self, cls, string): + if isinstance(string, six.binary_type): + string = string.decode(self.default_string_encoding) + + return self.duration_from_unicode(cls, string) + + def boolean_from_bytes(self, cls, string): + return string.lower() in ('true', '1') + + def byte_array_from_bytes(self, cls, value, suggested_encoding=None): + encoding = self.get_cls_attrs(cls).encoding + if encoding is BINARY_ENCODING_USE_DEFAULT: + encoding = suggested_encoding + return binary_decoding_handlers[encoding](value) + + def file_from_bytes(self, cls, value, suggested_encoding=None): + encoding = self.get_cls_attrs(cls).encoding + if encoding is BINARY_ENCODING_USE_DEFAULT: + encoding = suggested_encoding + + return File.Value(data=binary_decoding_handlers[encoding](value)) + + def complex_model_base_from_bytes(self, cls, string, **_): + raise TypeError("Only primitives can be deserialized from string.") + + def array_from_bytes(self, cls, string, **_): + if self.get_cls_attrs(cls).serialize_as != 'sd-list': + raise TypeError("Only primitives can be deserialized from string.") + + # sd-list being space-delimited list. + retval = [] + inner_type, = cls._type_info.values() + for s in string.split(): + retval.append(self.from_bytes(inner_type, s)) + + return retval + + def xmlattribute_from_bytes(self, cls, value): + return self.from_bytes(cls.type, value) + + def _datetime_from_unicode(self, cls, string): + cls_attrs = self.get_cls_attrs(cls) + + # get parser + parser = cls_attrs.parser + + # get date_format + dt_format = cls_attrs.dt_format + if dt_format is None: + dt_format = cls_attrs.date_format + if dt_format is None: + dt_format = cls_attrs.out_format + if dt_format is None: + dt_format = cls_attrs.format + + # parse the string + if parser is not None: + retval = parser(self, cls, string) + + elif dt_format is not None: + if six.PY2: + # FIXME: perhaps it should encode to string's encoding instead + # of utf8 all the time + if isinstance(dt_format, six.text_type): + dt_format = dt_format.encode('utf8') + if isinstance(string, six.text_type): + string = string.encode('utf8') + + retval = datetime.strptime(string, dt_format) + + astz = cls_attrs.as_timezone + if astz: + retval = retval.astimezone(cls_attrs.as_time_zone) + + else: + retval = self.datetime_from_unicode_iso(cls, string) + + return retval + + +_uuid_deserialize = { + None: lambda s: uuid.UUID(s), + 'hex': lambda s: uuid.UUID(hex=s), + 'urn': lambda s: uuid.UUID(hex=s), + 'bytes': lambda s: uuid.UUID(bytes=s), + 'bytes_le': lambda s: uuid.UUID(bytes_le=s), + 'fields': lambda s: uuid.UUID(fields=s), + 'int': lambda s: uuid.UUID(int=s), + ('int', int): lambda s: uuid.UUID(int=s), + ('int', str): lambda s: uuid.UUID(int=int(s)), +} + +if six.PY2: + _uuid_deserialize[('int', long)] = _uuid_deserialize[('int', int)] + + +def _parse_datetime_iso_match(date_match, tz=None): + fields = date_match.groupdict() + + year = int(fields.get('year')) + month = int(fields.get('month')) + day = int(fields.get('day')) + hour = int(fields.get('hr')) + minute = int(fields.get('min')) + second = int(fields.get('sec')) + usecond = fields.get("sec_frac") + if usecond is None: + usecond = 0 + else: + # we only get the most significant 6 digits because that's what + # datetime can handle. + usecond = min(999999, int(round(float(usecond) * 1e6))) + + return datetime(year, month, day, hour, minute, second, usecond, tz) + + +_dt_sec = lambda cls, val: \ + int(mktime(val.timetuple())) +_dt_sec_float = lambda cls, val: \ + mktime(val.timetuple()) + (val.microsecond / 1e6) + +_dt_msec = lambda cls, val: \ + int(mktime(val.timetuple())) * 1000 + (val.microsecond // 1000) +_dt_msec_float = lambda cls, val: \ + mktime(val.timetuple()) * 1000 + (val.microsecond / 1000.0) + +_dt_usec = lambda cls, val: \ + int(mktime(val.timetuple())) * 1000000 + val.microsecond + +_datetime_smap = { + 'sec': _dt_sec, + 'secs': _dt_sec, + 'second': _dt_sec, + 'seconds': _dt_sec, + + 'sec_float': _dt_sec_float, + 'secs_float': _dt_sec_float, + 'second_float': _dt_sec_float, + 'seconds_float': _dt_sec_float, + + 'msec': _dt_msec, + 'msecs': _dt_msec, + 'msecond': _dt_msec, + 'mseconds': _dt_msec, + 'millisecond': _dt_msec, + 'milliseconds': _dt_msec, + + 'msec_float': _dt_msec_float, + 'msecs_float': _dt_msec_float, + 'msecond_float': _dt_msec_float, + 'mseconds_float': _dt_msec_float, + 'millisecond_float': _dt_msec_float, + 'milliseconds_float': _dt_msec_float, + + 'usec': _dt_usec, + 'usecs': _dt_usec, + 'usecond': _dt_usec, + 'useconds': _dt_usec, + 'microsecond': _dt_usec, + 'microseconds': _dt_usec, +} + + +def _file_to_iter(f): + try: + data = f.read(65536) + while len(data) > 0: + yield data + data = f.read(65536) + + finally: + f.close() diff --git a/pym/calculate/contrib/spyne/protocol/_inbase.pyc b/pym/calculate/contrib/spyne/protocol/_inbase.pyc new file mode 100644 index 0000000..47c214e Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/_inbase.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/_outbase.py b/pym/calculate/contrib/spyne/protocol/_outbase.py new file mode 100644 index 0000000..f3ab858 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/_outbase.py @@ -0,0 +1,904 @@ + +# +# 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, unicode_literals + +import logging +logger = logging.getLogger(__name__) + +import re +import uuid +import errno + +from os.path import isabs, join, abspath +from collections import deque +from datetime import datetime +from decimal import Decimal as D +from mmap import mmap, ACCESS_READ +from time import mktime, strftime + +try: + from lxml import etree + from lxml import html +except ImportError: + etree = None + html = None + +from spyne.protocol._base import ProtocolMixin +from spyne.model import ModelBase, XmlAttribute, SimpleModel, Null, \ + ByteArray, File, ComplexModelBase, AnyXml, AnyHtml, Unicode, Decimal, \ + Double, Integer, Time, DateTime, Uuid, Duration, Boolean, AnyDict, \ + AnyUri, PushBase, Date +from spyne.model.relational import FileData + +from spyne.const.http import HTTP_400, HTTP_401, HTTP_404, HTTP_405, HTTP_413, \ + HTTP_500 +from spyne.error import Fault, InternalError, ResourceNotFoundError, \ + RequestTooLongError, RequestNotAllowed, InvalidCredentialsError +from spyne.model.binary import binary_encoding_handlers, \ + BINARY_ENCODING_USE_DEFAULT + +from spyne.util import six +from spyne.util.cdict import cdict + + +class OutProtocolBase(ProtocolMixin): + """This is the abstract base class for all out protocol implementations. + Child classes can implement only the required subset of the public methods. + + An output protocol must implement :func:`serialize` and + :func:`create_out_string`. + + The OutProtocolBase class supports the following events: + + * ``before_serialize``: + Called before after the serialization operation is attempted. + + * ``after_serialize``: + Called after the serialization operation is finished. + + The arguments the constructor takes are as follows: + + :param app: The application this protocol belongs to. + :param mime_type: The mime_type this protocol should set for transports + that support this. This is a quick way to override the mime_type by + default instead of subclassing the releavant protocol implementation. + :param ignore_uncap: Silently ignore cases when the protocol is not capable + of serializing return values instead of raising a TypeError. + """ + + def __init__(self, app=None, mime_type=None, ignore_uncap=False, + ignore_wrappers=False, binary_encoding=None, string_encoding=None): + + super(OutProtocolBase, self).__init__(app=app, mime_type=mime_type, + ignore_wrappers=ignore_wrappers, + binary_encoding=binary_encoding, string_encoding=string_encoding) + + self.ignore_uncap = ignore_uncap + self.message = None + + if mime_type is not None: + self.mime_type = mime_type + + self._to_bytes_handlers = cdict({ + ModelBase: self.model_base_to_bytes, + File: self.file_to_bytes, + Time: self.time_to_bytes, + Uuid: self.uuid_to_bytes, + Null: self.null_to_bytes, + Date: self.date_to_bytes, + Double: self.double_to_bytes, + AnyXml: self.any_xml_to_bytes, + Unicode: self.unicode_to_bytes, + Boolean: self.boolean_to_bytes, + Decimal: self.decimal_to_bytes, + Integer: self.integer_to_bytes, + AnyHtml: self.any_html_to_bytes, + DateTime: self.datetime_to_bytes, + Duration: self.duration_to_bytes, + ByteArray: self.byte_array_to_bytes, + XmlAttribute: self.xmlattribute_to_bytes, + ComplexModelBase: self.complex_model_base_to_bytes, + }) + + self._to_unicode_handlers = cdict({ + ModelBase: self.model_base_to_unicode, + File: self.file_to_unicode, + Time: self.time_to_unicode, + Date: self.date_to_unicode, + Uuid: self.uuid_to_unicode, + Null: self.null_to_unicode, + Double: self.double_to_unicode, + AnyXml: self.any_xml_to_unicode, + AnyUri: self.any_uri_to_unicode, + AnyDict: self.any_dict_to_unicode, + AnyHtml: self.any_html_to_unicode, + Unicode: self.unicode_to_unicode, + Boolean: self.boolean_to_unicode, + Decimal: self.decimal_to_unicode, + Integer: self.integer_to_unicode, + # FIXME: Would we need a to_unicode for localized dates? + DateTime: self.datetime_to_unicode, + Duration: self.duration_to_unicode, + ByteArray: self.byte_array_to_unicode, + XmlAttribute: self.xmlattribute_to_unicode, + ComplexModelBase: self.complex_model_base_to_unicode, + }) + + self._to_bytes_iterable_handlers = cdict({ + File: self.file_to_bytes_iterable, + ByteArray: self.byte_array_to_bytes_iterable, + ModelBase: self.model_base_to_bytes_iterable, + SimpleModel: self.simple_model_to_bytes_iterable, + ComplexModelBase: self.complex_model_to_bytes_iterable, + }) + + + def serialize(self, ctx, message): + """Serializes ``ctx.out_object``. + + If ctx.out_stream is not None, ``ctx.out_document`` and + ``ctx.out_string`` are skipped and the response is written directly to + ``ctx.out_stream``. + + :param ctx: :class:`MethodContext` instance. + :param message: One of ``(ProtocolBase.REQUEST, ProtocolBase.RESPONSE)``. + """ + + def create_out_string(self, ctx, out_string_encoding=None): + """Uses ctx.out_document to set ctx.out_string""" + + def fault_to_http_response_code(self, fault): + """Special function to convert native Python exceptions to Http response + codes. + """ + + if isinstance(fault, RequestTooLongError): + return HTTP_413 + + if isinstance(fault, ResourceNotFoundError): + return HTTP_404 + + if isinstance(fault, RequestNotAllowed): + return HTTP_405 + + if isinstance(fault, InvalidCredentialsError): + return HTTP_401 + + if isinstance(fault, Fault) and (fault.faultcode.startswith('Client.') + or fault.faultcode == 'Client'): + return HTTP_400 + + return HTTP_500 + + def set_validator(self, validator): + """You must override this function if you want your protocol to support + validation.""" + + assert validator is None + + self.validator = None + + def to_bytes(self, cls, value, *args, **kwargs): + if value is None: + return None + + handler = self._to_bytes_handlers[cls] + retval = handler(cls, value, *args, **kwargs) + + # enable this only for testing. we're not as strict for performance + # reasons + # assert isinstance(retval, six.binary_type), \ + # "AssertionError: %r %r %r handler: %r" % \ + # (type(retval), six.binary_type, retval, handler) + return retval + + def to_unicode(self, cls, value, *args, **kwargs): + if value is None: + return None + + handler = self._to_unicode_handlers[cls] + retval = handler(cls, value, *args, **kwargs) + # enable this only for testing. we're not as strict for performance + # reasons as well as not to take the joy of dealing with duck typing + # from the user + # assert isinstance(retval, six.text_type), \ + # "AssertionError: %r %r handler: %r" % \ + # (type(retval), retval, handler) + + return retval + + def to_bytes_iterable(self, cls, value): + if value is None: + return [] + + if isinstance(value, PushBase): + return value + + handler = self._to_bytes_iterable_handlers[cls] + return handler(cls, value) + + def null_to_bytes(self, cls, value, **_): + return b"" + + def null_to_unicode(self, cls, value, **_): + return u"" + + def any_xml_to_bytes(self, cls, value, **_): + return etree.tostring(value) + + def any_xml_to_unicode(self, cls, value, **_): + return etree.tostring(value, encoding='unicode') + + def any_dict_to_unicode(self, cls, value, **_): + return repr(value) + + def any_html_to_bytes(self, cls, value, **_): + return html.tostring(value) + + def any_html_to_unicode(self, cls, value, **_): + return html.tostring(value, encoding='unicode') + + def uuid_to_bytes(self, cls, value, suggested_encoding=None, **_): + ser_as = self.get_cls_attrs(cls).serialize_as + retval = self.uuid_to_unicode(cls, value, + suggested_encoding=suggested_encoding, **_) + + if ser_as in ('bytes', 'bytes_le', 'fields', 'int', six.binary_type): + return retval + + return retval.encode('ascii') + + def uuid_to_unicode(self, cls, value, suggested_encoding=None, **_): + attr = self.get_cls_attrs(cls) + ser_as = attr.serialize_as + encoding = attr.encoding + + if encoding is None: + encoding = suggested_encoding + + retval = _uuid_serialize[ser_as](value) + if ser_as in ('bytes', 'bytes_le'): + retval = binary_encoding_handlers[encoding]((retval,)) + return retval + + def unicode_to_bytes(self, cls, value, **_): + retval = value + + cls_attrs = self.get_cls_attrs(cls) + + if isinstance(value, six.text_type): + if cls_attrs.encoding is not None: + retval = value.encode(cls_attrs.encoding) + elif self.default_string_encoding is not None: + retval = value.encode(self.default_string_encoding) + elif not six.PY2: + logger.warning("You need to set either an encoding for %r " + "or a default_string_encoding for %r", cls, self) + + if cls_attrs.str_format is not None: + return cls_attrs.str_format.format(value) + elif cls_attrs.format is not None: + return cls_attrs.format % retval + + return retval + + def any_uri_to_unicode(self, cls, value, **_): + return self.unicode_to_unicode(cls, value, **_) + + def unicode_to_unicode(self, cls, value, **_): # :))) + cls_attrs = self.get_cls_attrs(cls) + + retval = value + #retval = str(value) + #DEBUG + #print("SPYNE DEBUG2") + + #print(retval) + #print(type(retval)) + #print(cls) + if isinstance(value, six.binary_type): + if cls_attrs.encoding is not None: + retval = value.decode(cls_attrs.encoding) + + if self.default_string_encoding is not None: + retval = value.decode(self.default_string_encoding) + + elif not six.PY2: + logger.warning("You need to set either an encoding for %r " + "or a default_string_encoding for %r", cls, self) + + if cls_attrs.str_format is not None: + return cls_attrs.str_format.format(value) + elif cls_attrs.format is not None: + return cls_attrs.format % retval + + return retval + + def decimal_to_bytes(self, cls, value, **_): + return self.decimal_to_unicode(cls, value, **_).encode('utf8') + + def decimal_to_unicode(self, cls, value, **_): + D(value) # sanity check + cls_attrs = self.get_cls_attrs(cls) + + if cls_attrs.str_format is not None: + return cls_attrs.str_format.format(value) + elif cls_attrs.format is not None: + return cls_attrs.format % value + + return str(value) + + def double_to_bytes(self, cls, value, **_): + return self.double_to_unicode(cls, value, **_).encode('utf8') + + def double_to_unicode(self, cls, value, **_): + float(value) # sanity check + cls_attrs = self.get_cls_attrs(cls) + + if cls_attrs.str_format is not None: + return cls_attrs.str_format.format(value) + elif cls_attrs.format is not None: + return cls_attrs.format % value + + return repr(value) + + def integer_to_bytes(self, cls, value, **_): + return self.integer_to_unicode(cls, value, **_).encode('utf8') + + def integer_to_unicode(self, cls, value, **_): + int(value) # sanity check + cls_attrs = self.get_cls_attrs(cls) + + if cls_attrs.str_format is not None: + return cls_attrs.str_format.format(value) + elif cls_attrs.format is not None: + return cls_attrs.format % value + + return str(value) + + def time_to_bytes(self, cls, value, **kwargs): + return self.time_to_unicode(cls, value, **kwargs) + + def time_to_unicode(self, cls, value, **_): + """Returns ISO formatted times.""" + if isinstance(value, datetime): + value = value.time() + return value.isoformat() + + def date_to_bytes(self, cls, val, **_): + return self.date_to_unicode(cls, val, **_).encode("utf8") + + def date_to_unicode(self, cls, val, **_): + if isinstance(val, datetime): + val = val.date() + + sa = self.get_cls_attrs(cls).serialize_as + + if sa is None or sa in (str, 'str'): + return self._date_to_bytes(cls, val) + + return _datetime_smap[sa](cls, val) + + def datetime_to_bytes(self, cls, val, **_): + retval = self.datetime_to_unicode(cls, val, **_) + sa = self.get_cls_attrs(cls).serialize_as + if sa is None or sa in (six.text_type, str, 'str'): + return retval.encode('ascii') + return retval + + def datetime_to_unicode(self, cls, val, **_): + sa = self.get_cls_attrs(cls).serialize_as + + if sa is None or sa in (six.text_type, str, 'str'): + return self._datetime_to_unicode(cls, val) + + return _datetime_smap[sa](cls, val) + + def duration_to_bytes(self, cls, value, **_): + return self.duration_to_unicode(cls, value, **_).encode("utf8") + + def duration_to_unicode(self, cls, value, **_): + if value.days < 0: + value = -value + negative = True + else: + negative = False + + tot_sec = int(value.total_seconds()) + seconds = value.seconds % 60 + minutes = value.seconds // 60 + hours = minutes // 60 + minutes %= 60 + seconds = float(seconds) + useconds = value.microseconds + + retval = deque() + if negative: + retval.append("-P") + else: + retval.append("P") + if value.days != 0: + retval.append("%iD" % value.days) + + if tot_sec != 0 and tot_sec % 86400 == 0 and useconds == 0: + return ''.join(retval) + + retval.append('T') + + if hours > 0: + retval.append("%iH" % hours) + + if minutes > 0: + retval.append("%iM" % minutes) + + if seconds > 0 or useconds > 0: + retval.append("%i" % seconds) + if useconds > 0: + retval.append(".%i" % useconds) + retval.append("S") + + if len(retval) == 2: + retval.append('0S') + + return ''.join(retval) + + def boolean_to_bytes(self, cls, value, **_): + return str(bool(value)).lower().encode('ascii') + + def boolean_to_unicode(self, cls, value, **_): + return str(bool(value)).lower() + + def byte_array_to_bytes(self, cls, value, suggested_encoding=None, **_): + cls_attrs = self.get_cls_attrs(cls) + + encoding = cls_attrs.encoding + if encoding is BINARY_ENCODING_USE_DEFAULT: + if suggested_encoding is None: + encoding = self.binary_encoding + else: + encoding = suggested_encoding + + if encoding is None and isinstance(value, (list, tuple)) \ + and len(value) == 1 and isinstance(value[0], mmap): + return value[0] + + encoder = binary_encoding_handlers[encoding] + logger.debug("Using binary encoder %r for encoding %r", + encoder, encoding) + retval = encoder(value) + if encoding is not None and isinstance(retval, six.text_type): + retval = retval.encode('ascii') + + return retval + + def byte_array_to_unicode(self, cls, value, suggested_encoding=None, **_): + encoding = self.get_cls_attrs(cls).encoding + if encoding is BINARY_ENCODING_USE_DEFAULT: + if suggested_encoding is None: + encoding = self.binary_encoding + else: + encoding = suggested_encoding + + if encoding is None: + raise ValueError("Arbitrary binary data can't be serialized to " + "unicode") + + retval = binary_encoding_handlers[encoding](value) + if not isinstance(retval, six.text_type): + retval = retval.decode('ascii') + + return retval + + def byte_array_to_bytes_iterable(self, cls, value, **_): + return value + + def file_to_bytes(self, cls, value, suggested_encoding=None): + """ + :param cls: A :class:`spyne.model.File` subclass + :param value: Either a sequence of byte chunks or a + :class:`spyne.model.File.Value` instance. + """ + + encoding = self.get_cls_attrs(cls).encoding + if encoding is BINARY_ENCODING_USE_DEFAULT: + if suggested_encoding is None: + encoding = self.binary_encoding + else: + encoding = suggested_encoding + + if isinstance(value, File.Value): + if value.data is not None: + return binary_encoding_handlers[encoding](value.data) + + if value.handle is not None: + # maybe we should have used the sweeping except: here. + if hasattr(value.handle, 'fileno'): + if six.PY2: + fileno = value.handle.fileno() + data = (mmap(fileno, 0, access=ACCESS_READ),) + else: + import io + try: + fileno = value.handle.fileno() + data = mmap(fileno, 0, access=ACCESS_READ) + except io.UnsupportedOperation: + data = (value.handle.read(),) + else: + data = (value.handle.read(),) + + return binary_encoding_handlers[encoding](data) + + if value.path is not None: + handle = open(value.path, 'rb') + fileno = handle.fileno() + data = mmap(fileno, 0, access=ACCESS_READ) + + return binary_encoding_handlers[encoding](data) + + assert False, "Unhandled file type" + + if isinstance(value, FileData): + try: + return binary_encoding_handlers[encoding](value.data) + except Exception as e: + logger.error("Error encoding value to binary. Error: %r, Value: %r", + e, value) + raise + + try: + return binary_encoding_handlers[encoding](value) + except Exception as e: + logger.error("Error encoding value to binary. Error: %r, Value: %r", + e, value) + raise + + def file_to_unicode(self, cls, value, suggested_encoding=None): + """ + :param cls: A :class:`spyne.model.File` subclass + :param value: Either a sequence of byte chunks or a + :class:`spyne.model.File.Value` instance. + """ + + cls_attrs = self.get_cls_attrs(cls) + encoding = cls_attrs.encoding + if encoding is BINARY_ENCODING_USE_DEFAULT: + encoding = suggested_encoding + + if encoding is None and cls_attrs.mode is File.TEXT: + raise ValueError("Arbitrary binary data can't be serialized to " + "unicode.") + + retval = self.file_to_bytes(cls, value, suggested_encoding) + if not isinstance(retval, six.text_type): + retval = retval.decode('ascii') + return retval + + def file_to_bytes_iterable(self, cls, value, **_): + if value.data is not None: + if isinstance(value.data, (list, tuple)) and \ + isinstance(value.data[0], mmap): + return _file_to_iter(value.data[0]) + return iter(value.data) + + if value.handle is not None: + f = value.handle + f.seek(0) + return _file_to_iter(f) + + assert value.path is not None, "You need to write data to " \ + "persistent storage first if you want to read it back." + + try: + path = value.path + if not isabs(value.path): + path = join(value.store, value.path) + assert abspath(path).startswith(value.store), \ + "No relative paths are allowed" + return _file_to_iter(open(path, 'rb')) + + except IOError as e: + if e.errno == errno.ENOENT: + raise ResourceNotFoundError(value.path) + else: + raise InternalError("Error accessing requested file") + + def simple_model_to_bytes_iterable(self, cls, value, **kwargs): + retval = self.to_bytes(cls, value, **kwargs) + if retval is None: + return (b'',) + return (retval,) + + def complex_model_to_bytes_iterable(self, cls, value, **_): + if self.ignore_uncap: + return tuple() + raise TypeError("This protocol can only serialize primitives.") + + def complex_model_base_to_bytes(self, cls, value, **_): + raise TypeError("Only primitives can be serialized to string.") + + def complex_model_base_to_unicode(self, cls, value, **_): + raise TypeError("Only primitives can be serialized to string.") + + def xmlattribute_to_bytes(self, cls, string, **kwargs): + return self.to_bytes(cls.type, string, **kwargs) + + def xmlattribute_to_unicode(self, cls, string, **kwargs): + return self.to_unicode(cls.type, string, **kwargs) + + def model_base_to_bytes_iterable(self, cls, value, **kwargs): + return cls.to_bytes_iterable(value, **kwargs) + + def model_base_to_bytes(self, cls, value, **kwargs): + return cls.to_bytes(value, **kwargs) + + def model_base_to_unicode(self, cls, value, **kwargs): + return cls.to_unicode(value, **kwargs) + + def _datetime_to_unicode(self, cls, value, **_): + """Returns ISO formatted datetimes.""" + + cls_attrs = self.get_cls_attrs(cls) + + if cls_attrs.as_timezone is not None and value.tzinfo is not None: + value = value.astimezone(cls_attrs.as_timezone) + + if not cls_attrs.timezone: + value = value.replace(tzinfo=None) + + dt_format = self._get_datetime_format(cls_attrs) + + if dt_format is None: + retval = value.isoformat() + + elif six.PY2 and isinstance(dt_format, unicode): + retval = self.strftime(value, dt_format.encode('utf8')).decode('utf8') + + else: + retval = self.strftime(value, dt_format) + + # FIXME: must deprecate string_format, this should have been str_format + str_format = cls_attrs.string_format + if str_format is None: + str_format = cls_attrs.str_format + if str_format is not None: + return str_format.format(value) + + # FIXME: must deprecate interp_format, this should have been just format + interp_format = cls_attrs.interp_format + if interp_format is not None: + return interp_format.format(value) + + return retval + + def _date_to_bytes(self, cls, value, **_): + cls_attrs = self.get_cls_attrs(cls) + + date_format = cls_attrs.date_format + if date_format is None: + retval = value.isoformat() + + elif six.PY2 and isinstance(date_format, unicode): + date_format = date_format.encode('utf8') + retval = self.strftime(value, date_format).decode('utf8') + + else: + retval = self.strftime(value, date_format) + + str_format = cls_attrs.str_format + if str_format is not None: + return str_format.format(value) + + format = cls_attrs.format + if format is not None: + return format.format(value) + + return retval + + # Format a datetime through its full proleptic Gregorian date range. + # http://code.activestate.com/recipes/ + # 306860-proleptic-gregorian-dates-and-strftime-before-1900/ + # http://stackoverflow.com/a/32206673 + # + # >>> strftime(datetime.date(1850, 8, 2), "%Y/%M/%d was a %A") + # '1850/00/02 was a Friday' + # >>> + + + # remove the unsupposed "%s" command. But don't + # do it if there's an even number of %s before the s + # because those are all escaped. Can't simply + # remove the s because the result of + # %sY + # should be %Y if %s isn't supported, not the + # 4 digit year. + _illegal_s = re.compile(r"((^|[^%])(%%)*%s)") + + @staticmethod + def _findall_datetime(text, substr): + # Also finds overlaps + sites = [] + i = 0 + while 1: + j = text.find(substr, i) + if j == -1: + break + sites.append(j) + i=j+1 + return sites + + # Every 28 years the calendar repeats, except through century leap + # years where it's 6 years. But only if you're using the Gregorian + # calendar. ;) + + @classmethod + def strftime(cls, dt, fmt): + if cls._illegal_s.search(fmt): + raise TypeError("This strftime implementation does not handle %s") + if dt.year > 1900: + return dt.strftime(fmt) + + year = dt.year + # For every non-leap year century, advance by + # 6 years to get into the 28-year repeat cycle + delta = 2000 - year + off = 6*(delta // 100 + delta // 400) + year += off + + # Move to around the year 2000 + year += ((2000 - year) // 28) * 28 + timetuple = dt.timetuple() + s1 = strftime(fmt, (year,) + timetuple[1:]) + sites1 = cls._findall_datetime(s1, str(year)) + + s2 = strftime(fmt, (year+28,) + timetuple[1:]) + sites2 = cls._findall_datetime(s2, str(year+28)) + + sites = [] + for site in sites1: + if site in sites2: + sites.append(site) + + s = s1 + syear = "%4d" % (dt.year,) + for site in sites: + s = s[:site] + syear + s[site+4:] + return s + + +_uuid_serialize = { + None: str, + str: str, + 'str': str, + + 'hex': lambda u: u.hex, + 'urn': lambda u: u.urn, + 'bytes': lambda u: u.bytes, + 'bytes_le': lambda u: u.bytes_le, + 'fields': lambda u: u.fields, + + int: lambda u: u.int, + 'int': lambda u: u.int, +} + +_uuid_deserialize = { + None: uuid.UUID, + str: uuid.UUID, + 'str': uuid.UUID, + + 'hex': lambda s: uuid.UUID(hex=s), + 'urn': lambda s: uuid.UUID(hex=s), + 'bytes': lambda s: uuid.UUID(bytes=s), + 'bytes_le': lambda s: uuid.UUID(bytes_le=s), + 'fields': lambda s: uuid.UUID(fields=s), + + int: lambda s: uuid.UUID(int=s), + 'int': lambda s: uuid.UUID(int=s), + + (int, int): lambda s: uuid.UUID(int=s), + ('int', int): lambda s: uuid.UUID(int=s), + + (int, str): lambda s: uuid.UUID(int=int(s)), + ('int', str): lambda s: uuid.UUID(int=int(s)), +} + +if six.PY2: + _uuid_deserialize[('int', long)] = _uuid_deserialize[('int', int)] + _uuid_deserialize[(int, long)] = _uuid_deserialize[('int', int)] + + +def _parse_datetime_iso_match(date_match, tz=None): + fields = date_match.groupdict() + + year = int(fields.get('year')) + month = int(fields.get('month')) + day = int(fields.get('day')) + hour = int(fields.get('hr')) + minute = int(fields.get('min')) + second = int(fields.get('sec')) + usecond = fields.get("sec_frac") + if usecond is None: + usecond = 0 + else: + # we only get the most significant 6 digits because that's what + # datetime can handle. + usecond = int(round(float(usecond) * 1e6)) + + return datetime(year, month, day, hour, minute, second, usecond, tz) + + +_dt_sec = lambda cls, val: \ + int(mktime(val.timetuple())) +_dt_sec_float = lambda cls, val: \ + mktime(val.timetuple()) + (val.microsecond / 1e6) + +_dt_msec = lambda cls, val: \ + int(mktime(val.timetuple())) * 1000 + (val.microsecond // 1000) +_dt_msec_float = lambda cls, val: \ + mktime(val.timetuple()) * 1000 + (val.microsecond / 1000.0) + +_dt_usec = lambda cls, val: \ + int(mktime(val.timetuple())) * 1000000 + val.microsecond + +_datetime_smap = { + 'sec': _dt_sec, + 'secs': _dt_sec, + 'second': _dt_sec, + 'seconds': _dt_sec, + + 'sec_float': _dt_sec_float, + 'secs_float': _dt_sec_float, + 'second_float': _dt_sec_float, + 'seconds_float': _dt_sec_float, + + 'msec': _dt_msec, + 'msecs': _dt_msec, + 'msecond': _dt_msec, + 'mseconds': _dt_msec, + 'millisecond': _dt_msec, + 'milliseconds': _dt_msec, + + 'msec_float': _dt_msec_float, + 'msecs_float': _dt_msec_float, + 'msecond_float': _dt_msec_float, + 'mseconds_float': _dt_msec_float, + 'millisecond_float': _dt_msec_float, + 'milliseconds_float': _dt_msec_float, + + 'usec': _dt_usec, + 'usecs': _dt_usec, + 'usecond': _dt_usec, + 'useconds': _dt_usec, + 'microsecond': _dt_usec, + 'microseconds': _dt_usec, +} + + +def _file_to_iter(f): + try: + data = f.read(8192) + while len(data) > 0: + yield data + data = f.read(8192) + + finally: + f.close() + + +META_ATTR = ['nullable', 'default_factory'] diff --git a/pym/calculate/contrib/spyne/protocol/_outbase.pyc b/pym/calculate/contrib/spyne/protocol/_outbase.pyc new file mode 100644 index 0000000..109829d Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/_outbase.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/cloth/__init__.py b/pym/calculate/contrib/spyne/protocol/cloth/__init__.py new file mode 100644 index 0000000..2bf9b93 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/cloth/__init__.py @@ -0,0 +1,26 @@ +# 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 diff --git a/pym/calculate/contrib/spyne/protocol/cloth/__init__.pyc b/pym/calculate/contrib/spyne/protocol/cloth/__init__.pyc new file mode 100644 index 0000000..a5b2db6 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/cloth/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/cloth/_base.py b/pym/calculate/contrib/spyne/protocol/cloth/_base.py new file mode 100644 index 0000000..4ea7fe6 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/cloth/_base.py @@ -0,0 +1,326 @@ +# 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 spyne import ProtocolContext, BODY_STYLE_WRAPPED, ByteArray, File, Array +from spyne.util import Break, coroutine +from spyne.protocol import ProtocolMixin + +from spyne.protocol.cloth.to_parent import ToParentMixin +from spyne.protocol.cloth.to_cloth import ToClothMixin +from spyne.util.six import BytesIO +from spyne.util.color import R, B +from spyne.util.tlist import tlist + + +class XmlClothProtocolContext(ProtocolContext): + def __init__(self, parent, transport, type=None): + super(XmlClothProtocolContext, self).__init__(parent, transport, type) + + self.inst_stack = tlist([], tuple) + self.prot_stack = tlist([], ProtocolMixin) + self.doctype_written = False + + +class XmlCloth(ToParentMixin, ToClothMixin): + mime_type = 'text/xml' + HtmlMicroFormat = None + + def __init__(self, app=None, encoding='utf8', doctype=None, + mime_type=None, ignore_uncap=False, ignore_wrappers=False, + cloth=None, cloth_parser=None, polymorphic=True, + strip_comments=True, use_ns=None, skip_root_tag=False): + + 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, strip_comments) + self.developer_mode = False + self.encoding = encoding + self.default_method = 'xml' + self.doctype = doctype + self.use_ns = use_ns + self.skip_root_tag = skip_root_tag + + 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 = BytesIO() + 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() + + if self.developer_mode: + # FIXME: the eff is this? + ctx.out_object = (inst,) + + retval = self._incgen(ctx, cls, inst, name) + else: + with self.docfile(ctx.out_stream, encoding=self.encoding) as xf: + retval = self.to_parent(ctx, cls, inst, xf, name) + + else: + assert message is self.RESPONSE + result_class = ctx.descriptor.out_message + + name = result_class.get_type_name() + if ctx.descriptor.body_style == BODY_STYLE_WRAPPED: + if self.ignore_wrappers: + result_inst = ctx.out_object[0] + while result_class.Attributes._wrapper and \ + len(result_class._type_info) == 1: + result_class, = result_class._type_info.values() + + else: + result_inst = result_class() + + for i, attr_name in enumerate( + result_class._type_info.keys()): + setattr(result_inst, attr_name, ctx.out_object[i]) + + else: + result_inst, = ctx.out_object + + retval = self._incgen(ctx, result_class, result_inst, 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, BytesIO): + ctx.out_string = [ctx.out_stream.getvalue()] + + @coroutine + def _incgen(self, ctx, cls, inst, name): + """Entry point to the (stack of) XmlCloth-based protocols. + + Not supposed to be overridden. + """ + + if name is None: + name = cls.get_type_name() + + try: + with self.docfile(ctx.out_stream, encoding=self.encoding) as xf: + ctx.outprot_ctx.doctype_written = False + ctx.protocol.prot_stack = tlist([], ProtocolMixin) + 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): + logger.debug("Starting file with %r %r", args, kwargs) + return etree.xmlfile(*args, **kwargs) + + def _get_doctype(self, cloth): + if self.doctype is not None: + return self.doctype + + if cloth is not None: + return cloth.getroottree().docinfo.doctype + + if self._root_cloth is not None: + return self._root_cloth.getroottree().docinfo.doctype + + if self._cloth is not None: + return self._cloth.getroottree().docinfo.doctype + + def write_doctype(self, ctx, parent, cloth=None): + dt = self._get_doctype(cloth) + if dt is None: + return + + parent.write_doctype(dt) + ctx.outprot_ctx.doctype_written = True + logger.debug("Doctype written as: '%s'", dt) + + @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.outprot_ctx.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.outprot_ctx.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 + + @coroutine + def subserialize(self, ctx, cls, inst, parent, name='', **kwargs): + """Bridge between multiple XmlCloth-based protocols. + + Not supposed to be overridden. + """ + + pstack = ctx.protocol.prot_stack + pstack.append(self) + logger.debug("%s push prot %r. newlen: %d", R("%"), self, len(pstack)) + + have_cloth = False + + cls_cloth = self.get_class_cloth(cls) + if cls_cloth is not None: + logger.debug("to object cloth for %s", cls.get_type_name()) + ret = self.to_parent_cloth(ctx, cls, inst, cls_cloth, parent, name) + + elif self._root_cloth is not None: + logger.debug("to root cloth for %s", cls.get_type_name()) + ret = self.to_root_cloth(ctx, cls, inst, self._root_cloth, + parent, name) + have_cloth = True + + elif self._cloth is not None: + logger.debug("to parent protocol cloth for %s", cls.get_type_name()) + ret = self.to_parent_cloth(ctx, cls, inst, self._cloth, parent, + name) + have_cloth = True + + else: + logger.debug("to parent for %s", cls.get_type_name()) + ret = self.start_to_parent(ctx, cls, inst, parent, name, **kwargs) + + 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 + finally: + self._finalize_protocol(ctx, parent, have_cloth) + else: + self._finalize_protocol(ctx, parent, have_cloth) + + pstack.pop() + logger.debug("%s pop prot %r. newlen: %d", B("%"), self, len(pstack)) + + def _finalize_protocol(self, ctx, parent, have_cloth): + if have_cloth: + self._close_cloth(ctx, parent) + return + + if len(ctx.protocol.prot_stack) == 1 and len(ctx.protocol.eltstack) > 0: + self._close_cloth(ctx, parent) + return + + @staticmethod + def _gen_tagname(ns, name): + if ns is not None: + name = "{%s}%s" % (ns, name) + return name + + def _gen_attrib_dict(self, inst, fti): + attrs = {} + + for field_name, field_type in fti.attrs.items(): + ns = field_type._ns + if ns is None: + ns = field_type.Attributes.sub_ns + + sub_name = field_type.Attributes.sub_name + if sub_name is None: + sub_name = field_name + + val = getattr(inst, field_name, None) + sub_name = self._gen_tagname(ns, sub_name) + + if issubclass(field_type.type, (ByteArray, File)): + valstr = self.to_unicode(field_type.type, val, + self.binary_encoding) + else: + valstr = self.to_unicode(field_type.type, val) + + if valstr is not None: + attrs[sub_name] = valstr + + return attrs + + def decompose_incoming_envelope(self, ctx, message): + raise NotImplementedError("This is an output-only protocol.") diff --git a/pym/calculate/contrib/spyne/protocol/cloth/_base.pyc b/pym/calculate/contrib/spyne/protocol/cloth/_base.pyc new file mode 100644 index 0000000..167be8b Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/cloth/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/cloth/to_cloth.py b/pym/calculate/contrib/spyne/protocol/cloth/to_cloth.py new file mode 100644 index 0000000..6dd59cd --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/cloth/to_cloth.py @@ -0,0 +1,865 @@ +# 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_c = logging.getLogger("%s.cloth" % __name__) +logger_s = logging.getLogger("%s.serializer" % __name__) + +from lxml import html, etree +from copy import deepcopy +from inspect import isgenerator + +from spyne.util import Break, coroutine +from spyne.util.oset import oset +from spyne.util.six import string_types +from spyne.util.color import R, B +from spyne.model import Array, AnyXml, AnyHtml, ModelBase, ComplexModelBase, \ + PushBase, XmlAttribute, AnyUri, XmlData, Any + +from spyne.protocol import OutProtocolBase +from spyne.util.cdict import cdict + +_revancestors = lambda elt: list(reversed(tuple(elt.iterancestors()))) + +_NODATA = type("_NODATA", (object,), {}) + + +def _prevsibls(elt, strip_comments, since=None): + return reversed(list(_prevsibls_since(elt, strip_comments, since))) + + +def _prevsibls_since(elt, strip_comments, since): + if since is elt: + return + + for prevsibl in elt.itersiblings(preceding=True): + if prevsibl is since: + break + + if strip_comments and isinstance(elt, etree.CommentBase): + if elt.text.startswith('[if ') and elt.text.endswith('[endif]'): + pass + else: + continue + + yield prevsibl + + +def _set_identifier_prefix(obj, prefix, mrpc_id='mrpc', id_attr='id', + data_tag='data', data_attr='data', attr_attr='attr', + root_attr='root', tagbag_attr='tagbag'): + obj.ID_PREFIX = prefix + + obj.MRPC_ID = '{}{}'.format(prefix, mrpc_id) + obj.ID_ATTR_NAME = '{}{}'.format(prefix, id_attr) + obj.DATA_TAG_NAME = '{}{}'.format(prefix, data_tag) + obj.DATA_ATTR_NAME = '{}{}'.format(prefix, data_attr) + obj.ATTR_ATTR_NAME = '{}{}'.format(prefix, attr_attr) + obj.ROOT_ATTR_NAME = '{}{}'.format(prefix, root_attr) + obj.TAGBAG_ATTR_NAME = '{}{}'.format(prefix, tagbag_attr) + # FIXME: get rid of this. We don't want logic creep inside cloths + obj.WRITE_CONTENTS_WHEN_NOT_NONE = '{}write-contents'.format(prefix) + + obj.SPYNE_ATTRS = { + obj.ID_ATTR_NAME, + obj.DATA_ATTR_NAME, + obj.ATTR_ATTR_NAME, + obj.ROOT_ATTR_NAME, + obj.TAGBAG_ATTR_NAME, + obj.WRITE_CONTENTS_WHEN_NOT_NONE, + } + + +class ClothParserMixin(object): + ID_PREFIX = 'spyne-' + + # these are here for documentation purposes. The are all reinitialized with + # the call ta _set_identifier_prefix below the class definition + ID_ATTR_NAME = 'spyne-id' + DATA_TAG_NAME = 'spyne-data' + DATA_ATTR_NAME = 'spyne-data' + ATTR_ATTR_NAME = 'spyne-attr' + ROOT_ATTR_NAME = 'spyne-root' + TAGBAG_ATTR_NAME = 'spyne-tagbag' + WRITE_CONTENTS_WHEN_NOT_NONE = 'spyne-write-contents' + + def set_identifier_prefix(self, what): + _set_identifier_prefix(self, what) + return self + + @classmethod + def from_xml_cloth(cls, cloth, strip_comments=True): + retval = cls() + retval._init_cloth(cloth, cloth_parser=etree.XMLParser(), + strip_comments=strip_comments) + return retval + + @classmethod + def from_html_cloth(cls, cloth, strip_comments=True): + retval = cls() + retval._init_cloth(cloth, cloth_parser=html.HTMLParser(), + strip_comments=strip_comments) + return retval + + @staticmethod + def _strip_comments(root): + for elt in root.iter(): + if isinstance(elt, etree.CommentBase): + if elt.getparent() is not None: + if elt.text.startswith('[if ') \ + and elt.text.endswith('[endif]'): + pass + else: + elt.getparent().remove(elt) + + def _parse_file(self, file_name, cloth_parser): + cloth = etree.parse(file_name, parser=cloth_parser) + return cloth.getroot() + + def _init_cloth(self, cloth, cloth_parser, strip_comments): + """Called from XmlCloth.__init__ in order to not break the dunder init + signature consistency""" + + self._cloth = None + self._root_cloth = None + self.strip_comments = strip_comments + + self._mrpc_cloth = self._root_cloth = None + + if cloth is None: + return + + if isinstance(cloth, string_types): + cloth = self._parse_file(cloth, cloth_parser) + + if strip_comments: + self._strip_comments(cloth) + + q = "//*[@%s]" % self.ROOT_ATTR_NAME + elts = cloth.xpath(q) + if len(elts) > 0: + logger_c.debug("Using %r as root cloth.", cloth) + self._root_cloth = elts[0] + else: + logger_c.debug("Using %r as plain cloth.", cloth) + self._cloth = cloth + + self._mrpc_cloth = self._pop_elt(cloth, 'mrpc_entry') + + def _pop_elt(self, elt, what): + query = '//*[@%s="%s"]' % (self.ID_ATTR_NAME, what) + retval = elt.xpath(query) + if len(retval) > 1: + raise ValueError("more than one element found for query %r" % query) + + elif len(retval) == 1: + retval = retval[0] + next(retval.iterancestors()).remove(retval) + return retval + + +_set_identifier_prefix(ClothParserMixin, ClothParserMixin.ID_PREFIX) + + +class ToClothMixin(OutProtocolBase, ClothParserMixin): + def __init__(self, app=None, mime_type=None, ignore_uncap=False, + ignore_wrappers=False, polymorphic=True): + super(ToClothMixin, self).__init__(app=app, mime_type=mime_type, + ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers) + + self.polymorphic = polymorphic + self.rendering_handlers = cdict({ + ModelBase: self.model_base_to_cloth, + AnyXml: self.xml_to_cloth, + Any: self.any_to_cloth, + AnyHtml: self.html_to_cloth, + AnyUri: self.any_uri_to_cloth, + ComplexModelBase: self.complex_to_cloth, + }) + + def _get_elts(self, elt, tag_id=None): + if tag_id is None: + return elt.xpath('.//*[@*[starts-with(name(), "%s")]]' % + self.ID_PREFIX) + return elt.xpath('.//*[@*[starts-with(name(), "%s")]="%s"]' % ( + self.ID_PREFIX, tag_id)) + + def _get_outmost_elts(self, tmpl, tag_id=None): + ids = set() + + # we assume xpath() returns elements in top to bottom (or outside to + # inside) order. + for elt in self._get_elts(tmpl, tag_id): + if elt is tmpl: # FIXME: kill this + logger_c.debug("Don't send myself") + continue # don't send myself + + if len(set((id(e) for e in elt.iterancestors())) & ids) > 0: + logger_c.debug("Don't send grandchildren") + continue # don't send grandchildren + + if id(elt) in ids: # FIXME: this check should be safe to remove + logger_c.debug("Don't send what's already been sent") + continue # don't send what's already been sent + + if self.ID_ATTR_NAME in elt.attrib: + # Prevent primitive attrs like spyne-attr from interfering + # with elt descent + ids.add(id(elt)) + + yield elt + + def _get_clean_elt(self, elt, what): + query = '//*[@%s="%s"]' % (self.ID_ATTR_NAME, what) + retval = elt.xpath(query) + if len(retval) > 1: + raise ValueError("more than one element found for query %r" % query) + + elif len(retval) == 1: + retval = retval[0] + del retval.attrib[self.ID_ATTR_NAME] + return retval + + def _get_elts_by_id(self, elt, what): + retval = elt.xpath('//*[@id="%s"]' % what) + logger_c.debug("id=%r got %r", what, retval) + return retval + + def _is_tagbag(self, elt): + return self.TAGBAG_ATTR_NAME in elt.attrib + + @staticmethod + def _methods(ctx, cls, inst): + while cls.Attributes._wrapper and len(cls._type_info) > 0: + cls, = cls._type_info.values() + + if cls.Attributes.methods is not None: + for k, v in cls.Attributes.methods.items(): + is_shown = True + if v.when is not None: + is_shown = v.when(inst, ctx) + + if is_shown: + yield k, v + + def _actions_to_cloth(self, ctx, cls, inst, template): + if self._mrpc_cloth is None: + logger_c.warning("missing 'mrpc_template'") + return + + for elt in self._get_elts(template, self.MRPC_ID): + for k, v in self._methods(ctx, cls, inst): + href = v.in_message.get_type_name() + text = v.translate(ctx.locale, v.in_message.get_type_name()) + + mrpc_template = deepcopy(self._mrpc_cloth) + anchor = self._get_clean_elt(mrpc_template, 'mrpc_link') + anchor.attrib['href'] = href + + text_elt = self._get_clean_elt(mrpc_template, 'mrpc_text') + if text_elt is not None: + text_elt.text = text + else: + anchor.text = text + + elt.append(mrpc_template) + # mutable default ok because readonly + def _enter_cloth(self, ctx, cloth, parent, attrib={}, skip=False, + method=None, skip_dupe=False): + """Enters the given tag in the document by using the shortest path from + current tag. + + 1. Moves up the tree by writing all tags so that the set of ancestors + of the current tag are a subset of the ancestors of the parent tag + 2. Writes all tags until hitting a direct ancestor, enters it, and + keeps writing previous siblings of ancestor tags and entering + ancestor tags until hitting the target tag. + 3. Enters the target tag and returns + + There is no _exit_cloth because exiting from tags is done + automatically with subsequent calls to _enter_cloth and finally to + _close_cloth. + + :param ctx: A MethodContext instance + :param cloth: The target cloth -- an ``lxml.etree._Element`` instance. + :param parent: The target stream -- typically an + ``lxml.etree._IncrementalFileWriter`` instance. + :param attrib: A dict of additional attributes for the target cloth. + :param skip: When True, the target tag is actually not entered. + Typically used for XmlData and friends. + :param method: One of ``(None, 'html', 'xml')``. When not ``None``, + overrides the output method of lxml. + :param skip_dupe: When ``False`` (the default) if this function is + called repeatedly for the same tag, the tag is exited and reentered. + This typically happens for types with ``max_occurs`` > 1 + (eg. arrays). + """ + + logger_c.debug("entering %s %r nsmap=%r attrib=%r skip=%s method=%s", + cloth.tag, cloth.attrib, cloth.nsmap, attrib, skip, method) + + if not ctx.outprot_ctx.doctype_written: + self.write_doctype(ctx, parent, cloth) + + tags = ctx.protocol.tags + rootstack = ctx.protocol.rootstack + assert isinstance(rootstack, oset) + + eltstack = ctx.protocol.eltstack + ctxstack = ctx.protocol.ctxstack + + cureltstack = eltstack[rootstack.back] + curctxstack = ctxstack[rootstack.back] + + if skip_dupe and len(cureltstack) > 0 and cureltstack[-1] is cloth: + return + + cloth_root = cloth.getroottree().getroot() + if not cloth_root in rootstack: + rootstack.add(cloth_root) + cureltstack = eltstack[rootstack.back] + curctxstack = ctxstack[rootstack.back] + + assert rootstack.back == cloth_root + + while rootstack.back != cloth_root: + self._close_cloth(ctx, parent) + + last_elt = None + if len(cureltstack) > 0: + last_elt = cureltstack[-1] + + ancestors = _revancestors(cloth) + + # move up in tag stack until the ancestors of both + # source and target tags match + while ancestors[:len(cureltstack)] != cureltstack: + elt = cureltstack.pop() + elt_ctx = curctxstack.pop() + + last_elt = elt + if elt_ctx is not None: + self.event_manager.fire_event(("before_exit", elt), ctx, parent) + elt_ctx.__exit__(None, None, None) + logger_c.debug("\texit norm %s %s", elt.tag, elt.attrib) + if elt.tail is not None: + parent.write(elt.tail) + + # unless we're at the same level as the relevant ancestor of the + # target node + if ancestors[:len(cureltstack)] != cureltstack: + # write following siblings before closing parent node + for sibl in elt.itersiblings(preceding=False): + logger_c.debug("\twrite exit sibl %s %r %d", + sibl.tag, sibl.attrib, id(sibl)) + parent.write(sibl) + + # write remaining ancestors of the target node. + for anc in ancestors[len(cureltstack):]: + # write previous siblings of ancestors (if any) + prevsibls = _prevsibls(anc, self.strip_comments, since=last_elt) + for elt in prevsibls: + if id(elt) in tags: + logger_c.debug("\tskip anc prevsibl %s %r", + elt.tag, elt.attrib) + continue + + logger_c.debug("\twrite anc prevsibl %s %r 0x%x", + elt.tag, elt.attrib, id(elt)) + parent.write(elt) + + # enter the ancestor node + kwargs = {} + if len(cureltstack) == 0: + # if this is the first node ever, initialize namespaces as well + kwargs['nsmap'] = anc.nsmap + + anc_ctx = parent.element(anc.tag, anc.attrib, **kwargs) + anc_ctx.__enter__() + logger_c.debug("\tenter norm %s %r 0x%x method: %r", anc.tag, + anc.attrib, id(anc), method) + if anc.text is not None: + parent.write(anc.text) + + rootstack.add(anc.getroottree().getroot()) + cureltstack = eltstack[rootstack.back] + curctxstack = ctxstack[rootstack.back] + cureltstack.append(anc) + curctxstack.append(anc_ctx) + + # now that at the same level as the target node, + # write its previous siblings + prevsibls = _prevsibls(cloth, self.strip_comments, since=last_elt) + for elt in prevsibls: + if elt is last_elt: + continue + + if id(elt) in tags: + logger_c.debug("\tskip cloth prevsibl %s %r", + elt.tag, elt.attrib) + continue + + logger_c.debug("\twrite cloth prevsibl %s %r", elt.tag, elt.attrib) + parent.write(elt) + + skip = skip or (cloth.tag == self.DATA_TAG_NAME) + + if skip: + tags.add(id(cloth)) + if method is not None: + curtag = parent.method(method) + curtag.__enter__() + else: + curtag = None + + else: + # finally, enter the target node. + cloth_attrib = dict([(k, v) for k, v in cloth.attrib.items() + if not k in self.SPYNE_ATTRS]) + + cloth_attrib.update(attrib) + + self.event_manager.fire_event(("before_entry", cloth), ctx, + parent, cloth_attrib) + + kwargs = {} + if len(cureltstack) == 0: + # if this is the first node ever, initialize namespaces as well + kwargs['nsmap'] = cloth.nsmap + if method is not None: + kwargs['method'] = method + curtag = parent.element(cloth.tag, cloth_attrib, **kwargs) + curtag.__enter__() + if cloth.text is not None: + parent.write(cloth.text) + + rootstack.add(cloth.getroottree().getroot()) + cureltstack = eltstack[rootstack.back] + curctxstack = ctxstack[rootstack.back] + + cureltstack.append(cloth) + curctxstack.append(curtag) + + logger_c.debug("") + + def _close_cloth(self, ctx, parent): + rootstack = ctx.protocol.rootstack + close_until = rootstack.back + cureltstack = ctx.protocol.eltstack[close_until] + curctxstack = ctx.protocol.ctxstack[close_until] + + for elt, elt_ctx in reversed(tuple(zip(cureltstack, curctxstack))): + if elt_ctx is not None: + self.event_manager.fire_event(("before_exit", elt), ctx, parent) + elt_ctx.__exit__(None, None, None) + logger_c.debug("exit %s close", elt.tag) + if elt.tail is not None: + parent.write(elt.tail) + + for sibl in elt.itersiblings(preceding=False): + logger_c.debug("write %s nextsibl", sibl.tag) + parent.write(sibl) + if sibl.tail is not None: + parent.write(sibl.tail) + + if elt is close_until: + logger_c.debug("closed until %r, breaking out", close_until) + break + + del ctx.protocol.eltstack[close_until] + del ctx.protocol.ctxstack[close_until] + + if len(rootstack) > 0: + rootstack.pop() + + @coroutine + def to_parent_cloth(self, ctx, cls, inst, cloth, parent, name, + from_arr=False, **kwargs): + cls_cloth = self.get_class_cloth(cls) + if cls_cloth is not None: + logger_c.debug("%r to object cloth", cls) + cloth = cls_cloth + ctx.protocol[self].rootstack.add(cloth) + + ret = self.to_cloth(ctx, cls, inst, cloth, parent, '') + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as e: + try: + ret.throw(e) + except (Break, StopIteration, GeneratorExit): + pass + + @coroutine + def to_root_cloth(self, ctx, cls, inst, cloth, parent, name): + if len(ctx.protocol.eltstack) > 0: + ctx.protocol[self].rootstack.add(cloth) + + cls_attrs = self.get_cls_attrs(cls) + self._enter_cloth(ctx, cloth, parent, method=cls_attrs.method) + + ret = self.start_to_parent(ctx, cls, inst, parent, name) + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as e: + try: + ret.throw(e) + except (Break, StopIteration, GeneratorExit): + pass + + # TODO: Maybe DRY this with to_parent? + @coroutine + def to_cloth(self, ctx, cls, inst, cloth, parent, name=None, + from_arr=False, as_attr=False, as_data=False, **kwargs): + + prot_name = self.__class__.__name__ + + if issubclass(cls, XmlAttribute): + cls = cls.type + as_attr = True + + elif issubclass(cls, XmlData): + cls = cls.type + as_data = True + + pushed = False + if cloth is None: + logger_c.debug("No cloth fround, switching to to_parent...") + ret = self.to_parent(ctx, cls, inst, parent, name, **kwargs) + + else: + cls, _ = self.get_polymorphic_target(cls, inst) + cls_attrs = self.get_cls_attrs(cls) + + inst = self._sanitize(cls_attrs, inst) + + # if instance is None, use the default factory to generate one + _df = cls_attrs.default_factory + if inst is None and callable(_df): + inst = _df() + + # if instance is still None, use the default value + if inst is None: + inst = cls_attrs.default + + # if there's a subprotocol, switch to it + subprot = cls_attrs.prot + if subprot is not None and not (subprot is self): + # we can't do this because subprotocols don't accept cloths. + # so we need to enter the cloth, which make it too late to + # set attributes. + assert not as_attr, "No subprot supported for fields " \ + "to be serialized as attributes, use type casting with " \ + "customized serializers in the current protocol instead." + + self._enter_cloth(ctx, cloth, parent, + method=cls_attrs.method, skip=as_data) + + ret = subprot.subserialize(ctx, cls, inst, parent, name, + as_attr=as_attr, as_data=as_data, **kwargs) + + # if there is no subprotocol, try rendering the value + else: + ret = None + + # try rendering the null value + if inst is None: + if cls_attrs.min_occurs > 0: + attrs = {} + if as_attr: + # FIXME: test needed + attrs[name] = '' + + self._enter_cloth(ctx, cloth, parent, attrib=attrs, + method=cls_attrs.method) + identifier = "%s.%s" % (prot_name, "null_to_cloth") + logger_s.debug("Writing '%s' using %s type: %s.", name, + identifier, cls.get_type_name()) + parent.write(cloth) + + else: + logger_s.debug("Skipping '%s' type: %s because empty.", + name, cls.get_type_name()) + self._enter_cloth(ctx, cloth, parent, skip=True, + method=cls_attrs.method) + + elif as_data: + # we only support XmlData of a primitive.,. is this a + # problem? + ret = self.to_unicode(cls, inst) + if ret is not None: + parent.write(ret) + + elif as_attr: + sub_name = cls_attrs.sub_name + if sub_name is None: + sub_name = name + attrs = {sub_name: self.to_unicode(cls, inst)} + + self._enter_cloth(ctx, cloth, parent, attrib=attrs, + method=cls_attrs.method) + + else: + # push the instance at hand to instance stack. this makes it + # easier for protocols to make decisions based on parents of + # instances at hand. + pushed = True + logger_c.debug("%s %r pushed %r %r", R("#"), self, cls, inst) + ctx.outprot_ctx.inst_stack.append((cls, inst, from_arr)) + + # try rendering the array value + if not from_arr and cls.Attributes.max_occurs > 1: + ret = self.array_to_cloth(ctx, cls, inst, cloth, parent, + as_attr=as_attr, name=name) + else: + # try rendering anything else + handler = self.rendering_handlers[cls] + + # disabled for performance reasons + # identifier = "%s.%s" % (prot_name, handler.__name__) + # from spyne.util.web import log_repr + # logger_s.debug("Writing %s using %s for %s. Inst: %r", + # name, identifier, cls.get_type_name(), + # log_repr(inst, cls, from_array=from_arr)) + + ret = handler(ctx, cls, inst, cloth, parent, name=name, + as_attr=as_attr) + + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as e: + try: + ret.throw(e) + except (Break, StopIteration, GeneratorExit): + pass + finally: + if pushed: + logger_c.debug("%s %r popped %r %r", B("#"), + self, cls, inst) + ctx.outprot_ctx.inst_stack.pop() + + else: + if pushed: + logger_c.debug("%s %r popped %r %r", B("#"), self, cls, inst) + ctx.outprot_ctx.inst_stack.pop() + + def model_base_to_cloth(self, ctx, cls, inst, cloth, parent, name, + **kwargs): + cls_attrs = self.get_cls_attrs(cls) + self._enter_cloth(ctx, cloth, parent, method=cls_attrs.method) + + # FIXME: Does it make sense to do this in other types? + if self.WRITE_CONTENTS_WHEN_NOT_NONE in cloth.attrib: + logger_c.debug("Writing contents for %r", cloth) + for c in cloth: + parent.write(c) + + else: + parent.write(self.to_unicode(cls, inst)) + + def xml_to_cloth(self, ctx, cls, inst, cloth, parent, name, **_): + cls_attrs = self.get_cls_attrs(cls) + self._enter_cloth(ctx, cloth, parent, method=cls_attrs.method) + if isinstance(inst, string_types): + inst = etree.fromstring(inst) + parent.write(inst) + + def any_to_cloth(self, ctx, cls, inst, cloth, parent, name, **_): + cls_attrs = self.get_cls_attrs(cls) + self._enter_cloth(ctx, cloth, parent, method=cls_attrs.method) + parent.write(inst) + + def html_to_cloth(self, ctx, cls, inst, cloth, parent, name, **_): + cls_attrs = self.get_cls_attrs(cls) + self._enter_cloth(ctx, cloth, parent, method=cls_attrs.method) + if isinstance(inst, string_types): + inst = html.fromstring(inst) + parent.write(inst) + + def any_uri_to_cloth(self, ctx, cls, inst, cloth, parent, name, **kwargs): + cls_attrs = self.get_cls_attrs(cls) + self._enter_cloth(ctx, cloth, parent, method=cls_attrs.method) + self.any_uri_to_parent(ctx, cls, inst, parent, name, **kwargs) + + @coroutine + def complex_to_cloth(self, ctx, cls, inst, cloth, parent, name=None, + as_attr=False, **kwargs): + fti = cls.get_flat_type_info(cls) + cls_attrs = self.get_cls_attrs(cls) + + # It's actually an odict but that's irrelevant here. + fti_check = dict(fti.items()) + elt_check = set() + + attrib = self._gen_attrib_dict(inst, fti) + self._enter_cloth(ctx, cloth, parent, attrib=attrib, + method=cls_attrs.method) + + for elt in self._get_elts(cloth, self.MRPC_ID): + self._actions_to_cloth(ctx, cls, inst, elt) + + if self._is_tagbag(cloth): + logger_c.debug("%r(%r) IS a tagbag", cloth, cloth.attrib) + elts = self._get_elts(cloth) + else: + logger_c.debug("%r(%r) is NOT a tagbag", cloth, cloth.attrib) + elts = self._get_outmost_elts(cloth) + + # Check for xmldata after entering the cloth. + as_data_field = cloth.attrib.get(self.DATA_ATTR_NAME, None) + if as_data_field is not None: + self._process_field(ctx, cls, inst, parent, cloth, fti, + as_data_field, as_attr, True, fti_check, elt_check, **kwargs) + + for elt in elts: + for k_attr, as_attr, as_data in ((self.ID_ATTR_NAME, False, False), + (self.ATTR_ATTR_NAME, True, False), + (self.DATA_ATTR_NAME, False, True)): + field_name = elt.attrib.get(k_attr, None) + if field_name is None: + continue + + if elt.tag == self.DATA_TAG_NAME: + as_data = True + + ret = self._process_field(ctx, cls, inst, parent, elt, fti, + field_name, as_attr=as_attr, as_data=as_data, + fti_check=fti_check, elt_check=elt_check, **kwargs) + + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as e: + try: + ret.throw(e) + except StopIteration: + pass + finally: + # cf below + if not (as_attr or as_data): + break + else: + # this is here so that attribute on complex model doesn't get + # mixed with in-line attr inside complex model. if an element + # has spyne-id, all other attrs are ignored and are processed + # by the object's serializer not its parent. + if not (as_attr or as_data): + break + + if len(fti_check) > 0: + logger_s.debug("No element found for the following fields: %r", + list(fti_check.keys())) + if len(elt_check) > 0: + logger_s.debug("No field found for element the following " + "elements: %r", list(elt_check)) + + def _process_field(self, ctx, cls, inst, parent, + elt, fti, field_name, as_attr, as_data, fti_check, elt_check, + **kwargs): + field_type = fti.get(field_name, None) + fti_check.pop(field_name, None) + + if field_type is None: + logger_c.warning("elt id %r not in %r", field_name, cls) + elt_check.add(field_name) + self._enter_cloth(ctx, elt, parent, skip=True) + return + + cls_attrs = self.get_cls_attrs(field_type) + if cls_attrs.exc: + logger_c.debug("Skipping elt id %r because " + "it was excluded", field_name) + return + + sub_name = cls_attrs.sub_name + if sub_name is None: + sub_name = field_name + + if issubclass(cls, Array): + # if cls is an array, inst should already be a sequence type + # (eg list), so there's no point in doing a getattr -- we will + # unwrap it and serialize it in the next round of to_cloth call. + val = inst + else: + val = getattr(inst, field_name, None) + + if as_data: + self._enter_cloth(ctx, elt, parent, skip=True, skip_dupe=True, + method=cls_attrs.method) + + return self.to_cloth(ctx, field_type, val, elt, parent, + name=sub_name, as_attr=as_attr, as_data=as_data, **kwargs) + + @coroutine + def array_to_cloth(self, ctx, cls, inst, cloth, parent, name=None, + **kwargs): + if isinstance(inst, PushBase): + while True: + sv = (yield) + ret = self.to_cloth(ctx, cls, sv, cloth, parent, + name=name, from_arr=True, **kwargs) + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as e: + try: + ret.throw(e) + except StopIteration: + pass + + else: + sv = _NODATA + + for sv in inst: + was_empty = False + + ret = self.to_cloth(ctx, cls, sv, cloth, parent, + from_arr=True, name=name, **kwargs) + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as e: + try: + ret.throw(e) + except StopIteration: + pass + + if sv is _NODATA: + # FIXME: what if min_occurs >= 1? + # fake entering the cloth to prevent it from being flushed as + # parent or sibling of another node later. + self._enter_cloth(ctx, cloth, parent, skip=True) diff --git a/pym/calculate/contrib/spyne/protocol/cloth/to_cloth.pyc b/pym/calculate/contrib/spyne/protocol/cloth/to_cloth.pyc new file mode 100644 index 0000000..d57a0d6 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/cloth/to_cloth.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/cloth/to_parent.py b/pym/calculate/contrib/spyne/protocol/cloth/to_parent.py new file mode 100644 index 0000000..e8b974c --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/cloth/to_parent.py @@ -0,0 +1,522 @@ +# 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 spyne.util.six.moves.collections_abc import Iterable + +from lxml import etree, html +from lxml.builder import E + +from spyne.const.xml import NS_XSI, NS_SOAP11_ENV, SOAP11_ENV +from spyne.model import PushBase, ComplexModelBase, AnyXml, Fault, AnyDict, \ + AnyHtml, ModelBase, ByteArray, XmlData, Any, AnyUri, ImageUri, XmlAttribute + +from spyne.model.enum import EnumBase +from spyne.protocol import OutProtocolBase +from spyne.protocol.xml import SchemaValidationError +from spyne.util import coroutine, Break, six +from spyne.util.cdict import cdict +from spyne.util.etreeconv import dict_to_etree +from spyne.util.color import R, B + +from spyne.util.six import string_types + + +class ToParentMixin(OutProtocolBase): + def __init__(self, app=None, mime_type=None, ignore_uncap=False, + ignore_wrappers=False, polymorphic=True): + super(ToParentMixin, self).__init__(app=app, mime_type=mime_type, + ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers) + + self.polymorphic = polymorphic + self.use_global_null_handler = True + + self.serialization_handlers = cdict({ + ModelBase: self.model_base_to_parent, + + AnyXml: self.any_xml_to_parent, + AnyUri: self.any_uri_to_parent, + ImageUri: self.imageuri_to_parent, + AnyDict: self.any_dict_to_parent, + AnyHtml: self.any_html_to_parent, + Any: self.any_to_parent, + + Fault: self.fault_to_parent, + EnumBase: self.enum_to_parent, + ByteArray: self.byte_array_to_parent, + ComplexModelBase: self.complex_to_parent, + SchemaValidationError: self.schema_validation_error_to_parent, + }) + + def start_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + """This is what subserialize calls""" + + # if no doctype was written, write it + if not ctx.outprot_ctx.doctype_written: + self.write_doctype(ctx, parent) + + return self.to_parent(ctx, cls, inst, parent, name, **kwargs) + + @staticmethod + def get_subprot(ctx, cls_attrs, nosubprot=False): + subprot = cls_attrs.prot + if subprot is not None and not nosubprot and not \ + (subprot in ctx.protocol.prot_stack): + return subprot + return None + + def to_subprot(self, ctx, cls, inst, parent, name, subprot, **kwargs): + return subprot.subserialize(ctx, cls, inst, parent, name, **kwargs) + + @coroutine + def to_parent(self, ctx, cls, inst, parent, name, nosubprot=False, **kwargs): + pushed = False + has_cloth = False + + prot_name = self.__class__.__name__ + + cls, switched = self.get_polymorphic_target(cls, inst) + cls_attrs = self.get_cls_attrs(cls) + if cls_attrs.out_type: + logger.debug("out_type from %r to %r", cls, cls_attrs.out_type) + cls = cls_attrs.out_type + cls_attrs = self.get_cls_attrs(cls) + + inst = self._sanitize(cls_attrs, inst) + + # if there is a subprotocol, switch to it + subprot = self.get_subprot(ctx, cls_attrs, nosubprot) + if subprot is not None: + logger.debug("Subprot from %r to %r", self, subprot) + ret = self.to_subprot(ctx, cls, inst, parent, name, subprot, + **kwargs) + else: + # if there is a class cloth, switch to it + has_cloth, cor_handle = self.check_class_cloths(ctx, cls, inst, + parent, name, **kwargs) + if has_cloth: + ret = cor_handle + + else: + # if instance is None, use the default factory to generate one + _df = cls_attrs.default_factory + if inst is None and callable(_df): + inst = _df() + + # if instance is still None, use the default value + if inst is None: + inst = cls_attrs.default + + # if instance is still None, use the global null handler to + # serialize it + if inst is None and self.use_global_null_handler: + identifier = prot_name + '.null_to_parent' + logger.debug("Writing %s using %s for %s.", name, + identifier, cls.get_type_name()) + self.null_to_parent(ctx, cls, inst, parent, name, **kwargs) + + return + + # if requested, ignore wrappers + if self.ignore_wrappers and issubclass(cls, ComplexModelBase): + cls, inst = self.strip_wrappers(cls, inst) + + # if cls is an iterable of values and it's not being iterated + # on, do it + from_arr = kwargs.get('from_arr', False) + # we need cls.Attributes here because we need the ACTUAL attrs + # that were set by the Array.__new__ + if not from_arr and cls.Attributes.max_occurs > 1: + ret = self.array_to_parent(ctx, cls, inst, parent, name, + **kwargs) + else: + # fetch the serializer for the class at hand + try: + handler = self.serialization_handlers[cls] + + except KeyError: + # if this protocol uncapable of serializing this class + if self.ignore_uncap: + logger.debug("Ignore uncap %r", name) + return # ignore it if requested + + # raise the error otherwise + logger.error("%r is missing handler for " + "%r for field %r", self, cls, name) + raise + + # push the instance at hand to instance stack. this makes it + # easier for protocols to make decisions based on parents + # of instances at hand. + ctx.outprot_ctx.inst_stack.append( (cls, inst, from_arr) ) + pushed = True + logger.debug("%s %r pushed %r using %r", + R("$"), self, cls, handler) + + # disabled for performance reasons + # from spyne.util.web import log_repr + # identifier = "%s.%s" % (prot_name, handler.__name__) + # log_str = log_repr(inst, cls, + # from_array=kwargs.get('from_arr', None)) + # logger.debug("Writing %s using %s for %s. Inst: %r", name, + # identifier, cls.get_type_name(), log_str) + + # finally, serialize the value. ret is the coroutine handle + ret = handler(ctx, cls, inst, parent, name, **kwargs) + + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + + except Break as e: + try: + ret.throw(e) + + except (Break, StopIteration, GeneratorExit): + pass + + finally: + if has_cloth: + self._close_cloth(ctx, parent) + + if pushed: + logger.debug("%s %r popped %r %r", B("$"), self, cls, + inst) + ctx.outprot_ctx.inst_stack.pop() + + else: + if has_cloth: + self._close_cloth(ctx, parent) + + if pushed: + logger.debug("%s %r popped %r %r", B("$"), self, cls, inst) + ctx.outprot_ctx.inst_stack.pop() + + @coroutine + def array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + if inst is None: + inst = () + + ser_subprot = self.get_subprot(ctx, self.get_cls_attrs(cls)) + + # FIXME: it's sad that this function has the same code twice. + + if isinstance(inst, PushBase): + # this will be popped by pusher_try_close + ctx.pusher_stack.append(inst) + + i = 0 + + try: + while True: + sv = (yield) + + # disabled because to_parent is supposed to take care of this + #ctx.protocol.inst_stack.append((cls, sv, True)) + kwargs['from_arr'] = True + kwargs['array_index'] = i + + if ser_subprot is not None: + ser_subprot.column_table_before_row(ctx, cls, inst, + parent, name, **kwargs) + + ret = self.to_parent(ctx, cls, sv, parent, name, **kwargs) + + i += 1 + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + + except Break as e: + try: + ret.throw(e) + except StopIteration: + pass + + finally: + # disabled because to_parent is supposed to take care of this + #popped_val = ctx.protocol.inst_stack.pop() + #assert popped_val is sv + + if ser_subprot is not None: + ser_subprot.column_table_before_row(ctx, cls, + inst, parent, name, **kwargs) + else: + # disabled because to_parent is supposed to take care of this + #popped_val = ctx.protocol.inst_stack.pop() + #assert popped_val is sv + + if ser_subprot is not None: + ser_subprot.column_table_after_row(ctx, cls, inst, + parent, name, **kwargs) + + except Break: + # pusher is done with pushing + pass + + else: + assert isinstance(inst, Iterable), ("%r is not iterable" % (inst,)) + + for i, sv in enumerate(inst): + # disabled because to_parent is supposed to take care of this + #ctx.protocol.inst_stack.append((cls, sv, True) + kwargs['from_arr'] = True + kwargs['array_index'] = i + + if ser_subprot is not None: + ser_subprot.column_table_before_row(ctx, cls, inst, parent, + name, **kwargs) + + ret = self.to_parent(ctx, cls, sv, parent, name, **kwargs) + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + + except Break as e: + try: + ret.throw(e) + except StopIteration: + pass + + finally: + # disabled because to_parent is supposed to take care of this + #popped_val = ctx.protocol.inst_stack.pop() + #assert popped_val is sv + + if ser_subprot is not None: + ser_subprot.column_table_after_row(ctx, cls, inst, + parent, name, **kwargs) + + else: + # disabled because to_parent is supposed to take care of this + #popped_val = ctx.protocol.inst_stack.pop() + #assert popped_val is sv + + if ser_subprot is not None: + ser_subprot.column_table_after_row(ctx, cls, inst, + parent, name, **kwargs) + + def not_supported(self, ctx, cls, *args, **kwargs): + if not self.ignore_uncap: + raise NotImplementedError("Serializing %r not supported!" % cls) + + def any_uri_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + self.model_base_to_parent(ctx, cls, inst, parent, name, **kwargs) + + def imageuri_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + self.model_base_to_parent(ctx, cls, inst, parent, name, **kwargs) + + def byte_array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + parent.write(E(name, self.to_unicode(cls, inst, self.binary_encoding))) + + def model_base_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + parent.write(E(name, self.to_unicode(cls, inst))) + + def null_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + parent.write(E(name, **{'{%s}nil' % NS_XSI: 'true'})) + + def enum_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + self.model_base_to_parent(ctx, cls, str(inst), parent, name) + + def any_xml_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + if isinstance(inst, string_types): + inst = etree.fromstring(inst) + + parent.write(E(name, inst)) + + def any_html_to_unicode(self, cls, inst, **_): + if isinstance(inst, (str, six.text_type)): + inst = html.fromstring(inst) + + return inst + + def any_html_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + cls_attrs = self.get_cls_attrs(cls) + + if cls_attrs.as_string: + if not (isinstance(inst, str) or isinstance(inst, six.text_type)): + inst = html.tostring(inst) + + else: + if isinstance(inst, str) or isinstance(inst, six.text_type): + inst = html.fromstring(inst) + + parent.write(E(name, inst)) + + def any_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + parent.write(E(name, inst)) + + def any_dict_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + elt = E(name) + dict_to_etree(inst, elt) + parent.write(E(name, elt)) + + def _gen_sub_name(self, cls, cls_attrs, k, use_ns=None): + if self.use_ns is not None and use_ns is None: + use_ns = self.use_ns + + sub_ns = cls_attrs.sub_ns + if sub_ns is None: + sub_ns = cls.get_namespace() + + sub_name = cls_attrs.sub_name + if sub_name is None: + sub_name = k + + if use_ns: + name = "{%s}%s" % (sub_ns, sub_name) + else: + name = sub_name + + return name + + @coroutine + def _write_members(self, ctx, cls, inst, parent, use_ns=None, **kwargs): + if self.use_ns is not None and use_ns is None: + use_ns = self.use_ns + + for k, v in self.sort_fields(cls): + attr = self.get_cls_attrs(v) + if attr.exc: + prot_name = self.__class__.__name__ + logger.debug("%s: excluded for %s.", k, prot_name) + continue + + if issubclass(v, XmlAttribute): + continue + + try: # e.g. SqlAlchemy could throw NoSuchColumnError + subvalue = getattr(inst, k, None) + except: + subvalue = None + + # This is a tight loop, so enable this only when necessary. + # logger.debug("get %r(%r) from %r: %r" % (k, v, inst, subvalue)) + + sub_name = self._gen_sub_name(cls, attr, k, use_ns) + + if issubclass(v, XmlData): + subvalstr = self.to_unicode(v.type, subvalue) + if subvalstr is not None: + parent.write(subvalstr) + continue + + if subvalue is not None or attr.min_occurs > 0: + ret = self.to_parent(ctx, v, subvalue, parent, sub_name, + use_ns=use_ns, **kwargs) + if ret is not None: + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as b: + try: + ret.throw(b) + except StopIteration: + pass + + @coroutine + def _complex_to_parent_do(self, ctx, cls, inst, parent, **kwargs): + # parent.write(u"\u200c") # zero-width non-joiner + parent.write(" ") # FIXME: to force empty tags to be sent as + # instead of + ret = self._write_members(ctx, cls, inst, parent, **kwargs) + if ret is not None: + try: + while True: + sv2 = (yield) # may throw Break + ret.send(sv2) + + except Break: + try: + ret.throw(Break()) + except StopIteration: + pass + + def complex_to_parent(self, ctx, cls, inst, parent, name, + from_arr=False, use_ns=None, **kwargs): + if not from_arr: + inst = cls.get_serialization_instance(inst) + + attrib = self._gen_attrib_dict(inst, cls.get_flat_type_info(cls)) + + if self.skip_root_tag: + self._complex_to_parent_do(ctx, cls, inst, parent, + from_arr=from_arr, **kwargs) + + else: + if name is None or name == '': + name = self._gen_sub_name(cls, self.get_cls_attrs(cls), + cls.get_type_name(), use_ns) + logger.debug("name is empty, long live name: %s, cls: %r", + name, cls) + + with parent.element(name, attrib=attrib): + self._complex_to_parent_do(ctx, cls, inst, parent, + from_arr=from_arr, **kwargs) + + def fault_to_parent(self, ctx, cls, inst, parent, name): + PREF_SOAP_ENV = ctx.app.interface.prefmap[NS_SOAP11_ENV] + tag_name = SOAP11_ENV("Fault") + + with parent.element(tag_name): + parent.write( + E("faultcode", '%s:%s' % (PREF_SOAP_ENV, inst.faultcode)), + E("faultstring", inst.faultstring), + E("faultactor", inst.faultactor), + ) + + if isinstance(inst.detail, etree._Element): + parent.write(E.detail(inst.detail)) + + # add other nonstandard fault subelements with get_members_etree + self._write_members(ctx, cls, inst, parent) + # no need to track the returned generator because we expect no + # PushBase instance here. + + def schema_validation_error_to_parent(self, ctx, cls, inst, parent, **_): + PREF_SOAP_ENV = ctx.app.interface.prefmap[NS_SOAP11_ENV] + tag_name = SOAP11_ENV("Fault") + + with parent.element(tag_name): + parent.write( + E("faultcode", '%s:%s' % (PREF_SOAP_ENV, inst.faultcode)), + # HACK: Does anyone know a better way of injecting raw xml entities? + E("faultstring", html.fromstring(inst.faultstring).text), + E("faultactor", inst.faultactor), + ) + + if isinstance(inst.detail, etree._Element): + parent.write(E.detail(inst.detail)) + + # add other nonstandard fault subelements with get_members_etree + self._write_members(ctx, cls, inst, parent) + # no need to track the returned generator because we expect no + # PushBase instance here. diff --git a/pym/calculate/contrib/spyne/protocol/cloth/to_parent.pyc b/pym/calculate/contrib/spyne/protocol/cloth/to_parent.pyc new file mode 100644 index 0000000..b342c77 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/cloth/to_parent.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/csv.py b/pym/calculate/contrib/spyne/protocol/csv.py new file mode 100644 index 0000000..915ca88 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/csv.py @@ -0,0 +1,136 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.protocol.csv`` package contains the Csv output protocol. + +This protocol is here merely for illustration purposes. While it is in a +somewhat working state, it is not that easy to use. Expect a revamp in the +coming versions. +""" + +from __future__ import absolute_import + +import logging +logger = logging.getLogger(__name__) + +import csv + +from spyne import ComplexModelBase +from spyne.util import six +from spyne.protocol.dictdoc import HierDictDocument + +if six.PY2: + from StringIO import StringIO +else: + from io import StringIO + + +def _complex_to_csv(prot, ctx): + cls, = ctx.descriptor.out_message._type_info.values() + + queue = StringIO() + + serializer, = cls._type_info.values() + + if issubclass(serializer, ComplexModelBase): + type_info = serializer.get_flat_type_info(serializer) + keys = [k for k, _ in prot.sort_fields(serializer)] + + else: + type_info = {serializer.get_type_name(): serializer} + keys = list(type_info.keys()) + + if ctx.out_error is not None: + writer = csv.writer(queue, dialect=csv.excel) + writer.writerow(['Error in generating the document']) + if ctx.out_error is not None: + for r in ctx.out_error.to_bytes_iterable(ctx.out_error): + writer.writerow([r]) + + yield queue.getvalue() + queue.truncate(0) + + else: + writer = csv.DictWriter(queue, dialect=csv.excel, fieldnames=keys) + if prot.header: + titles = {} + for k in keys: + v = type_info[k] + titles[k] = prot.trc(v, ctx.locale, k) + + writer.writerow(titles) + + yield queue.getvalue() + queue.truncate(0) + + if ctx.out_object[0] is not None: + for v in ctx.out_object[0]: + d = prot._to_dict_value(serializer, v, set()) + if six.PY2: + for k in d: + if isinstance(d[k], unicode): + d[k] = d[k].encode('utf8') + + writer.writerow(d) + yval = queue.getvalue() + yield yval + queue.truncate(0) + + +class Csv(HierDictDocument): + mime_type = 'text/csv' + text_based = True + + type = set(HierDictDocument.type) + type.add('csv') + + def __init__(self, app=None, validator=None, mime_type=None, + ignore_uncap=False, ignore_wrappers=True, complex_as=dict, + ordered=False, polymorphic=False, header=True): + + super(Csv, self).__init__(app=app, validator=validator, + mime_type=mime_type, ignore_uncap=ignore_uncap, + ignore_wrappers=ignore_wrappers, complex_as=complex_as, + ordered=ordered, polymorphic=polymorphic) + + self.header = header + + def create_in_document(self, ctx): + raise NotImplementedError() + + def serialize(self, ctx, message): + assert message in (self.RESPONSE, ) + + if ctx.out_object is None: + ctx.out_object = [] + + assert len(ctx.descriptor.out_message._type_info) == 1, \ + "CSV Serializer supports functions with exactly one return type: " \ + "%r" % ctx.descriptor.out_message._type_info + + def create_out_string(self, ctx): + ctx.out_string = _complex_to_csv(self, ctx) + if 'http' in ctx.transport.type: + ctx.transport.resp_headers['Content-Disposition'] = ( + 'attachment; filename=%s.csv;' % ctx.descriptor.name) + + def any_uri_to_unicode(self, cls, value, **_): + if isinstance(value, cls.Value): + value = value.text + return super(Csv, self).any_uri_to_unicode(cls, value, **_) diff --git a/pym/calculate/contrib/spyne/protocol/csv.pyc b/pym/calculate/contrib/spyne/protocol/csv.pyc new file mode 100644 index 0000000..e0a044d Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/csv.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/dictdoc/__init__.py b/pym/calculate/contrib/spyne/protocol/dictdoc/__init__.py new file mode 100644 index 0000000..c0cee4d --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/dictdoc/__init__.py @@ -0,0 +1,137 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.protocol.dictdoc`` module contains an abstract +protocol that deals with hierarchical and flat dicts as {in,out}_documents. + +Flattening +========== + +Plain HTTP does not support hierarchical key-value stores. Spyne makes plain +HTTP fake hierarchical dicts with two small hacks. + +Let's look at the following object hierarchy: :: + + class Inner(ComplexModel): + c = Integer + d = Array(Integer) + + class Outer(ComplexModel): + a = Integer + b = Inner + +For example, the ``Outer(a=1, b=Inner(c=2))`` object would correspond to the +following hierarchichal dict representation: :: + + {'a': 1, 'b': { 'c': 2 }} + +Here's what we do to deserialize the above object structure from a flat dict: + +1. Object hierarchies are flattened. e.g. the flat representation of the above + dict is: ``{'a': 1, 'b.c': 2}``. +2. Arrays of objects are sent using variables with array indexes in square + brackets. So the request with the following query object: :: + + {'a': 1, 'b.d[0]': 1, 'b.d[1]': 2}} + + ... corresponds to: :: + + {'a': 1, 'b': { 'd': [1,2] }} + + If we had: :: + + class Inner(ComplexModel): + c = Integer + + class Outer(ComplexModel): + a = Integer + b = Array(SomeObject) + + Or the following object: :: + + {'a': 1, 'b[0].c': 1, 'b[1].c': 2}} + + ... would correspond to: :: + + {'a': 1, 'b': [{ 'c': 1}, {'c': 2}]} + + ... which would deserialize as: :: + + Outer(a=1, b=[Inner(c=1), Inner(c=2)]) + +These hacks are both slower to process and bulkier on wire, so use class +hierarchies with HTTP only when performance is not that much of a concern. + +Cookies +======= + +Cookie headers are parsed and fields within HTTP requests are assigned to +fields in the ``in_header`` class, if defined. + +It's also possible to get the ``Cookie`` header intact by defining an +``in_header`` object with a field named ``Cookie`` (case sensitive). + +As an example, let's assume the following HTTP request: :: + + GET / HTTP/1.0 + Cookie: v1=4;v2=8 + (...) + +The keys ``v1`` and ``v2`` are passed to the instance of the ``in_header`` +class if it has fields named ``v1`` or ``v2``\\. + +Wrappers +======== + +Wrapper objects are an artifact of the Xml world, which don't really make sense +in other protocols. Let's look at the following object: :: + + v = Permission(application='app', feature='f1'), + +Here's how it would be serialized to XML: :: + + + app + f1 + + +With ``ignore_wrappers=True`` (which is the default) This gets serialized to +dict as follows: :: + + { + "application": "app", + "feature": "f1" + } + +When ``ignore_wrappers=False``, the same value/type combination would result in +the following dict: :: + + {"Permission": { + { + "application": "app", + "feature": "f1" + } + }, + +This could come in handy in case you don't know what type to expect. +""" + +from spyne.protocol.dictdoc._base import DictDocument +from spyne.protocol.dictdoc.hier import HierDictDocument +from spyne.protocol.dictdoc.simple import SimpleDictDocument diff --git a/pym/calculate/contrib/spyne/protocol/dictdoc/__init__.pyc b/pym/calculate/contrib/spyne/protocol/dictdoc/__init__.pyc new file mode 100644 index 0000000..85905a9 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/dictdoc/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/dictdoc/_base.py b/pym/calculate/contrib/spyne/protocol/dictdoc/_base.py new file mode 100644 index 0000000..a10b121 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/dictdoc/_base.py @@ -0,0 +1,147 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logger = logging.getLogger(__name__) + +import re +RE_HTTP_ARRAY_INDEX = re.compile("\\[([0-9]+)\\]") + +from spyne.error import ValidationError + +from spyne.model import Fault, Array, AnyXml, AnyHtml, Uuid, DateTime, Date, \ + Time, Duration + +from spyne.protocol import ProtocolBase + + +class DictDocument(ProtocolBase): + """An abstract protocol that can use hierarchical or flat dicts as input + and output documents. + + Implement ``serialize()``, ``deserialize()``, ``create_in_document()`` and + ``create_out_string()`` to use this. + """ + + # flags to be used in tests + _decimal_as_string = False + _huge_numbers_as_string = False + text_based = False + + def __init__(self, app=None, validator=None, mime_type=None, + ignore_uncap=False, ignore_wrappers=True, complex_as=dict, + ordered=False, polymorphic=False, key_encoding=None): + + super(DictDocument, self).__init__(app, validator, mime_type, + ignore_uncap, ignore_wrappers) + + self.key_encoding = key_encoding + self.polymorphic = polymorphic + self.complex_as = complex_as + self.ordered = ordered + if ordered: + raise NotImplementedError('ordered=True') + + self.stringified_types = (DateTime, Date, Time, Uuid, Duration, + AnyXml, AnyHtml) + + def set_validator(self, validator): + """Sets the validator for the protocol. + + :param validator: one of ('soft', None) + """ + + if validator == 'soft' or validator is self.SOFT_VALIDATION: + self.validator = self.SOFT_VALIDATION + elif validator is None: + self.validator = None + else: + raise ValueError(validator) + + def decompose_incoming_envelope(self, ctx, message): + """Sets ``ctx.in_body_doc``, ``ctx.in_header_doc`` and + ``ctx.method_request_string`` using ``ctx.in_document``. + """ + + assert message in (ProtocolBase.REQUEST, ProtocolBase.RESPONSE) + + # set ctx.in_header + ctx.transport.in_header_doc = None # use an rpc protocol if you want headers. + + doc = ctx.in_document + + ctx.in_header_doc = None + ctx.in_body_doc = doc + + if message is ProtocolBase.REQUEST: + #logger.debug('\theader : %r', ctx.in_header_doc) + #logger.debug('\tbody : %r', ctx.in_body_doc) + + if not isinstance(doc, dict) or len(doc) != 1: + raise ValidationError(doc, + "Need a dictionary with exactly one key as method name.") + + if len(doc) == 0: + raise Fault("Client", "Empty request") + + ctx.method_request_string = self.gen_method_request_string(ctx) + + def gen_method_request_string(self, ctx): + """Uses information in context object to return a method_request_string. + + Returns a string in the form of "{namespaces}method name". + """ + + mrs, = ctx.in_body_doc.keys() + return '{%s}%s' % (self.app.interface.get_tns(), mrs) + + def deserialize(self, ctx, message): + raise NotImplementedError() + + def serialize(self, ctx, message): + raise NotImplementedError() + + def create_in_document(self, ctx, in_string_encoding=None): + raise NotImplementedError() + + def create_out_string(self, ctx, out_string_encoding='utf8'): + raise NotImplementedError() + + def _check_freq_dict(self, cls, d, fti=None): + if fti is None: + fti = cls.get_flat_type_info(cls) + + for k, v in fti.items(): + val = d[k] + + attrs = self.get_cls_attrs(v) + min_o, max_o = attrs.min_occurs, attrs.max_occurs + + if issubclass(v, Array) and v.Attributes.max_occurs == 1: + v, = v._type_info.values() + attrs = self.get_cls_attrs(v) + min_o, max_o = attrs.min_occurs, attrs.max_occurs + + if val < min_o: + raise ValidationError("%r.%s" % (cls, k), + '%%s member must occur at least %d times.' % min_o) + + elif val > max_o: + raise ValidationError("%r.%s" % (cls, k), + '%%s member must occur at most %d times.' % max_o) diff --git a/pym/calculate/contrib/spyne/protocol/dictdoc/_base.pyc b/pym/calculate/contrib/spyne/protocol/dictdoc/_base.pyc new file mode 100644 index 0000000..2589be6 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/dictdoc/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/dictdoc/hier.py b/pym/calculate/contrib/spyne/protocol/dictdoc/hier.py new file mode 100644 index 0000000..c0ea199 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/dictdoc/hier.py @@ -0,0 +1,569 @@ + +# +# 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__) + +import re +RE_HTTP_ARRAY_INDEX = re.compile("\\[([0-9]+)\\]") + +from mmap import mmap +from collections import defaultdict +from spyne.util.six.moves.collections_abc import Iterable as AbcIterable + +from spyne.util import six +from spyne.error import ValidationError +from spyne.error import ResourceNotFoundError + +from spyne.model import ByteArray, File, Fault, ComplexModelBase, Array, Any, \ + AnyDict, Uuid, Unicode + +from spyne.protocol.dictdoc import DictDocument + + +class HierDictDocument(DictDocument): + """This protocol contains logic for protocols that serialize and deserialize + hierarchical dictionaries. Examples include: Json, MessagePack and Yaml. + + Implement ``create_in_document()`` and ``create_out_string()`` to use this. + """ + + VALID_UNICODE_SOURCES = (six.text_type, six.binary_type, memoryview, + mmap, bytearray) + + from_serstr = DictDocument.from_unicode + to_serstr = DictDocument.to_unicode + + def get_class_name(self, cls): + class_name = cls.get_type_name() + if not six.PY2: + if isinstance(class_name, bytes): + class_name = class_name.decode('utf8') + + return class_name + + def get_complex_as(self, attr): + if attr.complex_as is None: + return self.complex_as + return attr.complex_as + + def deserialize(self, ctx, message): + assert message in (self.REQUEST, self.RESPONSE) + + self.event_manager.fire_event('before_deserialize', ctx) + + if ctx.descriptor is None: + raise ResourceNotFoundError(ctx.method_request_string) + + # instantiate the result message + if message is self.REQUEST: + body_class = ctx.descriptor.in_message + elif message is self.RESPONSE: + body_class = ctx.descriptor.out_message + else: + raise ValueError(message) # should be impossible + + if body_class: + # assign raw result to its wrapper, result_message + doc = ctx.in_body_doc + + class_name = self.get_class_name(body_class) + if self.ignore_wrappers: + doc = doc.get(class_name, None) + + result_message = self._doc_to_object(ctx, body_class, doc, + self.validator) + ctx.in_object = result_message + + else: + ctx.in_object = [] + + self.event_manager.fire_event('after_deserialize', ctx) + + def _fault_to_doc(self, inst, cls=None): + if cls is None: + cls = Fault + + if self.complex_as is list: + return [cls.to_list(inst.__class__, inst, self)] + + elif self.complex_as is tuple: + fault_as_list = [Fault.to_list(inst.__class__, inst, self)] + return tuple(fault_as_list) + + else: + return [Fault.to_dict(inst.__class__, inst, self)] + + def serialize(self, ctx, message): + assert message in (self.REQUEST, self.RESPONSE) + + self.event_manager.fire_event('before_serialize', ctx) + + if ctx.out_error is not None: + ctx.out_document = self._fault_to_doc(ctx.out_error) + return + + # get the result message + if message is self.REQUEST: + out_type = ctx.descriptor.in_message + + elif message is self.RESPONSE: + out_type = ctx.descriptor.out_message + + else: + assert False + + if out_type is None: + return + + # assign raw result to its wrapper, result_message + if ctx.descriptor.is_out_bare(): + out_instance, = ctx.out_object + + else: + out_type_info = out_type.get_flat_type_info(out_type) + + # instantiate the result message + out_instance = out_type() + + for i, (k, v) in enumerate(out_type_info.items()): + attrs = self.get_cls_attrs(v) + out_instance._safe_set(k, ctx.out_object[i], v, attrs) + + ctx.out_document = self._object_to_doc(out_type, out_instance, set()), + + logger.debug("Retval: %r", ctx.out_document) + self.event_manager.fire_event('after_serialize', ctx) + + def validate(self, key, cls, inst): + if inst is None and self.get_cls_attrs(cls).nullable: + pass + + elif issubclass(cls, Unicode) and not isinstance(inst, + self.VALID_UNICODE_SOURCES): + raise ValidationError([key, inst]) + + def _from_dict_value(self, ctx, key, cls, inst, validator): + if validator is self.SOFT_VALIDATION: + self.validate(key, cls, inst) + + cls_attrs = self.get_cls_attrs(cls) + complex_as = self.get_complex_as(cls_attrs) + if complex_as is list or complex_as is tuple: + check_complex_as = (list, tuple) + else: + check_complex_as = complex_as + + # get native type + if issubclass(cls, File): + if isinstance(inst, check_complex_as): + cls = cls_attrs.type or cls + inst = self._parse(cls_attrs, inst) + retval = self._doc_to_object(ctx, cls, inst, validator) + + else: + retval = self.from_serstr(cls, inst, self.binary_encoding) + + else: + inst = self._parse(cls_attrs, inst) + + if issubclass(cls, (Any, AnyDict)): + retval = inst + + elif issubclass(cls, ComplexModelBase): + retval = self._doc_to_object(ctx, cls, inst, validator) + + else: + if cls_attrs.empty_is_none and inst in (u'', b''): + inst = None + + if (validator is self.SOFT_VALIDATION + and isinstance(inst, six.string_types) + and not cls.validate_string(cls, inst)): + raise ValidationError([key, inst]) + + if issubclass(cls, (ByteArray, Uuid)): + retval = self.from_serstr(cls, inst, self.binary_encoding) + + elif issubclass(cls, Unicode): + if isinstance(inst, bytearray): + retval = six.text_type(inst, + encoding=cls_attrs.encoding or 'ascii', + errors=cls_attrs.unicode_errors) + + elif isinstance(inst, memoryview): + # FIXME: memoryview needs a .decode() function to avoid + # needless copying here + retval = inst.tobytes().decode( + cls_attrs.encoding or 'ascii', + errors=cls_attrs.unicode_errors) + + elif isinstance(inst, mmap): + # FIXME: mmap needs a .decode() function to avoid + # needless copying here + retval = mmap[:].decode(cls_attrs.encoding, + errors=cls_attrs.unicode_errors) + + elif isinstance(inst, six.binary_type): + retval = self.unicode_from_bytes(cls, inst) + + else: + retval = inst + + else: + retval = self.from_serstr(cls, inst) + + # validate native type + if validator is self.SOFT_VALIDATION: + if not cls.validate_native(cls, retval): + raise ValidationError([key, retval]) + + return retval + + def _doc_to_object(self, ctx, cls, doc, validator=None): + if doc is None: + return [] + + if issubclass(cls, Any): + doc = self._cast(self.get_cls_attrs(cls), doc) + return doc + + if issubclass(cls, Array): + doc = self._cast(self.get_cls_attrs(cls), doc) + retval = [] + (serializer,) = cls._type_info.values() + + if not isinstance(doc, AbcIterable): + raise ValidationError(doc) + + for i, child in enumerate(doc): + retval.append(self._from_dict_value(ctx, i, serializer, child, + validator)) + + return retval + + cls_attrs = self.get_cls_attrs(cls) + if not self.ignore_wrappers and not cls_attrs.not_wrapped: + if not isinstance(doc, dict): + raise ValidationError(doc, "Wrapper documents must be dicts") + + if len(doc) == 0: + return None + + if len(doc) > 1: + raise ValidationError(doc, "There can be only one entry in a " + "wrapper dict") + + subclasses = cls.get_subclasses() + (class_name, doc), = doc.items() + if not six.PY2 and isinstance(class_name, bytes): + class_name = class_name.decode('utf8') + + if cls.get_type_name() != class_name and subclasses is not None \ + and len(subclasses) > 0: + for subcls in subclasses: + if subcls.get_type_name() == class_name: + break + else: + raise ValidationError(class_name, + "Class name %%r is not registered as a subclass of %r" % + cls.get_type_name()) + + if not self.issubclass(subcls, cls): + raise ValidationError(class_name, + "Class name %%r is not a subclass of %r" % + cls.get_type_name()) + cls = subcls + + inst = cls.get_deserialization_instance(ctx) + + # get all class attributes, including the ones coming from + # parent classes. + flat_type_info = cls.get_flat_type_info(cls) + if flat_type_info is None: + logger.critical("No flat_type_info found for type %r", cls) + raise TypeError(cls) + + # this is for validating cls.Attributes.{min,max}_occurs + frequencies = defaultdict(int) + + try: + items = doc.items() + except AttributeError: + # Input is not a dict, so we assume it's a sequence that we can pair + # with the incoming sequence with field names. + # TODO: cache this + try: + items = zip([k for k, v in flat_type_info.items() + if not self.get_cls_attrs(v).exc], doc) + except TypeError as e: + logger.error("Invalid document %r for %r", doc, cls) + raise ValidationError(doc) + + # parse input to set incoming data to related attributes. + for k, v in items: + if self.key_encoding is not None and isinstance(k, bytes): + try: + k = k.decode(self.key_encoding) + except UnicodeDecodeError: + raise ValidationError(k) + + member = flat_type_info.get(k, None) + if member is None: + member, k = flat_type_info.alt.get(k, (None, k)) + if member is None: + continue + + member_attrs = self.get_cls_attrs(member) + + if member_attrs.exc: + continue + + mo = member_attrs.max_occurs + if mo > 1: + subinst = getattr(inst, k, None) + if subinst is None: + subinst = [] + + for a in v: + subinst.append( + self._from_dict_value(ctx, k, member, a, validator)) + + else: + subinst = self._from_dict_value(ctx, k, member, v, validator) + + inst._safe_set(k, subinst, member, member_attrs) + + frequencies[k] += 1 + + attrs = self.get_cls_attrs(cls) + if validator is self.SOFT_VALIDATION and attrs.validate_freq: + self._check_freq_dict(cls, frequencies, flat_type_info) + + return inst + + def _object_to_doc(self, cls, inst, tags=None): + if inst is None: + return None + + if tags is None: + tags = set() + + retval = None + + if isinstance(inst, Fault): + retval = None + inst_id = id(inst) + if not (inst_id in tags): + retval = self._fault_to_doc(inst, cls) + tags.add(inst_id) + return retval + + cls_attrs = self.get_cls_attrs(cls) + if cls_attrs.exc: + return + + cls_orig = None + if cls_attrs.out_type is not None: + cls_orig = cls + cls = cls_attrs.out_type + # remember to do this if cls_attrs are needed below + # (currently cls_attrs is not used so we don't do this) + # cls_attrs = self.get_cls_attrs(cls) + + elif cls_attrs.type is not None: + cls_orig = cls + cls = cls_attrs.type + # remember to do this if cls_attrs are needed below + # (currently cls_attrs is not used so we don't do this) + # cls_attrs = self.get_cls_attrs(cls) + + if self.ignore_wrappers: + ti = getattr(cls, '_type_info', {}) + + while cls.Attributes._wrapper and len(ti) == 1: + # Wrappers are auto-generated objects that have exactly one + # child type. + key, = ti.keys() + if not issubclass(cls, Array): + inst = getattr(inst, key, None) + cls, = ti.values() + ti = getattr(cls, '_type_info', {}) + + # transform the results into a dict: + if cls.Attributes.max_occurs > 1: + if inst is not None: + retval = [] + + for subinst in inst: + if id(subinst) in tags: + # even when there is ONE already-serialized instance, + # we throw the whole thing away. + logger.debug("Throwing the whole array away because " + "found %d", id(subinst)) + + # this is DANGEROUS + #logger.debug("Said array: %r", inst) + + return None + + retval.append(self._to_dict_value(cls, subinst, tags, + cls_orig=cls_orig or cls)) + + else: + retval = self._to_dict_value(cls, inst, tags, + cls_orig=cls_orig or cls) + + return retval + + def _get_member_pairs(self, cls, inst, tags): + old_len = len(tags) + tags = tags | {id(inst)} + assert len(tags) > old_len, ("Offending instance: %r" % inst) + + for k, v in self.sort_fields(cls): + subattr = self.get_cls_attrs(v) + + if subattr.exc: + continue + + try: + subinst = getattr(inst, k, None) + + # to guard against e.g. sqlalchemy throwing NoSuchColumnError + except Exception as e: + logger.error("Error getting %r: %r" % (k, e)) + subinst = None + + if subinst is None: + subinst = subattr.default + else: + if id(subinst) in tags: + continue + + logger.debug("%s%r%r", " " * len(tags), k, v) + val = self._object_to_doc(v, subinst, tags) + min_o = subattr.min_occurs + + complex_as = self.get_complex_as(subattr) + if val is not None or min_o > 0 or complex_as is list: + sub_name = subattr.sub_name + if sub_name is None: + sub_name = k + + yield (sub_name, val) + + def _to_dict_value(self, cls, inst, tags, cls_orig=None): + if cls_orig is None: + cls_orig = cls + cls, switched = self.get_polymorphic_target(cls, inst) + cls_attrs = self.get_cls_attrs(cls) + + inst = self._sanitize(cls_attrs, inst) + + if issubclass(cls_orig, File): + cls_orig_attrs = self.get_cls_attrs(cls_orig) + if not isinstance(inst, cls_orig_attrs.type): + return self.to_serstr(cls_orig, inst, self.binary_encoding) + + retval = self._complex_to_doc(cls_orig_attrs.type, inst, tags) + complex_as = self.get_complex_as(cls_orig_attrs) + + if complex_as is dict and not self.ignore_wrappers: + retval = next(iter(retval.values())) + + return retval + + if issubclass(cls, (Any, AnyDict)): + return inst + + if issubclass(cls, Array): + st, = cls._type_info.values() + return self._object_to_doc(st, inst, tags) + + if issubclass(cls, ComplexModelBase): + return self._complex_to_doc(cls, inst, tags) + + if issubclass(cls, (ByteArray, Uuid)): + return self.to_serstr(cls, inst, self.binary_encoding) + + return self.to_serstr(cls, inst) + + def _complex_to_doc(self, cls, inst, tags): + cls_attrs = self.get_cls_attrs(cls) + sf = cls_attrs.simple_field + if sf is not None: + # we want this to throw when sf does not exist + subcls = cls.get_flat_type_info(cls)[sf] + + subinst = getattr(inst, sf, None) + + logger.debug("Render complex object %s to the value %r of its " + "field '%s'", cls.get_type_name(), subinst, sf) + + return self.to_unicode(subcls, subinst) + + cls_attr = self.get_cls_attrs(cls) + complex_as = self.get_complex_as(cls_attr) + if complex_as is list or \ + getattr(cls.Attributes, 'serialize_as', False) is list: + return list(self._complex_to_list(cls, inst, tags)) + return self._complex_to_dict(cls, inst, tags) + + def _complex_to_dict(self, cls, inst, tags): + inst = cls.get_serialization_instance(inst) + cls_attr = self.get_cls_attrs(cls) + complex_as = self.get_complex_as(cls_attr) + + if self.key_encoding is None: + d = complex_as(self._get_member_pairs(cls, inst, tags)) + + if (self.ignore_wrappers or cls_attr.not_wrapped) \ + and not bool(cls_attr.wrapper): + return d + + else: + if isinstance(cls_attr.wrapper, + (six.text_type, six.binary_type)): + return {cls_attr.wrapper: d} + else: + return {cls.get_type_name(): d} + else: + d = complex_as( (k.encode(self.key_encoding), v) for k, v in + self._get_member_pairs(cls, inst, tags) ) + + if (self.ignore_wrappers or cls_attr.not_wrapped) \ + and not bool(cls_attr.wrapper): + return d + + else: + if isinstance(cls_attr.wrapper, six.text_type): + return {cls_attr.wrapper.encode(self.key_encoding): d} + elif isinstance(cls_attr.wrapper, six.binary_type): + return {cls_attr.wrapper: d} + else: + return {cls.get_type_name().encode(self.key_encoding): d} + + def _complex_to_list(self, cls, inst, tags): + inst = cls.get_serialization_instance(inst) + + for k, v in self._get_member_pairs(cls, inst, tags): + yield v diff --git a/pym/calculate/contrib/spyne/protocol/dictdoc/hier.pyc b/pym/calculate/contrib/spyne/protocol/dictdoc/hier.pyc new file mode 100644 index 0000000..92629f2 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/dictdoc/hier.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/dictdoc/simple.py b/pym/calculate/contrib/spyne/protocol/dictdoc/simple.py new file mode 100644 index 0000000..6b608bd --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/dictdoc/simple.py @@ -0,0 +1,404 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logger = logging.getLogger(__name__) + +import re +RE_HTTP_ARRAY_INDEX = re.compile("\\[([0-9]+)\\]") + +from collections import deque +from collections import defaultdict + +from spyne.util import six +from spyne.error import ValidationError + +from spyne.model import ByteArray, String, File, ComplexModelBase, Array, \ + SimpleModel, Any, AnyDict, Unicode + +from spyne.protocol.dictdoc import DictDocument + + +def _s2cmi(m, nidx): + """ + Sparse to contiguous mapping inserter. + + >>> m1={3:0, 4:1, 7:2} + >>> _s2cmi(m1, 5); m1 + 1 + {3: 0, 4: 1, 5: 2, 7: 3} + >>> _s2cmi(m1, 0); m1 + 0 + {0: 0, 3: 1, 4: 2, 5: 3, 7: 4} + >>> _s2cmi(m1, 8); m1 + 4 + {0: 0, 3: 1, 4: 2, 5: 3, 7: 4, 8: 5} + """ + nv = -1 + for i, v in m.items(): + if i >= nidx: + m[i] += 1 + elif v > nv: + nv = v + m[nidx] = nv + 1 + return nv + 1 + + +def _fill(inst_class, frequencies): + """This function initializes the frequencies dict with null values. If this + is not done, it won't be possible to catch missing elements when validating + the incoming document. + """ + + ctype_info = inst_class.get_flat_type_info(inst_class) + cfreq_key = inst_class, 0 + + for k, v in ctype_info.items(): + if v.Attributes.min_occurs > 0: + frequencies[cfreq_key][k] = 0 + + +class SimpleDictDocument(DictDocument): + """This protocol contains logic for protocols that serialize and deserialize + flat dictionaries. The only example as of now is Http. + """ + + def __init__(self, app=None, validator=None, mime_type=None, + ignore_uncap=False, ignore_wrappers=True, complex_as=dict, + ordered=False, hier_delim='.', strict_arrays=False): + super(SimpleDictDocument, self).__init__(app=app, validator=validator, + mime_type=mime_type, ignore_uncap=ignore_uncap, + ignore_wrappers=ignore_wrappers, complex_as=complex_as, + ordered=ordered) + + self.hier_delim = hier_delim + self.strict_arrays = strict_arrays + + def _to_native_values(self, cls, member, orig_k, k, v, req_enc, validator): + value = [] + + for v2 in v: + # some wsgi implementations pass unicode strings, some pass str + # strings. we get unicode here when we can and should. + if v2 is not None and req_enc is not None \ + and not issubclass(member.type, String) \ + and issubclass(member.type, Unicode) \ + and not isinstance(v2, six.text_type): + try: + v2 = v2.decode(req_enc) + except UnicodeDecodeError as e: + raise ValidationError(v2, "%r while decoding %%r" % e) + + # validate raw data (before deserialization) + try: + if (validator is self.SOFT_VALIDATION and not + member.type.validate_string(member.type, v2)): + raise ValidationError([orig_k, v2]) + + except TypeError: + raise ValidationError([orig_k, v2]) + + cls_attrs = self.get_cls_attrs(member.type) + v2 = self._parse(cls_attrs, v2) + + # deserialize to native type + if issubclass(member.type, File): + if isinstance(v2, File.Value): + native_v2 = v2 + else: + native_v2 = self.from_unicode(member.type, v2, + self.binary_encoding) + + elif issubclass(member.type, ByteArray): + native_v2 = self.from_unicode(member.type, v2, + self.binary_encoding) + else: + try: + native_v2 = self.from_unicode(member.type, v2) + except ValidationError as e: + ns = "%s.%s" % (cls.get_namespace(), cls.get_type_name()) + raise ValidationError(e.faultstring, + "Validation failed for %s.%s: %%s" % (ns, k)) + + # validate native data (after deserialization) + native_v2 = self._sanitize(cls_attrs, native_v2) + if validator is self.SOFT_VALIDATION: + if not member.type.validate_native(member.type, native_v2): + raise ValidationError([orig_k, v2]) + + value.append(native_v2) + + return value + + def simple_dict_to_object(self, ctx, doc, cls, validator=None, req_enc=None): + """Converts a flat dict to a native python object. + + See :func:`spyne.model.complex.ComplexModelBase.get_flat_type_info`. + """ + + if issubclass(cls, (Any, AnyDict)): + return doc + + if not issubclass(cls, ComplexModelBase): + raise NotImplementedError("Interestingly, deserializing non complex" + " types is not yet implemented. You can" + " use a ComplexModel to wrap that field." + " Otherwise, patches are welcome.") + + # this is for validating cls.Attributes.{min,max}_occurs + frequencies = defaultdict(lambda: defaultdict(int)) + if validator is self.SOFT_VALIDATION: + _fill(cls, frequencies) + + if issubclass(cls, Array): + # we need the wrapper object instance here as it's a root object + retval = cls.get_serialization_instance([]) + else: + retval = cls.get_deserialization_instance(ctx) + + simple_type_info = cls.get_simple_type_info_with_prot(cls, self, + hier_delim=self.hier_delim) + + logger.debug("Simple type info key: %r", simple_type_info.keys()) + + idxmap = defaultdict(dict) + for orig_k, v in sorted(doc.items(), key=lambda _k: _k[0]): + k = RE_HTTP_ARRAY_INDEX.sub("", orig_k) + + member = simple_type_info.get(k, None) + if member is None: + logger.debug("\tdiscarding field %r" % k) + continue + + if member.can_be_empty: + if v != ['empty']: # maybe raise a ValidationError instead? + # 'empty' is the only valid value at this point after all + continue + + assert issubclass(member.type, ComplexModelBase) + + if issubclass(member.type, Array): + value = [] + + elif self.get_cls_attrs(member.type).max_occurs > 1: + value = [] + + else: + value = [member.type.get_deserialization_instance(ctx)] + # do we have to ignore later assignments? they're illegal + # but not harmful. + else: + # extract native values from the list of strings in the flat dict + # entries. + value = self._to_native_values(cls, member, orig_k, k, v, + req_enc, validator) + + + # assign the native value to the relevant class in the nested object + # structure. + cinst = retval + ctype_info = cls.get_flat_type_info(cls) + ccls_attr = self.get_cls_attrs(cls) + value = self._cast(ccls_attr, value) + + idx, nidx = 0, 0 + pkey = member.path[0] + cfreq_key = cls, idx + + indexes = deque(RE_HTTP_ARRAY_INDEX.findall(orig_k)) + for pkey in member.path[:-1]: + nidx = 0 + ncls, ninst = ctype_info[pkey], getattr(cinst, pkey, None) + nattrs = self.get_cls_attrs(ncls) + if issubclass(ncls, Array): + ncls, = ncls._type_info.values() + + ncls_attrs = self.get_cls_attrs(ncls) + mo = ncls_attrs.max_occurs + if mo > 1: + if len(indexes) == 0: + nidx = 0 + else: + nidx = int(indexes.popleft()) + + if ninst is None: + ninst = [] + cinst._safe_set(pkey, ninst, ncls, nattrs) + + if self.strict_arrays: + if len(ninst) == 0: + newval = ncls.get_deserialization_instance(ctx) + ninst.append(newval) + frequencies[cfreq_key][pkey] += 1 + + if nidx > len(ninst): + raise ValidationError(orig_k, + "%%r Invalid array index %d." % idx) + if nidx == len(ninst): + ninst.append(ncls.get_deserialization_instance(ctx)) + frequencies[cfreq_key][pkey] += 1 + + cinst = ninst[nidx] + + else: + _m = idxmap[id(ninst)] + cidx = _m.get(nidx, None) + if cidx is None: + cidx = _s2cmi(_m, nidx) + newval = ncls.get_deserialization_instance(ctx) + ninst.insert(cidx, newval) + frequencies[cfreq_key][pkey] += 1 + cinst = ninst[cidx] + + assert cinst is not None, ninst + + else: + if ninst is None: + ninst = ncls.get_deserialization_instance(ctx) + cinst._safe_set(pkey, ninst, ncls, nattrs) + frequencies[cfreq_key][pkey] += 1 + + cinst = ninst + + cfreq_key = cfreq_key + (ncls, nidx) + idx = nidx + ctype_info = ncls.get_flat_type_info(ncls) + + frequencies[cfreq_key][member.path[-1]] += len(value) + + member_attrs = self.get_cls_attrs(member.type) + if member_attrs.max_occurs > 1: + _v = getattr(cinst, member.path[-1], None) + is_set = True + if _v is None: + is_set = cinst._safe_set(member.path[-1], value, + member.type, member_attrs) + else: + _v.extend(value) + + set_skip = 'set ' if is_set else 'SKIP' + logger.debug("\t%s arr %r(%r) = %r" % + (set_skip, member.path, pkey, value)) + + else: + is_set = cinst._safe_set(member.path[-1], value[0], + member.type, member_attrs) + + set_skip = 'set ' if is_set else 'SKIP' + logger.debug("\t%s val %r(%r) = %r" % + (set_skip, member.path, pkey, value[0])) + + if validator is self.SOFT_VALIDATION: + logger.debug("\tvalidate_freq: \n%r", frequencies) + for k, d in frequencies.items(): + for i, path_cls in enumerate(k[:-1:2]): + attrs = self.get_cls_attrs(path_cls) + if not attrs.validate_freq: + logger.debug("\t\tskip validate_freq: %r", k[:i*2]) + break + else: + path_cls = k[-2] + logger.debug("\t\tdo validate_freq: %r", k) + self._check_freq_dict(path_cls, d) + + if issubclass(cls, Array): + # unwrap the request object + array_name, = cls._type_info.keys() + retval = getattr(retval, array_name) + + return retval + + def object_to_simple_dict(self, cls, inst, retval=None, + prefix=None, subinst_eater=lambda prot, v, t: v, tags=None): + """Converts a native python object to a flat dict. + + See :func:`spyne.model.complex.ComplexModelBase.get_flat_type_info`. + """ + + if retval is None: + retval = {} + + if prefix is None: + prefix = [] + + if inst is None and self.get_cls_attrs(cls).min_occurs == 0: + return retval + + if tags is None: + tags = set([id(inst)]) + else: + if id(inst) in tags: + return retval + + if issubclass(cls, ComplexModelBase): + fti = cls.get_flat_type_info(cls) + + for k, v in fti.items(): + new_prefix = list(prefix) + cls_attrs = self.get_cls_attrs(v) + sub_name = cls_attrs.sub_name + if sub_name is None: + sub_name = k + new_prefix.append(sub_name) + subinst = getattr(inst, k, None) + + if (issubclass(v, Array) or v.Attributes.max_occurs > 1) and \ + subinst is not None: + if issubclass(v, Array): + subtype, = v._type_info.values() + else: + subtype = v + + # for simple types, the same key is repeated with multiple + # values + if issubclass(subtype, SimpleModel): + key = self.hier_delim.join(new_prefix) + l = [] + for ssv in subinst: + l.append(subinst_eater(self, ssv, subtype)) + retval[key] = l + + else: + # for complex types, brackets are used for each value. + last_prefix = new_prefix[-1] + i = -1 + for i, ssv in enumerate(subinst): + new_prefix[-1] = '%s[%d]' % (last_prefix, i) + self.object_to_simple_dict(subtype, ssv, + retval, new_prefix, + subinst_eater=subinst_eater, tags=tags) + + if i == -1: + key = self.hier_delim.join(new_prefix) + retval[key] = 'empty' + + else: + self.object_to_simple_dict(v, subinst, retval, new_prefix, + subinst_eater=subinst_eater, tags=tags) + + else: + key = self.hier_delim.join(prefix) + + if key in retval: + raise ValueError("%r.%s conflicts with previous value %r" % + (cls, key, retval[key])) + + retval[key] = subinst_eater(self, inst, cls) + + return retval diff --git a/pym/calculate/contrib/spyne/protocol/dictdoc/simple.pyc b/pym/calculate/contrib/spyne/protocol/dictdoc/simple.pyc new file mode 100644 index 0000000..7f84860 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/dictdoc/simple.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/html/__init__.py b/pym/calculate/contrib/spyne/protocol/html/__init__.py new file mode 100644 index 0000000..43b6c5c --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/html/__init__.py @@ -0,0 +1,41 @@ +# encoding: utf8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +""" +This package contains some basic html output protocols. +""" + +from spyne.protocol.html._base import HtmlBase +from spyne.protocol.html._base import HtmlCloth +from spyne.protocol.html._base import parse_html_fragment_file +from spyne.protocol.html.table import HtmlColumnTable +from spyne.protocol.html.table import HtmlRowTable +from spyne.protocol.html.microformat import HtmlMicroFormat +from spyne.protocol.html.addtl import PrettyFormat +from spyne.protocol.html.addtl import BooleanListProtocol + + +# FIXME: REMOVE ME +def translate(cls, locale, default): + retval = None + if cls.Attributes.translations is not None: + retval = cls.Attributes.translations.get(locale, None) + if retval is None: + return default + return retval diff --git a/pym/calculate/contrib/spyne/protocol/html/__init__.pyc b/pym/calculate/contrib/spyne/protocol/html/__init__.pyc new file mode 100644 index 0000000..d85df77 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/html/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/html/_base.py b/pym/calculate/contrib/spyne/protocol/html/_base.py new file mode 100644 index 0000000..142118e --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/html/_base.py @@ -0,0 +1,251 @@ +# encoding: utf8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logger = logging.getLogger(__name__) + +from collections import defaultdict + +from lxml import etree, html +from lxml.html.builder import E + +from spyne.util import coroutine, Break, six +from spyne.util.oset import oset +from spyne.util.etreeconv import dict_to_etree + +from spyne.protocol.cloth import XmlCloth +from spyne.protocol.cloth._base import XmlClothProtocolContext + + +def parse_html_fragment_file(T_FILES): + elt = html.fromstring(open(T_FILES).read()) + elt.getparent().remove(elt) + return elt + + +class HtmlClothProtocolContext(XmlClothProtocolContext): + def __init__(self, parent, transport, type=None): + super(HtmlClothProtocolContext, self).__init__(parent, transport, type) + + self.assets = [] + self.eltstack = defaultdict(list) + self.ctxstack = defaultdict(list) + self.rootstack = oset() + self.tags = set() + self.objcache = dict() + + # these are supposed to be for neurons.base.screen.ScreenBase subclasses + self.screen = None + self.prev_view = None + self.next_view = None + + +class HtmlCloth(XmlCloth): + mime_type = 'text/html; charset=UTF-8' + + def __init__(self, app=None, encoding='utf8', + mime_type=None, ignore_uncap=False, ignore_wrappers=False, + cloth=None, cloth_parser=None, polymorphic=True, + strip_comments=True, hier_delim='.', doctype=None): + + super(HtmlCloth, self).__init__(app=app, encoding=encoding, + mime_type=mime_type, ignore_uncap=ignore_uncap, + ignore_wrappers=ignore_wrappers, cloth=cloth, + cloth_parser=cloth_parser, polymorphic=polymorphic, + strip_comments=strip_comments) + + self.hier_delim = hier_delim + self.doctype = doctype + self.default_method = 'html' + + def _parse_file(self, file_name, cloth_parser): + if cloth_parser is None: + cloth_parser = html.HTMLParser() + + cloth = html.parse(file_name, parser=cloth_parser) + return cloth.getroot() + + def docfile(self, *args, **kwargs): + logger.debug("Starting file with %r %r", args, kwargs) + return etree.htmlfile(*args, **kwargs) + + def get_context(self, parent, transport): + return HtmlClothProtocolContext(parent, transport) + + @staticmethod + def get_class_cloth(cls): + return cls.Attributes._html_cloth + + @staticmethod + def get_class_root_cloth(cls): + return cls.Attributes._html_root_cloth + + def dict_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + parent.write(repr(inst)) + + @staticmethod + def add_html_attr(attr_name, attr_dict, class_name): + if attr_name in attr_dict: + attr_dict[attr_name] = ' '.join( + (attr_dict.get('class', ''), class_name)) + else: + attr_dict[attr_name] = class_name + + @staticmethod + def add_style(attr_dict, data): + style = attr_dict.get('style', None) + + if style is not None: + attr_dict['style'] = ';'.join(style, data) + + else: + attr_dict['style'] = data + + @staticmethod + def selsafe(s): + return s.replace('[', '').replace(']', '').replace('.', '__') + + @coroutine + def complex_to_parent(self, ctx, cls, inst, parent, name, use_ns=False, + **kwargs): + inst = cls.get_serialization_instance(inst) + + # TODO: Put xml attributes as well in the below element() call. + with parent.element(name): + ret = self._write_members(ctx, cls, inst, parent, use_ns=False, + **kwargs) + if ret is not None: + try: + while True: + sv2 = (yield) # may throw Break + ret.send(sv2) + + except Break: + try: + ret.throw(Break()) + except StopIteration: + pass + + def gen_anchor(self, cls, inst, name, anchor_class=None): + assert name is not None + cls_attrs = self.get_cls_attrs(cls) + + href = getattr(inst, 'href', None) + if href is None: # this is not a AnyUri.Value instance. + href = inst + + content = None + text = cls_attrs.text + + else: + content = getattr(inst, 'content', None) + text = getattr(inst, 'text', None) + if text is None: + text = cls_attrs.text + + if anchor_class is None: + anchor_class = cls_attrs.anchor_class + + if text is None: + text = name + + retval = E.a(text) + + if href is not None: + retval.attrib['href'] = href + + if anchor_class is not None: + retval.attrib['class'] = anchor_class + + if content is not None: + retval.append(content) + + return retval + + def any_uri_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + retval = self.gen_anchor(cls, inst, name) + parent.write(retval) + + def imageuri_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + # with ImageUri, content is ignored. + href = getattr(inst, 'href', None) + if href is None: # this is not a AnyUri.Value instance. + href = inst + text = getattr(cls.Attributes, 'text', None) + + else: + text = getattr(inst, 'text', None) + if text is None: + text = getattr(cls.Attributes, 'text', None) + + retval = E.img(src=href) + if text is not None: + retval.attrib['alt'] = text + + parent.write(retval) + + def byte_array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + ret = self.to_unicode(cls, inst, self.binary_encoding) + + if ret is not None: + parent.write(ret) + + def model_base_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + ret = self.to_unicode(cls, inst) + + if ret is not None: + parent.write(ret) + + def null_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + pass + + def any_xml_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + if isinstance(inst, (six.text_type, six.binary_type)): + inst = etree.fromstring(inst) + + parent.write(inst) + + def any_html_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + cls_attrs = self.get_cls_attrs(cls) + + if cls_attrs.as_string: + if not (isinstance(inst, str) or isinstance(inst, six.text_type)): + inst = html.tostring(inst) + + else: + if isinstance(inst, str) or isinstance(inst, six.text_type): + inst = html.fromstring(inst) + + parent.write(inst) + + def any_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + parent.write(inst) + + def any_dict_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + elt = E('foo') + dict_to_etree(inst, elt) + + parent.write(elt[0]) + + def fault_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + self.complex_to_parent(ctx, cls, inst, parent, name, **kwargs) + + +# FIXME: Deprecated +HtmlBase = HtmlCloth diff --git a/pym/calculate/contrib/spyne/protocol/html/_base.pyc b/pym/calculate/contrib/spyne/protocol/html/_base.pyc new file mode 100644 index 0000000..179ab79 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/html/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/html/addtl.py b/pym/calculate/contrib/spyne/protocol/html/addtl.py new file mode 100644 index 0000000..c89af8a --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/html/addtl.py @@ -0,0 +1,52 @@ +# encoding: utf8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from lxml.builder import E +from pprint import pformat + +from spyne import Boolean +from spyne.protocol.html import HtmlBase + + +class PrettyFormat(HtmlBase): + def to_parent(self, ctx, cls, inst, parent, name, **kwargs): + parent.write(E.pre(pformat(inst))) + + +class BooleanListProtocol(HtmlBase): + def __init__(self, nothing=None): + super(BooleanListProtocol, self).__init__() + + self.nothing = nothing + + def to_parent(self, ctx, cls, inst, parent, name, nosubprot=False, **kwargs): + if inst is None: + return + + wrote_nothing = True + for k, v in cls.get_flat_type_info(cls).items(): + if not issubclass(v, Boolean): + continue + + if getattr(inst, k, False): + parent.write(E.p(self.trc(cls, ctx.locale, k))) + wrote_nothing = False + + if wrote_nothing and self.nothing is not None: + parent.write(E.p(self.nothing)) diff --git a/pym/calculate/contrib/spyne/protocol/html/addtl.pyc b/pym/calculate/contrib/spyne/protocol/html/addtl.pyc new file mode 100644 index 0000000..9b814c8 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/html/addtl.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/html/microformat.py b/pym/calculate/contrib/spyne/protocol/html/microformat.py new file mode 100644 index 0000000..b78e1e1 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/html/microformat.py @@ -0,0 +1,197 @@ +# encoding: utf8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from inspect import isgenerator + +from lxml.html.builder import E + +from spyne.util import six, coroutine, Break +from spyne.util.cdict import cdict + +from spyne.model import Array, AnyHtml, ComplexModelBase, ByteArray, \ + ModelBase, PushBase, ImageUri, AnyUri + +from spyne.protocol.html import HtmlBase + + +class HtmlMicroFormat(HtmlBase): + def __init__(self, app=None, ignore_uncap=False, ignore_wrappers=False, + cloth=None, cloth_parser=None, polymorphic=True, + doctype="", + root_tag='div', child_tag='div', field_name_attr='class', + field_name_tag=None, field_name_class='field_name', + before_first_root=None): + """Protocol that returns the response object according to the "html + microformat" specification. See + https://en.wikipedia.org/wiki/Microformats for more info. + + The simple flavour is like the XmlDocument protocol, but returns data in +
or tags. + + :param app: A spyne.application.Application instance. + :param root_tag: The type of the root tag that encapsulates the return + data. + :param child_tag: The type of the tag that encapsulates the fields of + the returned object. + :param field_name_attr: The name of the attribute that will contain the + field names of the complex object children. + """ + + super(HtmlMicroFormat, self).__init__(app=app, + ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers, + cloth=cloth, cloth_parser=cloth_parser, polymorphic=polymorphic, + hier_delim=None, doctype=doctype) + + if six.PY2: + text_type = basestring + else: + text_type = str + + assert isinstance(root_tag, text_type) + assert isinstance(child_tag, text_type) + assert isinstance(field_name_attr, text_type) + assert field_name_tag is None or isinstance(field_name_tag, text_type) + + self.root_tag = root_tag + self.child_tag = child_tag + self.field_name_attr = field_name_attr + self.field_name_tag = field_name_tag + if field_name_tag is not None: + self.field_name_tag = E(field_name_tag) + self._field_name_class = field_name_class + if before_first_root is not None: + self.event_manager.add_listener("before_first_root", + before_first_root) + + self.serialization_handlers = cdict({ + Array: self.array_to_parent, + AnyUri: self.any_uri_to_parent, + AnyHtml: self.any_html_to_parent, + ImageUri: self.imageuri_to_parent, + ByteArray: self.not_supported, + ModelBase: self.model_base_to_parent, + ComplexModelBase: self.complex_model_to_parent, + }) + + def anyuri_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + retval = self.gen_anchor(cls, inst, parent) + retval.attrib[self.field_name_attr] = name + parent.write(retval) + + def model_base_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + retval = E(self.child_tag, **{self.field_name_attr: name}) + data_str = self.to_unicode(cls, inst) + + if self.field_name_tag is not None: + field_name = cls.Attributes.translations.get( name) + field_name_tag = self.field_name_tag(field_name, + **{'class':self._field_name_class}) + field_name_tag.tail = data_str + retval.append(field_name_tag) + + else: + retval.text = data_str + + parent.write(retval) + + def start_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + """This is what subserialize calls""" + + # if no doctype was written, write it + if not getattr(ctx.outprot_ctx, 'doctype_written', False): + if len(ctx.protocol.prot_stack) == 1: + if self.doctype is not None: + parent.write_doctype(self.doctype) + + # set this to true as no doctype can be written after this + # stage anyway. + ctx.outprot_ctx.doctype_written = True + + return self.to_parent(ctx, cls, inst, parent, name, **kwargs) + + @coroutine + def complex_model_to_parent(self, ctx, cls, inst, parent, name, + use_ns=False, **kwargs): + attrs = {self.field_name_attr: name} + + if not getattr(ctx.protocol, 'before_first_root', False): + self.event_manager.fire_event("before_first_root", + ctx, cls, inst, parent, name, **kwargs) + ctx.protocol.before_first_root = True + + with parent.element(self.root_tag, attrs): + ret = self._write_members(ctx, cls, inst, parent, use_ns=False, + **kwargs) + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as e: + try: + ret.throw(e) + except StopIteration: + pass + + @coroutine + def array_to_parent(self, ctx, cls, inst, parent, name, from_arr=False, **kwargs): + attrs = {self.field_name_attr: name} + + if issubclass(cls, Array): + cls, = cls._type_info.values() + + name = cls.get_type_name() + with parent.element(self.root_tag, attrs): + if isinstance(inst, PushBase): + while True: + sv = (yield) + ret = self.to_parent(ctx, cls, sv, parent, name, + from_arr=True, **kwargs) + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as e: + try: + ret.throw(e) + except StopIteration: + pass + + else: + for sv in inst: + ret = self.to_parent(ctx, cls, sv, parent, name, + from_arr=True, **kwargs) + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as e: + try: + ret.throw(e) + except StopIteration: + pass + + def null_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + return [ E(self.child_tag, **{self.field_name_attr: name}) ] + +# FIXME: yuck. +from spyne.protocol.cloth import XmlCloth +XmlCloth.HtmlMicroFormat = HtmlMicroFormat diff --git a/pym/calculate/contrib/spyne/protocol/html/microformat.pyc b/pym/calculate/contrib/spyne/protocol/html/microformat.pyc new file mode 100644 index 0000000..4fe6d13 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/html/microformat.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/html/table/__init__.py b/pym/calculate/contrib/spyne/protocol/html/table/__init__.py new file mode 100644 index 0000000..0c52d57 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/html/table/__init__.py @@ -0,0 +1,4 @@ + +from spyne.protocol.html.table._base import HtmlTableBase +from spyne.protocol.html.table.row import HtmlRowTable +from spyne.protocol.html.table.column import HtmlColumnTable diff --git a/pym/calculate/contrib/spyne/protocol/html/table/__init__.pyc b/pym/calculate/contrib/spyne/protocol/html/table/__init__.pyc new file mode 100644 index 0000000..97e2cba Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/html/table/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/html/table/_base.py b/pym/calculate/contrib/spyne/protocol/html/table/_base.py new file mode 100644 index 0000000..3471419 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/html/table/_base.py @@ -0,0 +1,69 @@ +# 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 spyne.protocol.html import HtmlBase + + +class HtmlTableBase(HtmlBase): + def __init__(self, app=None, ignore_uncap=False, ignore_wrappers=True, + cloth=None, cloth_parser=None, header=True, table_name_attr='class', + table_name=None, table_class=None, border=0, row_class=None, + field_name_attr='class', field_type_name_attr='class', + cell_class=None, header_cell_class=None, polymorphic=True, + hier_delim='.', doctype=None, link_gen=None, mrpc_delim_text='|', + table_width=None): + + super(HtmlTableBase, self).__init__(app=app, + ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers, + cloth=cloth, cloth_parser=cloth_parser, polymorphic=polymorphic, + hier_delim=hier_delim, doctype=doctype) + + self.header = header + self.table_name_attr = table_name_attr + self.table_name = table_name + self.field_name_attr = field_name_attr + self.field_type_name_attr = field_type_name_attr + self.border = border + self.row_class = row_class + self.cell_class = cell_class + self.header_cell_class = header_cell_class + self.link_gen = link_gen + self.table_class = table_class + self.table_width = table_width + self.mrpc_delim_text = mrpc_delim_text + + def null_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + pass + + def add_field_attrs(self, attr_dict, name, cls): + if self.field_name_attr: + self.add_html_attr(self.field_name_attr, attr_dict, name) + + if self.field_type_name_attr: + types = set() + c = cls + while c is not None: + if c.Attributes._explicit_type_name or c.__extends__ is None: + types.add(c.get_type_name()) + + c = c.__extends__ + + self.add_html_attr(self.field_type_name_attr, attr_dict, + ' '.join(types)) diff --git a/pym/calculate/contrib/spyne/protocol/html/table/_base.pyc b/pym/calculate/contrib/spyne/protocol/html/table/_base.pyc new file mode 100644 index 0000000..0409841 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/html/table/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/html/table/column.py b/pym/calculate/contrib/spyne/protocol/html/table/column.py new file mode 100644 index 0000000..36b5f50 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/html/table/column.py @@ -0,0 +1,336 @@ +# 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.html.builder import E + +from spyne import ModelBase, ComplexModelBase, Array +from spyne.util import coroutine, Break, urlencode +from spyne.util.oset import oset +from spyne.protocol.html.table import HtmlTableBase + + +class HtmlColumnTableRowProtocol(object): + def column_table_gen_header(self, ctx, cls, parent, name, **kwargs): + return False + + def column_table_before_row(self, ctx, cls, inst, parent, name, **kwargs): + pass + + def column_table_after_row(self, ctx, cls, inst, parent, name, **kwargs): + pass + + + +class HtmlColumnTable(HtmlTableBase, HtmlColumnTableRowProtocol): + """Protocol that returns the response object as a html table. + + Returns one record per table row in a table that has as many columns as + field names, just like a regular spreadsheet. + + This is not quite unlike the HtmlMicroFormatprotocol, but returns data + as a html table using the tag. + + Generally used to serialize Array()'s of ComplexModel objects. If an + array has prot=HtmlColumnTable, its serializer (what's inside the Array( )) + must implement HtmlColumnTableRowProtocol interface. + + :param app: A spyne.application.Application instance. + :param header: Boolean value to determine whether to show field + names in the beginning of the table or not. Defaults to True. Set to + False to skip headers. + :param table_name_attr: The name of the attribute that will contain the + response name of the complex object in the table tag. Set to None to + disable. + :param table_name: When not none, overrides what goes in `table_name_attr`. + :param table_class: When not none, specifies what goes in `class` attribute + in the `
` tag. Table name gets appended when + `table_name_attr == 'class'` + :param field_name_attr: The name of the attribute that will contain the + field names of the complex object children for every table cell. Set + to None to disable. + :param row_class: value that goes inside the + :param cell_class: value that goes inside the tags are generated before exiting the
+ :param header_cell_class: value that goes inside the + :param mrpc_delim_text: The text that goes between mrpc calls. + """ + + def __init__(self, *args, **kwargs): + before_table = kwargs.pop('before_table', None) + + super(HtmlColumnTable, self).__init__(*args, **kwargs) + + self.serialization_handlers.update({ + ModelBase: self.model_base_to_parent, + ComplexModelBase: self.complex_model_to_parent, + Array: self.array_to_parent, + }) + + if before_table is not None: + self.event_manager.add_listener("before_table", before_table) + + def model_base_to_parent(self, ctx, cls, inst, parent, name, + from_arr=False, **kwargs): + inst_str = '' + if inst is not None: + inst_str = self.to_unicode(cls, inst) + + if from_arr: + td_attrs = {} + + self.add_field_attrs(td_attrs, name, cls) + + parent.write(E.tr(E.td(inst_str, **td_attrs))) + + else: + parent.write(inst_str) + + @coroutine + def _gen_row(self, ctx, cls, inst, parent, name, from_arr=False, + array_index=None, **kwargs): + + # because HtmlForm* protocols don't use the global null handler, it's + # possible for null values to reach here. + if inst is None: + return + + logger.debug("Generate row for %r", cls) + + mrpc_delim_elt = '' + if self.mrpc_delim_text is not None: + mrpc_delim_elt = E.span(self.mrpc_delim_text, + **{'class': 'mrpc-delimiter'}) + mrpc_delim_elt.tail = ' ' + + with parent.element('tr'): + for k, v in self.sort_fields(cls): + cls_attr = self.get_cls_attrs(v) + if cls_attr.exc: + logger.debug("\tExclude table cell %r type %r for %r", + k, v, cls) + continue + + try: + sub_value = getattr(inst, k, None) + except: # e.g. SQLAlchemy could throw NoSuchColumnError + sub_value = None + + sub_name = cls_attr.sub_name + if sub_name is None: + sub_name = k + + if self.hier_delim is not None: + if array_index is None: + sub_name = "%s%s%s" % (name, self.hier_delim, sub_name) + else: + sub_name = "%s[%d]%s%s" % (name, array_index, + self.hier_delim, sub_name) + + logger.debug("\tGenerate table cell %r type %r for %r", + sub_name, v, cls) + + td_attrs = {} + + self.add_field_attrs(td_attrs, cls_attr.sub_name or k, v) + + if cls_attr.hidden: + self.add_style(td_attrs, 'display:None') + + with parent.element('td', td_attrs): + ret = self.to_parent(ctx, v, sub_value, parent, sub_name, + from_arr=from_arr, array_index=array_index, **kwargs) + + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + + except Break as b: + try: + ret.throw(b) + except StopIteration: + pass + + m = cls.Attributes.methods + if m is not None and len(m) > 0: + td_attrs = {'class': 'mrpc-cell'} + + with parent.element('td', td_attrs): + first = True + + for mn, md in self._methods(ctx, cls, inst): + if first: + first = False + elif mrpc_delim_elt is not None: + parent.write(" ") + parent.write(mrpc_delim_elt) + + pd = {} + for k, v in self.sort_fields(cls): + if getattr(v.Attributes, 'primary_key', None): + r = self.to_unicode(v, getattr(inst, k, None)) + if r is not None: + pd[k] = r + + params = urlencode(pd) + + mdid2key = ctx.app.interface.method_descriptor_id_to_key + href = mdid2key[id(md)].rsplit("}", 1)[-1] + text = md.translate(ctx.locale, + md.in_message.get_type_name()) + parent.write(E.a( + text, + href="%s?%s" % (href, params), + **{'class': 'mrpc-operation'} + )) + + logger.debug("Generate row for %r done.", cls) + self.extend_data_row(ctx, cls, inst, parent, name, + array_index=array_index, **kwargs) + + def _gen_thead(self, ctx, cls, parent, name): + logger.debug("Generate header for %r", cls) + + with parent.element('thead'): + with parent.element('tr'): + if issubclass(cls, ComplexModelBase): + fti = self.sort_fields(cls) + for k, v in fti: + cls_attr = self.get_cls_attrs(v) + if cls_attr.exc: + continue + + th_attrs = {} + self.add_field_attrs(th_attrs, k, cls) + + if cls_attr.hidden: + self.add_style(th_attrs, 'display:None') + + header_name = self.trc(v, ctx.locale, k) + parent.write(E.th(header_name, **th_attrs)) + + m = cls.Attributes.methods + if m is not None and len(m) > 0: + th_attrs = {'class': 'mrpc-cell'} + parent.write(E.th(**th_attrs)) + + else: + th_attrs = {} + self.add_field_attrs(th_attrs, name, cls) + + header_name = self.trc(cls, ctx.locale, name) + + parent.write(E.th(header_name, **th_attrs)) + + self.extend_header_row(ctx, cls, parent, name) + + @coroutine + def _gen_table(self, ctx, cls, inst, parent, name, gen_rows, **kwargs): + logger.debug("Generate table for %r", cls) + cls_attrs = self.get_cls_attrs(cls) + + attrib = {} + table_class = oset() + if self.table_class is not None: + table_class.add(self.table_class) + + if self.table_name_attr is not None: + tn = (self.table_name + if self.table_name is not None else cls.get_type_name()) + + if self.table_name_attr == 'class': + table_class.add(tn) + else: + attrib[self.table_name_attr] = tn + + attrib['class'] = ' '.join(table_class) + if self.table_width is not None: + attrib['width'] = self.table_width + + self.event_manager.fire_event('before_table', ctx, cls, inst, parent, + name, prot=self, **kwargs) + + with parent.element('table', attrib): + write_header = self.header + if cls_attrs.header is False: + write_header = cls_attrs.header + + if write_header: + ret = False + + subprot = self.get_subprot(ctx, cls_attrs) + if subprot is not None: + ret = subprot.column_table_gen_header(ctx, cls, parent, + name) + if not ret: + self._gen_thead(ctx, cls, parent, name) + + with parent.element('tbody'): + ret = gen_rows(ctx, cls, inst, parent, name, **kwargs) + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as b: + try: + ret.throw(b) + except StopIteration: + pass + + self.extend_table(ctx, cls, parent, name, **kwargs) + + def complex_model_to_parent(self, ctx, cls, inst, parent, name, + from_arr=False, **kwargs): + # If this is direct child of an array, table is already set up in + # array_to_parent. + if from_arr: + return self._gen_row(ctx, cls, inst, parent, name, **kwargs) + else: + return self.wrap_table(ctx, cls, inst, parent, name, self._gen_row, + **kwargs) + + def array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + return self.wrap_table(ctx, cls, inst, parent, name, + super(HtmlColumnTable, self).array_to_parent, **kwargs) + + def wrap_table(self, ctx, cls, inst, parent, name, gen_rows, **kwargs): + return self._gen_table(ctx, cls, inst, parent, name, gen_rows, **kwargs) + + def extend_table(self, ctx, cls, parent, name, **kwargs): + """This is called as the last operation during the table body generation + after all the
tag + which in turn is inside a tag.""" + + def extend_data_row(self, ctx, cls, inst, parent, name, **kwargs): + """This is called as the last operation during the row generation + after all the tag which + in turn is inside a tag.""" + + def extend_header_row(self, ctx, cls, parent, name, **kwargs): + """This is called once as the last operation during the table header + generation after all the + tag which in turn is inside a tag.""" diff --git a/pym/calculate/contrib/spyne/protocol/html/table/column.pyc b/pym/calculate/contrib/spyne/protocol/html/table/column.pyc new file mode 100644 index 0000000..e637190 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/html/table/column.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/html/table/row.py b/pym/calculate/contrib/spyne/protocol/html/table/row.py new file mode 100644 index 0000000..5a33e1a --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/html/table/row.py @@ -0,0 +1,216 @@ +# 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.html.builder import E + +from spyne import ModelBase, ByteArray, ComplexModelBase, Array, AnyUri, \ + ImageUri +from spyne.util import coroutine, Break +from spyne.util.cdict import cdict +from spyne.protocol.html.table import HtmlTableBase + + +class HtmlRowTable(HtmlTableBase): + """Protocol that returns the response object as a html table. + + The simple flavour is like the HtmlMicroFormatprotocol, but returns data + as a html table using the
tags are generated before exiting the
tags are generated before exiting the
tag. + + Returns one record per table in a table with two columns. + + :param app: A spyne.application.Application instance. + :param header: Boolean value to determine whether to show field + names in the beginning of the table or not. Defaults to True. Set to + False to skip headers. + :param table_name_attr: The name of the attribute that will contain the + response name of the complex object in the table tag. Set to None to + disable. + :param table_name: When not none, overrides what goes in `table_name_attr`. + :param table_class: When not none, specifies what goes in `class` attribute + in the `
` tag. Table name gets appended when + `table_name_attr == 'class'` + :param field_name_attr: The name of the attribute that will contain the + field names of the complex object children for every table cell. Set + to None to disable. + :param row_class: value that goes inside the + :param cell_class: value that goes inside the
+ :param header_cell_class: value that goes inside the + """ + + def __init__(self, *args, **kwargs): + super(HtmlRowTable, self).__init__(*args, **kwargs) + + self.serialization_handlers = cdict({ + ModelBase: self.model_base_to_parent, + AnyUri: self.any_uri_to_parent, + ImageUri: self.imageuri_to_parent, + ByteArray: self.not_supported, + ComplexModelBase: self.complex_model_to_parent, + Array: self.array_to_parent, + }) + + def model_base_to_parent(self, ctx, cls, inst, parent, name, from_arr=False, + **kwargs): + if from_arr: + td_attrib = {} + if False and self.field_name_attr: + td_attrib[self.field_name_attr] = name + + parent.write(E.tr(E.td(self.to_unicode(cls, inst), **td_attrib))) + else: + parent.write(self.to_unicode(cls, inst)) + + @coroutine + def complex_model_to_parent(self, ctx, cls, inst, parent, name, + from_arr=False, **kwargs): + attrib = {} + if self.table_name_attr is not None: + attrib[self.table_name_attr] = cls.get_type_name() + if self.table_width is not None: + attrib['width'] = self.table_width + + with parent.element('table', attrib): + with parent.element('tbody'): + for k, v in self.sort_fields(cls): + sub_attrs = self.get_cls_attrs(v) + if sub_attrs.exc: + logger.debug("\tExclude table cell %r type %r for %r", + k, v, cls) + continue + try: + sub_value = getattr(inst, k, None) + except: # e.g. SQLAlchemy could throw NoSuchColumnError + sub_value = None + + sub_name = v.Attributes.sub_name + if sub_name is None: + sub_name = k + + tr_attrs = {} + if self.row_class is not None: + self.add_html_attr('class', tr_attrs, self.row_class) + + with parent.element('tr', tr_attrs): + th_attrs = {} + + if self.header_cell_class is not None: + self.add_html_attr('class', th_attrs, + self.header_cell_class) + + self.add_field_attrs(th_attrs, sub_name, v) + + if sub_attrs.hidden: + self.add_style(th_attrs, 'display:None') + + if self.header: + parent.write(E.th( + self.trc(v, ctx.locale, sub_name), + **th_attrs + )) + + td_attrs = {} + if self.cell_class is not None: + self.add_html_attr('class', td_attrs, + self.cell_class) + + self.add_field_attrs(td_attrs, sub_name, v) + + if sub_attrs.hidden: + self.add_style(td_attrs, 'display:None') + + with parent.element('td', td_attrs): + ret = self.to_parent(ctx, v, sub_value, parent, + sub_name, **kwargs) + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as b: + try: + ret.throw(b) + except StopIteration: + pass + + @coroutine + def array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): + with parent.element('div'): + if issubclass(cls, ComplexModelBase): + ret = super(HtmlRowTable, self).array_to_parent( + ctx, cls, inst, parent, name, **kwargs) + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as b: + try: + ret.throw(b) + except StopIteration: + pass + else: + table_attrib = {} + if self.table_name_attr: + table_attrib = {self.table_name_attr: name} + if self.table_width is not None: + table_attrib['width'] = self.table_width + + with parent.element('table', table_attrib): + tr_attrib = {} + if self.row_class is not None: + tr_attrib['class'] = self.row_class + with parent.element('tr', tr_attrib): + if self.header: + parent.write(E.th(self.trc(cls, ctx.locale, + cls.get_type_name()))) + td_attrs = {} + + if self.cell_class is not None: + self.add_html_attr('class', td_attrs, + self.cell_class) + + self.add_field_attrs(td_attrs, name, cls) + + cls_attrs = self.get_cls_attrs(cls) + + if cls_attrs.hidden: + self.add_style(td_attrs, 'display:None') + + with parent.element('td', td_attrs): + with parent.element('table'): + ret = super(HtmlRowTable, self) \ + .array_to_parent(ctx, cls, inst, parent, + name, **kwargs) + if isgenerator(ret): + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as b: + try: + ret.throw(b) + except StopIteration: + pass diff --git a/pym/calculate/contrib/spyne/protocol/html/table/row.pyc b/pym/calculate/contrib/spyne/protocol/html/table/row.pyc new file mode 100644 index 0000000..60630fe Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/html/table/row.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/http.py b/pym/calculate/contrib/spyne/protocol/http.py new file mode 100644 index 0000000..62f130e --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/http.py @@ -0,0 +1,475 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.protocol.http`` module contains the HttpRpc protocol +implementation. + +This module is EXPERIMENTAL. You may not recognize the code here next time you +look at it. +""" + +import logging +logger = logging.getLogger(__name__) + +import re +import pytz +import tempfile + +from spyne import BODY_STYLE_WRAPPED, MethodDescriptor, PushBase +from spyne.util import six, coroutine, Break +from spyne.util.six import string_types, BytesIO +from spyne.error import ResourceNotFoundError +from spyne.model.binary import BINARY_ENCODING_URLSAFE_BASE64, File +from spyne.model.primitive import DateTime +from spyne.protocol.dictdoc import SimpleDictDocument + + +TEMPORARY_DIR = None +STREAM_READ_BLOCK_SIZE = 0x4000 +SWAP_DATA_TO_FILE_THRESHOLD = 512 * 1024 + + + +_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") +_QuotePatt = re.compile(r"[\\].") +_nulljoin = ''.join + +def _unquote_cookie(str): + """Handle double quotes and escaping in cookie values. + This method is copied verbatim from the Python 3.5 standard + library (http.cookies._unquote) so we don't have to depend on + non-public interfaces. + """ + # If there aren't any doublequotes, + # then there can't be any special characters. See RFC 2109. + if str is None or len(str) < 2: + return str + if str[0] != '"' or str[-1] != '"': + return str + + # We have to assume that we must decode this string. + # Down to work. + + # Remove the "s + str = str[1:-1] + + # Check for special sequences. Examples: + # \012 --> \n + # \" --> " + # + i = 0 + n = len(str) + res = [] + while 0 <= i < n: + o_match = _OctalPatt.search(str, i) + q_match = _QuotePatt.search(str, i) + if not o_match and not q_match: # Neither matched + res.append(str[i:]) + break + # else: + j = k = -1 + if o_match: + j = o_match.start(0) + if q_match: + k = q_match.start(0) + if q_match and (not o_match or k < j): # QuotePatt matched + res.append(str[i:k]) + res.append(str[k+1]) + i = k + 2 + else: # OctalPatt matched + res.append(str[i:j]) + res.append(chr(int(str[j+1:j+4], 8))) + i = j + 4 + return _nulljoin(res) + + +def _parse_cookie(cookie): + """Parse a ``Cookie`` HTTP header into a dict of name/value pairs. + This function attempts to mimic browser cookie parsing behavior; + it specifically does not follow any of the cookie-related RFCs + (because browsers don't either). + The algorithm used is identical to that used by Django version 1.9.10. + """ + cookiedict = {} + for chunk in cookie.split(str(';')): + if str('=') in chunk: + key, val = chunk.split(str('='), 1) + else: + # Assume an empty name per + # https://bugzilla.mozilla.org/show_bug.cgi?id=169091 + key, val = str(''), chunk + key, val = key.strip(), val.strip() + if key or val: + # unquote using Python's algorithm. + cookiedict[key] = _unquote_cookie(val) + return cookiedict + + +def get_stream_factory(dir=None, delete=True): + def stream_factory(total_content_length, filename, content_type, + content_length=None): + if total_content_length >= SWAP_DATA_TO_FILE_THRESHOLD or \ + delete == False: + if delete == False: + # You need python >= 2.6 for this. + retval = tempfile.NamedTemporaryFile('wb+', dir=dir, + delete=delete) + else: + retval = tempfile.NamedTemporaryFile('wb+', dir=dir) + else: + retval = BytesIO() + + return retval + + return stream_factory + +_weekday = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] +_month = ['w00t', "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", + "Oct", "Nov", "Dec"] + +def _header_to_bytes(prot, val, cls): + if issubclass(cls, DateTime): + if val.tzinfo is not None: + val = val.astimezone(pytz.utc) + else: + val = val.replace(tzinfo=pytz.utc) + + return "%s, %02d %s %04d %02d:%02d:%02d GMT" % ( + _weekday[val.weekday()], val.day, _month[val.month], + val.year, val.hour, val.minute, val.second) + else: + # because wsgi_ref wants header values in unicode. + return prot.to_unicode(cls, val) + + +class HttpRpc(SimpleDictDocument): + """The so-called HttpRpc protocol implementation. It only works with Http + (wsgi and twisted) transports. + + :param app: An :class:'spyne.application.Application` instance. + :param validator: Validation method to use. One of (None, 'soft') + :param mime_type: Default mime type to set. Default is + 'application/octet-stream' + :param tmp_dir: Temporary directory to store partial file uploads. Default + is to use the OS default. + :param tmp_delete_on_close: The ``delete`` argument to the + :class:`tempfile.NamedTemporaryFile`. + See: http://docs.python.org/2/library/tempfile.html#tempfile.NamedTemporaryFile. + :param ignore_uncap: As HttpRpc can't serialize complex models, it throws a + server exception when the return type of the user function is Complex. + Passing ``True`` to this argument prevents that by ignoring the return + value. + """ + + mime_type = 'text/plain' + default_binary_encoding = BINARY_ENCODING_URLSAFE_BASE64 + default_string_encoding = 'UTF-8' + + type = set(SimpleDictDocument.type) + type.add('http') + + def __init__(self, app=None, validator=None, mime_type=None, + tmp_dir=None, tmp_delete_on_close=True, ignore_uncap=False, + parse_cookie=True, hier_delim=".", strict_arrays=False): + super(HttpRpc, self).__init__(app, validator, mime_type, + ignore_uncap=ignore_uncap, hier_delim=hier_delim, + strict_arrays=strict_arrays) + + self.tmp_dir = tmp_dir + self.tmp_delete_on_close = tmp_delete_on_close + self.parse_cookie = parse_cookie + + def get_tmp_delete_on_close(self): + return self.__tmp_delete_on_close + + def set_tmp_delete_on_close(self, val): + self.__tmp_delete_on_close = val + self.stream_factory = get_stream_factory(self.tmp_dir, + self.__tmp_delete_on_close) + + tmp_delete_on_close = property(get_tmp_delete_on_close, + set_tmp_delete_on_close) + + def set_validator(self, validator): + if validator == 'soft' or validator is self.SOFT_VALIDATION: + self.validator = self.SOFT_VALIDATION + elif validator is None: + self.validator = None + else: + raise ValueError(validator) + + def create_in_document(self, ctx, in_string_encoding=None): + assert ctx.transport.type.endswith('http'), \ + ("This protocol only works with an http transport, not %r, (in %r)" + % (ctx.transport.type, ctx.transport)) + + ctx.in_document = ctx.transport.req + ctx.transport.request_encoding = in_string_encoding + + def decompose_incoming_envelope(self, ctx, message): + assert message == SimpleDictDocument.REQUEST + + ctx.transport.itself.decompose_incoming_envelope(self, ctx, message) + + if self.parse_cookie: + cookies = ctx.in_header_doc.get('cookie', None) + if cookies is None: + cookies = ctx.in_header_doc.get('Cookie', None) + + if cookies is not None: + for cookie_string in cookies: + logger.debug("Loading cookie string %r", cookie_string) + cookie = _parse_cookie(cookie_string) + for k, v in cookie.items(): + l = ctx.in_header_doc.get(k, []) + l.append(v) + ctx.in_header_doc[k] = l + + logger.debug('\theader : %r' % (ctx.in_header_doc)) + logger.debug('\tbody : %r' % (ctx.in_body_doc)) + + def deserialize(self, ctx, message): + assert message in (self.REQUEST,) + + self.event_manager.fire_event('before_deserialize', ctx) + + if ctx.descriptor is None: + raise ResourceNotFoundError(ctx.method_request_string) + + req_enc = getattr(ctx.transport, 'request_encoding', None) + if req_enc is None: + req_enc = ctx.in_protocol.default_string_encoding + + if ctx.descriptor.in_header is not None: + # HttpRpc supports only one header class + in_header_class = ctx.descriptor.in_header[0] + ctx.in_header = self.simple_dict_to_object(ctx, ctx.in_header_doc, + in_header_class, self.validator, req_enc=req_enc) + + if ctx.descriptor.in_message is not None: + ctx.in_object = self.simple_dict_to_object(ctx, ctx.in_body_doc, + ctx.descriptor.in_message, self.validator, req_enc=req_enc) + + self.event_manager.fire_event('after_deserialize', ctx) + + def serialize(self, ctx, message): + retval = None + + assert message in (self.RESPONSE,) + + if ctx.out_document is not None: + return + + if ctx.out_error is not None: + ctx.transport.mime_type = 'text/plain' + ctx.out_document = ctx.out_error.to_bytes_iterable(ctx.out_error) + + else: + retval = self._handle_rpc(ctx) + + self.event_manager.fire_event('serialize', ctx) + + return retval + + @coroutine + def _handle_rpc_nonempty(self, ctx): + result_class = ctx.descriptor.out_message + + out_class = None + out_object = None + + if ctx.descriptor.body_style is BODY_STYLE_WRAPPED: + fti = result_class.get_flat_type_info(result_class) + + if len(fti) > 1 and not self.ignore_uncap: + raise TypeError("HttpRpc protocol can only serialize " + "functions with a single return type.") + + if len(fti) == 1: + out_class, = fti.values() + out_object, = ctx.out_object + + else: + out_class = result_class + out_object, = ctx.out_object + + if out_class is not None: + if issubclass(out_class, File) and not \ + isinstance(out_object, (list, tuple, string_types)) \ + and out_object.type is not None: + ctx.transport.set_mime_type(str(out_object.type)) + + ret = self.to_bytes_iterable(out_class, out_object) + + if not isinstance(ret, PushBase): + ctx.out_document = ret + + else: + ctx.transport.itself.set_out_document_push(ctx) + while True: + sv = yield + ctx.out_document.send(sv) + + def _handle_rpc(self, ctx): + retval = None + + # assign raw result to its wrapper, result_message + if ctx.out_object is None or len(ctx.out_object) < 1: + ctx.out_document = [''] + + else: + retval = self._handle_rpc_nonempty(ctx) + + header_class = ctx.descriptor.out_header + if header_class is not None: + # HttpRpc supports only one header class + header_class = header_class[0] + + # header + if ctx.out_header is not None: + out_header = ctx.out_header + if isinstance(ctx.out_header, (list, tuple)): + out_header = ctx.out_header[0] + + ctx.out_header_doc = self.object_to_simple_dict(header_class, + out_header, subinst_eater=_header_to_bytes) + + return retval + + def create_out_string(self, ctx, out_string_encoding='utf8'): + if ctx.out_string is not None: + return + + ctx.out_string = ctx.out_document + + def boolean_from_bytes(self, cls, string): + return string.lower() in ('true', '1', 'checked', 'on') + + def integer_from_bytes(self, cls, string): + if string == '': + return None + + return super(HttpRpc, self).integer_from_bytes(cls, string) + + +_fragment_pattern_re = re.compile('<([A-Za-z0-9_]+)>') +_full_pattern_re = re.compile('{([A-Za-z0-9_]+)}') + + +class HttpPattern(object): + """Experimental. Stay away. + + :param address: Address pattern + :param verb: HTTP Verb pattern + :param host: HTTP "Host:" header pattern + """ + + @staticmethod + def _compile_url_pattern(pattern): + """where <> placeholders don't contain slashes.""" + + if pattern is None: + return None + pattern = _fragment_pattern_re.sub(r'(?P<\1>[^/]*)', pattern) + pattern = _full_pattern_re.sub(r'(?P<\1>[^/]*)', pattern) + return re.compile(pattern) + + @staticmethod + def _compile_host_pattern(pattern): + """where <> placeholders don't contain dots.""" + + if pattern is None: + return None + pattern = _fragment_pattern_re.sub(r'(?P<\1>[^\.]*)', pattern) + pattern = _full_pattern_re.sub(r'(?P<\1>.*)', pattern) + return re.compile(pattern) + + @staticmethod + def _compile_verb_pattern(pattern): + """where <> placeholders are same as {} ones.""" + + if pattern is None: + return None + pattern = _fragment_pattern_re.sub(r'(?P<\1>.*)', pattern) + pattern = _full_pattern_re.sub(r'(?P<\1>.*)', pattern) + return re.compile(pattern) + + def __init__(self, address=None, verb=None, host=None, endpoint=None): + self.address = address + self.host = host + self.verb = verb + + self.endpoint = endpoint + if self.endpoint is not None: + assert isinstance(self.endpoint, MethodDescriptor) + + def hello(self, descriptor): + if self.address is None: + self.address = descriptor.name + + @property + def address(self): + return self.__address + + @address.setter + def address(self, what): + if what is not None and not what.startswith('/'): + what = '/' + what + + self.__address = what + self.address_re = self._compile_url_pattern(what) + + @property + def host(self): + return self.__host + + @host.setter + def host(self, what): + self.__host = what + self.host_re = self._compile_host_pattern(what) + + @property + def verb(self): + return self.__verb + + @verb.setter + def verb(self, what): + self.__verb = what + self.verb_re = self._compile_verb_pattern(what) + + def as_werkzeug_rule(self): + from werkzeug.routing import Rule + from spyne.util.invregexp import invregexp + + methods = None + if self.verb is not None: + methods = invregexp(self.verb) + + host = self.host + if host is None: + host = '<__ignored>' # for some reason, this is necessary when + # host_matching is enabled. + + return Rule(self.address, host=host, endpoint=self.endpoint.name, + methods=methods) + + def __repr__(self): + return "HttpPattern(address=%r, host=%r, verb=%r, endpoint=%r)" % ( + self.address, self.host, self.verb, + None if self.endpoint is None else self.endpoint.name) diff --git a/pym/calculate/contrib/spyne/protocol/http.pyc b/pym/calculate/contrib/spyne/protocol/http.pyc new file mode 100644 index 0000000..cd0d592 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/http.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/json.py b/pym/calculate/contrib/spyne/protocol/json.py new file mode 100644 index 0000000..16acb11 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/json.py @@ -0,0 +1,425 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.protocol.json`` package contains the Json-related protocols. +Currently, only :class:`spyne.protocol.json.JsonDocument` is supported. + +Initially released in 2.8.0-rc. + +Missing Types +============= + +The JSON standard does not define every type that Spyne supports. These include +Date/Time types as well as arbitrary-length integers and arbitrary-precision +decimals. Integers are parsed to ``int``\s or ``long``\s seamlessly but +``Decimal``\s are only parsed correctly when they come off as strings. + +While it's possible to e.g. (de)serialize floats to ``Decimal``\s by adding +hooks to ``parse_float`` [#]_ (and convert later as necessary), such +customizations apply to the whole incoming document which pretty much messes up +``AnyDict`` serialization and deserialization. + +It also wasn't possible to work with ``object_pairs_hook`` as Spyne's parsing +is always "from outside to inside" whereas ``object_pairs_hook`` is passed +``dict``\s basically in any order "from inside to outside". + +.. [#] http://docs.python.org/2/library/json.html#json.loads +""" + +from __future__ import absolute_import + +import logging +logger = logging.getLogger(__name__) + +from itertools import chain +from spyne.util import six + + +try: + import simplejson as json + from simplejson.decoder import JSONDecodeError +except ImportError: + import json + JSONDecodeError = ValueError + +from spyne.error import ValidationError +from spyne.error import ResourceNotFoundError + +from spyne.model.binary import BINARY_ENCODING_BASE64 +from spyne.model.primitive import Date +from spyne.model.primitive import Time +from spyne.model.primitive import DateTime +from spyne.model.primitive import Double +from spyne.model.primitive import Integer +from spyne.model.primitive import Boolean +from spyne.model.fault import Fault +from spyne.protocol.dictdoc import HierDictDocument + + +# TODO: use this as default +class JsonEncoder(json.JSONEncoder): + def default(self, o): + try: + return super(JsonEncoder, self).default(o) + + except TypeError as e: + # if json can't serialize it, it's possibly a generator. If not, + # additional hacks are welcome :) + if logger.level == logging.DEBUG: + logger.exception(e) + return list(o) + + +NON_NUMBER_TYPES = tuple({list, dict, six.text_type, six.binary_type}) + + +class JsonDocument(HierDictDocument): + """An implementation of the json protocol that uses simplejson package when + available, json package otherwise. + + :param ignore_wrappers: Does not serialize wrapper objects. + :param complex_as: One of (list, dict). When list, the complex objects are + serialized to a list of values instead of a dict of key/value pairs. + """ + + mime_type = 'application/json' + text_based = True + + type = set(HierDictDocument.type) + type.add('json') + + default_binary_encoding = BINARY_ENCODING_BASE64 + + # flags used just for tests + _decimal_as_string = True + + def __init__(self, app=None, validator=None, mime_type=None, + ignore_uncap=False, + # DictDocument specific + ignore_wrappers=True, complex_as=dict, ordered=False, + default_string_encoding=None, polymorphic=False, + **kwargs): + + super(JsonDocument, self).__init__(app, validator, mime_type, ignore_uncap, + ignore_wrappers, complex_as, ordered, polymorphic) + + # this is needed when we're overriding a regular instance attribute + # with a property. + self.__message = HierDictDocument.__getattribute__(self, 'message') + + self._from_unicode_handlers[Double] = self._ret_number + self._from_unicode_handlers[Boolean] = self._ret_bool + self._from_unicode_handlers[Integer] = self._ret_number + + self._to_unicode_handlers[Double] = self._ret + self._to_unicode_handlers[Boolean] = self._ret + self._to_unicode_handlers[Integer] = self._ret + + self.default_string_encoding = default_string_encoding + self.kwargs = kwargs + + def _ret(self, cls, value): + return value + + def _ret_number(self, cls, value): + if isinstance(value, NON_NUMBER_TYPES): + raise ValidationError(value) + if value in (True, False): + return int(value) + return value + + def _ret_bool(self, cls, value): + if value is None or value in (True, False): + return value + raise ValidationError(value) + + def validate(self, key, cls, val): + super(JsonDocument, self).validate(key, cls, val) + + if issubclass(cls, (DateTime, Date, Time)) and not ( + isinstance(val, six.string_types) and + cls.validate_string(cls, val)): + raise ValidationError(key, val) + + @property + def message(self): + return self.__message + + @message.setter + def message(self, val): + if val is self.RESPONSE and not ('cls' in self.kwargs): + self.kwargs['cls'] = JsonEncoder + self.__message = val + + def create_in_document(self, ctx, in_string_encoding=None): + """Sets ``ctx.in_document`` using ``ctx.in_string``.""" + + try: + in_string = b''.join(ctx.in_string) + if not isinstance(in_string, six.text_type): + if in_string_encoding is None: + in_string_encoding = self.default_string_encoding + if in_string_encoding is not None: + in_string = in_string.decode(in_string_encoding) + ctx.in_document = json.loads(in_string, **self.kwargs) + + except JSONDecodeError as e: + raise Fault('Client.JsonDecodeError', repr(e)) + + def create_out_string(self, ctx, out_string_encoding='utf8'): + """Sets ``ctx.out_string`` using ``ctx.out_document``.""" + if out_string_encoding is None: + ctx.out_string = (json.dumps(o, **self.kwargs) + for o in ctx.out_document) + else: + ctx.out_string = ( + json.dumps(o, **self.kwargs).encode(out_string_encoding) + for o in ctx.out_document) + + +# Continuation of http://stackoverflow.com/a/24184379/1520211 +class HybridHttpJsonDocument(JsonDocument): + """This protocol lets you have the method name as the last fragment in the + request url. Eg. instead of sending a HTTP POST request to + + http://api.endpoint/json/ + + containing: :: + + { + "method_name": { + "arg1" : 42, + "arg2" : "foo" + } + } + + you will have to send the request to + + http://api.endpoint/json/method_name + + containing: :: + + { + "arg1" : 42, + "arg2" : "foo" + } + + Part of request data comes from HTTP and part of it comes from Json, hence + the name. + """ + + def create_in_document(self, ctx, in_string_encoding=None): + super(HybridHttpJsonDocument, self).create_in_document(ctx) + + url_fragment = ctx.transport.get_path().split('/')[-1] + + ctx.in_document = {url_fragment: ctx.in_document} + + + +class JsonP(JsonDocument): + """The JsonP protocol puts the reponse document inside a designated + javascript function call. The input protocol is identical to the + JsonDocument protocol. + + :param callback_name: The name of the function call that will wrapp all + response documents. + + For other arguents, see :class:`spyne.protocol.json.JsonDocument`. + """ + + type = set(HierDictDocument.type) + type.add('jsonp') + + def __init__(self, callback_name, *args, **kwargs): + super(JsonP, self).__init__(*args, **kwargs) + self.callback_name = callback_name + + def create_out_string(self, ctx, out_string_encoding='utf8'): + super(JsonP, self).create_out_string(ctx, + out_string_encoding=out_string_encoding) + + if out_string_encoding is None: + ctx.out_string = chain( + (self.callback_name, '('), + ctx.out_string, + (');',), + ) + else: + ctx.out_string = chain( + [self.callback_name.encode(out_string_encoding), b'('], + ctx.out_string, + [b');'], + ) + + +class _SpyneJsonRpc1(JsonDocument): + version = 1 + VERSION = 'ver' + BODY = 'body' + HEAD = 'head' + FAULT = 'fault' + + def decompose_incoming_envelope(self, ctx, message=JsonDocument.REQUEST): + indoc = ctx.in_document + if not isinstance(indoc, dict): + raise ValidationError(indoc, "Invalid Request") + + ver = indoc.get(self.VERSION) + if ver is None: + raise ValidationError(ver, "Unknown Version") + + body = indoc.get(self.BODY) + err = indoc.get(self.FAULT) + if body is None and err is None: + raise ValidationError((body, err), "Request data not found") + + ctx.protocol.error = False + if err is not None: + ctx.in_body_doc = err + ctx.protocol.error = True + else: + if not isinstance(body, dict): + raise ValidationError(body, "Request body not found") + if not len(body) == 1: + raise ValidationError(body, "Need len(body) == 1") + + ctx.in_header_doc = indoc.get(self.HEAD) + if not isinstance(ctx.in_header_doc, list): + ctx.in_header_doc = [ctx.in_header_doc] + + (ctx.method_request_string,ctx.in_body_doc), = body.items() + + def deserialize(self, ctx, message): + assert message in (self.REQUEST, self.RESPONSE) + + self.event_manager.fire_event('before_deserialize', ctx) + + if ctx.descriptor is None: + raise ResourceNotFoundError(ctx.method_request_string) + + if ctx.protocol.error: + ctx.in_object = None + ctx.in_error = self._doc_to_object(ctx, Fault, ctx.in_body_doc) + + else: + if message is self.REQUEST: + header_class = ctx.descriptor.in_header + body_class = ctx.descriptor.in_message + + elif message is self.RESPONSE: + header_class = ctx.descriptor.out_header + body_class = ctx.descriptor.out_message + + # decode header objects + if (ctx.in_header_doc is not None and header_class is not None): + headers = [None] * len(header_class) + for i, (header_doc, head_class) in enumerate( + zip(ctx.in_header_doc, header_class)): + if header_doc is not None and i < len(header_doc): + headers[i] = self._doc_to_object(ctx, head_class, + header_doc) + + if len(headers) == 1: + ctx.in_header = headers[0] + else: + ctx.in_header = headers + # decode method arguments + if ctx.in_body_doc is None: + ctx.in_object = [None] * len(body_class._type_info) + else: + ctx.in_object = self._doc_to_object(ctx, body_class, + ctx.in_body_doc) + + self.event_manager.fire_event('after_deserialize', ctx) + + def serialize(self, ctx, message): + assert message in (self.REQUEST, self.RESPONSE) + + self.event_manager.fire_event('before_serialize', ctx) + + ctx.out_document = { + "ver": self.version, + } + if ctx.out_error is not None: + ctx.out_document[self.FAULT] = Fault.to_dict(Fault, + ctx.out_error, self) + + else: + if message is self.REQUEST: + header_message_class = ctx.descriptor.in_header + body_message_class = ctx.descriptor.in_message + + elif message is self.RESPONSE: + header_message_class = ctx.descriptor.out_header + body_message_class = ctx.descriptor.out_message + + # assign raw result to its wrapper, result_message + out_type_info = body_message_class._type_info + out_object = body_message_class() + bm_attrs = self.get_cls_attrs(body_message_class) + + keys = iter(out_type_info) + values = iter(ctx.out_object) + while True: + try: + k = next(keys) + except StopIteration: + break + try: + v = next(values) + except StopIteration: + v = None + + out_object._safe_set(k, v, body_message_class, bm_attrs) + + ctx.out_document[self.BODY] = ctx.out_body_doc = \ + self._object_to_doc(body_message_class, out_object) + + # header + if ctx.out_header is not None and header_message_class is not None: + if isinstance(ctx.out_header, (list, tuple)): + out_headers = ctx.out_header + else: + out_headers = (ctx.out_header,) + + ctx.out_header_doc = out_header_doc = [] + + for header_class, out_header in zip(header_message_class, + out_headers): + out_header_doc.append(self._object_to_doc(header_class, + out_header)) + + if len(out_header_doc) > 1: + ctx.out_document[self.HEAD] = out_header_doc + else: + ctx.out_document[self.HEAD] = out_header_doc[0] + + self.event_manager.fire_event('after_serialize', ctx) + + +_json_rpc_flavors = { + 'spyne': _SpyneJsonRpc1 +} + +def JsonRpc(flavour, *args, **kwargs): + assert flavour in _json_rpc_flavors, "Unknown JsonRpc flavor. " \ + "Accepted ones are: %r" % tuple(_json_rpc_flavors) + + return _json_rpc_flavors[flavour](*args, **kwargs) diff --git a/pym/calculate/contrib/spyne/protocol/json.pyc b/pym/calculate/contrib/spyne/protocol/json.pyc new file mode 100644 index 0000000..b0a90d4 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/json.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/msgpack.py b/pym/calculate/contrib/spyne/protocol/msgpack.py new file mode 100644 index 0000000..71563fa --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/msgpack.py @@ -0,0 +1,361 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.protocol.msgpack`` module contains implementations for protocols +that use MessagePack as serializer. + +Initially released in 2.8.0-rc. + +""" + +from __future__ import absolute_import + +import logging +logger = logging.getLogger(__name__) + +import msgpack + +from spyne import ValidationError +from spyne.util import six +from spyne.model.fault import Fault +from spyne.model.primitive import Double +from spyne.model.primitive import Boolean +from spyne.model.primitive import Integer +from spyne.protocol.dictdoc import HierDictDocument + + +class MessagePackDecodeError(Fault): + CODE = "Client.MessagePackDecodeError" + + def __init__(self, data=None): + super(MessagePackDecodeError, self) \ + .__init__(self.CODE, data) + + +NON_NUMBER_TYPES = tuple({list, dict, six.text_type, six.binary_type}) + + +class MessagePackDocument(HierDictDocument): + """An integration class for the msgpack protocol.""" + + mime_type = 'application/x-msgpack' + text_based = False + + type = set(HierDictDocument.type) + type.add('msgpack') + + default_string_encoding = 'UTF-8' + from_serstr = HierDictDocument.from_bytes + to_serstr = HierDictDocument.to_bytes + + # flags to be used in tests + _decimal_as_string = True + _huge_numbers_as_string = True + + def __init__(self, app=None, validator=None, mime_type=None, + ignore_uncap=False, + # DictDocument specific + ignore_wrappers=True, + complex_as=dict, + ordered=False, + polymorphic=False, + key_encoding='utf8', + # MessagePackDocument specific + mw_packer=msgpack.Unpacker, + mw_unpacker=msgpack.Unpacker, + use_list=False, + raw=False, + use_bin_type=True, + **kwargs): + + super(MessagePackDocument, self).__init__(app, validator, mime_type, + ignore_uncap, ignore_wrappers, complex_as, ordered, polymorphic, + key_encoding) + + self.mw_packer = mw_packer + self.mw_unpacker = mw_unpacker + + # unpacker + if not raw: + self.from_serstr = self.from_unicode + + if use_bin_type: + self.from_serstr = self.from_unicode + + self.kwargs = kwargs + if raw != False: + kwargs['raw'] = raw + + if use_list != True: + kwargs['use_list'] = use_list + + if use_bin_type != True: + kwargs['use_bin_type'] = use_bin_type + + self._from_bytes_handlers[Double] = self._ret_number + self._from_bytes_handlers[Boolean] = self._ret_bool + self._from_bytes_handlers[Integer] = self.integer_from_bytes + + self._from_unicode_handlers[Double] = self._ret_number + self._from_unicode_handlers[Boolean] = self._ret_bool + self._from_unicode_handlers[Integer] = self.integer_from_bytes + + self._to_bytes_handlers[Double] = self._ret_number + self._to_bytes_handlers[Boolean] = self._ret_bool + self._to_bytes_handlers[Integer] = self.integer_to_bytes + + self._to_unicode_handlers[Double] = self._ret_number + self._to_unicode_handlers[Boolean] = self._ret_bool + self._to_unicode_handlers[Integer] = self.integer_to_bytes + + def _ret(self, _, value): + return value + + def _ret_number(self, _, value): + if isinstance(value, NON_NUMBER_TYPES): + raise ValidationError(value) + if value in (True, False): + return int(value) + return value + + def _ret_bool(self, _, value): + if value is None or value in (True, False): + return value + raise ValidationError(value) + + def get_class_name(self, cls): + class_name = cls.get_type_name() + if not six.PY2: + if not isinstance(class_name, bytes): + class_name = class_name.encode(self.default_string_encoding) + + return class_name + + def create_in_document(self, ctx, in_string_encoding=None): + """Sets ``ctx.in_document``, using ``ctx.in_string``. + + :param ctx: The MethodContext object + :param in_string_encoding: MessagePack is a binary protocol. So this + argument is ignored. + """ + + # handle mmap objects from in ctx.in_string as returned by + # TwistedWebResource.handle_rpc. + if isinstance(ctx.in_string, (list, tuple)) \ + and len(ctx.in_string) == 1 \ + and isinstance(ctx.in_string[0], memoryview): + unpacker = self.mw_unpacker(**self.kwargs) + unpacker.feed(ctx.in_string[0]) + ctx.in_document = next(x for x in unpacker) + + else: + try: + ctx.in_document = msgpack.unpackb(b''.join(ctx.in_string)) + except ValueError as e: + raise MessagePackDecodeError(' '.join(e.args)) + + def gen_method_request_string(self, ctx): + """Uses information in context object to return a method_request_string. + + Returns a string in the form of "{namespaces}method name". + """ + + mrs, = ctx.in_body_doc.keys() + if not six.PY2 and isinstance(mrs, bytes): + mrs = mrs.decode(self.key_encoding) + + return '{%s}%s' % (self.app.interface.get_tns(), mrs) + + def create_out_string(self, ctx, out_string_encoding='utf8'): + ctx.out_string = (msgpack.packb(o) for o in ctx.out_document) + + def integer_from_bytes(self, cls, value): + if isinstance(value, (six.text_type, six.binary_type)): + return super(MessagePackDocument, self) \ + .integer_from_bytes(cls, value) + return value + + def integer_to_bytes(self, cls, value, **_): + # if it's inside the range msgpack can deal with + if -1<<63 <= value < 1<<64: + return value + else: + return super(MessagePackDocument, self).integer_to_bytes(cls, value) + + +class MessagePackRpc(MessagePackDocument): + """An integration class for the msgpack-rpc protocol.""" + + mime_type = 'application/x-msgpack' + + MSGPACK_REQUEST = 0 + MSGPACK_RESPONSE = 1 + MSGPACK_NOTIFY = 2 + MSGPACK_ERROR = 3 + + def create_out_string(self, ctx, out_string_encoding='utf8'): + ctx.out_string = (msgpack.packb(o) for o in ctx.out_document) + + def create_in_document(self, ctx, in_string_encoding=None): + """Sets ``ctx.in_document``, using ``ctx.in_string``. + + :param ctx: The MethodContext object + :param in_string_encoding: MessagePack is a binary protocol. So this + argument is ignored. + """ + + # TODO: Use feed api + try: + ctx.in_document = msgpack.unpackb(b''.join(ctx.in_string), + **self.kwargs) + + except ValueError as e: + raise MessagePackDecodeError(''.join(e.args)) + + try: + len(ctx.in_document) + except TypeError: + raise MessagePackDecodeError("Input must be a sequence.") + + if not (3 <= len(ctx.in_document) <= 4): + raise MessagePackDecodeError("Length of input iterable must be " + "either 3 or 4") + + def decompose_incoming_envelope(self, ctx, message): + # FIXME: For example: {0: 0, 1: 0, 2: "some_call", 3: [1,2,3]} will also + # work. Is this a problem? + + # FIXME: Msgid is ignored. Is this a problem? + msgparams = [] + if len(ctx.in_document) == 3: + msgtype, msgid, msgname_or_error = ctx.in_document + + else: + msgtype, msgid, msgname_or_error, msgparams = ctx.in_document + + if not six.PY2: + if isinstance(msgname_or_error, bytes): + msgname_or_error = msgname_or_error.decode( + self.default_string_encoding) + + if msgtype == MessagePackRpc.MSGPACK_REQUEST: + assert message == MessagePackRpc.REQUEST + + elif msgtype == MessagePackRpc.MSGPACK_RESPONSE: + assert message == MessagePackRpc.RESPONSE + + elif msgtype == MessagePackRpc.MSGPACK_NOTIFY: + raise NotImplementedError() + + else: + raise MessagePackDecodeError("Unknown message type %r" % msgtype) + + ctx.method_request_string = '{%s}%s' % (self.app.interface.get_tns(), + msgname_or_error) + + # MessagePackRpc does not seem to have Header support + ctx.in_header_doc = None + + if isinstance(msgname_or_error, dict) and msgname_or_error: + # we got an error + ctx.in_error = msgname_or_error + else: + ctx.in_body_doc = msgparams + + # logger.debug('\theader : %r', ctx.in_header_doc) + # logger.debug('\tbody : %r', ctx.in_body_doc) + + def deserialize(self, ctx, message): + assert message in (self.REQUEST, self.RESPONSE) + + self.event_manager.fire_event('before_deserialize', ctx) + + if ctx.descriptor is None: + raise Fault("Client", "Method %r not found." % + ctx.method_request_string) + + # instantiate the result message + if message is self.REQUEST: + body_class = ctx.descriptor.in_message + elif message is self.RESPONSE: + body_class = ctx.descriptor.out_message + else: + raise Exception("what?") + + if ctx.in_error: + ctx.in_error = Fault(**ctx.in_error) + + elif body_class: + ctx.in_object = self._doc_to_object(ctx, + body_class, ctx.in_body_doc, self.validator) + + else: + ctx.in_object = [] + + self.event_manager.fire_event('after_deserialize', ctx) + + def serialize(self, ctx, message): + assert message in (self.REQUEST, self.RESPONSE) + + self.event_manager.fire_event('before_serialize', ctx) + + if ctx.out_error is not None: + ctx.out_document = [ + [MessagePackRpc.MSGPACK_ERROR, 0, + Fault.to_dict(ctx.out_error.__class__, ctx.out_error)] + ] + return + + # get the result message + if message is self.REQUEST: + out_type = ctx.descriptor.in_message + msgtype = MessagePackRpc.MSGPACK_REQUEST + method_name_or_error = ctx.descriptor.operation_name + + elif message is self.RESPONSE: + out_type = ctx.descriptor.out_message + msgtype = MessagePackRpc.MSGPACK_RESPONSE + method_name_or_error = None + + else: + raise Exception("what?") + + if out_type is None: + return + + out_type_info = out_type._type_info + + # instantiate the result message + out_instance = out_type() + + # assign raw result to its wrapper, result_message + for i, (k, v) in enumerate(out_type_info.items()): + attrs = self.get_cls_attrs(v) + out_instance._safe_set(k, ctx.out_object[i], v, attrs) + + # transform the results into a dict: + if out_type.Attributes.max_occurs > 1: + params = (self._to_dict_value(out_type, inst, set()) + for inst in out_instance) + else: + params = self._to_dict_value(out_type, out_instance, set()) + + ctx.out_document = [[msgtype, 0, method_name_or_error, params]] + + self.event_manager.fire_event('after_serialize', ctx) diff --git a/pym/calculate/contrib/spyne/protocol/msgpack.pyc b/pym/calculate/contrib/spyne/protocol/msgpack.pyc new file mode 100644 index 0000000..4f7be98 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/msgpack.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/soap/__init__.py b/pym/calculate/contrib/spyne/protocol/soap/__init__.py new file mode 100644 index 0000000..754461f --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/soap/__init__.py @@ -0,0 +1,30 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.protocol.soap`` package contains an implementation of a subset +of the Soap 1.1 standard and awaits for volunteers for implementing the +brand new Soap 1.2 standard. + +Patches are welcome. +""" + +from spyne.protocol.soap.soap11 import Soap11 +from spyne.protocol.soap.soap11 import _from_soap +from spyne.protocol.soap.soap11 import _parse_xml_string +from spyne.protocol.soap.soap12 import Soap12 \ No newline at end of file diff --git a/pym/calculate/contrib/spyne/protocol/soap/__init__.pyc b/pym/calculate/contrib/spyne/protocol/soap/__init__.pyc new file mode 100644 index 0000000..ae0609d Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/soap/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/soap/mime.py b/pym/calculate/contrib/spyne/protocol/soap/mime.py new file mode 100644 index 0000000..d4aaa1d --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/soap/mime.py @@ -0,0 +1,315 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.protocol.soap.mime`` module contains additional logic for using +optimized encodings for binary when encapsulating Soap 1.1 messages in Http. + +The functionality in this code seems to work at first glance but is not well +tested. + +Testcases and preferably improvements are most welcome. +""" + +from __future__ import print_function, unicode_literals + +import logging +logger = logging.getLogger(__name__) + +import re + +from base64 import b64encode +from itertools import chain + +from lxml import etree + +from email import generator +from email.mime.multipart import MIMEMultipart +from email.mime.application import MIMEApplication +from email.encoders import encode_7or8bit + +from spyne import ValidationError +from spyne.util import six +from spyne.model.binary import ByteArray, File +from spyne.const.xml import NS_XOP + +if six.PY2: + from email import message_from_string as message_from_bytes +else: + from email import message_from_bytes + + +XPATH_NSDICT = dict(xop=NS_XOP) + + +def _join_attachment(ns_soap_env, href_id, envelope, payload, prefix=True): + """Places the data from an attachment back into a SOAP message, replacing + its xop:Include element or href. + + Returns a tuple of length 2 with the new message and the number of + replacements made + + :param id: content-id or content-location of attachment + :param prefix: Set this to true if id is content-id or false if it is + content-location. It prefixes a "cid:" to the href value. + :param envelope: soap envelope string to be operated on + :param payload: attachment data + """ + + # grab the XML element of the message in the SOAP body + soaptree = etree.fromstring(envelope) + soapbody = soaptree.find("{%s}Body" % ns_soap_env) + + if soapbody is None: + raise ValidationError(None, "SOAP Body tag not found") + + message = None + for child in list(soapbody): + if child.tag != "{%s}Fault" % ns_soap_env: + message = child + break + + idprefix = '' + + if prefix: + idprefix = "cid:" + href_id = "%s%s" % (idprefix, href_id,) + + num = 0 + xpath = ".//xop:Include[@href=\"{}\"]".format(href_id) + + for num, node in enumerate(message.xpath(xpath, namespaces=XPATH_NSDICT)): + parent = node.getparent() + parent.remove(node) + parent.text = payload + + return etree.tostring(soaptree), num + + +def collapse_swa(ctx, content_type, ns_soap_env): + """ + Translates an SwA multipart/related message into an application/soap+xml + message. + + Returns the 'appication/soap+xml' version of the given HTTP body. + + References: + SwA http://www.w3.org/TR/SOAP-attachments + XOP http://www.w3.org/TR/xop10/ + MTOM http://www.w3.org/TR/soap12-mtom/ + http://www.w3.org/Submission/soap11mtom10/ + + :param content_type: value of the Content-Type header field, parsed by + cgi.parse_header() function + :param ctx: request context + """ + + envelope = ctx.in_string + # convert multipart messages back to pure SOAP + mime_type, content_data = content_type + if not six.PY2: + assert isinstance(mime_type, six.text_type) + + if u'multipart/related' not in mime_type: + return envelope + + charset = content_data.get('charset', None) + if charset is None: + charset = 'ascii' + + boundary = content_data.get('boundary', None) + if boundary is None: + raise ValidationError(None, u"Missing 'boundary' value from " + u"Content-Type header") + + envelope = list(envelope) + + # What an ugly hack... + request = MIMEMultipart('related', boundary=boundary) + msg_string = re.sub(r"\n\n.*", '', request.as_string()) + msg_string = chain( + (msg_string.encode(charset), generator.NL.encode('ascii')), + (e for e in envelope), + ) + + msg_string = b''.join(msg_string) + msg = message_from_bytes(msg_string) # our message + + soapmsg = None + root = msg.get_param('start') + + # walk through sections, reconstructing pure SOAP + for part in msg.walk(): + # skip the multipart container section + if part.get_content_maintype() == 'multipart': + continue + + # detect main soap section + if (part.get('Content-ID') and part.get('Content-ID') == root) or \ + (root is None and part == msg.get_payload()[0]): + soapmsg = part.get_payload() + continue + + # binary packages + cte = part.get("Content-Transfer-Encoding") + + if cte != 'base64': + payload = b64encode(part.get_payload(decode=True)) + else: + payload = part.get_payload() + + cid = part.get("Content-ID").strip("<>") + cloc = part.get("Content-Location") + numreplaces = None + + # Check for Content-ID and make replacement + if cid: + soapmsg, numreplaces = _join_attachment( + ns_soap_env, cid, soapmsg, payload) + + # Check for Content-Location and make replacement + if cloc and not cid and not numreplaces: + soapmsg, numreplaces = _join_attachment( + ns_soap_env, cloc, soapmsg, payload, + False) + + if soapmsg is None: + raise ValidationError(None, "Invalid MtoM request") + + return (soapmsg,) + + +def apply_mtom(headers, envelope, params, paramvals): + """Apply MTOM to a SOAP envelope, separating attachments into a + MIME multipart message. + + Returns a tuple of length 2 with dictionary of headers and string of body + that can be sent with HTTPConnection + + References: + XOP http://www.w3.org/TR/xop10/ + MTOM http://www.w3.org/TR/soap12-mtom/ + http://www.w3.org/Submission/soap11mtom10/ + + :param headers Headers dictionary of the SOAP message that would + originally be sent. + :param envelope Iterable containing SOAP envelope string that would have + originally been sent. + :param params params attribute from the Message object used for the SOAP + :param paramvals values of the params, passed to Message.to_parent + """ + + # grab the XML element of the message in the SOAP body + envelope = ''.join(envelope) + + soaptree = etree.fromstring(envelope) + soapbody = soaptree.find("{%s}Body" % _ns_soap_env) + + message = None + for child in list(soapbody): + if child.tag == ("{%s}Fault" % _ns_soap_env): + return headers, envelope + else: + message = child + break + + # Get additional parameters from original Content-Type + ctarray = [] + for n, v in headers.items(): + if n.lower() == 'content-type': + ctarray = v.split(';') + break + + roottype = ctarray[0].strip() + rootparams = {} + for ctparam in ctarray[1:]: + n, v = ctparam.strip().split('=') + rootparams[n] = v.strip("\"'") + + # Set up initial MIME parts. + mtompkg = MIMEMultipart('related', boundary='?//<><>spyne_MIME_boundary<>') + rootpkg = MIMEApplication(envelope, 'xop+xml', encode_7or8bit) + + # Set up multipart headers. + del mtompkg['mime-version'] + mtompkg.set_param('start-info', roottype) + mtompkg.set_param('start', '') + if 'SOAPAction' in headers: + mtompkg.add_header('SOAPAction', headers.get('SOAPAction')) + + # Set up root SOAP part headers. + del rootpkg['mime-version'] + + rootpkg.add_header('Content-ID', '') + + for n, v in rootparams.items(): + rootpkg.set_param(n, v) + + rootpkg.set_param('type', roottype) + + mtompkg.attach(rootpkg) + + # Extract attachments from SOAP envelope. + for i in range(len(params)): + name, typ = params[i] + + if issubclass(typ, (ByteArray, File)): + id = "SpyneAttachment_%s" % (len(mtompkg.get_payload()), ) + + param = message[i] + param.text = "" + + incl = etree.SubElement(param, "{%s}Include" % _ns_xop) + incl.attrib["href"] = "cid:%s" % id + + if paramvals[i].fileName and not paramvals[i].data: + paramvals[i].load_from_file() + + if issubclass(type, File): + data = paramvals[i].data + else: + data = ''.join(paramvals[i]) + + attachment = MIMEApplication(data, _encoder=encode_7or8bit) + + del attachment['mime-version'] + + attachment.add_header('Content-ID', '<%s>' % (id, )) + mtompkg.attach(attachment) + + # Update SOAP envelope. + rootpkg.set_payload(etree.tostring(soaptree)) + + # extract body string from MIMEMultipart message + bound = '--%s' % (mtompkg.get_boundary(), ) + marray = mtompkg.as_string().split(bound) + mtombody = bound + mtombody += bound.join(marray[1:]) + + # set Content-Length + mtompkg.add_header("Content-Length", str(len(mtombody))) + + # extract dictionary of headers from MIMEMultipart message + mtomheaders = {} + for name, value in mtompkg.items(): + mtomheaders[name] = value + + if len(mtompkg.get_payload()) <= 1: + return headers, envelope + + return mtomheaders, [mtombody] diff --git a/pym/calculate/contrib/spyne/protocol/soap/mime.pyc b/pym/calculate/contrib/spyne/protocol/soap/mime.pyc new file mode 100644 index 0000000..3689b4b Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/soap/mime.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/soap/soap11.py b/pym/calculate/contrib/spyne/protocol/soap/soap11.py new file mode 100644 index 0000000..75e1add --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/soap/soap11.py @@ -0,0 +1,379 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.protocol.soap.soap11`` module contains the implementation of a +subset of the Soap 1.1 standard. + +Except the binary optimizations (MtoM, attachments, etc) that are beta quality, +this protocol is production quality. + +One must specifically enable the debug output for the Xml protocol to see the +actual document exchange. That's because the xml formatting code is run only +when explicitly enabled due to performance reasons. :: + + logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) + +Initially released in soaplib-0.8.0. + +Logs valid documents to %r and invalid documents to %r. +""" % (__name__, __name__ + ".invalid") + +import logging +logger = logging.getLogger(__name__) +logger_invalid = logging.getLogger(__name__ + ".invalid") + +import cgi + +from itertools import chain + +import spyne.const.xml as ns + +from lxml import etree +from lxml.etree import XMLSyntaxError +from lxml.etree import XMLParser + +from spyne import BODY_STYLE_WRAPPED +from spyne.util import six +from spyne.const.xml import DEFAULT_NS +from spyne.const.http import HTTP_405, HTTP_500 +from spyne.error import RequestNotAllowed +from spyne.model.fault import Fault +from spyne.model.primitive import Date, Time, DateTime +from spyne.protocol.xml import XmlDocument +from spyne.protocol.soap.mime import collapse_swa +from spyne.server.http import HttpTransportContext + + +def _from_soap(in_envelope_xml, xmlids=None, **kwargs): + """Parses the xml string into the header and payload. + """ + ns_soap = kwargs.pop('ns', ns.NS_SOAP11_ENV) + + if xmlids: + resolve_hrefs(in_envelope_xml, xmlids) + + if in_envelope_xml.tag != '{%s}Envelope' % ns_soap: + raise Fault('Client.SoapError', 'No {%s}Envelope element was found!' % + ns_soap) + + header_envelope = in_envelope_xml.xpath('e:Header', + namespaces={'e': ns_soap}) + body_envelope = in_envelope_xml.xpath('e:Body', + namespaces={'e': ns_soap}) + + if len(header_envelope) == 0 and len(body_envelope) == 0: + raise Fault('Client.SoapError', 'Soap envelope is empty!') + + header = None + if len(header_envelope) > 0: + header = header_envelope[0].getchildren() + + body = None + if len(body_envelope) > 0 and len(body_envelope[0]) > 0: + body = body_envelope[0][0] + + return header, body + + +def _parse_xml_string(xml_string, parser, charset=None): + xml_string = iter(xml_string) + chunk = next(xml_string) + if isinstance(chunk, six.binary_type): + string = b''.join(chain( (chunk,), xml_string )) + else: + string = ''.join(chain( (chunk,), xml_string )) + + if charset: + string = string.decode(charset) + + try: + try: + root, xmlids = etree.XMLID(string, parser) + + except ValueError as e: + logger.debug('ValueError: Deserializing from unicode strings with ' + 'encoding declaration is not supported by lxml.') + root, xmlids = etree.XMLID(string.encode(charset), parser) + + except XMLSyntaxError as e: + logger_invalid.error("%r in string %r", e, string) + raise Fault('Client.XMLSyntaxError', str(e)) + + return root, xmlids + + +# see http://www.w3.org/TR/2000/NOTE-SOAP-20000508/ +# section 5.2.1 for an example of how the id and href attributes are used. +def resolve_hrefs(element, xmlids): + for e in element: + if e.get('id'): + continue # don't need to resolve this element + + elif e.get('href'): + resolved_element = xmlids[e.get('href').replace('#', '')] + if resolved_element is None: + continue + resolve_hrefs(resolved_element, xmlids) + + # copies the attributes + [e.set(k, v) for k, v in resolved_element.items()] + + # copies the children + [e.append(child) for child in resolved_element.getchildren()] + + # copies the text + e.text = resolved_element.text + + else: + resolve_hrefs(e, xmlids) + + return element + + +class Soap11(XmlDocument): + """The base implementation of a subset of the Soap 1.1 standard. The + document is available here: http://www.w3.org/TR/soap11/ + + :param app: The owner application instance. + :param validator: One of (None, 'soft', 'lxml', 'schema', + ProtocolBase.SOFT_VALIDATION, XmlDocument.SCHEMA_VALIDATION). + Both ``'lxml'`` and ``'schema'`` values are equivalent to + ``XmlDocument.SCHEMA_VALIDATION``. + :param xml_declaration: Whether to add xml_declaration to the responses + Default is 'True'. + :param cleanup_namespaces: Whether to add clean up namespace declarations + in the response document. Default is 'True'. + :param encoding: The suggested string encoding for the returned xml + documents. The transport can override this. + :param pretty_print: When ``True``, returns the document in a pretty-printed + format. + """ + + mime_type = 'text/xml; charset=utf-8' + + type = set(XmlDocument.type) + type.update(('soap', 'soap11')) + + ns_soap_env = ns.NS_SOAP11_ENV + ns_soap_enc = ns.NS_SOAP11_ENC + + def __init__(self, *args, **kwargs): + super(Soap11, self).__init__(*args, **kwargs) + + # SOAP requires DateTime strings to be in iso format. The following + # lines make sure custom datetime formatting via + # DateTime(dt_format="...") (or similar) is bypassed. + self._to_unicode_handlers[Time] = lambda cls, value: value.isoformat() + self._to_unicode_handlers[DateTime] = lambda cls, value: value.isoformat() + + self._from_unicode_handlers[Date] = self.date_from_unicode_iso + self._from_unicode_handlers[DateTime] = self.datetime_from_unicode_iso + + def create_in_document(self, ctx, charset=None): + if isinstance(ctx.transport, HttpTransportContext): + # according to the soap-via-http standard, soap requests must only + # work with proper POST requests. + content_type = ctx.transport.get_request_content_type() + http_verb = ctx.transport.get_request_method() + if content_type is None or http_verb != "POST": + ctx.transport.resp_code = HTTP_405 + raise RequestNotAllowed( + "You must issue a POST request with the Content-Type " + "header properly set.") + + content_type = cgi.parse_header(content_type) + ctx.in_string = collapse_swa(ctx, content_type, self.ns_soap_env) + + ctx.in_document = _parse_xml_string(ctx.in_string, + XMLParser(**self.parser_kwargs), + charset) + + def decompose_incoming_envelope(self, ctx, message=XmlDocument.REQUEST): + envelope_xml, xmlids = ctx.in_document + header_document, body_document = _from_soap(envelope_xml, xmlids, + ns=self.ns_soap_env) + + ctx.in_document = envelope_xml + + if body_document.tag == '{%s}Fault' % self.ns_soap_env: + ctx.in_body_doc = body_document + + else: + ctx.in_header_doc = header_document + ctx.in_body_doc = body_document + ctx.method_request_string = ctx.in_body_doc.tag + self.validate_body(ctx, message) + + def deserialize(self, ctx, message): + """Takes a MethodContext instance and a string containing ONE soap + message. + Returns the corresponding native python object + + Not meant to be overridden. + """ + + assert message in (self.REQUEST, self.RESPONSE) + + self.event_manager.fire_event('before_deserialize', ctx) + + if ctx.in_body_doc.tag == "{%s}Fault" % self.ns_soap_env: + ctx.in_object = None + ctx.in_error = self.from_element(ctx, Fault, ctx.in_body_doc) + + else: + if message is self.REQUEST: + header_class = ctx.descriptor.in_header + body_class = ctx.descriptor.in_message + + elif message is self.RESPONSE: + header_class = ctx.descriptor.out_header + body_class = ctx.descriptor.out_message + + # decode header objects + # header elements are returned in header_class order which need not match the incoming XML + if (ctx.in_header_doc is not None and header_class is not None): + headers = [None] * len(header_class) + in_header_dict = dict( [(element.tag, element) + for element in ctx.in_header_doc]) + for i, head_class in enumerate(header_class): + if i < len(header_class): + nsval = "{%s}%s" % (head_class.__namespace__, + head_class.__type_name__) + header_doc = in_header_dict.get(nsval, None) + if header_doc is not None: + headers[i] = self.from_element(ctx, head_class, + header_doc) + + if len(headers) == 1: + ctx.in_header = headers[0] + else: + ctx.in_header = headers + + # decode method arguments + if ctx.in_body_doc is None: + ctx.in_object = [None] * len(body_class._type_info) + else: + ctx.in_object = self.from_element(ctx, body_class, + ctx.in_body_doc) + + self.event_manager.fire_event('after_deserialize', ctx) + + def serialize(self, ctx, message): + """Uses ctx.out_object, ctx.out_header or ctx.out_error to set + ctx.out_body_doc, ctx.out_header_doc and ctx.out_document as an + lxml.etree._Element instance. + + Not meant to be overridden. + """ + + assert message in (self.REQUEST, self.RESPONSE) + + self.event_manager.fire_event('before_serialize', ctx) + + # construct the soap response, and serialize it + nsmap = self.app.interface.nsmap + ctx.out_document = etree.Element('{%s}Envelope' % self.ns_soap_env, + nsmap=nsmap) + if ctx.out_error is not None: + # FIXME: There's no way to alter soap response headers for the user. + ctx.out_body_doc = out_body_doc = etree.SubElement(ctx.out_document, + '{%s}Body' % self.ns_soap_env, nsmap=nsmap) + self.to_parent(ctx, ctx.out_error.__class__, ctx.out_error, + out_body_doc, self.app.interface.get_tns()) + + else: + if message is self.REQUEST: + header_message_class = ctx.descriptor.in_header + body_message_class = ctx.descriptor.in_message + + elif message is self.RESPONSE: + header_message_class = ctx.descriptor.out_header + body_message_class = ctx.descriptor.out_message + + # body + ctx.out_body_doc = out_body_doc = etree.Element( + '{%s}Body' % self.ns_soap_env) + + # assign raw result to its wrapper, result_message + if ctx.descriptor.body_style is BODY_STYLE_WRAPPED: + out_type_info = body_message_class._type_info + out_object = body_message_class() + bm_attrs = self.get_cls_attrs(body_message_class) + + keys = iter(out_type_info) + values = iter(ctx.out_object) + while True: + try: + k = next(keys) + except StopIteration: + break + try: + v = next(values) + except StopIteration: + v = None + + out_object._safe_set(k, v, body_message_class, bm_attrs) + + self.to_parent(ctx, body_message_class, out_object, + out_body_doc, body_message_class.get_namespace()) + + else: + out_object = ctx.out_object[0] + + sub_ns = body_message_class.Attributes.sub_ns + if sub_ns is None: + sub_ns = body_message_class.get_namespace() + if sub_ns is DEFAULT_NS: + sub_ns = self.app.interface.get_tns() + + sub_name = body_message_class.Attributes.sub_name + if sub_name is None: + sub_name = body_message_class.get_type_name() + + self.to_parent(ctx, body_message_class, out_object, out_body_doc, + sub_ns, sub_name) + + # header + if ctx.out_header is not None and header_message_class is not None: + ctx.out_header_doc = soap_header_elt = etree.SubElement( + ctx.out_document, '{%s}Header' % self.ns_soap_env) + + if isinstance(ctx.out_header, (list, tuple)): + out_headers = ctx.out_header + else: + out_headers = (ctx.out_header,) + + for header_class, out_header in zip(header_message_class, + out_headers): + self.to_parent(ctx, + header_class, out_header, + soap_header_elt, + header_class.get_namespace(), + header_class.get_type_name(), + ) + + ctx.out_document.append(ctx.out_body_doc) + + if self.cleanup_namespaces: + etree.cleanup_namespaces(ctx.out_document) + + self.event_manager.fire_event('after_serialize', ctx) + + def fault_to_http_response_code(self, fault): + return HTTP_500 diff --git a/pym/calculate/contrib/spyne/protocol/soap/soap11.pyc b/pym/calculate/contrib/spyne/protocol/soap/soap11.pyc new file mode 100644 index 0000000..eeecbc7 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/soap/soap11.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/soap/soap12.py b/pym/calculate/contrib/spyne/protocol/soap/soap12.py new file mode 100644 index 0000000..69858a1 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/soap/soap12.py @@ -0,0 +1,152 @@ +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +""" +The ``spyne.protoco.soap.soap12`` module contains the implementation of a +subset of the Soap 1.2 standard. + +This modules is EXPERIMENTAL. +More info can be found at: https://www.w3.org/TR/soap12-part1/ +""" + +import logging + +from lxml.builder import E + +from spyne.protocol.soap.soap11 import Soap11 +from spyne.protocol.xml import _append +from spyne.util.six import string_types +from spyne.util.etreeconv import root_dict_to_etree +from spyne.const.xml import NS_SOAP12_ENV, NS_XML, PREFMAP + + +logger = logging.getLogger(__name__) +logger_invalid = logging.getLogger(__name__ + ".invalid") + + +class Soap12(Soap11): + """ + The base implementation of a subset of the Soap 1.2 standard. The + document is available here: http://www.w3.org/TR/soap12/ + """ + mime_type = 'application/soap+xml; charset=utf-8' + + soap_env = PREFMAP[NS_SOAP12_ENV] + ns_soap_env = NS_SOAP12_ENV + + type = set(Soap11.type) + type.discard('soap11') + type.update(('soap', 'soap12')) + + def generate_subcode(self, value, subcode=None): + subcode_node = E("{%s}Subcode" % self.ns_soap_env) + subcode_node.append(E("{%s}Value" % self.ns_soap_env, value)) + if subcode: + subcode_node.append(subcode) + return subcode_node + + def gen_fault_codes(self, faultstring): + faultstrings = faultstring.split('.') + value = faultstrings.pop(0) + if value == 'Client': + value = '%s:Sender' % self.soap_env + elif value == 'Server': + value = '%s:Receiver' % self.soap_env + else: + raise TypeError('Wrong fault code, got', type(faultstring)) + + return value, faultstrings + + def generate_faultcode(self, element): + nsmap = element.nsmap + faultcode = [] + faultcode.append(element.find('soap:Code/soap:Value', namespaces=nsmap).text) + subcode = element.find('soap:Code/soap:Subcode', namespaces=nsmap) + while subcode is not None: + faultcode.append(subcode.find('soap:Value', namespaces=nsmap).text) + subcode = subcode.find('soap:Subcode', namespaces=nsmap) + + return '.'.join(faultcode) + + def fault_to_parent(self, ctx, cls, inst, parent, ns, **_): + reason = E("{%s}Reason" % self.ns_soap_env) + reason.append(E("{%s}Text" % self.ns_soap_env, inst.faultstring, + **{'{%s}lang' % NS_XML: inst.lang})) + + subelts = [ + reason, + E("{%s}Role" % self.ns_soap_env, inst.faultactor), + ] + + return self._fault_to_parent_impl(ctx, cls, inst, parent, ns, subelts) + + def _fault_to_parent_impl(self, ctx, cls, inst, parent, ns, subelts, **_): + tag_name = "{%s}Fault" % self.ns_soap_env + + if isinstance(inst.faultcode, string_types): + value, faultcodes = self.gen_fault_codes(inst.faultcode) + + code = E("{%s}Code" % self.ns_soap_env) + code.append(E("{%s}Value" % self.ns_soap_env, value)) + + child_subcode = False + for value in faultcodes[::-1]: + if child_subcode: + child_subcode = self.generate_subcode(value, child_subcode) + else: + child_subcode = self.generate_subcode(value) + if child_subcode != 0: + code.append(child_subcode) + + _append(subelts, code) + + if isinstance(inst.detail, dict): + _append(subelts, E('{%s}Detail' % self.ns_soap_env, root_dict_to_etree(inst.detail))) + + elif inst.detail is None: + pass + + else: + raise TypeError('Fault detail Must be dict, got', type(inst.detail)) + + return self.gen_members_parent(ctx, cls, inst, parent, tag_name, + subelts, add_type=False) + + def schema_validation_error_to_parent(self, ctx, cls, inst, parent, ns, **_): + subelts = [ + E("{%s}Reason" % self.soap_env, inst.faultstring), + E("{%s}Role" % self.soap_env, inst.faultactor), + ] + + return self._fault_to_parent_impl(ctx, cls, inst, parent, ns, subelts) + + def fault_from_element(self, ctx, cls, element): + nsmap = element.nsmap + + code = self.generate_faultcode(element) + reason = element.find("soap:Reason/soap:Text", namespaces=nsmap).text.strip() + role = element.find("soap:Role", namespaces=nsmap) + node = element.find("soap:Node", namespaces=nsmap) + detail = element.find("soap:Detail", namespaces=nsmap) + faultactor = '' + if role is not None: + faultactor += role.text.strip() + if node is not None: + faultactor += node.text.strip() + return cls(faultcode=code, faultstring=reason, + faultactor=faultactor, detail=detail) diff --git a/pym/calculate/contrib/spyne/protocol/soap/soap12.pyc b/pym/calculate/contrib/spyne/protocol/soap/soap12.pyc new file mode 100644 index 0000000..c1db0a0 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/soap/soap12.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/xml.py b/pym/calculate/contrib/spyne/protocol/xml.py new file mode 100644 index 0000000..f993032 --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/xml.py @@ -0,0 +1,1160 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + + +"""The ``spyne.protocol.xml`` module contains an xml-based protocol that +serializes python objects to xml using Xml Schema conventions. + +Logs valid documents to ``'spyne.protocol.xml'`` and invalid documents to +``spyne.protocol.xml.invalid``. Use the usual ``logging.getLogger()`` and +friends to configure how these get logged. + +Warning! You can get a lot of crap in the 'invalid' logger. You're not advised +to turn it on for a production system. +""" + + +import logging +logger = logging.getLogger('spyne.protocol.xml') +logger_invalid = logging.getLogger('spyne.protocol.xml.invalid') + +from inspect import isgenerator +from collections import defaultdict + +from lxml import etree +from lxml import html +from lxml.builder import E +from lxml.etree import XMLSyntaxError +from lxml.etree import XMLParser + +from spyne import BODY_STYLE_WRAPPED + +from spyne.util import Break, coroutine +from spyne.util.six import text_type, string_types +from spyne.util.cdict import cdict +from spyne.util.etreeconv import etree_to_dict, dict_to_etree,\ + root_dict_to_etree +from spyne.const.xml import XSI, NS_SOAP11_ENC + +from spyne.error import Fault +from spyne.error import ValidationError +from spyne.const.ansi_color import LIGHT_GREEN +from spyne.const.ansi_color import LIGHT_RED +from spyne.const.ansi_color import END_COLOR +from spyne.const.xml import NS_SOAP11_ENV +from spyne.const.xml import PREFMAP, DEFAULT_NS + +from spyne.model import Any, ModelBase, Array, Iterable, ComplexModelBase, \ + AnyHtml, AnyXml, AnyDict, Unicode, PushBase, File, ByteArray, XmlData, \ + XmlAttribute +from spyne.model.binary import BINARY_ENCODING_BASE64 +from spyne.model.enum import EnumBase + +from spyne.protocol import ProtocolBase + +from spyne.util import six + +if six.PY2: + STR_TYPES = (str, unicode) +else: + STR_TYPES = (str, bytes) + + +NIL_ATTR = {XSI('nil'): 'true'} +XSI_TYPE = XSI('type') + + +def _append(parent, child_elt): + if hasattr(parent, 'append'): + parent.append(child_elt) + else: + parent.write(child_elt) + + +def _gen_tagname(ns, name): + if ns is not None: + name = "{%s}%s" % (ns, name) + return name + + +class SchemaValidationError(Fault): + """Raised when the input stream could not be validated by the Xml Schema.""" + + CODE = 'Client.SchemaValidationError' + + def __init__(self, faultstring): + super(SchemaValidationError, self).__init__(self.CODE, faultstring) + + +class SubXmlBase(ProtocolBase): + def subserialize(self, ctx, cls, inst, parent, ns=None, name=None): + return self.to_parent(ctx, cls, inst, parent, name) + + def to_parent(self, ctx, cls, inst, parent, ns, *args, **kwargs): + """Serializes inst to an Element instance and appends it to the 'parent'. + + :param self: The protocol that will be used to serialize the given + value. + :param cls: The type of the value that's going to determine how to + pack the given value. + :param inst: The value to be set for the 'text' element of the newly + created SubElement + :param parent: The parent Element to which the new child will be + appended. + :param ns: The target namespace of the new SubElement, used with + 'name' to set the tag. + :param name: The tag name of the new SubElement, 'retval' by default. + """ + raise NotImplementedError() + + +class XmlDocument(SubXmlBase): + """The Xml input and output protocol, using the information from the Xml + Schema generated by Spyne types. + + See the following material for more (much much more!) information. + + * http://www.w3.org/TR/xmlschema-0/ + * http://www.w3.org/TR/xmlschema-1/ + * http://www.w3.org/TR/xmlschema-2/ + + Receiving Xml from untrusted sources is a dodgy security dance as the Xml + attack surface is /huge/. + + Spyne's ```lxml.etree.XMLParser``` instance has ```resolve_pis```, + ```load_dtd```, ```resolve_entities```, ```dtd_validation```, + ```huge_tree``` Defaults to ``False`` + + Having ```resolve_entities``` disabled will prevent the 'lxml' validation + for documents with custom xml entities defined in the DTD. See the example + in examples/xml/validation_error to play with the settings that work best + for you. Please note that enabling ```resolve_entities``` is a security + hazard that can lead to disclosure of sensitive information. + + See https://pypi.python.org/pypi/defusedxml for a pragmatic overview of + Xml security in Python world. + + :param app: The owner application instance. + + :param validator: One of (None, 'soft', 'lxml', 'schema', + ProtocolBase.SOFT_VALIDATION, XmlDocument.SCHEMA_VALIDATION). + Both ``'lxml'`` and ``'schema'`` values are equivalent to + ``XmlDocument.SCHEMA_VALIDATION``. + + Defaults to ``None``. + + :param replace_null_with_default: If ``False``, does not replace incoming + explicit null values with denoted default values. This is against Xml + Schema standard but consistent with other Spyne protocol + implementations. Set this to False if you want cross-protocol + compatibility. + + Defaults to ``True``. + + Relevant quote from xml schema primer + (http://www.w3.org/TR/xmlschema-0/): + + .. + When a value is present and is null The schema processor treats + defaulted elements slightly differently. When an element is declared + with a default value, the value of the element is whatever value + appears as the element's content in the instance document; if the + element appears without any content, the schema processor provides + the element with a value equal to that of the default attribute. + However, if the element does not appear in the instance document, + the schema processor does not provide the element at all. In + summary, the differences between element and attribute defaults can + be stated as: Default attribute values apply when attributes are + missing, and default element values apply when elements are empty. + + :param xml_declaration: Whether to add xml_declaration to the responses + + Defaults to ``True``. + + :param cleanup_namespaces: Whether to add clean up namespace declarations + in the response document. + + Defaults to ``True``. + + :param encoding: The suggested string encoding for the returned xml + documents. The transport can override this. + + Defaults to ``None``. + + :param pretty_print: When ``True``, returns the document in a pretty-printed + format. + + Defaults to ``False``. + + :param parse_xsi_type: Set to ``False`` to disable parsing of ``xsi:type`` + attribute, effectively disabling polymorphism. + + Defaults to ``True``. + + The following are passed straight to the ``XMLParser()`` instance from + lxml. Docs are also plagiarized from the lxml documentation. Please note + that some of the defaults are different to make parsing safer by default. + + :param attribute_defaults: read the DTD (if referenced by the document) and + add the default attributes from it. + + Defaults to ``False`` + + :param dtd_validation: validate while parsing (if a DTD was referenced). + + Defaults to ``False`` + + :param load_dtd: load and parse the DTD while parsing (no validation is + performed). + + Defaults to ``False``. + + :param no_network: prevent network access when looking up external + documents. + + Defaults to ``True``. + + :param ns_clean: try to clean up redundant namespace declarations. + Please note that this is for incoming documents. + See ``cleanup_namespaces`` parameter for output documents. + + Defaults to ``False``. + + :param recover: try hard to parse through broken Xml. + + Defaults to ``False``. + + :param remove_blank_text: discard blank text nodes between tags, also known + as ignorable whitespace. This is best used together with a DTD or schema + (which tells data and noise apart), otherwise a heuristic will be + applied. + + Defaults to ``False``. + + :param remove_pis: When ``True`` xml parser discards processing + instructions. + + Defaults to ``True``. + + :param strip_cdata: replace CDATA sections by normal text content. + + Defaults to ``True`` + + :param resolve_entities: replace entities by their text value. + + Defaults to ``False``. + + :param huge_tree: disable security restrictions and support very deep trees + and very long text content. (only affects libxml2 2.7+) + + Defaults to ``False``. + + :param compact: use compact storage for short text content. + + Defaults to ``True``. + + """ + + SCHEMA_VALIDATION = type("Schema", (object,), {}) + + mime_type = 'text/xml' + default_binary_encoding = BINARY_ENCODING_BASE64 + + type = set(ProtocolBase.type) + type.add('xml') + + soap_env = PREFMAP[NS_SOAP11_ENV] + ns_soap_env = NS_SOAP11_ENV + ns_soap_enc = NS_SOAP11_ENC + + def __init__(self, app=None, validator=None, + replace_null_with_default=True, + xml_declaration=True, + cleanup_namespaces=True, encoding=None, pretty_print=False, + attribute_defaults=False, + dtd_validation=False, + load_dtd=False, + no_network=True, + ns_clean=False, + recover=False, + remove_blank_text=False, + remove_pis=True, + strip_cdata=True, + resolve_entities=False, + huge_tree=False, + compact=True, + binary_encoding=None, + parse_xsi_type=True, + polymorphic=False, + ): + + super(XmlDocument, self).__init__(app, validator, + binary_encoding=binary_encoding) + + self.validation_schema = None + self.xml_declaration = xml_declaration + self.cleanup_namespaces = cleanup_namespaces + self.replace_null_with_default = replace_null_with_default + + if encoding is None: + self.encoding = 'UTF-8' + else: + self.encoding = encoding + + self.polymorphic = polymorphic + self.pretty_print = pretty_print + self.parse_xsi_type = parse_xsi_type + + self.serialization_handlers = cdict({ + Any: self.any_to_parent, + Fault: self.fault_to_parent, + EnumBase: self.enum_to_parent, + AnyXml: self.any_xml_to_parent, + XmlData: self.xmldata_to_parent, + AnyDict: self.any_dict_to_parent, + AnyHtml: self.any_html_to_parent, + ModelBase: self.modelbase_to_parent, + ByteArray: self.byte_array_to_parent, + ComplexModelBase: self.complex_to_parent, + XmlAttribute: self.xmlattribute_to_parent, + SchemaValidationError: self.schema_validation_error_to_parent, + }) + + self.deserialization_handlers = cdict({ + AnyHtml: self.html_from_element, + AnyXml: self.xml_from_element, + Any: self.xml_from_element, + Array: self.array_from_element, + Fault: self.fault_from_element, + AnyDict: self.dict_from_element, + EnumBase: self.enum_from_element, + ModelBase: self.base_from_element, + Unicode: self.unicode_from_element, + Iterable: self.iterable_from_element, + ByteArray: self.byte_array_from_element, + ComplexModelBase: self.complex_from_element, + }) + + self.parser_kwargs = dict( + attribute_defaults=attribute_defaults, + dtd_validation=dtd_validation, + load_dtd=load_dtd, + no_network=no_network, + ns_clean=ns_clean, + recover=recover, + remove_blank_text=remove_blank_text, + remove_comments=True, + remove_pis=remove_pis, + strip_cdata=strip_cdata, + resolve_entities=resolve_entities, + huge_tree=huge_tree, + compact=compact, + encoding=encoding, + ) + + def set_validator(self, validator): + if validator in ('lxml', 'schema') or \ + validator is self.SCHEMA_VALIDATION: + self.validate_document = self.__validate_lxml + self.validator = self.SCHEMA_VALIDATION + + elif validator == 'soft' or validator is self.SOFT_VALIDATION: + self.validator = self.SOFT_VALIDATION + + elif validator is None: + pass + + else: + raise ValueError(validator) + + self.validation_schema = None + + def validate_body(self, ctx, message): + """Sets ctx.method_request_string and calls :func:`generate_contexts` + for validation.""" + + assert message in (self.REQUEST, self.RESPONSE), message + + line_header = LIGHT_RED + "Error:" + END_COLOR + try: + self.validate_document(ctx.in_body_doc) + if message is self.REQUEST: + line_header = LIGHT_GREEN + "Method request string:" + END_COLOR + else: + line_header = LIGHT_RED + "Response:" + END_COLOR + finally: + if logger.level == logging.DEBUG: + logger.debug("%s %s" % (line_header, ctx.method_request_string)) + logger.debug(etree.tostring(ctx.in_document, pretty_print=True)) + + def set_app(self, value): + ProtocolBase.set_app(self, value) + + self.validation_schema = None + + if self.validator is self.SCHEMA_VALIDATION and value is not None: + from spyne.interface.xml_schema import XmlSchema + + xml_schema = XmlSchema(value.interface) + xml_schema.build_validation_schema() + + self.validation_schema = xml_schema.validation_schema + + def __validate_lxml(self, payload): + ret = self.validation_schema.validate(payload) + + logger.debug("Validated ? %r" % ret) + if ret == False: + error_text = text_type(self.validation_schema.error_log.last_error) + raise SchemaValidationError(error_text.encode('ascii', + 'xmlcharrefreplace')) + + def create_in_document(self, ctx, charset=None): + """Uses the iterable of string fragments in ``ctx.in_string`` to set + ``ctx.in_document``.""" + + string = b''.join(ctx.in_string) + try: + try: + ctx.in_document = etree.fromstring(string, + parser=XMLParser(**self.parser_kwargs)) + + except ValueError: + logger.debug('ValueError: Deserializing from unicode strings ' + 'with encoding declaration is not supported by ' + 'lxml.') + ctx.in_document = etree.fromstring(string.decode(charset), + self.parser) + except XMLSyntaxError as e: + logger_invalid.error("%r in string %r", e, string) + raise Fault('Client.XMLSyntaxError', str(e)) + + def decompose_incoming_envelope(self, ctx, message): + assert message in (self.REQUEST, self.RESPONSE) + + ctx.in_header_doc = None # If you need header support, you should use Soap + ctx.in_body_doc = ctx.in_document + ctx.method_request_string = ctx.in_body_doc.tag + self.validate_body(ctx, message) + + def from_element(self, ctx, cls, element): + cls_attrs = self.get_cls_attrs(cls) + + if bool(element.get(XSI('nil'))): + if self.validator is self.SOFT_VALIDATION and not \ + cls_attrs.nillable: + raise ValidationError(None) + + if self.replace_null_with_default: + return cls_attrs.default + + return None + + # if present, use the xsi:type="ns0:ObjectName" + # attribute to instantiate subclass objects + if self.parse_xsi_type: + xsi_type = element.get(XSI_TYPE, None) + if xsi_type is not None: + if ":" in xsi_type: + prefix, objtype = xsi_type.split(':', 1) + else: + prefix, objtype = None, xsi_type + + ns = element.nsmap.get(prefix) + if ns is not None: + classkey = "{%s}%s" % (ns, objtype) + + else: + logger.error("xsi:type namespace prefix " + "'%s' in '%s' not found in %r", + ns, xsi_type, element.nsmap) + + raise ValidationError(xsi_type) + + newclass = ctx.app.interface.classes.get(classkey, None) + if newclass is None: + logger.error("xsi:type '%s' interpreted as class key '%s' " + "is not recognized", xsi_type, classkey) + raise ValidationError(xsi_type) + + cls = newclass + logger.debug("xsi:type '%s' overrides %r to %r", xsi_type, + cls, newclass) + + handler = self.deserialization_handlers[cls] + return handler(ctx, cls, element) + + def to_parent(self, ctx, cls, inst, parent, ns, *args, **kwargs): + cls, add_type = self.get_polymorphic_target(cls, inst) + cls_attrs = self.get_cls_attrs(cls) + + subprot = cls_attrs.prot + if subprot is not None and isinstance(subprot, SubXmlBase): + return subprot.subserialize(ctx, cls, inst, parent, ns, + *args, **kwargs) + + handler = self.serialization_handlers[cls] + + if inst is None: + inst = cls_attrs.default + + if inst is None: + return self.null_to_parent(ctx, cls, inst, parent, ns, + *args, **kwargs) + + if cls_attrs.exc: + return + kwargs['add_type'] = add_type + return handler(ctx, cls, inst, parent, ns, *args, **kwargs) + + def deserialize(self, ctx, message): + """Takes a MethodContext instance and a string containing ONE root xml + tag. + + Returns the corresponding native python object. + + Not meant to be overridden. + """ + + assert message in (self.REQUEST, self.RESPONSE) + + self.event_manager.fire_event('before_deserialize', ctx) + + if ctx.descriptor is None: + if ctx.in_error is None: + raise Fault("Client", "Method %r not found." % + ctx.method_request_string) + else: + raise ctx.in_error + + if message is self.REQUEST: + body_class = ctx.descriptor.in_message + elif message is self.RESPONSE: + body_class = ctx.descriptor.out_message + + # decode method arguments + if ctx.in_body_doc is None: + ctx.in_object = [None] * len(body_class._type_info) + else: + ctx.in_object = self.from_element(ctx, body_class, ctx.in_body_doc) + + if logger.level == logging.DEBUG and message is self.REQUEST: + line_header = '%sRequest%s' % (LIGHT_GREEN, END_COLOR) + + outdoc_str = None + if ctx.out_document is not None: + outdoc_str = etree.tostring(ctx.out_document, + xml_declaration=self.xml_declaration, pretty_print=True) + + logger.debug("%s %s" % (line_header, outdoc_str)) + + self.event_manager.fire_event('after_deserialize', ctx) + + def serialize(self, ctx, message): + """Uses ``ctx.out_object``, ``ctx.out_header`` or ``ctx.out_error`` to + set ``ctx.out_body_doc``, ``ctx.out_header_doc`` and + ``ctx.out_document`` as an ``lxml.etree._Element instance``. + + Not meant to be overridden. + """ + + assert message in (self.REQUEST, self.RESPONSE) + + self.event_manager.fire_event('before_serialize', ctx) + + if ctx.out_error is not None: + tmp_elt = etree.Element('punk') + retval = self.to_parent(ctx, ctx.out_error.__class__, ctx.out_error, + tmp_elt, self.app.interface.get_tns()) + ctx.out_document = tmp_elt[0] + + else: + if message is self.REQUEST: + result_message_class = ctx.descriptor.in_message + elif message is self.RESPONSE: + result_message_class = ctx.descriptor.out_message + + # assign raw result to its wrapper, result_message + if ctx.descriptor.body_style == BODY_STYLE_WRAPPED: + result_inst = result_message_class() + + for i, (k, v) in enumerate( + result_message_class._type_info.items()): + attrs = self.get_cls_attrs(v) + result_inst._safe_set(k, ctx.out_object[i], v, attrs) + + else: + result_inst = ctx.out_object + + if ctx.out_stream is None: + tmp_elt = etree.Element('punk') + retval = self.to_parent(ctx, result_message_class, + result_inst, tmp_elt, self.app.interface.get_tns()) + ctx.out_document = tmp_elt[0] + + else: + retval = self.incgen(ctx, result_message_class, + result_inst, self.app.interface.get_tns()) + + if self.cleanup_namespaces and ctx.out_document is not None: + etree.cleanup_namespaces(ctx.out_document) + + self.event_manager.fire_event('after_serialize', ctx) + + return retval + + def create_out_string(self, ctx, charset=None): + """Sets an iterable of string fragments to ctx.out_string""" + + if charset is None: + charset = self.encoding + + ctx.out_string = [etree.tostring(ctx.out_document, + encoding=charset, + pretty_print=self.pretty_print, + xml_declaration=self.xml_declaration)] + + if logger.level == logging.DEBUG: + logger.debug('%sResponse%s %s' % (LIGHT_RED, END_COLOR, + etree.tostring(ctx.out_document, + pretty_print=True, encoding='UTF-8'))) + + @coroutine + def incgen(self, ctx, cls, inst, ns, name=None): + if name is None: + name = cls.get_type_name() + with etree.xmlfile(ctx.out_stream) as xf: + ret = self.to_parent(ctx, cls, inst, xf, ns, name) + if isgenerator(ret): + try: + while True: + y = (yield) # may throw Break + ret.send(y) + + except Break: + try: + ret.throw(Break()) + except StopIteration: + pass + + if hasattr(ctx.out_stream, 'finish'): + ctx.out_stream.finish() + + def _gen_tag(self, cls, ns, name, add_type=False, **_): + if ns is not None: + name = "{%s}%s" % (ns, name) + + retval = E(name) + if add_type: + retval.attrib[XSI_TYPE] = cls.get_type_name_ns(self.app.interface) + + return retval + + def byte_array_to_parent(self, ctx, cls, inst, parent, ns, name='retval', + **kwargs): + elt = self._gen_tag(cls, ns, name, **kwargs) + elt.text = self.to_unicode(cls, inst, self.binary_encoding) + _append(parent, elt) + + def modelbase_to_parent(self, ctx, cls, inst, parent, ns, name='retval', + **kwargs): + elt = self._gen_tag(cls, ns, name, **kwargs) + elt.text = self.to_unicode(cls, inst) + _append(parent, elt) + + def null_to_parent(self, ctx, cls, inst, parent, ns, name='retval', + **kwargs): + if issubclass(cls, XmlAttribute): + return + + elif issubclass(cls, XmlData): + parent.attrib.update(NIL_ATTR) + + else: + elt = self._gen_tag(cls, ns, name, **kwargs) + elt.attrib.update(NIL_ATTR) + _append(parent, elt) + + def null_from_element(self, ctx, cls, element): + return None + + def xmldata_to_parent(self, ctx, cls, inst, parent, ns, name, + add_type=False, **_): + cls_attrs = self.get_cls_attrs(cls) + + ns = cls._ns + if ns is None: + ns = cls_attrs.sub_ns + + name = _gen_tagname(ns, name) + + if add_type: + parent.attrib[XSI_TYPE] = cls.get_type_name_ns(self.app.interface) + + cls.marshall(self, name, inst, parent) + + def xmlattribute_to_parent(self, ctx, cls, inst, parent, ns, name, **_): + ns = cls._ns + cls_attrs = self.get_cls_attrs(cls) + if ns is None: + ns = cls_attrs.sub_ns + + name = _gen_tagname(ns, name) + + if inst is not None: + if issubclass(cls.type, (ByteArray, File)): + parent.set(name, self.to_unicode(cls.type, inst, + self.binary_encoding)) + else: + parent.set(name, self.to_unicode(cls.type, inst)) + + @coroutine + def gen_members_parent(self, ctx, cls, inst, parent, tag_name, subelts, + add_type): + attrib = {} + if add_type: + tnn = cls.get_type_name_ns(self.app.interface) + if tnn != None: + attrib[XSI_TYPE] = tnn + else: + # this only happens on incomplete interface states for eg. + # get_object_as_xml where the full init is not performed for + # perf reasons + attrib[XSI_TYPE] = cls.get_type_name() + + if isinstance(parent, etree._Element): + elt = etree.SubElement(parent, tag_name, attrib=attrib) + elt.extend(subelts) + ret = self._get_members_etree(ctx, cls, inst, elt) + + if isgenerator(ret): + try: + while True: + y = (yield) # may throw Break + ret.send(y) + + except Break: + try: + ret.throw(Break()) + except StopIteration: + pass + + else: + with parent.element(tag_name, attrib=attrib): + for e in subelts: + parent.write(e) + ret = self._get_members_etree(ctx, cls, inst, parent) + if isgenerator(ret): + try: + while True: + y = (yield) + ret.send(y) + + except Break: + try: + ret.throw(Break()) + except StopIteration: + pass + + @coroutine + def _get_members_etree(self, ctx, cls, inst, parent): + try: + parent_cls = getattr(cls, '__extends__', None) + if not (parent_cls is None): + ret = self._get_members_etree(ctx, parent_cls, inst, parent) + if ret is not None: + try: + while True: + sv2 = (yield) # may throw Break + ret.send(sv2) + + except Break: + try: + ret.throw(Break()) + except StopIteration: + pass + + for k, v in cls._type_info.items(): + sub_cls_attrs = self.get_cls_attrs(v) + if sub_cls_attrs.exc: + continue + try: + subvalue = getattr(inst, k, None) + except: # e.g. SqlAlchemy could throw NoSuchColumnError + subvalue = None + + # This is a tight loop, so enable this only when necessary. + # logger.debug("get %r(%r) from %r: %r" % (k, v, inst, subvalue)) + + sub_ns = v.Attributes.sub_ns + if sub_ns is None: + sub_ns = cls.get_namespace() + + sub_name = v.Attributes.sub_name + if sub_name is None: + sub_name = k + + mo = v.Attributes.max_occurs + if subvalue is not None and mo > 1: + if isinstance(subvalue, PushBase): + while True: + sv = (yield) + ret = self.to_parent(ctx, v, sv, parent, sub_ns, + sub_name) + if ret is not None: + try: + while True: + sv2 = (yield) # may throw Break + ret.send(sv2) + + except Break: + try: + ret.throw(Break()) + except StopIteration: + pass + + else: + for sv in subvalue: + ret = self.to_parent(ctx, v, sv, parent, sub_ns, + sub_name) + + if ret is not None: + try: + while True: + sv2 = (yield) # may throw Break + ret.send(sv2) + + except Break: + try: + ret.throw(Break()) + except StopIteration: + pass + + # Don't include empty values for + # non-nillable optional attributes. + elif subvalue is not None or v.Attributes.min_occurs > 0: + ret = self.to_parent(ctx, v, subvalue, parent, sub_ns, + sub_name) + if ret is not None: + try: + while True: + sv2 = (yield) + ret.send(sv2) + except Break as b: + try: + ret.throw(b) + except StopIteration: + pass + + except Break: + pass + + def complex_to_parent(self, ctx, cls, inst, parent, ns, name=None, + add_type=False, **_): + cls_attrs = self.get_cls_attrs(cls) + + sub_name = cls_attrs.sub_name + if sub_name is not None: + name = sub_name + if name is None: + name = cls.get_type_name() + + sub_ns = cls_attrs.sub_ns + if not sub_ns in (None, DEFAULT_NS): + ns = sub_ns + + tag_name = _gen_tagname(ns, name) + + inst = cls.get_serialization_instance(inst) + + return self.gen_members_parent(ctx, cls, inst, parent, tag_name, [], + add_type) + + def _fault_to_parent_impl(self, ctx, cls, inst, parent, ns, subelts, **_): + tag_name = "{%s}Fault" % self.ns_soap_env + + # Accepting raw lxml objects as detail is DEPRECATED. It's also not + # documented. It's kept for backwards-compatibility purposes. + if isinstance(inst.detail, string_types + (etree._Element,)): + _append(subelts, E('detail', inst.detail)) + + elif isinstance(inst.detail, dict): + if len(inst.detail) > 0: + _append(subelts, root_dict_to_etree({'detail':inst.detail})) + + elif inst.detail is None: + pass + + else: + raise TypeError('Fault detail Must be dict, got', type(inst.detail)) + + # add other nonstandard fault subelements with get_members_etree + return self.gen_members_parent(ctx, cls, inst, parent, tag_name, + subelts, add_type=False) + + def fault_to_parent(self, ctx, cls, inst, parent, ns, *args, **kwargs): + subelts = [ + E("faultcode", '%s:%s' % (self.soap_env, inst.faultcode)), + E("faultstring", inst.faultstring), + E("faultactor", inst.faultactor), + ] + + return self._fault_to_parent_impl(ctx, cls, inst, parent, ns, subelts) + + def schema_validation_error_to_parent(self, ctx, cls, inst, parent, ns,**_): + subelts = [ + E("faultcode", '%s:%s' % (self.soap_env, inst.faultcode)), + # HACK: Does anyone know a better way of injecting raw xml entities? + E("faultstring", html.fromstring(inst.faultstring).text), + E("faultactor", inst.faultactor), + ] + if inst.detail != None: + _append(subelts, E('detail', inst.detail)) + + # add other nonstandard fault subelements with get_members_etree + return self._fault_to_parent_impl(ctx, cls, inst, parent, ns, subelts) + + def enum_to_parent(self, ctx, cls, inst, parent, ns, name='retval', **kwargs): + self.modelbase_to_parent(ctx, cls, str(inst), parent, ns, name, **kwargs) + + def any_xml_to_parent(self, ctx, cls, inst, parent, ns, name, **_): + if isinstance(inst, STR_TYPES): + inst = etree.fromstring(inst) + + _append(parent, E(_gen_tagname(ns, name), inst)) + + def any_to_parent(self, ctx, cls, inst, parent, ns, name, **_): + _append(parent, E(_gen_tagname(ns, name), inst)) + + def any_html_to_parent(self, ctx, cls, inst, parent, ns, name, **_): + if isinstance(inst, string_types) and len(inst) > 0: + inst = html.fromstring(inst) + + _append(parent, E(_gen_tagname(ns, name), inst)) + + def any_dict_to_parent(self, ctx, cls, inst, parent, ns, name, **_): + elt = E(_gen_tagname(ns, name)) + dict_to_etree(inst, elt) + + _append(parent, elt) + + def complex_from_element(self, ctx, cls, elt): + inst = cls.get_deserialization_instance(ctx) + + flat_type_info = cls.get_flat_type_info(cls) + + # this is for validating cls.Attributes.{min,max}_occurs + frequencies = defaultdict(int) + cls_attrs = self.get_cls_attrs(cls) + + if cls_attrs._xml_tag_body_as is not None: + for xtba_key, xtba_type in cls_attrs._xml_tag_body_as: + xtba_attrs = self.get_cls_attrs(xtba_type.type) + if issubclass(xtba_type.type, (ByteArray, File)): + value = self.from_unicode(xtba_type.type, elt.text, + self.binary_encoding) + else: + value = self.from_unicode(xtba_type.type, elt.text) + + inst._safe_set(xtba_key, value, xtba_type.type, xtba_attrs) + + # parse input to set incoming data to related attributes. + for c in elt: + if isinstance(c, etree._Comment): + continue + + key = c.tag.split('}', 1)[-1] + frequencies[key] += 1 + + member = flat_type_info.get(key, None) + if member is None: + member, key = cls._type_info_alt.get(key, (None, key)) + if member is None: + member, key = cls._type_info_alt.get(c.tag, (None, key)) + if member is None: + continue + + member_attrs = self.get_cls_attrs(member) + mo = member_attrs.max_occurs + if mo > 1: + value = getattr(inst, key, None) + if value is None: + value = [] + + value.append(self.from_element(ctx, member, c)) + + else: + value = self.from_element(ctx, member, c) + + inst._safe_set(key, value, member, member_attrs) + + for key, value_str in c.attrib.items(): + submember = flat_type_info.get(key, None) + + if submember is None: + submember, key = cls._type_info_alt.get(key, (None, key)) + if submember is None: + continue + + submember_attrs = self.get_cls_attrs(submember) + mo = submember_attrs.max_occurs + if mo > 1: + value = getattr(inst, key, None) + if value is None: + value = [] + + value.append(self.from_unicode(submember.type, value_str)) + + else: + value = self.from_unicode(submember.type, value_str) + + inst._safe_set(key, value, submember.type, submember_attrs) + + for key, value_str in elt.attrib.items(): + member = flat_type_info.get(key, None) + if member is None: + member, key = cls._type_info_alt.get(key, (None, key)) + if member is None: + continue + + if not issubclass(member, XmlAttribute): + continue + + if issubclass(member.type, (ByteArray, File)): + value = self.from_unicode(member.type, value_str, + self.binary_encoding) + else: + value = self.from_unicode(member.type, value_str) + + member_attrs = self.get_cls_attrs(member.type) + inst._safe_set(key, value, member.type, member_attrs) + + if self.validator is self.SOFT_VALIDATION: + for key, c in flat_type_info.items(): + val = frequencies.get(key, 0) + attr = self.get_cls_attrs(c) + if val < attr.min_occurs or val > attr.max_occurs: + raise Fault('Client.ValidationError', '%r member does not ' + 'respect frequency constraints.' % key) + + return inst + + def array_from_element(self, ctx, cls, element): + retval = [ ] + (serializer,) = cls._type_info.values() + + for child in element.getchildren(): + retval.append(self.from_element(ctx, serializer, child)) + + return retval + + def iterable_from_element(self, ctx, cls, element): + (serializer,) = cls._type_info.values() + + for child in element.getchildren(): + yield self.from_element(ctx, serializer, child) + + def enum_from_element(self, ctx, cls, element): + if self.validator is self.SOFT_VALIDATION and not ( + cls.validate_string(cls, element.text)): + raise ValidationError(element.text) + return getattr(cls, element.text) + + def fault_from_element(self, ctx, cls, element): + code = element.find('faultcode').text + string = element.find('faultstring').text + factor = element.find('faultactor') + if factor is not None: + factor = factor.text + detail = element.find('detail') + + return cls(faultcode=code, faultstring=string, faultactor=factor, + detail=detail) + + def xml_from_element(self, ctx, cls, element): + children = element.getchildren() + retval = None + + if children: + retval = element.getchildren()[0] + + return retval + + def html_from_element(self, ctx, cls, element): + children = element.getchildren() + retval = None + + if len(children) == 1: + retval = children[0] + # this is actually a workaround to a case that should never exist -- + # anyXml types should only have one child tag. + elif len(children) > 1: + retval = E.html(*children) + + return retval + + def dict_from_element(self, ctx, cls, element): + children = element.getchildren() + if children: + return etree_to_dict(element) + + return None + + def unicode_from_element(self, ctx, cls, element): + if self.validator is self.SOFT_VALIDATION and not ( + cls.validate_string(cls, element.text)): + raise ValidationError(element.text) + + s = element.text + if s is None: + s = '' + + retval = self.from_unicode(cls, s) + + if self.validator is self.SOFT_VALIDATION and not ( + cls.validate_native(cls, retval)): + raise ValidationError(retval) + + return retval + + def base_from_element(self, ctx, cls, element): + if self.validator is self.SOFT_VALIDATION and not ( + cls.validate_string(cls, element.text)): + raise ValidationError(element.text) + + retval = self.from_unicode(cls, element.text) + + if self.validator is self.SOFT_VALIDATION and not ( + cls.validate_native(cls, retval)): + raise ValidationError(retval) + + return retval + + def byte_array_from_element(self, ctx, cls, element): + if self.validator is self.SOFT_VALIDATION and not ( + cls.validate_string(cls, element.text)): + raise ValidationError(element.text) + + retval = self.from_unicode(cls, element.text, self.binary_encoding) + + if self.validator is self.SOFT_VALIDATION and not ( + cls.validate_native(cls, retval)): + raise ValidationError(retval) + + return retval diff --git a/pym/calculate/contrib/spyne/protocol/xml.pyc b/pym/calculate/contrib/spyne/protocol/xml.pyc new file mode 100644 index 0000000..2b4eea8 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/xml.pyc differ diff --git a/pym/calculate/contrib/spyne/protocol/yaml.py b/pym/calculate/contrib/spyne/protocol/yaml.py new file mode 100644 index 0000000..1a9a46a --- /dev/null +++ b/pym/calculate/contrib/spyne/protocol/yaml.py @@ -0,0 +1,187 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.protocol.yaml`` package contains the Yaml-related protocols. +Currently, only :class:`spyne.protocol.yaml.YamlDocument` is supported. + +Initially released in 2.10.0-rc. +""" + +from __future__ import absolute_import + +import logging +logger = logging.getLogger(__name__) + +from spyne import ValidationError +from spyne.util import six +from spyne.model.binary import BINARY_ENCODING_BASE64 +from spyne.model.primitive import Boolean +from spyne.model.primitive import Integer +from spyne.model.primitive import Double +from spyne.model.fault import Fault +from spyne.protocol.dictdoc import HierDictDocument + +import yaml + +from yaml.parser import ParserError +try: + from yaml import CLoader as Loader + from yaml import CDumper as Dumper + from yaml import CSafeLoader as SafeLoader + from yaml import CSafeDumper as SafeDumper + +except ImportError: + from yaml import Loader + from yaml import Dumper + from yaml import SafeLoader + from yaml import SafeDumper + + +NON_NUMBER_TYPES = tuple({list, dict, six.text_type, six.binary_type}) + + +class YamlDocument(HierDictDocument): + """An implementation of the Yaml protocol that uses the PyYaml package. + See ProtocolBase ctor docstring for its arguments. Yaml-specific arguments + follow: + + :param safe: Use ``safe_dump`` instead of ``dump`` and ``safe_load`` instead + of ``load``. This is not a security feature, search for 'safe_dump' in + http://www.pyyaml.org/wiki/PyYAMLDocumentation + :param kwargs: See the yaml documentation in ``load, ``safe_load``, ``dump`` + or ``safe_dump`` depending on whether you use yaml as an input or output + protocol. + + For the output case, Spyne sets ``default_flow_style=False`` and + ``indent=4`` by default. + """ + + mime_type = 'text/yaml' + + type = set(HierDictDocument.type) + type.add('yaml') + + text_based = True + + default_binary_encoding = BINARY_ENCODING_BASE64 + + # for test classes + _decimal_as_string = True + + def __init__(self, app=None, validator=None, mime_type=None, + ignore_uncap=False, + # DictDocument specific + ignore_wrappers=True, + complex_as=dict, + ordered=False, + polymorphic=False, + # YamlDocument specific + safe=True, + encoding='UTF-8', + allow_unicode=True, + **kwargs): + + super(YamlDocument, self).__init__(app, validator, mime_type, + ignore_uncap, ignore_wrappers, complex_as, ordered, polymorphic) + + self._from_unicode_handlers[Double] = self._ret_number + self._from_unicode_handlers[Boolean] = self._ret_bool + self._from_unicode_handlers[Integer] = self._ret_number + + self._to_unicode_handlers[Double] = self._ret + self._to_unicode_handlers[Boolean] = self._ret + self._to_unicode_handlers[Integer] = self._ret + + loader = Loader + dumper = Dumper + if safe: + loader = SafeLoader + dumper = SafeDumper + + self.in_kwargs = dict(kwargs) + self.out_kwargs = dict(kwargs) + + self.in_kwargs['Loader'] = loader + self.out_kwargs['Dumper'] = dumper + + loader.add_constructor('tag:yaml.org,2002:python/unicode', + _unicode_loader) + + self.out_kwargs['encoding'] = encoding + self.out_kwargs['allow_unicode'] = allow_unicode + + if not 'indent' in self.out_kwargs: + self.out_kwargs['indent'] = 4 + + if not 'default_flow_style' in self.out_kwargs: + self.out_kwargs['default_flow_style'] = False + + def _ret(self, _, value): + return value + + def _ret_number(self, _, value): + if isinstance(value, NON_NUMBER_TYPES): + raise ValidationError(value) + if value in (True, False): + return int(value) + return value + + def _ret_bool(self, _, value): + if value is None or value in (True, False): + return value + raise ValidationError(value) + + def create_in_document(self, ctx, in_string_encoding=None): + """Sets ``ctx.in_document`` using ``ctx.in_string``.""" + + if in_string_encoding is None: + in_string_encoding = 'UTF-8' + + try: + try: + s = b''.join(ctx.in_string).decode(in_string_encoding) + except TypeError: + s = ''.join(ctx.in_string) + + ctx.in_document = yaml.load(s, **self.in_kwargs) + + except ParserError as e: + raise Fault('Client.YamlDecodeError', repr(e)) + + def create_out_string(self, ctx, out_string_encoding='utf8'): + """Sets ``ctx.out_string`` using ``ctx.out_document``.""" + + ctx.out_string = (yaml.dump(o, **self.out_kwargs) + for o in ctx.out_document) + if six.PY2 and out_string_encoding is not None: + ctx.out_string = ( + yaml.dump(o, **self.out_kwargs).encode(out_string_encoding) + for o in ctx.out_document) + + +def _unicode_loader(loader, node): + return node.value + + +def _decimal_to_bytes(): + pass + + +def _decimal_from_bytes(): + pass diff --git a/pym/calculate/contrib/spyne/protocol/yaml.pyc b/pym/calculate/contrib/spyne/protocol/yaml.pyc new file mode 100644 index 0000000..9f233a3 Binary files /dev/null and b/pym/calculate/contrib/spyne/protocol/yaml.pyc differ diff --git a/pym/calculate/contrib/spyne/server/__init__.py b/pym/calculate/contrib/spyne/server/__init__.py new file mode 100644 index 0000000..5ab6584 --- /dev/null +++ b/pym/calculate/contrib/spyne/server/__init__.py @@ -0,0 +1,23 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.server`` package contains the server transports.""" + +from spyne.server._base import ServerBase +from spyne.server.null import NullServer diff --git a/pym/calculate/contrib/spyne/server/__init__.pyc b/pym/calculate/contrib/spyne/server/__init__.pyc new file mode 100644 index 0000000..ee14965 Binary files /dev/null and b/pym/calculate/contrib/spyne/server/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/server/_base.py b/pym/calculate/contrib/spyne/server/_base.py new file mode 100644 index 0000000..5f27140 --- /dev/null +++ b/pym/calculate/contrib/spyne/server/_base.py @@ -0,0 +1,260 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logger = logging.getLogger(__name__) + +from inspect import isgenerator + +from spyne import EventManager +from spyne.auxproc import process_contexts +from spyne.interface import AllYourInterfaceDocuments +from spyne.model import Fault, PushBase +from spyne.protocol import ProtocolBase +from spyne.util import Break, coroutine + + +class ServerBase(object): + """This class is the abstract base class for all server transport + implementations. Unlike the client transports, this class does not define + a pure-virtual method that needs to be implemented by all base classes. + + If there needs to be a call to start the main loop, it's called + ``serve_forever()`` by convention. + """ + + transport = None + """The transport type, which is a URI string to its definition by + convention.""" + + def __init__(self, app): + self.app = app + self.app.transport = self.transport # FIXME: this is weird + self.appinit() + + self.event_manager = EventManager(self) + self.doc = AllYourInterfaceDocuments(app.interface) + + def appinit(self): + self.app.reinitialize(self) + + def generate_contexts(self, ctx, in_string_charset=None): + """Calls create_in_document and decompose_incoming_envelope to get + method_request string in order to generate contexts. + """ + try: + # sets ctx.in_document + self.app.in_protocol.create_in_document(ctx, in_string_charset) + + # sets ctx.in_body_doc, ctx.in_header_doc and + # ctx.method_request_string + self.app.in_protocol.decompose_incoming_envelope(ctx, + ProtocolBase.REQUEST) + + # returns a list of contexts. multiple contexts can be returned + # when the requested method also has bound auxiliary methods. + retval = self.app.in_protocol.generate_method_contexts(ctx) + + except Fault as e: + ctx.in_object = None + ctx.in_error = e + ctx.out_error = e + + retval = (ctx,) + + ctx.fire_event('method_exception_object') + + return retval + + def get_in_object(self, ctx): + """Uses the ``ctx.in_string`` to set ``ctx.in_body_doc``, which in turn + is used to set ``ctx.in_object``.""" + + try: + # sets ctx.in_object and ctx.in_header + self.app.in_protocol.deserialize(ctx, + message=self.app.in_protocol.REQUEST) + + except Fault as e: + logger.exception(e) + logger.debug("Failed document is: %s", ctx.in_document) + + ctx.in_object = None + ctx.in_error = e + ctx.out_error = e + + ctx.fire_event('method_exception_object') + + def get_out_object(self, ctx): + """Calls the matched user function by passing it the ``ctx.in_object`` + to set ``ctx.out_object``.""" + + if ctx.in_error is None: + # event firing is done in the spyne.application.Application + self.app.process_request(ctx) + else: + raise ctx.in_error + + def convert_pull_to_push(self, ctx, gen): + oobj, = ctx.out_object + if oobj is None: + gen.throw(Break()) + + elif isinstance(oobj, PushBase): + pass + + elif len(ctx.pusher_stack) > 0: + oobj = ctx.pusher_stack[-1] + assert isinstance(oobj, PushBase) + + else: + raise ValueError("%r is not a PushBase instance" % oobj) + + retval = self.init_interim_push(oobj, ctx, gen) + return self.pusher_try_close(ctx, oobj, retval) + + def get_out_string_pull(self, ctx): + """Uses the ``ctx.out_object`` to set ``ctx.out_document`` and later + ``ctx.out_string``.""" + + # This means the user wanted to override the way Spyne generates the + # outgoing byte stream. So we leave it alone. + if ctx.out_string is not None: + return + + if ctx.out_document is None: + ret = ctx.out_protocol.serialize(ctx, message=ProtocolBase.RESPONSE) + + if isgenerator(ret) and ctx.out_object is not None and \ + len(ctx.out_object) == 1: + if len(ctx.pusher_stack) > 0: + # we suspend request processing here because there now + # seems to be a PushBase waiting for input. + return self.convert_pull_to_push(ctx, ret) + + self.finalize_context(ctx) + + def finalize_context(self, ctx): + if ctx.out_error is None: + ctx.fire_event('method_return_document') + else: + ctx.fire_event('method_exception_document') + + ctx.out_protocol.create_out_string(ctx) + + if ctx.out_error is None: + ctx.fire_event('method_return_string') + else: + ctx.fire_event('method_exception_string') + + if ctx.out_string is None: + ctx.out_string = (b'',) + + # for backwards compatibility + get_out_string = get_out_string_pull + + @coroutine + def get_out_string_push(self, ctx): + """Uses the ``ctx.out_object`` to directly set ``ctx.out_string``.""" + + ret = ctx.out_protocol.serialize(ctx, message=ProtocolBase.RESPONSE) + if isgenerator(ret): + try: + while True: + y = (yield) + ret.send(y) + + except Break: + try: + ret.throw(Break()) + except StopIteration: + pass + + self.finalize_context(ctx) + + def serve_forever(self): + """Implement your event loop here, if needed.""" + + raise NotImplementedError() + + def init_interim_push(self, ret, p_ctx, gen): + assert isinstance(ret, PushBase) + assert p_ctx.out_stream is not None + + # we don't add interim pushers to the stack because we don't know + # where to find them in the out_object's hierarchy. whatever finds + # one in the serialization pipeline has to push it to pusher_stack so + # the machinery in ServerBase can initialize them using this function. + + # fire events + p_ctx.fire_event('method_return_push') + + def _cb_push_finish(): + process_contexts(self, (), p_ctx) + + return self.pusher_init(p_ctx, gen, _cb_push_finish, ret, interim=True) + + def pusher_init(self, p_ctx, gen, _cb_push_finish, pusher, interim): + return pusher.init(p_ctx, gen, _cb_push_finish, None, interim) + + def pusher_try_close(self, ctx, pusher, _): + logger.debug("Closing pusher with ret=%r", pusher) + + pusher.close() + + popped = ctx.pusher_stack.pop() + assert popped is pusher + + def init_root_push(self, ret, p_ctx, others): + assert isinstance(ret, PushBase) + + if ret in p_ctx.pusher_stack: + logger.warning('PushBase reinit avoided.') + return + + p_ctx.pusher_stack.append(ret) + + # fire events + p_ctx.fire_event('method_return_push') + + # start push serialization + gen = self.get_out_string_push(p_ctx) + + assert isgenerator(gen), "It looks like this protocol is not " \ + "async-compliant yet." + + def _cb_push_finish(): + process_contexts(self, others, p_ctx) + + retval = self.pusher_init(p_ctx, gen, _cb_push_finish, ret, + interim=False) + + self.pusher_try_close(p_ctx, ret, retval) + + return retval + + @staticmethod + def set_out_document_push(ctx): + ctx.out_document = _write() + ctx.out_document.send(None) + + +def _write(): + v = yield + yield v diff --git a/pym/calculate/contrib/spyne/server/_base.pyc b/pym/calculate/contrib/spyne/server/_base.pyc new file mode 100644 index 0000000..5fddb85 Binary files /dev/null and b/pym/calculate/contrib/spyne/server/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/server/django.py b/pym/calculate/contrib/spyne/server/django.py new file mode 100644 index 0000000..a611057 --- /dev/null +++ b/pym/calculate/contrib/spyne/server/django.py @@ -0,0 +1,390 @@ +# encoding: utf-8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.server.django`` module contains a Django-compatible Http +transport. It's a thin wrapper around +:class:`spyne.server.wsgi.WsgiApplication`. +""" + +from __future__ import absolute_import + +import logging +logger = logging.getLogger(__name__) + +from functools import update_wrapper + +from spyne import Address +from spyne.application import get_fault_string_from_exception, Application +from spyne.auxproc import process_contexts +from spyne.interface import AllYourInterfaceDocuments +from spyne.model.fault import Fault +from spyne.protocol.soap import Soap11 +from spyne.protocol.http import HttpRpc +from spyne.server.http import HttpBase, HttpMethodContext, HttpTransportContext +from spyne.server.wsgi import WsgiApplication +from spyne.util import _bytes_join +from spyne.util.address import address_parser + +from django.http import HttpResponse, HttpResponseNotAllowed, Http404 +from django.views.decorators.csrf import csrf_exempt + +try: + from django.http import StreamingHttpResponse +except ImportError as _import_error: + _local_import_error = _import_error + def StreamingHttpResponse(*args, **kwargs): + raise _local_import_error + + +class DjangoApplication(WsgiApplication): + """You should use this for regular RPC.""" + + HttpResponseObject = HttpResponse + + # noinspection PyMethodOverriding + # because this is VERY similar to a Wsgi app + # but not that much. + def __call__(self, request): + retval = self.HttpResponseObject() + + def start_response(status, headers): + # Status is one of spyne.const.http + status, reason = status.split(' ', 1) + + retval.status_code = int(status) + for header, value in headers: + retval[header] = value + + environ = request.META.copy() + + # FIXME: No idea what these two did. + # They were commented out to fix compatibility issues with + # Django-1.2.x + # See http://github.com/arskom/spyne/issues/222. + + # If you don't override wsgi.input django and spyne will read + # the same buffer twice. If django read whole buffer spyne + # would hang waiting for extra request data. Use DjangoServer instead + # of monkeypatching wsgi.inpu. + + #environ['wsgi.input'] = request + #environ['wsgi.multithread'] = False + + response = WsgiApplication.__call__(self, environ, start_response) + self.set_response(retval, response) + + return retval + + def set_response(self, retval, response): + retval.content = _bytes_join(response, b"") + + +class StreamingDjangoApplication(DjangoApplication): + """You should use this when you're generating HUGE data as response. + + New in Django 1.5. + """ + + HttpResponseObject = StreamingHttpResponse + + def set_response(self, retval, response): + retval.streaming_content = response + + +class DjangoHttpTransportContext(HttpTransportContext): + def get_path(self): + return self.req.path + + def get_request_method(self): + return self.req.method + + def get_request_content_type(self): + return self.req.META['CONTENT_TYPE'] + + def get_path_and_qs(self): + return self.req.get_full_path() + + def get_cookie(self, key): + return self.req.COOKIES[key] + + def get_peer(self): + addr, port = address_parser.get_ip(self.req.META),\ + address_parser.get_port(self.req.META) + + if address_parser.is_valid_ipv4(addr, port): + return Address(type=Address.TCP4, host=addr, port=port) + + if address_parser.is_valid_ipv6(addr, port): + return Address(type=Address.TCP6, host=addr, port=port) + + +class DjangoHttpMethodContext(HttpMethodContext): + HttpTransportContext = DjangoHttpTransportContext + + +class DjangoServer(HttpBase): + """Server talking in Django request/response objects.""" + + def __init__(self, app, chunked=False, cache_wsdl=True): + super(DjangoServer, self).__init__(app, chunked=chunked) + self._wsdl = None + self._cache_wsdl = cache_wsdl + + def handle_rpc(self, request, *args, **kwargs): + """Handle rpc request. + + :params request: Django HttpRequest instance. + :returns: HttpResponse instance. + + """ + contexts = self.get_contexts(request) + p_ctx, others = contexts[0], contexts[1:] + + # TODO: Rate limiting + p_ctx.active = True + + if p_ctx.in_error: + return self.handle_error(p_ctx, others, p_ctx.in_error) + + self.get_in_object(p_ctx) + if p_ctx.in_error: + logger.error(p_ctx.in_error) + return self.handle_error(p_ctx, others, p_ctx.in_error) + + self.get_out_object(p_ctx) + if p_ctx.out_error: + return self.handle_error(p_ctx, others, p_ctx.out_error) + + try: + self.get_out_string(p_ctx) + + except Exception as e: + logger.exception(e) + p_ctx.out_error = Fault('Server', + get_fault_string_from_exception(e)) + return self.handle_error(p_ctx, others, p_ctx.out_error) + + have_protocol_headers = (isinstance(p_ctx.out_protocol, HttpRpc) and + p_ctx.out_header_doc is not None) + + if have_protocol_headers: + p_ctx.transport.resp_headers.update(p_ctx.out_header_doc) + + if p_ctx.descriptor and p_ctx.descriptor.mtom: + raise NotImplementedError + + if self.chunked: + response = StreamingHttpResponse(p_ctx.out_string) + else: + response = HttpResponse(b''.join(p_ctx.out_string)) + + return self.response(response, p_ctx, others) + + def handle_wsdl(self, request, *args, **kwargs): + """Return services WSDL.""" + ctx = HttpMethodContext(self, request, + 'text/xml; charset=utf-8') + + if self.doc.wsdl11 is None: + raise Http404('WSDL is not available') + + if self._wsdl is None: + # Interface document building is not thread safe so we don't use + # server interface document shared between threads. Instead we + # create and build interface documents in current thread. This + # section can be safely repeated in another concurrent thread. + doc = AllYourInterfaceDocuments(self.app.interface) + doc.wsdl11.build_interface_document(request.build_absolute_uri()) + wsdl = doc.wsdl11.get_interface_document() + + if self._cache_wsdl: + self._wsdl = wsdl + else: + wsdl = self._wsdl + + ctx.transport.wsdl = wsdl + + response = HttpResponse(ctx.transport.wsdl) + return self.response(response, ctx, ()) + + def handle_error(self, p_ctx, others, error): + """Serialize errors to an iterable of strings and return them. + + :param p_ctx: Primary (non-aux) context. + :param others: List if auxiliary contexts (can be empty). + :param error: One of ctx.{in,out}_error. + """ + + if p_ctx.transport.resp_code is None: + p_ctx.transport.resp_code = \ + p_ctx.out_protocol.fault_to_http_response_code(error) + + self.get_out_string(p_ctx) + resp = HttpResponse(b''.join(p_ctx.out_string)) + return self.response(resp, p_ctx, others, error) + + def get_contexts(self, request): + """Generate contexts for rpc request. + + :param request: Django HttpRequest instance. + :returns: generated contexts + """ + + initial_ctx = DjangoHttpMethodContext(self, request, + self.app.out_protocol.mime_type) + + initial_ctx.in_string = [request.body] + return self.generate_contexts(initial_ctx) + + def response(self, response, p_ctx, others, error=None): + """Populate response with transport headers and finalize it. + + :param response: Django HttpResponse. + :param p_ctx: Primary (non-aux) context. + :param others: List if auxiliary contexts (can be empty). + :param error: One of ctx.{in,out}_error. + :returns: Django HttpResponse + + """ + for h, v in p_ctx.transport.resp_headers.items(): + if v is not None: + response[h] = v + + if p_ctx.transport.resp_code: + response.status_code = int(p_ctx.transport.resp_code[:3]) + + try: + process_contexts(self, others, p_ctx, error=error) + except Exception as e: + # Report but ignore any exceptions from auxiliary methods. + logger.exception(e) + + p_ctx.close() + + return response + + +class DjangoView(object): + """Represent spyne service as Django class based view.""" + + application = None + server = None + services = () + tns = 'spyne.application' + name = 'Application' + in_protocol = Soap11(validator='lxml') + out_protocol = Soap11() + interface = None + chunked = False + cache_wsdl = True + + http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', + 'options', 'trace'] + + def __init__(self, server, **kwargs): + self.server = server + + for key, value in kwargs.items(): + setattr(self, key, value) + + @classmethod + def as_view(cls, **initkwargs): + """Register application, server and create new view. + + :returns: callable view function + """ + + # sanitize keyword arguments + for key in initkwargs: + if key in cls.http_method_names: + raise TypeError("You tried to pass in the %s method name as a " + "keyword argument to %s(). Don't do that." + % (key, cls.__name__)) + if not hasattr(cls, key): + raise TypeError("%s() received an invalid keyword %r. as_view " + "only accepts arguments that are already " + "attributes of the class." % (cls.__name__, + key)) + + def get(key): + value = initkwargs.get(key) + return value if value is not None else getattr(cls, key) + + application = get('application') or Application( + services=get('services'), + tns=get('tns'), + name=get('name'), + in_protocol=get('in_protocol'), + out_protocol=get('out_protocol'), + ) + server = get('server') or DjangoServer(application, + chunked=get('chunked'), + cache_wsdl=get('cache_wsdl')) + + def view(request, *args, **kwargs): + self = cls(server=server, **initkwargs) + if hasattr(self, 'get') and not hasattr(self, 'head'): + self.head = self.get + self.request = request + self.args = args + self.kwargs = kwargs + return self.dispatch(request, *args, **kwargs) + + # take name and docstring from class + update_wrapper(view, cls, updated=()) + + # and possible attributes set by decorators + # like csrf_exempt from dispatch + update_wrapper(view, cls.dispatch, assigned=()) + return view + + @csrf_exempt + def dispatch(self, request, *args, **kwargs): + # Try to dispatch to the right method; if a method doesn't exist, + # defer to the error handler. Also defer to the error handler if the + # request method isn't on the approved list. + if request.method.lower() in self.http_method_names: + handler = getattr(self, request.method.lower(), + self.http_method_not_allowed) + else: + handler = self.http_method_not_allowed + return handler(request, *args, **kwargs) + + def get(self, request, *args, **kwargs): + return self.server.handle_wsdl(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + return self.server.handle_rpc(request, *args, **kwargs) + + def http_method_not_allowed(self, request, *args, **kwargs): + logger.warning('Method Not Allowed (%s): %s', request.method, + request.path, extra={'status_code': 405, 'request': + self.request}) + return HttpResponseNotAllowed(self._allowed_methods()) + + def options(self, request, *args, **kwargs): + """Handle responding to requests for the OPTIONS HTTP verb.""" + + response = HttpResponse() + response['Allow'] = ', '.join(self._allowed_methods()) + response['Content-Length'] = '0' + return response + + def _allowed_methods(self): + return [m.upper() for m in self.http_method_names if hasattr(self, m)] diff --git a/pym/calculate/contrib/spyne/server/django.pyc b/pym/calculate/contrib/spyne/server/django.pyc new file mode 100644 index 0000000..98193e2 Binary files /dev/null and b/pym/calculate/contrib/spyne/server/django.pyc differ diff --git a/pym/calculate/contrib/spyne/server/http.py b/pym/calculate/contrib/spyne/server/http.py new file mode 100644 index 0000000..6c621b2 --- /dev/null +++ b/pym/calculate/contrib/spyne/server/http.py @@ -0,0 +1,326 @@ +# 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 collections import defaultdict + +from email import utils +from email.utils import encode_rfc2231 +from email.message import tspecials + +from spyne import TransportContext, MethodDescriptor, MethodContext, Redirect +from spyne.server import ServerBase +from spyne.protocol.http import HttpPattern +from spyne.const.http import gen_body_redirect, HTTP_301, HTTP_302, HTTP_303, \ + HTTP_307 + + +class HttpRedirect(Redirect): + def __init__(self, ctx, location, orig_exc=None, code=HTTP_302): + super(HttpRedirect, self) \ + .__init__(ctx, location, orig_exc=orig_exc) + + self.ctx = ctx + self.location = location + self.orig_exc = orig_exc + self.code = code + + def do_redirect(self): + if not isinstance(self.ctx.transport, HttpTransportContext): + if self.orig_exc is not None: + raise self.orig_exc + raise TypeError(self.ctx.transport) + + self.ctx.transport.respond(self.code, location=self.location) + +# +# Plagiarized HttpTransport.add_header() and _formatparam() function from +# Python 2.7 stdlib. +# +# Copyright (C) 2001-2007 Python Software Foundation +# Author: Barry Warsaw +# Contact: email-sig@python.org +# +def _formatparam(param, value=None, quote=True): + """Convenience function to format and return a key=value pair. + + This will quote the value if needed or if quote is true. If value is a + three tuple (charset, language, value), it will be encoded according + to RFC2231 rules. If it contains non-ascii characters it will likewise + be encoded according to RFC2231 rules, using the utf-8 charset and + a null language. + """ + if value is None or len(value) == 0: + return param + + # A tuple is used for RFC 2231 encoded parameter values where items + # are (charset, language, value). charset is a string, not a Charset + # instance. RFC 2231 encoded values are never quoted, per RFC. + if isinstance(value, tuple): + # Encode as per RFC 2231 + param += '*' + value = encode_rfc2231(value[2], value[0], value[1]) + return '%s=%s' % (param, value) + + try: + value.encode('ascii') + + except UnicodeEncodeError: + param += '*' + value = encode_rfc2231(value, 'utf-8', '') + return '%s=%s' % (param, value) + + # BAW: Please check this. I think that if quote is set it should + # force quoting even if not necessary. + if quote or tspecials.search(value): + return '%s="%s"' % (param, utils.quote(value)) + + return '%s=%s' % (param, value) + + +class HttpTransportContext(TransportContext): + """The abstract base class that is used in the transport attribute of the + :class:`HttpMethodContext` class and its subclasses.""" + + def __init__(self, parent, transport, request, content_type): + super(HttpTransportContext, self).__init__(parent, transport, 'http') + + self.req = request + """HTTP Request. This is transport-specific""" + + self.resp_headers = {} + """HTTP Response headers.""" + + self.mime_type = content_type + + self.resp_code = None + """HTTP Response code.""" + + self.wsdl = None + """The WSDL document that is being returned. Only relevant when handling + WSDL requests.""" + + self.wsdl_error = None + """The error when handling WSDL requests.""" + + def get_mime_type(self): + return self.resp_headers.get('Content-Type', None) + + def set_mime_type(self, what): + self.resp_headers['Content-Type'] = what + + def respond(self, resp_code, **kwargs): + self.resp_code = resp_code + if resp_code in (HTTP_301, HTTP_302, HTTP_303, HTTP_307): + l = kwargs.pop('location') + self.resp_headers['Location'] = l + self.parent.out_string = [gen_body_redirect(resp_code, l)] + self.mime_type = 'text/html' + + else: + # So that deserialization is skipped. + self.parent.out_string = [] + + def get_path(self): + raise NotImplementedError() + + def get_request_method(self): + raise NotImplementedError() + + def get_request_content_type(self): + raise NotImplementedError() + + def get_path_and_qs(self): + raise NotImplementedError() + + def get_cookie(self, key): + raise NotImplementedError() + + def get_peer(self): + raise NotImplementedError() + + @staticmethod + def gen_header(_value, **kwargs): + parts = [] + + for k, v in kwargs.items(): + if v is None: + parts.append(k.replace('_', '-')) + + else: + parts.append(_formatparam(k.replace('_', '-'), v)) + + if _value is not None: + parts.insert(0, _value) + + return '; '.join(parts) + + def add_header(self, _name, _value, **kwargs): + """Extended header setting. + + name is the header field to add. keyword arguments can be used to set + additional parameters for the header field, with underscores converted + to dashes. Normally the parameter will be added as key="value" unless + value is None, in which case only the key will be added. If a + parameter value contains non-ASCII characters it can be specified as a + three-tuple of (charset, language, value), in which case it will be + encoded according to RFC2231 rules. Otherwise it will be encoded using + the utf-8 charset and a language of ''. + + Examples: + + msg.add_header('content-disposition', 'attachment', filename='bud.gif') + msg.add_header('content-disposition', 'attachment', + filename=('utf-8', '', Fußballer.ppt')) + msg.add_header('content-disposition', 'attachment', + filename='Fußballer.ppt')) + """ + + self.resp_headers[_name] = self.gen_header(_value, **kwargs) + + mime_type = property( + lambda self: self.get_mime_type(), + lambda self, what: self.set_mime_type(what), + ) + """Provides an easy way to set outgoing mime type. Synonym for + `content_type`""" + + content_type = mime_type + """Provides an easy way to set outgoing mime type. Synonym for + `mime_type`""" + + +class HttpMethodContext(MethodContext): + """The Http-Specific method context. Http-Specific information is stored in + the transport attribute using the :class:`HttpTransportContext` class. + """ + + # because ctor signatures differ between TransportContext and + # HttpTransportContext, we needed a new variable + TransportContext = None + HttpTransportContext = HttpTransportContext + + def __init__(self, transport, req_env, content_type): + super(HttpMethodContext, self).__init__(transport, MethodContext.SERVER) + + self.transport = self.HttpTransportContext(self, transport, + req_env, content_type) + """Holds the HTTP-specific information""" + + def set_out_protocol(self, what): + self._out_protocol = what + if self._out_protocol.app is None: + self._out_protocol.set_app(self.app) + if isinstance(self.transport, HttpTransportContext): + self.transport.set_mime_type(what.mime_type) + + out_protocol = property(MethodContext.get_out_protocol, set_out_protocol) + """Assigning an out protocol overrides the mime type of the transport.""" + + +class HttpBase(ServerBase): + transport = 'http://schemas.xmlsoap.org/soap/http' + + def __init__(self, app, chunked=False, + max_content_length=2 * 1024 * 1024, + block_length=8 * 1024): + super(HttpBase, self).__init__(app) + + self.chunked = chunked + self.max_content_length = max_content_length + self.block_length = block_length + + self._http_patterns = set() + + for k, v in self.app.interface.service_method_map.items(): + # p_ stands for primary + p_method_descriptor = v[0] + for patt in p_method_descriptor.patterns: + if isinstance(patt, HttpPattern): + self._http_patterns.add(patt) + + # this makes sure similar addresses with patterns are evaluated after + # addresses with wildcards, which puts the more specific addresses to + # the front. + self._http_patterns = list(reversed(sorted(self._http_patterns, + key=lambda x: (x.address, x.host) ))) + + def match_pattern(self, ctx, method='', path='', host=''): + """Sets ctx.method_request_string if there's a match. It's O(n) which + means you should keep your number of patterns as low as possible. + + :param ctx: A MethodContext instance + :param method: The verb in the HTTP Request (GET, POST, etc.) + :param host: The contents of the ``Host:`` header + :param path: Path but not the arguments. (i.e. stuff before '?', if it's + there) + """ + + if not path.startswith('/'): + path = '/{}'.format(path) + + params = defaultdict(list) + for patt in self._http_patterns: + assert isinstance(patt, HttpPattern) + + if patt.verb is not None: + match = patt.verb_re.match(method) + if match is None: + continue + if not (match.span() == (0, len(method))): + continue + + for k,v in match.groupdict().items(): + params[k].append(v) + + if patt.host is not None: + match = patt.host_re.match(host) + if match is None: + continue + if not (match.span() == (0, len(host))): + continue + + for k, v in match.groupdict().items(): + params[k].append(v) + + if patt.address is None: + if path.split('/')[-1] != patt.endpoint.name: + continue + + else: + match = patt.address_re.match(path) + if match is None: + continue + + if not (match.span() == (0, len(path))): + continue + + for k,v in match.groupdict().items(): + params[k].append(v) + + d = patt.endpoint + assert isinstance(d, MethodDescriptor) + ctx.method_request_string = d.name + + break + + return params + + @property + def has_patterns(self): + return len(self._http_patterns) > 0 diff --git a/pym/calculate/contrib/spyne/server/http.pyc b/pym/calculate/contrib/spyne/server/http.pyc new file mode 100644 index 0000000..dcfda64 Binary files /dev/null and b/pym/calculate/contrib/spyne/server/http.pyc differ diff --git a/pym/calculate/contrib/spyne/server/msgpack.py b/pym/calculate/contrib/spyne/server/msgpack.py new file mode 100644 index 0000000..1c0fd4d --- /dev/null +++ b/pym/calculate/contrib/spyne/server/msgpack.py @@ -0,0 +1,217 @@ +# encoding: utf8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from __future__ import absolute_import + +import logging +logger = logging.getLogger(__name__) + +import msgpack + +from mmap import mmap +from collections import OrderedDict + +from spyne import MethodContext, TransportContext, Address +from spyne.auxproc import process_contexts +from spyne.error import ValidationError, InternalError +from spyne.server import ServerBase +from spyne.util.six import binary_type + +try: + from twisted.internet.defer import Deferred +except ImportError as e: + def Deferred(*_, **__): raise e + + +MSGPACK_SHELL_OVERHEAD = 10 + + +def _process_v1_msg(prot, msg): + header = None + body = msg[1] + if not isinstance(body, (binary_type, mmap, memoryview)): + raise ValidationError(body, "Body must be a bytestream.") + + if len(msg) > 2: + header = msg[2] + if not isinstance(header, dict): + raise ValidationError(header, "Header must be a dict.") + for k, v in header.items(): + header[k] = msgpack.unpackb(v) + + ctx = MessagePackMethodContext(prot, MessagePackMethodContext.SERVER) + ctx.in_string = [body] + ctx.transport.in_header = header + + return ctx + + +class MessagePackTransportContext(TransportContext): + def __init__(self, parent, transport): + super(MessagePackTransportContext, self).__init__(parent, transport) + + self.in_header = None + self.protocol = None + self.inreq_queue = OrderedDict() + self.request_len = None + + def get_peer(self): + if self.protocol is not None: + peer = self.protocol.transport.getPeer() + return Address.from_twisted_address(peer) + + +class MessagePackOobMethodContext(object): + __slots__ = 'd' + + def __init__(self): + if Deferred is not None: + self.d = Deferred() + else: + self.d = None + + def close(self): + if self.d is not None and not self.d.called: + self.d.cancel() + + +class MessagePackMethodContext(MethodContext): + TransportContext = MessagePackTransportContext + + def __init__(self, transport, way): + self.oob_ctx = None + + super(MessagePackMethodContext, self).__init__(transport, way) + + def close(self): + super(MessagePackMethodContext, self).close() + if self.transport is not None: + self.transport.protocol = None + self.transport = None + + if self.oob_ctx is not None: + self.oob_ctx.close() + + +class MessagePackTransportBase(ServerBase): + # These are all placeholders that need to be overridden in subclasses + OUT_RESPONSE_NO_ERROR = None + OUT_RESPONSE_CLIENT_ERROR = None + OUT_RESPONSE_SERVER_ERROR = None + + IN_REQUEST = None + + def __init__(self, app): + super(MessagePackTransportBase, self).__init__(app) + + self._version_map = { + self.IN_REQUEST: _process_v1_msg + } + + def produce_contexts(self, msg): + """Produce contexts based on incoming message. + + :param msg: Parsed request in this format: `[IN_REQUEST, body, header]` + """ + + if not isinstance(msg, (list, tuple)): + logger.debug("Incoming request: %r", msg) + raise ValidationError(msg, "Request must be a list") + + if not len(msg) >= 2: + logger.debug("Incoming request: %r", msg) + raise ValidationError(len(msg), "Request must have at least two " + "elements. It has %r") + + if not isinstance(msg[0], int): + logger.debug("Incoming request: %r", msg) + raise ValidationError(msg[0], "Request version must be an integer. " + "It was %r") + + processor = self._version_map.get(msg[0], None) + if processor is None: + logger.debug("Invalid incoming request: %r", msg) + raise ValidationError(msg[0], "Unknown request type %r") + + msglen = len(msg[1]) + # shellen = len(msgpack.packb(msg)) + # logger.debug("Shell size: %d, message size: %d, diff: %d", + # shellen, msglen, shellen - msglen) + # some approx. msgpack overhead based on observations of what's above. + msglen += MSGPACK_SHELL_OVERHEAD + + initial_ctx = processor(self, msg) + contexts = self.generate_contexts(initial_ctx) + + p_ctx, others = contexts[0], contexts[1:] + p_ctx.transport.request_len = msglen + + return p_ctx, others + + def process_contexts(self, contexts): + p_ctx, others = contexts[0], contexts[1:] + + if p_ctx.in_error: + return self.handle_error(p_ctx, others, p_ctx.in_error) + + self.get_in_object(p_ctx) + if p_ctx.in_error: + logger.error(p_ctx.in_error) + return self.handle_error(p_ctx, others, p_ctx.in_error) + + self.get_out_object(p_ctx) + if p_ctx.out_error: + return self.handle_error(p_ctx, others, p_ctx.out_error) + + try: + self.get_out_string(p_ctx) + + except Exception as e: + logger.exception(e) + contexts.out_error = InternalError("Serialization Error.") + return self.handle_error(contexts, others, contexts.out_error) + + def handle_error(self, p_ctx, others, error): + self.get_out_string(p_ctx) + + try: + process_contexts(self, others, p_ctx, error=error) + except Exception as e: + # Report but ignore any exceptions from auxiliary methods. + logger.exception(e) + + def handle_transport_error(self, error): + return msgpack.dumps(str(error)) + + def pack(self, ctx): + ctx.out_string = msgpack.packb([self.OUT_RESPONSE_NO_ERROR, + b''.join(ctx.out_string)]), + + +class MessagePackServerBase(MessagePackTransportBase): + """Contains the transport protocol logic but not the transport itself. + + Subclasses should implement logic to move bitstreams in and out of this + class.""" + + OUT_RESPONSE_NO_ERROR = 0 + OUT_RESPONSE_CLIENT_ERROR = 1 + OUT_RESPONSE_SERVER_ERROR = 2 + + IN_REQUEST = 1 diff --git a/pym/calculate/contrib/spyne/server/msgpack.pyc b/pym/calculate/contrib/spyne/server/msgpack.pyc new file mode 100644 index 0000000..9540328 Binary files /dev/null and b/pym/calculate/contrib/spyne/server/msgpack.pyc differ diff --git a/pym/calculate/contrib/spyne/server/null.py b/pym/calculate/contrib/spyne/server/null.py new file mode 100644 index 0000000..2279bd3 --- /dev/null +++ b/pym/calculate/contrib/spyne/server/null.py @@ -0,0 +1,228 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.server.null`` module contains the NullServer class and its helper +objects. + +The name comes from the "null modem connection". Look it up. +""" + +from __future__ import absolute_import + +import logging +logger = logging.getLogger(__name__) + +from spyne import MethodContext, BODY_STYLE_BARE, ComplexModelBase, \ + BODY_STYLE_EMPTY, BODY_STYLE_OUT_BARE, BODY_STYLE_EMPTY_OUT_BARE + +from spyne.client import Factory +from spyne.const.ansi_color import LIGHT_RED +from spyne.const.ansi_color import LIGHT_BLUE +from spyne.const.ansi_color import END_COLOR +from spyne.server import ServerBase + + +_big_header = ('=' * 40) + LIGHT_RED +_big_footer = END_COLOR + ('=' * 40) +_small_header = ('-' * 20) + LIGHT_BLUE +_small_footer = END_COLOR + ('-' * 20) + + +class NullServer(ServerBase): + """A server that doesn't support any transport at all -- it's implemented + to test services without having to run a server. + + Note that: + 1) ``**kwargs`` overwrite ``*args``. + 2) You can do: :: + + logging.getLogger('spyne.server.null').setLevel(logging.CRITICAL) + + to hide context delimiters in logs. + """ + + transport = 'noconn://null.spyne' + MethodContext = MethodContext + + def __init__(self, app, ostr=False, locale='C', appinit=True): + self.do_appinit = appinit + + super(NullServer, self).__init__(app) + + self.service = _FunctionProxy(self, self.app, is_async=False) + self.is_async = _FunctionProxy(self, self.app, is_async=True) + self.factory = Factory(self.app) + self.ostr = ostr + self.locale = locale + self.url = "http://spyne.io/null" + + def appinit(self): + if self.do_appinit: + super(NullServer, self).appinit() + + def get_wsdl(self): + return self.app.get_interface_document(self.url) + + def set_options(self, **kwargs): + self.service.in_header = kwargs.get('soapheaders', + self.service.in_header) + + def get_services(self): + return self.app.interface.service_method_map + + +class _FunctionProxy(object): + def __init__(self, server, app, is_async): + self._app = app + self._server = server + self.in_header = None + self.is_async = is_async + + def __getattr__(self, key): + return _FunctionCall(self._app, self._server, key, self.in_header, + self._server.ostr, self._server.locale, self.is_async) + + def __getitem__(self, key): + return self.__getattr__(key) + + +class _FunctionCall(object): + def __init__(self, app, server, key, in_header, ostr, locale, async_): + self.app = app + + self._key = key + self._server = server + self._in_header = in_header + self._ostr = ostr + self._locale = locale + self._async = async_ + + def __call__(self, *args, **kwargs): + initial_ctx = self._server.MethodContext(self, MethodContext.SERVER) + initial_ctx.method_request_string = self._key + initial_ctx.in_header = self._in_header + initial_ctx.transport.type = NullServer.transport + initial_ctx.locale = self._locale + + contexts = self.app.in_protocol.generate_method_contexts(initial_ctx) + + retval = None + logger.warning("%s start request %s" % (_big_header, _big_footer)) + + if self._async: + from twisted.internet.defer import Deferred + + for cnt, ctx in enumerate(contexts): + # this reconstruction is quite costly. I wonder whether it's a + # problem though. + + _type_info = ctx.descriptor.in_message._type_info + ctx.in_object = [None] * len(_type_info) + for i in range(len(args)): + ctx.in_object[i] = args[i] + + for i, k in enumerate(_type_info.keys()): + val = kwargs.get(k, None) + if val is not None: + ctx.in_object[i] = val + + if ctx.descriptor.body_style == BODY_STYLE_BARE: + ctx.in_object = ctx.descriptor.in_message \ + .get_serialization_instance(ctx.in_object) + + if cnt == 0: + p_ctx = ctx + else: + ctx.descriptor.aux.initialize_context(ctx, p_ctx, error=None) + + # do logging.getLogger('spyne.server.null').setLevel(logging.CRITICAL) + # to hide the following + logger.warning("%s start context %s" % (_small_header, + _small_footer)) + logger.info("%r.%r" % (ctx.service_class, + ctx.descriptor.function)) + try: + self.app.process_request(ctx) + finally: + logger.warning("%s end context %s" % (_small_header, + _small_footer)) + + if cnt == 0: + if self._async and isinstance(ctx.out_object[0], Deferred): + retval = ctx.out_object[0] + retval.addCallback(_cb_async, ctx, cnt, self) + + else: + retval = _cb_sync(ctx, cnt, self) + + if not self._async: + p_ctx.close() + + logger.warning("%s end request %s" % (_big_header, _big_footer)) + + return retval + + +def _cb_async(ret, ctx, cnt, fc): + if issubclass(ctx.descriptor.out_message, ComplexModelBase): + if len(ctx.descriptor.out_message._type_info) == 0: + ctx.out_object = [None] + + elif len(ctx.descriptor.out_message._type_info) == 1: + ctx.out_object = [ret] + + else: + ctx.out_object = ret + + else: + ctx.out_object = [ret] + + return _cb_sync(ctx, cnt, fc) + + +def _cb_sync(ctx, cnt, fc): + retval = None + + if ctx.out_error: + raise ctx.out_error + + else: + if ctx.descriptor.is_out_bare(): + retval = ctx.out_object[0] + + elif ctx.descriptor.body_style is BODY_STYLE_EMPTY: + retval = None + + elif len(ctx.descriptor.out_message._type_info) == 0: + retval = None + + elif len(ctx.descriptor.out_message._type_info) == 1: + retval = ctx.out_object[0] + + else: + retval = ctx.out_object + + if cnt == 0 and fc._ostr: + fc._server.get_out_string(ctx) + retval = ctx.out_string + + if cnt > 0: + ctx.close() + + return retval diff --git a/pym/calculate/contrib/spyne/server/null.pyc b/pym/calculate/contrib/spyne/server/null.pyc new file mode 100644 index 0000000..4f2c41b Binary files /dev/null and b/pym/calculate/contrib/spyne/server/null.pyc differ diff --git a/pym/calculate/contrib/spyne/server/pyramid.py b/pym/calculate/contrib/spyne/server/pyramid.py new file mode 100644 index 0000000..7319da2 --- /dev/null +++ b/pym/calculate/contrib/spyne/server/pyramid.py @@ -0,0 +1,58 @@ +# encoding: utf-8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.server.pyramid`` module contains a Pyramid-compatible Http +transport. It's a thin wrapper around +:class:`spyne.server.wsgi.WsgiApplication`. +""" + +from __future__ import absolute_import + +from pyramid.response import Response +from spyne.server.wsgi import WsgiApplication + + +class PyramidApplication(WsgiApplication): + """Pyramid View Wrapper. Use this for regular RPC""" + + def __call__(self, request): + retval = Response() + + def start_response(status, headers): + status, reason = status.split(' ', 1) + + retval.status_int = int(status) + for header, value in headers: + retval.headers[header] = value + + response = WsgiApplication.__call__(self, request.environ, + start_response) + retval.body = b"".join(response) + + return retval + + def set_response(self, retval, response): + retval.body = b"".join(response) + + +class StreamingPyramidApplication(WsgiApplication): + """You should use this when you're generating HUGE data as response.""" + + def set_response(self, retval, response): + retval.app_iter = response diff --git a/pym/calculate/contrib/spyne/server/pyramid.pyc b/pym/calculate/contrib/spyne/server/pyramid.pyc new file mode 100644 index 0000000..1d8a9f3 Binary files /dev/null and b/pym/calculate/contrib/spyne/server/pyramid.pyc differ diff --git a/pym/calculate/contrib/spyne/server/twisted/__init__.py b/pym/calculate/contrib/spyne/server/twisted/__init__.py new file mode 100644 index 0000000..c22dcaa --- /dev/null +++ b/pym/calculate/contrib/spyne/server/twisted/__init__.py @@ -0,0 +1,27 @@ + +# +# 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 +# + + +def log_and_let_go(err, logger): + logger.error(err.getTraceback()) + return err + + +from spyne.server.twisted.http import TwistedWebResource +from spyne.server.twisted.websocket import TwistedWebSocketResource diff --git a/pym/calculate/contrib/spyne/server/twisted/__init__.pyc b/pym/calculate/contrib/spyne/server/twisted/__init__.pyc new file mode 100644 index 0000000..337bc95 Binary files /dev/null and b/pym/calculate/contrib/spyne/server/twisted/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/server/twisted/_base.py b/pym/calculate/contrib/spyne/server/twisted/_base.py new file mode 100644 index 0000000..98a386c --- /dev/null +++ b/pym/calculate/contrib/spyne/server/twisted/_base.py @@ -0,0 +1,79 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from twisted.internet.defer import Deferred +from twisted.internet.interfaces import IPullProducer +from twisted.web.iweb import UNKNOWN_LENGTH + +from zope.interface import implementer + + +@implementer(IPullProducer) +class Producer(object): + deferred = None + + def __init__(self, body, consumer): + """:param body: an iterable of strings""" + + # check to see if we can determine the length + try: + len(body) # iterator? + self.length = sum([len(fragment) for fragment in body]) + self.body = iter(body) + + except TypeError: + self.length = UNKNOWN_LENGTH + self.body = body + + self.deferred = Deferred() + + self.consumer = consumer + + def resumeProducing(self): + try: + chunk = next(self.body) + + except StopIteration as e: + self.consumer.unregisterProducer() + if self.deferred is not None: + self.deferred.callback(self.consumer) + self.deferred = None + return + + self.consumer.write(chunk) + + def pauseProducing(self): + pass + + def stopProducing(self): + if self.deferred is not None: + self.deferred.errback( + Exception("Consumer asked us to stop producing")) + self.deferred = None + + +from spyne import Address +_TYPE_MAP = {'TCP': Address.TCP4, 'TCP6': Address.TCP6, + 'UDP': Address.UDP4, 'UDP6': Address.UDP6} + +def _address_from_twisted_address(peer): + return Address( + type=_TYPE_MAP.get(peer.type, None), host=peer.host, port=peer.port) + +Address.from_twisted_address = staticmethod(_address_from_twisted_address) diff --git a/pym/calculate/contrib/spyne/server/twisted/_base.pyc b/pym/calculate/contrib/spyne/server/twisted/_base.pyc new file mode 100644 index 0000000..88a49df Binary files /dev/null and b/pym/calculate/contrib/spyne/server/twisted/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/server/twisted/http.py b/pym/calculate/contrib/spyne/server/twisted/http.py new file mode 100644 index 0000000..0c76176 --- /dev/null +++ b/pym/calculate/contrib/spyne/server/twisted/http.py @@ -0,0 +1,790 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.server.twisted`` module contains a server transport compatible +with the Twisted event loop. It uses the TwistedWebResource object as transport. + +Also see the twisted examples in the examples directory of the source +distribution. + +If you want to have a hard-coded URL in the wsdl document, this is how to do +it: :: + + resource = TwistedWebResource(...) + resource.http_transport.doc.wsdl11.build_interface_document("http://example.com") + +This is not strictly necessary. If you don't do this, Spyne will get the URL +from the first request, build the wsdl on-the-fly and cache it as a string in +memory for later requests. However, if you want to make sure you only have this +url on the WSDL, this is how to do it. Note that if your client takes the +information in wsdl seriously, all requests will go to the designated url above +which can make testing a bit difficult. Use in moderation. +""" + +from __future__ import absolute_import + +import logging +logger = logging.getLogger(__name__) + +import re +import cgi +import gzip +import shutil +import threading + +from os import fstat +from mmap import mmap +from inspect import isclass +from collections import namedtuple +from tempfile import TemporaryFile + +from twisted.web import static +from twisted.web.server import NOT_DONE_YET, Request +from twisted.web.resource import Resource, NoResource, ForbiddenResource +from twisted.web.static import getTypeAndEncoding +from twisted.python.log import err +from twisted.python.failure import Failure +from twisted.internet import reactor +from twisted.internet.task import deferLater +from twisted.internet.defer import Deferred +from twisted.internet.threads import deferToThread + +from spyne import Redirect, Address +from spyne.application import logger_server +from spyne.application import get_fault_string_from_exception + +from spyne.util import six +from spyne.error import InternalError +from spyne.auxproc import process_contexts +from spyne.const.ansi_color import LIGHT_GREEN +from spyne.const.ansi_color import END_COLOR +from spyne.const.http import HTTP_404, HTTP_200 + +from spyne.model import PushBase, File, ComplexModelBase +from spyne.model.fault import Fault + +from spyne.protocol.http import HttpRpc + +from spyne.server.http import HttpBase +from spyne.server.http import HttpMethodContext +from spyne.server.http import HttpTransportContext +from spyne.server.twisted._base import Producer +from spyne.server.twisted import log_and_let_go + +from spyne.util.address import address_parser +from spyne.util.six import text_type, string_types +from spyne.util.six.moves.urllib.parse import unquote + +if not six.PY2: + from urllib.request import unquote_to_bytes + + +def _render_file(file, request): + """ + Begin sending the contents of this L{File} (or a subset of the + contents, based on the 'range' header) to the given request. + """ + file.restat(False) + + if file.type is None: + file.type, file.encoding = getTypeAndEncoding(file.basename(), + file.contentTypes, + file.contentEncodings, + file.defaultType) + + if not file.exists(): + return file.childNotFound.render(request) + + if file.isdir(): + return file.redirect(request) + + request.setHeader('accept-ranges', 'bytes') + + try: + fileForReading = file.openForReading() + except IOError as e: + import errno + + if e[0] == errno.EACCES: + return ForbiddenResource().render(request) + else: + raise + + #if request.setLastModified(file.getmtime()) is CACHED: + # return '' + + producer = file.makeProducer(request, fileForReading) + + if request.method == 'HEAD': + return '' + + producer.start() + # and make sure the connection doesn't get closed + return NOT_DONE_YET + + +def _set_response_headers(request, headers): + retval = [] + + for k, v in headers.items(): + if isinstance(v, (list, tuple)): + request.responseHeaders.setRawHeaders(k, v) + else: + request.responseHeaders.setRawHeaders(k, [v]) + + return retval + + +def _reconstruct_url(request): + # HTTP "Hosts" header only supports ascii + + server_name = request.getHeader(b"x-forwarded-host") + server_port = request.getHeader(b"x-forwarded-port") + if server_port is not None: + try: + server_port = int(server_port) + except Exception as e: + logger.debug("Ignoring exception: %r for value %r", e, server_port) + server_port = None + + is_secure = request.getHeader(b"x-forwarded-proto") + if is_secure is not None: + is_secure = is_secure == 'https' + + if server_name is None: + server_name = request.getRequestHostname().decode('ascii') + if server_port is None: + server_port = request.getHost().port + if is_secure is None: + is_secure = bool(request.isSecure()) + + if (is_secure, server_port) not in ((True, 443), (False, 80)): + server_name = '%s:%d' % (server_name, server_port) + + if is_secure: + url_scheme = 'https' + else: + url_scheme = 'http' + + uri = _decode_path(request.uri) + return ''.join([url_scheme, "://", server_name, uri]) + + +class _Transformer(object): + def __init__(self, req): + self.req = req + + def get(self, key, default): + key = key.lower() + if key.startswith((b'http_', b'http-')): + key = key[5:] + + retval = self.req.getHeader(key) + if retval is None: + retval = default + return retval + + +class TwistedHttpTransportContext(HttpTransportContext): + def set_mime_type(self, what): + if isinstance(what, text_type): + what = what.encode('ascii', errors='replace') + super(TwistedHttpTransportContext, self).set_mime_type(what) + self.req.setHeader('Content-Type', what) + + def get_cookie(self, key): + return self.req.getCookie(key) + + def get_path(self): + return self.req.URLPath().path + + def get_path_and_qs(self): + return self.req.uri + + def get_request_method(self): + return self.req.method + + def get_request_content_type(self): + return self.req.getHeader("Content-Type") + + def get_peer(self): + peer = Address.from_twisted_address(self.req.transport.getPeer()) + addr = address_parser.get_ip(_Transformer(self.req)) + + if addr is None: + return peer + + if address_parser.is_valid_ipv4(addr): + return Address(type=Address.TCP4, host=addr, port=0) + + if address_parser.is_valid_ipv6(addr): + return Address(type=Address.TCP6, host=addr, port=0) + + +class TwistedHttpMethodContext(HttpMethodContext): + HttpTransportContext = TwistedHttpTransportContext + + +def _decode_path(fragment): + if six.PY2: + return unquote(fragment).decode('utf8') + + return unquote_to_bytes(fragment).decode('utf8') + + +class TwistedHttpTransport(HttpBase): + def __init__(self, app, chunked=False, max_content_length=2 * 1024 * 1024, + block_length=8 * 1024): + super(TwistedHttpTransport, self).__init__(app, chunked=chunked, + max_content_length=max_content_length, block_length=block_length) + + self.reactor_thread = None + def _cb(): + self.reactor_thread = threading.current_thread() + + deferLater(reactor, 0, _cb) + + def pusher_init(self, p_ctx, gen, _cb_push_finish, pusher, interim): + if pusher.orig_thread != self.reactor_thread: + return deferToThread(super(TwistedHttpTransport, self).pusher_init, + p_ctx, gen, _cb_push_finish, pusher, interim) + + return super(TwistedHttpTransport, self).pusher_init( + p_ctx, gen, _cb_push_finish, pusher, interim) + + @staticmethod + def set_out_document_push(ctx): + class _ISwearImAGenerator(object): + def send(self, data): + if not data: return + ctx.out_stream.write(data) + + ctx.out_document = _ISwearImAGenerator() + + def pusher_try_close(self, ctx, pusher, retval): + # the whole point of this function is to call ctx.out_stream.finish() + # when a *root* pusher has no more data to send. interim pushers don't + # have to close anything. + if isinstance(retval, Deferred): + def _eb_push_close(f): + assert isinstance(f, Failure) + + logger.error(f.getTraceback()) + + subretval = super(TwistedHttpTransport, self) \ + .pusher_try_close(ctx, pusher, retval) + + if not pusher.interim: + ctx.out_stream.finish() + + return subretval + + def _cb_push_close(r): + def _eb_inner(f): + if not pusher.interim: + ctx.out_stream.finish() + + return f + + if not isinstance(r, Deferred): + retval = super(TwistedHttpTransport, self) \ + .pusher_try_close(ctx, pusher, r) + if not pusher.interim: + ctx.out_stream.finish() + + return retval + + return r \ + .addCallback(_cb_push_close) \ + .addErrback(_eb_inner) \ + .addErrback(log_and_let_go, logger) + + return retval \ + .addCallback(_cb_push_close) \ + .addErrback(_eb_push_close) \ + .addErrback(log_and_let_go, logger) + + super(TwistedHttpTransport, self).pusher_try_close(ctx, pusher, retval) + + if not pusher.interim: + retval = ctx.out_stream.finish() + + return retval + + def decompose_incoming_envelope(self, prot, ctx, message): + """This function is only called by the HttpRpc protocol to have the + twisted web's Request object is parsed into ``ctx.in_body_doc`` and + ``ctx.in_header_doc``. + """ + + request = ctx.in_document + assert isinstance(request, Request) + + ctx.in_header_doc = dict(request.requestHeaders.getAllRawHeaders()) + ctx.in_body_doc = request.args + + for fi in ctx.transport.file_info: + assert isinstance(fi, _FileInfo) + if fi.file_name is None: + continue + + l = ctx.in_body_doc.get(fi.field_name, None) + if l is None: + l = ctx.in_body_doc[fi.field_name] = [] + + l.append( + File.Value(name=fi.file_name, type=fi.file_type, data=fi.data) + ) + + # this is a huge hack because twisted seems to take the slashes in urls + # too seriously. + postpath = getattr(request, 'realpostpath', None) + if postpath is None: + postpath = request.path + + if postpath is not None: + postpath = _decode_path(postpath) + + params = self.match_pattern(ctx, request.method, postpath, + request.getHeader(b'Host')) + + if ctx.method_request_string is None: # no pattern match + ctx.method_request_string = u'{%s}%s' % ( + self.app.interface.get_tns(), + _decode_path(request.path.rsplit(b'/', 1)[-1])) + + logger.debug(u"%sMethod name: %r%s" % (LIGHT_GREEN, + ctx.method_request_string, END_COLOR)) + + for k, v in params.items(): + val = ctx.in_body_doc.get(k, []) + val.extend(v) + ctx.in_body_doc[k] = val + + r = {} + for k, v in ctx.in_body_doc.items(): + l = [] + for v2 in v: + if isinstance(v2, string_types): + l.append(unquote(v2)) + else: + l.append(v2) + r[k] = l + ctx.in_body_doc = r + + # This is consistent with what server.wsgi does. + if request.method in ('POST', 'PUT', 'PATCH'): + for k, v in ctx.in_body_doc.items(): + if v == ['']: + ctx.in_body_doc[k] = [None] + + +FIELD_NAME_RE = re.compile(r'name="([^"]+)"') +FILE_NAME_RE = re.compile(r'filename="([^"]+)"') +_FileInfo = namedtuple("_FileInfo", "field_name file_name file_type data") + + +def _get_file_info(ctx): + """We need this hack because twisted doesn't offer a way to get file name + from Content-Disposition header. + """ + + retval = [] + + request = ctx.transport.req + headers = request.getAllHeaders() + content_type = headers.get('content-type', None) + if content_type is None: + return retval + + content = request.content + + content_encoding = headers.get('content-encoding', None) + if content_encoding == b'gzip': + request.content.seek(0) + content = TemporaryFile() + with gzip.GzipFile(fileobj=request.content) as ifstr: + shutil.copyfileobj(ifstr, content) + content.seek(0) + + img = cgi.FieldStorage( + fp=content, + headers=ctx.in_header_doc, + environ={ + 'REQUEST_METHOD': request.method, + 'CONTENT_TYPE': content_type, + } + ) + + try: + keys = img.keys() + except TypeError: + return retval + + for k in keys: + fields = img[k] + + if isinstance(fields, cgi.FieldStorage): + fields = (fields,) + + for field in fields: + file_type = field.type + file_name = field.disposition_options.get('filename', None) + if file_name is not None: + retval.append(_FileInfo(k, file_name, file_type, + [mmap(field.file.fileno(), 0)])) + + return retval + + +def _has_fd(istr): + if not hasattr(istr, 'fileno'): + return False + try: + istr.fileno() + except IOError: + return False + else: + return True + + +def get_twisted_child_with_default(res, path, request): + # this hack is necessary because twisted takes the slash character in + # http requests too seriously. i.e. it insists that a leaf node can only + # handle the last path fragment. + if res.prepath is None: + request.realprepath = b'/' + b'/'.join(request.prepath) + else: + if not res.prepath.startswith('/'): + request.realprepath = b'/' + res.prepath + else: + request.realprepath = res.prepath + + if path in res.children: + retval = res.children[path] + else: + retval = res.getChild(path, request) + + if isinstance(retval, NoResource): + retval = res + else: + request.realpostpath = request.path[ + len(path) + (0 if path.startswith(b'/') else 1):] + + return retval + + +class TwistedWebResource(Resource): + """A server transport that exposes the application as a twisted web + Resource. + """ + + def __init__(self, app, chunked=False, max_content_length=2 * 1024 * 1024, + block_length=8 * 1024, prepath=None): + Resource.__init__(self) + self.app = app + + self.http_transport = TwistedHttpTransport(app, chunked, + max_content_length, block_length) + self._wsdl = None + self.prepath = prepath + + def getChildWithDefault(self, path, request): + return get_twisted_child_with_default(self, path, request) + + def render(self, request): + if request.method == b'GET' and ( + request.uri.endswith(b'.wsdl') or request.uri.endswith(b'?wsdl')): + return self.__handle_wsdl_request(request) + return self.handle_rpc(request) + + def handle_rpc_error(self, p_ctx, others, error, request): + logger.error(error) + resp_code = p_ctx.transport.resp_code + # If user code set its own response code, don't touch it. + if resp_code is None: + resp_code = p_ctx.out_protocol.fault_to_http_response_code(error) + + request.setResponseCode(int(resp_code[:3])) + _set_response_headers(request, p_ctx.transport.resp_headers) + + # In case user code set its own out_* attributes before failing. + p_ctx.out_document = None + p_ctx.out_string = None + + p_ctx.out_object = error + self.http_transport.get_out_string(p_ctx) + + retval = b''.join(p_ctx.out_string) + + p_ctx.close() + + process_contexts(self.http_transport, others, p_ctx, error=error) + + return retval + + def handle_rpc(self, request): + initial_ctx = TwistedHttpMethodContext(self.http_transport, request, + self.http_transport.app.out_protocol.mime_type) + + if _has_fd(request.content): + f = request.content + + # it's best to avoid empty mappings. + if fstat(f.fileno()).st_size == 0: + initial_ctx.in_string = [''] + else: + initial_ctx.in_string = [mmap(f.fileno(), 0)] + else: + request.content.seek(0) + initial_ctx.in_string = [request.content.read()] + + initial_ctx.transport.file_info = _get_file_info(initial_ctx) + + contexts = self.http_transport.generate_contexts(initial_ctx) + p_ctx, others = contexts[0], contexts[1:] + + p_ctx.active = True + p_ctx.out_stream = request + # TODO: Rate limiting + p_ctx.active = True + + if p_ctx.in_error: + return self.handle_rpc_error(p_ctx, others, p_ctx.in_error, request) + + else: + self.http_transport.get_in_object(p_ctx) + + if p_ctx.in_error: + return self.handle_rpc_error(p_ctx, others, p_ctx.in_error, + request) + + self.http_transport.get_out_object(p_ctx) + if p_ctx.out_error: + return self.handle_rpc_error(p_ctx, others, p_ctx.out_error, + request) + + ret = p_ctx.out_object[0] + retval = NOT_DONE_YET + if isinstance(ret, Deferred): + ret.addCallback(_cb_deferred, request, p_ctx, others, resource=self) + ret.addErrback(_eb_deferred, request, p_ctx, others, resource=self) + ret.addErrback(log_and_let_go, logger) + + elif isinstance(ret, PushBase): + self.http_transport.init_root_push(ret, p_ctx, others) + + else: + try: + retval = _cb_deferred(p_ctx.out_object, request, p_ctx, others, + self, cb=False) + except Exception as e: + logger_server.exception(e) + try: + _eb_deferred(Failure(), request, p_ctx, others, + resource=self) + except Exception as e: + logger_server.exception(e) + + return retval + + def __handle_wsdl_request(self, request): + # disabled for performance reasons. + # logger.debug("WSDL request headers: %r", + # list(request.requestHeaders.getAllRawHeaders())) + + ctx = TwistedHttpMethodContext(self.http_transport, request, + "text/xml; charset=utf-8") + url = _reconstruct_url(request) + + if self.http_transport.doc.wsdl11 is None: + return HTTP_404 + + if self._wsdl is None: + self._wsdl = self.http_transport.doc.wsdl11.get_interface_document() + + ctx.transport.wsdl = self._wsdl + _set_response_headers(request, ctx.transport.resp_headers) + + try: + if self._wsdl is None: + self.http_transport.doc.wsdl11.build_interface_document(url) + ctx.transport.wsdl = self._wsdl = \ + self.http_transport.doc.wsdl11.get_interface_document() + + assert ctx.transport.wsdl is not None + + self.http_transport.event_manager.fire_event('wsdl', ctx) + + return ctx.transport.wsdl + + except Exception as e: + ctx.transport.wsdl_error = e + self.http_transport.event_manager.fire_event('wsdl_exception', ctx) + raise + + finally: + ctx.close() + + +def _cb_request_finished(retval, request, p_ctx): + request.finish() + p_ctx.close() + + +def _eb_request_finished(retval, request, p_ctx): + err(request) + p_ctx.close() + request.finish() + + +def _cb_deferred(ret, request, p_ctx, others, resource, cb=True): + ### set response headers + resp_code = p_ctx.transport.resp_code + + # If user code set its own response code, don't touch it. + if resp_code is None: + resp_code = HTTP_200 + request.setResponseCode(int(resp_code[:3])) + + _set_response_headers(request, p_ctx.transport.resp_headers) + + ### normalize response data + om = p_ctx.descriptor.out_message + single_class = None + if cb: + if p_ctx.descriptor.is_out_bare(): + p_ctx.out_object = [ret] + + elif (not issubclass(om, ComplexModelBase)) or len(om._type_info) <= 1: + p_ctx.out_object = [ret] + if len(om._type_info) == 1: + single_class, = om._type_info.values() + else: + p_ctx.out_object = ret + else: + p_ctx.out_object = ret + + ### start response + retval = NOT_DONE_YET + + if isinstance(ret, PushBase): + resource.http_transport.init_root_push(ret, p_ctx, others) + + elif ((isclass(om) and issubclass(om, File)) or + (isclass(single_class) and issubclass(single_class, File))) and \ + isinstance(p_ctx.out_protocol, HttpRpc) and \ + getattr(ret, 'abspath', None) is not None: + + file = static.File(ret.abspath, + defaultType=str(ret.type) or 'application/octet-stream') + retval = _render_file(file, request) + if retval != NOT_DONE_YET and cb: + request.write(retval) + request.finish() + p_ctx.close() + else: + def _close_only_context(ret): + p_ctx.close() + + request.notifyFinish() \ + .addCallback(_close_only_context) \ + .addErrback(_eb_request_finished, request, p_ctx) \ + .addErrback(log_and_let_go, logger) + + else: + ret = resource.http_transport.get_out_string(p_ctx) + + if not isinstance(ret, Deferred): + producer = Producer(p_ctx.out_string, request) + producer.deferred \ + .addCallback(_cb_request_finished, request, p_ctx) \ + .addErrback(_eb_request_finished, request, p_ctx) \ + .addErrback(log_and_let_go, logger) + + try: + request.registerProducer(producer, False) + except Exception as e: + logger_server.exception(e) + try: + _eb_deferred(Failure(), request, p_ctx, others, resource) + except Exception as e: + logger_server.exception(e) + raise + + else: + def _cb(ret): + if isinstance(ret, Deferred): + return ret \ + .addCallback(_cb) \ + .addErrback(_eb_request_finished, request, p_ctx) \ + .addErrback(log_and_let_go, logger) + else: + return _cb_request_finished(ret, request, p_ctx) + + ret \ + .addCallback(_cb) \ + .addErrback(_eb_request_finished, request, p_ctx) \ + .addErrback(log_and_let_go, logger) + + process_contexts(resource.http_transport, others, p_ctx) + + return retval + + +def _eb_deferred(ret, request, p_ctx, others, resource): + # DRY this with what's in Application.process_request + if ret.check(Redirect): + try: + ret.value.do_redirect() + + # Now that the processing is switched to the outgoing message, + # point ctx.protocol to ctx.out_protocol + p_ctx.protocol = p_ctx.outprot_ctx + + _cb_deferred(None, request, p_ctx, others, resource, cb=False) + + p_ctx.fire_event('method_redirect') + + except Exception as e: + logger_server.exception(e) + p_ctx.out_error = Fault('Server', get_fault_string_from_exception(e)) + + p_ctx.fire_event('method_redirect_exception') + + elif ret.check(Fault): + p_ctx.out_error = ret.value + + ret = resource.handle_rpc_error(p_ctx, others, p_ctx.out_error, request) + + p_ctx.fire_event('method_exception_object') + + request.write(ret) + + else: + p_ctx.out_error = InternalError(ret.value) + logger.error(ret.getTraceback()) + + ret = resource.handle_rpc_error(p_ctx, others, p_ctx.out_error, request) + + p_ctx.fire_event('method_exception_object') + + request.write(ret) + + request.finish() diff --git a/pym/calculate/contrib/spyne/server/twisted/http.pyc b/pym/calculate/contrib/spyne/server/twisted/http.pyc new file mode 100644 index 0000000..0b4d0ef Binary files /dev/null and b/pym/calculate/contrib/spyne/server/twisted/http.pyc differ diff --git a/pym/calculate/contrib/spyne/server/twisted/msgpack.py b/pym/calculate/contrib/spyne/server/twisted/msgpack.py new file mode 100644 index 0000000..8d548d7 --- /dev/null +++ b/pym/calculate/contrib/spyne/server/twisted/msgpack.py @@ -0,0 +1,477 @@ +# encoding: utf8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from __future__ import absolute_import + +import logging +logger = logging.getLogger(__name__) + +import io + +import msgpack + +from time import time +from hashlib import md5 +from collections import deque, OrderedDict +from itertools import chain + +from twisted.internet import reactor +from twisted.internet.task import deferLater +from twisted.internet.defer import Deferred, CancelledError +from twisted.internet.protocol import Protocol, Factory, connectionDone, \ + ClientFactory +from twisted.python.failure import Failure + +from spyne import EventManager, Address, ServerBase, Fault +from spyne.auxproc import process_contexts +from spyne.error import InternalError +from spyne.server.twisted import log_and_let_go + + +class TwistedMessagePackProtocolFactory(Factory): + IDLE_TIMEOUT_SEC = None + + def __init__(self, tpt): + assert isinstance(tpt, ServerBase) + + self.tpt = tpt + self.event_manager = EventManager(self) + + def buildProtocol(self, address): + retval = TwistedMessagePackProtocol(self.tpt, factory=self) + + if self.IDLE_TIMEOUT_SEC is not None: + retval.IDLE_TIMEOUT_SEC = self.IDLE_TIMEOUT_SEC + + return retval + +TwistedMessagePackProtocolServerFactory = TwistedMessagePackProtocolFactory + + +class TwistedMessagePackProtocolClientFactory(ClientFactory): + def __init__(self, tpt, max_buffer_size=2 * 1024 * 1024): + assert isinstance(tpt, ServerBase), \ + "%r is not a ServerBase instance" % tpt + + self.tpt = tpt + self.max_buffer_size = max_buffer_size + self.event_manager = EventManager(self) + + def buildProtocol(self, address): + return TwistedMessagePackProtocol(self.tpt, + max_buffer_size=self.max_buffer_size, factory=self) + + +def _cha(*args): + return args + + +class TwistedMessagePackProtocol(Protocol): + IDLE_TIMEOUT_SEC = 0 + IDLE_TIMEOUT_MSG = 'idle timeout' + MAX_INACTIVE_CONTEXTS = float('inf') + + def __init__(self, tpt, max_buffer_size=2 * 1024 * 1024, out_chunk_size=0, + out_chunk_delay_sec=1, max_in_queue_size=0, factory=None): + """Twisted protocol implementation for Spyne's MessagePack transport. + + :param tpt: Spyne transport. It's an app-wide instance. + :param max_buffer_size: Max. encoded message size. + :param out_chunk_size: Split + :param factory: Twisted protocol factory + + Supported events: + * ``outresp_flushed(ctx, ctxid, data)`` + Called right after response data is flushed to the socket. + * ctx: Always None + * ctxid: Integer equal to ``id(ctx)`` + * data: Flushed bytes object + + """ + + from spyne.server.msgpack import MessagePackTransportBase + assert isinstance(tpt, MessagePackTransportBase), \ + "Expected {!r} got {!r}".format(MessagePackTransportBase, type(tpt)) + + self.spyne_tpt = tpt + self._buffer = msgpack.Unpacker(raw=True, + max_buffer_size=max_buffer_size) + self.out_chunk_size = out_chunk_size + self.out_chunk_delay_sec = out_chunk_delay_sec + self.max_in_queue_size = max_in_queue_size + self.factory = factory + + self.sessid = '' + self._delaying = None + self.sent_bytes = 0 + self.recv_bytes = 0 + self.idle_timer = None + self.out_chunks = deque() + self.inreq_queue = OrderedDict() + self.inactive_queue = list() + self.disconnecting = False # FIXME: should we use this to raise an + # invalid connection state exception ? + + @staticmethod + def gen_chunks(l, n): + """Yield successive n-sized chunks from l.""" + if isinstance(l, io.BufferedIOBase): + while True: + data = l.read(n) + if not data: + break + yield data + l.close() + + else: + for i in range(0, len(l), n): + yield l[i:i+n] + + def gen_sessid(self, *args): + """It's up to you to use this in a subclass.""" + + retval = _cha( + Address.from_twisted_address(self.transport.getPeer()), + time(), + *args + ) + + return md5(repr(retval).encode('utf8')).hexdigest() + + def connectionMade(self): + logger.debug("%08x connection made", id(self)) + self.sessid = '' + self._delaying = None + self.sent_bytes = 0 + self.recv_bytes = 0 + self.idle_timer = None + self.out_chunks = deque() + self.inreq_queue = OrderedDict() + self.inactive_queue = list() + self.active_queue = dict() + self.disconnecting = False # FIXME: should we use this to raise an + # invalid connection state exception ? + + self._reset_idle_timer() + if self.factory is not None: + self.factory.event_manager.fire_event("connection_made", self) + + def connectionLost(self, reason=connectionDone): + if reason is connectionDone: + logger.debug("%08x connection done", id(self)) + else: + logger.debug("%08x connection lost: %s", id(self), reason) + self.disconnecting = False + if self.factory is not None: + self.factory.event_manager.fire_event("connection_lost", self) + self._cancel_idle_timer() + + def _cancel_idle_timer(self): + if self.idle_timer is not None: + if not self.idle_timer.called: + # FIXME: Workaround for a bug in Twisted 18.9.0 when + # DelayedCall.debug == True + try: + self.idle_timer.cancel() + except AttributeError: + del self.idle_timer.func + del self.idle_timer.args + del self.idle_timer.kw + + self.idle_timer = None + + def dataReceived(self, data): + self._buffer.feed(data) + self.recv_bytes += len(data) + + self._reset_idle_timer() + + for msg in self._buffer: + self.process_incoming_message(msg) + + if self.disconnecting: + return + + def _reset_idle_timer(self): + if self.idle_timer is not None: + t = self.idle_timer + self.idle_timer = None + if not t.called: + t.cancel() + + if self.IDLE_TIMEOUT_SEC is not None and self.IDLE_TIMEOUT_SEC > 0: + self.idle_timer = deferLater(reactor, self.IDLE_TIMEOUT_SEC, + self.loseConnection, self.IDLE_TIMEOUT_MSG) \ + .addErrback(self._err_idle_cancelled) \ + .addErrback(self._err_idle_cancelled_unknown_error) + + def _err_idle_cancelled(self, err): + err.trap(CancelledError) + + # do nothing. + + def _err_idle_cancelled_unknown_error(self, err): + logger.error("Sessid %s error cancelling idle timer: %s", + self.sessid, err.getTraceback()) + self.idle_timer = None + + def loseConnection(self, reason=None): + self.disconnecting = True + self.idle_timer = None + logger.debug("Aborting connection because %s", reason) + self.transport.abortConnection() + + def process_incoming_message(self, msg, oob=None): + p_ctx, others = self.spyne_tpt.produce_contexts(msg) + p_ctx.oob_ctx = oob + p_ctx.transport.remote_addr = Address.from_twisted_address( + self.transport.getPeer()) + p_ctx.transport.protocol = self + p_ctx.transport.sessid = self.sessid + + self.inactive_queue.append((p_ctx, others)) + self.process_inactive() + + @property + def num_active_contexts(self): + return len(self.inreq_queue) + + @property + def num_inactive_contexts(self): + return len(self.inactive_queue) + + def process_inactive(self): + peer = self.transport.getPeer() + addr_str = Address.from_twisted_address(peer) + + if self.max_in_queue_size == 0: + while self.num_inactive_contexts > 0: + p_ctx, others = self.inactive_queue.pop() + self.active_queue[id(p_ctx)] = p_ctx + + self.inreq_queue[id(p_ctx)] = None + self.process_contexts(p_ctx, others) + + else: + while self.num_active_contexts < self.max_in_queue_size and \ + self.num_inactive_contexts > 0: + p_ctx, others = self.inactive_queue.pop() + self.active_queue[id(p_ctx)] = p_ctx + + self.inreq_queue[id(p_ctx)] = None + self.process_contexts(p_ctx, others) + + if self.num_active_contexts > self.MAX_INACTIVE_CONTEXTS: + logger.error("%s Too many inactive contexts. " + "Closing connection.", addr_str) + self.loseConnection("Too many inactive contexts") + + logger.debug("%s active %d inactive %d", addr_str, + self.num_active_contexts, self.num_inactive_contexts) + + def enqueue_outresp_data(self, ctxid, data): + assert self.inreq_queue[ctxid] is None + self.inreq_queue[ctxid] = data + + for k, v in list(self.inreq_queue.items()): + if v is None: + break + + self.out_write(v) + self.spyne_tpt.event_manager.fire_event('outresp_flushed', + None, k, v) + del self.inreq_queue[k] + self.active_queue[k].close() + del self.active_queue[k] + + self.process_inactive() + + def out_write(self, reqdata): + if self.out_chunk_size == 0: + if isinstance(reqdata, io.BufferedIOBase): + nbytes = reqdata.tell() + reqdata.seek(0) + self.transport.write(reqdata.read()) + else: + nbytes = len(reqdata) + self.transport.write(reqdata) + + self.sent_bytes += nbytes + + else: + if isinstance(reqdata, io.BufferedIOBase): + reqdata.seek(0) + + chunks = self.gen_chunks(reqdata, self.out_chunk_size) + self.out_chunks.append(chunks) + deferLater(reactor, 0, self._write_single_chunk) + + def _wait_for_next_chunk(self): + return deferLater(reactor, self.out_chunk_delay_sec, + self._write_single_chunk) + + def _write_single_chunk(self): + try: + chunk = next(chain.from_iterable(self.out_chunks)) + except StopIteration: + chunk = None + self.out_chunks.clear() + + if chunk is None: + self._delaying = None + + logger.debug("%s no more chunks...", self.sessid) + + else: + self.transport.write(chunk) + self.sent_bytes += len(chunk) + + if self.connected and not self.disconnecting: + self._delaying = self._wait_for_next_chunk() + + logger.debug("%s One chunk of %d bytes written. Delaying " + "before next chunk write...", self.sessid, len(chunk)) + + else: + logger.debug("%s Disconnection detected, discarding " + "remaining chunks", self.sessid) + self.out_chunks.clear() + + def handle_error(self, p_ctx, others, exc): + self.spyne_tpt.get_out_string(p_ctx) + + if isinstance(exc, InternalError): + error = self.spyne_tpt.OUT_RESPONSE_SERVER_ERROR + else: + error = self.spyne_tpt.OUT_RESPONSE_CLIENT_ERROR + + data = p_ctx.out_document[0] + if isinstance(data, dict): + data = list(data.values()) + + out_object = (error, msgpack.packb(data),) + if p_ctx.oob_ctx is not None: + p_ctx.oob_ctx.d.callback(out_object) + return + + out_string = msgpack.packb(out_object) + p_ctx.transport.resp_length = len(out_string) + self.enqueue_outresp_data(id(p_ctx), out_string) + + try: + process_contexts(self, others, p_ctx, error=error) + + except Exception as e: + # Report but ignore any exceptions from auxiliary methods. + logger.error("Exception ignored from auxiliary method: %r", e) + logger.exception(e) + + def _register_callbacks(self, d, p_ctx, others): + return d \ + .addCallback(self._cb_deferred, p_ctx, others) \ + .addErrback(self._eb_deferred, p_ctx, others) \ + .addErrback(log_and_let_go, logger) + + def process_contexts(self, p_ctx, others): + if p_ctx.in_error: + self.handle_error(p_ctx, others, p_ctx.in_error) + return + + self.spyne_tpt.get_in_object(p_ctx) + if p_ctx.in_error: + logger.error(p_ctx.in_error) + self.handle_error(p_ctx, others, p_ctx.in_error) + return + + self.spyne_tpt.get_out_object(p_ctx) + if p_ctx.out_error: + self.handle_error(p_ctx, others, p_ctx.out_error) + return + + ret = p_ctx.out_object + if isinstance(ret, Deferred): + self._register_callbacks(ret, p_ctx, others) + + else: + ret = p_ctx.out_object[0] + if isinstance(ret, Deferred): + self._register_callbacks(ret, p_ctx, others) + + else: + self._cb_deferred(p_ctx.out_object, p_ctx, others, nowrap=True) + + def _eb_deferred(self, fail, p_ctx, others): + assert isinstance(fail, Failure) + + if isinstance(fail.value, Fault): + p_ctx.out_error = fail.value + + else: + p_ctx.out_error = InternalError(fail.value) + if not getattr(fail, 'logged', False): + logger.error(fail.getTraceback()) + + try: + self.handle_error(p_ctx, others, p_ctx.out_error) + + except Exception as e: + logger.exception(e) + raise + + def _cb_deferred(self, ret, p_ctx, others, nowrap=False): + # this means callback is not invoked directly instead of as part of a + # deferred chain + if not nowrap: + # if there is one return value or the output is bare (which means + # there can't be anything other than 1 return value case) use the + # enclosing list. otherwise, the return value is a tuple anyway, so + # leave it be. + if p_ctx.descriptor.is_out_bare(): + p_ctx.out_object = [ret] + + else: + if len(p_ctx.descriptor.out_message._type_info) > 1: + p_ctx.out_object = ret + else: + p_ctx.out_object = [ret] + + if p_ctx.oob_ctx is not None: + assert isinstance(p_ctx.oob_ctx.d, Deferred) + + p_ctx.oob_ctx.d.callback(p_ctx.out_object) + return + + try: + self.spyne_tpt.get_out_string(p_ctx) + self.spyne_tpt.pack(p_ctx) + + out_string = b''.join(p_ctx.out_string) + p_ctx.transport.resp_length = len(out_string) + + self.enqueue_outresp_data(id(p_ctx), out_string) + + except Exception as e: + logger.exception(e) + logger.error("%r", p_ctx) + self.handle_error(p_ctx, others, InternalError(e)) + + finally: + p_ctx.close() + + process_contexts(self.spyne_tpt, others, p_ctx) diff --git a/pym/calculate/contrib/spyne/server/twisted/msgpack.pyc b/pym/calculate/contrib/spyne/server/twisted/msgpack.pyc new file mode 100644 index 0000000..f068061 Binary files /dev/null and b/pym/calculate/contrib/spyne/server/twisted/msgpack.pyc differ diff --git a/pym/calculate/contrib/spyne/server/twisted/websocket.py b/pym/calculate/contrib/spyne/server/twisted/websocket.py new file mode 100644 index 0000000..2e74cab --- /dev/null +++ b/pym/calculate/contrib/spyne/server/twisted/websocket.py @@ -0,0 +1,255 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.server.twisted`` module contains a server transport compatible +with the Twisted event loop. It uses the TwistedWebResource object as transport. + +Also see the twisted examples in the examples directory of the source +distribution. + +If you want to have a hard-coded URL in the wsdl document, this is how to do +it: :: + + resource = TwistedWebResource(...) + resource.http_transport.doc.wsdl11.build_interface_document("http://example.com") + +This is not strictly necessary -- if you don't do this, Spyne will get the +URL from the first request, build the wsdl on-the-fly and cache it as a +string in memory for later requests. However, if you want to make sure +you only have this url on the WSDL, this is how to do it. Note that if +your client takes the information in wsdl seriously, all requests will go +to the designated url above which can make testing a bit difficult. Use +in moderation. + +This module is EXPERIMENTAL. Your mileage may vary. Patches are welcome. +""" + + +from __future__ import absolute_import + +import logging +logger = logging.getLogger(__name__) + +from twisted.internet.defer import Deferred +from twisted.internet.protocol import Factory + +# FIXME: Switch to: +# from twisted.web.websockets import WebSocketsProtocol +# from twisted.web.websockets import WebSocketsResource +# from twisted.web.websockets import CONTROLS + +from spyne.util._twisted_ws import WebSocketsProtocol +from spyne.util._twisted_ws import WebSocketsResource +from spyne.util._twisted_ws import CONTROLS + + +from spyne import MethodContext, TransportContext, Address +from spyne.auxproc import process_contexts +from spyne.model import PushBase +from spyne.model.complex import ComplexModel +from spyne.model.fault import Fault +from spyne.server import ServerBase + + +class WebSocketTransportContext(TransportContext): + def __init__(self, parent, transport, type, client_handle): + TransportContext.__init__(self, parent, transport, type) + + self.client_handle = client_handle + """TwistedWebSocketProtocol instance.""" + + self.parent = parent + """Parent Context""" + + def get_peer(self): + if self.client_handle is not None: + peer = self.client_handle.transport.getPeer() + return Address.from_twisted_address(peer) + + +class WebSocketMethodContext(MethodContext): + def __init__(self, transport, client_handle): + MethodContext.__init__(self, transport, MethodContext.SERVER) + + self.transport = WebSocketTransportContext(self, transport, 'ws', + client_handle) + + +class TwistedWebSocketProtocol(WebSocketsProtocol): + """A protocol that parses and generates messages in a WebSocket stream.""" + + def __init__(self, transport, bookkeep=False, _clients=None): + self._spyne_transport = transport + self._clients = _clients + self.__app_id = id(self) + if bookkeep: + self.connectionMade = self._connectionMade + self.connectionLost = self._connectionLost + + @property + def app_id(self): + return self.__app_id + + @app_id.setter + def app_id(self, what): + entry = self._clients.get(self.__app_id, None) + + if entry: + del self._clients[self.__app_id] + self._clients[what] = entry + + self.__app_id = what + + def _connectionMade(self): + WebSocketsProtocol.connectionMade(self) + + self._clients[self.app_id] = self + + def _connectionLost(self, reason): + del self._clients[id(self)] + + + def frameReceived(self, opcode, data, fin): + tpt = self._spyne_transport + + initial_ctx = WebSocketMethodContext(tpt, client_handle=self) + initial_ctx.in_string = [data] + + contexts = tpt.generate_contexts(initial_ctx) + p_ctx, others = contexts[0], contexts[1:] + + if p_ctx.in_error: + p_ctx.out_object = p_ctx.in_error + + else: + tpt.get_in_object(p_ctx) + + if p_ctx.in_error: + p_ctx.out_object = p_ctx.in_error + + else: + tpt.get_out_object(p_ctx) + if p_ctx.out_error: + p_ctx.out_object = p_ctx.out_error + + def _cb_deferred(retval, cb=True): + if cb and len(p_ctx.descriptor.out_message._type_info) <= 1: + p_ctx.out_object = [retval] + else: + p_ctx.out_object = retval + + tpt.get_out_string(p_ctx) + self.sendFrame(opcode, ''.join(p_ctx.out_string), fin) + p_ctx.close() + process_contexts(tpt, others, p_ctx) + + def _eb_deferred(err): + p_ctx.out_error = err.value + if not issubclass(err.type, Fault): + logger.error(err.getTraceback()) + + tpt.get_out_string(p_ctx) + self.sendFrame(opcode, ''.join(p_ctx.out_string), fin) + p_ctx.close() + + ret = p_ctx.out_object + if isinstance(ret, (list, tuple)): + ret = ret[0] + + if isinstance(ret, Deferred): + ret.addCallback(_cb_deferred) + ret.addErrback(_eb_deferred) + + elif isinstance(ret, PushBase): + raise NotImplementedError() + + else: + _cb_deferred(p_ctx.out_object, cb=False) + + +class TwistedWebSocketFactory(Factory): + def __init__(self, app, bookkeep=False, _clients=None): + self.app = app + self.transport = ServerBase(app) + self.bookkeep = bookkeep + self._clients = _clients + if _clients is None: + self._clients = {} + + def buildProtocol(self, addr): + return TwistedWebSocketProtocol(self.transport, self.bookkeep, + self._clients) + +class _Fake(object): + pass + + +def _FakeWrap(cls): + class _Ret(ComplexModel): + _type_info = {"ugh ": cls} + + return _Ret + + +class _FakeCtx(object): + def __init__(self, obj, cls): + self.out_object = obj + self.out_error = None + self.descriptor = _Fake() + self.descriptor.out_message = cls + + +class InvalidRequestError(Exception): + pass + + +class TwistedWebSocketResource(WebSocketsResource): + def __init__(self, app, bookkeep=False, clients=None): + self.app = app + self.clients = clients + if clients is None: + self.clients = {} + + if bookkeep: + self.propagate = self.do_propagate + + WebSocketsResource.__init__(self, TwistedWebSocketFactory(app, + bookkeep, self.clients)) + + def propagate(self): + raise InvalidRequestError("You must enable bookkeeping to have " + "message propagation work.") + + def get_doc(self, obj, cls=None): + if cls is None: + cls = obj.__class__ + + op = self.app.out_protocol + ctx = _FakeCtx(obj, cls) + op.serialize(ctx, op.RESPONSE) + op.create_out_string(ctx) + + return ''.join(ctx.out_string) + + def do_propagate(self, obj, cls=None): + doc = self.get_doc(obj, cls) + + for c in self.clients.itervalues(): + print('sending to', c) + c.sendFrame(CONTROLS.TEXT, doc, True) diff --git a/pym/calculate/contrib/spyne/server/twisted/websocket.pyc b/pym/calculate/contrib/spyne/server/twisted/websocket.pyc new file mode 100644 index 0000000..f8ecff5 Binary files /dev/null and b/pym/calculate/contrib/spyne/server/twisted/websocket.pyc differ diff --git a/pym/calculate/contrib/spyne/server/wsgi.py b/pym/calculate/contrib/spyne/server/wsgi.py new file mode 100644 index 0000000..9928c98 --- /dev/null +++ b/pym/calculate/contrib/spyne/server/wsgi.py @@ -0,0 +1,624 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + + +""" +A server that uses http as transport via wsgi. It doesn't contain any server +logic. +""" + +import logging +logger = logging.getLogger(__name__) + +import cgi +import threading + +from inspect import isgenerator +from itertools import chain + +from spyne import Address +from spyne.util.six.moves.http_cookies import SimpleCookie +from spyne.util.six.moves.urllib.parse import unquote, quote + +from spyne import File, Fault +from spyne.application import get_fault_string_from_exception +from spyne.auxproc import process_contexts +from spyne.error import RequestTooLongError +from spyne.protocol.http import HttpRpc +from spyne.server.http import HttpBase, HttpMethodContext, HttpTransportContext +from spyne.util.odict import odict +from spyne.util.address import address_parser + +from spyne.const.ansi_color import LIGHT_GREEN +from spyne.const.ansi_color import END_COLOR +from spyne.const.http import HTTP_200 +from spyne.const.http import HTTP_404 +from spyne.const.http import HTTP_500 + + +try: + from spyne.protocol.soap.mime import apply_mtom +except ImportError as _import_error_1: + _local_import_error_1 = _import_error_1 # python 3 workaround + def apply_mtom(*args, **kwargs): + raise _local_import_error_1 + +try: + from werkzeug.formparser import parse_form_data +except ImportError as _import_error_2: + _local_import_error_2 = _import_error_2 # python 3 workaround + def parse_form_data(*args, **kwargs): + raise _local_import_error_2 + + +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 _parse_qs(qs): + pairs = (s2 for s1 in qs.split('&') for s2 in s1.split(';')) + retval = odict() + + for name_value in pairs: + if name_value is None or len(name_value) == 0: + continue + nv = name_value.split('=', 1) + + if len(nv) != 2: + # Handle case of a control-name with no equal sign + nv.append(None) + + name = unquote(nv[0].replace('+', ' ')) + + value = None + if nv[1] is not None: + value = unquote(nv[1].replace('+', ' ')) + + l = retval.get(name, None) + if l is None: + l = retval[name] = [] + l.append(value) + + return retval + + +def _get_http_headers(req_env): + retval = {} + + for k, v in req_env.items(): + if k.startswith("HTTP_"): + key = k[5:].lower() + val = [v] + retval[key]= val + logger.debug("Add http header %r = %r", key, val) + + return retval + + +def _gen_http_headers(headers): + retval = [] + + for k,v in headers.items(): + if isinstance(v, (list, tuple)): + for v2 in v: + retval.append((k, v2)) + else: + retval.append((k, v)) + + return retval + + +class WsgiTransportContext(HttpTransportContext): + """The class that is used in the transport attribute of the + :class:`WsgiMethodContext` class.""" + + def __init__(self, parent, transport, req_env, content_type): + super(WsgiTransportContext, self).__init__(parent, transport, + req_env, content_type) + + self.req_env = self.req + """WSGI Request environment""" + + self.req_method = req_env.get('REQUEST_METHOD', None) + """HTTP Request verb, as a convenience to users.""" + + self.headers = _get_http_headers(self.req_env) + + def get_path(self): + return self.req_env['PATH_INFO'] + + def get_path_and_qs(self): + retval = quote(self.req_env.get('PATH_INFO', '')) + qs = self.req_env.get('QUERY_STRING', None) + if qs is not None: + retval += '?' + qs + return retval + + def get_cookie(self, key): + cookie_string = self.req_env.get('HTTP_COOKIE', None) + if cookie_string is None: + return + + cookie = SimpleCookie() + cookie.load(cookie_string) + + return cookie.get(key, None).value + + def get_request_method(self): + return self.req['REQUEST_METHOD'].upper() + + def get_request_content_type(self): + return self.req.get("CONTENT_TYPE", None) + + def get_peer(self): + addr, port = address_parser.get_ip(self.req),\ + address_parser.get_port(self.req) + + if address_parser.is_valid_ipv4(addr): + return Address(type=Address.TCP4, host=addr, port=port) + + if address_parser.is_valid_ipv6(addr): + return Address(type=Address.TCP6, host=addr, port=port) + + +class WsgiMethodContext(HttpMethodContext): + """The WSGI-Specific method context. WSGI-Specific information is stored in + the transport attribute using the :class:`WsgiTransportContext` class. + """ + + TransportContext = None + HttpTransportContext = WsgiTransportContext + + +class WsgiApplication(HttpBase): + """A `PEP-3333 `_ + compliant callable class. + + If you want to have a hard-coded URL in the wsdl document, this is how to do + it: :: + + wsgi_app = WsgiApplication(...) + wsgi_app.doc.wsdl11.build_interface_document("http://example.com") + + This is not strictly necessary -- if you don't do this, Spyne will get the + URL from the first request, build the wsdl on-the-fly and cache it as a + string in memory for later requests. However, if you want to make sure + you only have this url on the WSDL, this is how to do it. Note that if + your client takes the information in the Wsdl document seriously (not all + do), all requests will go to the designated url above even when you get the + Wsdl from another location, which can make testing a bit difficult. Use in + moderation. + + Supported events: + * ``wsdl`` + Called right before the wsdl data is returned to the client. + + * ``wsdl_exception`` + Called right after an exception is thrown during wsdl generation. + The exception object is stored in ctx.transport.wsdl_error + attribute. + + * ``wsgi_call`` + Called first when the incoming http request is identified as a rpc + request. + + * ``wsgi_return`` + Called right before the output stream is returned to the WSGI + handler. + + * ``wsgi_exception`` + Called right before returning the exception to the client. + + * ``wsgi_close`` + Called after the whole data has been returned to the client. It's + called both from success and error cases. + """ + + def __init__(self, app, chunked=True, max_content_length=2 * 1024 * 1024, + block_length=8 * 1024): + super(WsgiApplication, self).__init__(app, chunked, max_content_length, + block_length) + + self._mtx_build_interface_document = threading.Lock() + + self._wsdl = None + if self.doc.wsdl11 is not None: + self._wsdl = self.doc.wsdl11.get_interface_document() + + def __call__(self, req_env, start_response, wsgi_url=None): + """This method conforms to the WSGI spec for callable wsgi applications + (PEP 333). It looks in environ['wsgi.input'] for a fully formed rpc + message envelope, will deserialize the request parameters and call the + method on the object returned by the get_handler() method. + """ + + url = wsgi_url + if url is None: + url = _reconstruct_url(req_env).split('.wsdl')[0] + + if self.is_wsdl_request(req_env): + # Format the url for location + url = url.split('?')[0].split('.wsdl')[0] + return self.handle_wsdl_request(req_env, start_response, url) + + else: + return self.handle_rpc(req_env, start_response) + + def is_wsdl_request(self, req_env): + # Get the wsdl for the service. Assume path_info matches pattern: + # /stuff/stuff/stuff/serviceName.wsdl or + # /stuff/stuff/stuff/serviceName/?wsdl + + return ( + req_env['REQUEST_METHOD'].upper() == 'GET' + and ( + ( + 'QUERY_STRING' in req_env + and req_env['QUERY_STRING'].split('=')[0].lower() == 'wsdl' + ) + or req_env['PATH_INFO'].endswith('.wsdl') + ) + ) + + def handle_wsdl_request(self, req_env, start_response, url): + ctx = WsgiMethodContext(self, req_env, 'text/xml; charset=utf-8') + + if self.doc.wsdl11 is None: + start_response(HTTP_404, + _gen_http_headers(ctx.transport.resp_headers)) + return [HTTP_404] + + if self._wsdl is None: + self._wsdl = self.doc.wsdl11.get_interface_document() + + ctx.transport.wsdl = self._wsdl + + if ctx.transport.wsdl is None: + try: + self._mtx_build_interface_document.acquire() + + ctx.transport.wsdl = self._wsdl + + if ctx.transport.wsdl is None: + self.doc.wsdl11.build_interface_document(url) + ctx.transport.wsdl = self._wsdl = \ + self.doc.wsdl11.get_interface_document() + + except Exception as e: + logger.exception(e) + ctx.transport.wsdl_error = e + + self.event_manager.fire_event('wsdl_exception', ctx) + + start_response(HTTP_500, + _gen_http_headers(ctx.transport.resp_headers)) + + return [HTTP_500] + + finally: + self._mtx_build_interface_document.release() + + self.event_manager.fire_event('wsdl', ctx) + + ctx.transport.resp_headers['Content-Length'] = \ + str(len(ctx.transport.wsdl)) + start_response(HTTP_200, _gen_http_headers(ctx.transport.resp_headers)) + + retval = ctx.transport.wsdl + return [retval] + + def handle_error(self, p_ctx, others, error, start_response): + """Serialize errors to an iterable of strings and return them. + + :param p_ctx: Primary (non-aux) context. + :param others: List if auxiliary contexts (can be empty). + :param error: One of ctx.{in,out}_error. + :param start_response: See the WSGI spec for more info. + """ + + if p_ctx.transport.resp_code is None: + p_ctx.transport.resp_code = \ + p_ctx.out_protocol.fault_to_http_response_code(error) + + self.get_out_string(p_ctx) + + # consume the generator to get the length + p_ctx.out_string = list(p_ctx.out_string) + + p_ctx.transport.resp_headers['Content-Length'] = \ + str(sum((len(s) for s in p_ctx.out_string))) + self.event_manager.fire_event('wsgi_exception', p_ctx) + + start_response(p_ctx.transport.resp_code, + _gen_http_headers(p_ctx.transport.resp_headers)) + + try: + process_contexts(self, others, p_ctx, error=error) + except Exception as e: + # Report but ignore any exceptions from auxiliary methods. + logger.exception(e) + + return chain(p_ctx.out_string, self.__finalize(p_ctx)) + + def handle_rpc(self, req_env, start_response): + initial_ctx = WsgiMethodContext(self, req_env, + self.app.out_protocol.mime_type) + + self.event_manager.fire_event('wsgi_call', initial_ctx) + initial_ctx.in_string, in_string_charset = \ + self.__reconstruct_wsgi_request(req_env) + + contexts = self.generate_contexts(initial_ctx, in_string_charset) + p_ctx, others = contexts[0], contexts[1:] + + # TODO: rate limiting + p_ctx.active = True + + if p_ctx.in_error: + return self.handle_error(p_ctx, others, p_ctx.in_error, + start_response) + + self.get_in_object(p_ctx) + if p_ctx.in_error: + logger.error(p_ctx.in_error) + return self.handle_error(p_ctx, others, p_ctx.in_error, + start_response) + + self.get_out_object(p_ctx) + if p_ctx.out_error: + return self.handle_error(p_ctx, others, p_ctx.out_error, + start_response) + + assert p_ctx.out_object is not None + g = next(iter(p_ctx.out_object)) + is_generator = len(p_ctx.out_object) == 1 and isgenerator(g) + + # if the out_object is a generator function, this hack makes the user + # code run until first yield, which lets it set response headers and + # whatnot before calling start_response. It's important to run this + # here before serialization as the user function can also set output + # protocol. Is there a better way? + if is_generator: + first_obj = next(g) + p_ctx.out_object = ( chain((first_obj,), g), ) + + if p_ctx.transport.resp_code is None: + p_ctx.transport.resp_code = HTTP_200 + + try: + self.get_out_string(p_ctx) + + except Exception as e: + logger.exception(e) + p_ctx.out_error = Fault('Server', get_fault_string_from_exception(e)) + return self.handle_error(p_ctx, others, p_ctx.out_error, + start_response) + + + if isinstance(p_ctx.out_protocol, HttpRpc) and \ + p_ctx.out_header_doc is not None: + p_ctx.transport.resp_headers.update(p_ctx.out_header_doc) + + if p_ctx.descriptor and p_ctx.descriptor.mtom: + # when there is more than one return type, the result is + # encapsulated inside a list. when there's just one, the result + # is returned in a non-encapsulated form. the apply_mtom always + # expects the objects to be inside an iterable, hence the + # following test. + out_type_info = p_ctx.descriptor.out_message._type_info + if len(out_type_info) == 1: + p_ctx.out_object = [p_ctx.out_object] + + p_ctx.transport.resp_headers, p_ctx.out_string = apply_mtom( + p_ctx.transport.resp_headers, p_ctx.out_string, + p_ctx.descriptor.out_message._type_info.values(), + p_ctx.out_object, + ) + + self.event_manager.fire_event('wsgi_return', p_ctx) + + if self.chunked: + # the user has not set a content-length, so we delete it as the + # input is just an iterable. + if 'Content-Length' in p_ctx.transport.resp_headers: + del p_ctx.transport.resp_headers['Content-Length'] + else: + p_ctx.out_string = [''.join(p_ctx.out_string)] + + try: + len(p_ctx.out_string) + + p_ctx.transport.resp_headers['Content-Length'] = \ + str(sum([len(a) for a in p_ctx.out_string])) + except TypeError: + pass + + start_response(p_ctx.transport.resp_code, + _gen_http_headers(p_ctx.transport.resp_headers)) + + retval = chain(p_ctx.out_string, self.__finalize(p_ctx)) + + try: + process_contexts(self, others, p_ctx, error=None) + except Exception as e: + # Report but ignore any exceptions from auxiliary methods. + logger.exception(e) + + return retval + + def __finalize(self, p_ctx): + p_ctx.close() + self.event_manager.fire_event('wsgi_close', p_ctx) + + return () + + def __reconstruct_wsgi_request(self, http_env): + """Reconstruct http payload using information in the http header.""" + + content_type = http_env.get("CONTENT_TYPE") + charset = None + if content_type is not None: + # fyi, here's what the parse_header function returns: + # >>> import cgi; cgi.parse_header("text/xml; charset=utf-8") + # ('text/xml', {'charset': 'utf-8'}) + content_type = cgi.parse_header(content_type) + charset = content_type[1].get('charset', None) + + return self.__wsgi_input_to_iterable(http_env), charset + + def __wsgi_input_to_iterable(self, http_env): + istream = http_env.get('wsgi.input') + + length = str(http_env.get('CONTENT_LENGTH', self.max_content_length)) + if len(length) == 0: + length = 0 + else: + length = int(length) + + if length > self.max_content_length: + raise RequestTooLongError() + bytes_read = 0 + + while bytes_read < length: + bytes_to_read = min(self.block_length, length - bytes_read) + + if bytes_to_read + bytes_read > self.max_content_length: + raise RequestTooLongError() + + data = istream.read(bytes_to_read) + if data is None or len(data) == 0: + break + + bytes_read += len(data) + + yield data + + def decompose_incoming_envelope(self, prot, ctx, message): + """This function is only called by the HttpRpc protocol to have the wsgi + environment parsed into ``ctx.in_body_doc`` and ``ctx.in_header_doc``. + """ + + params = {} + wsgi_env = ctx.in_document + + if self.has_patterns: + # http://legacy.python.org/dev/peps/pep-0333/#url-reconstruction + domain = wsgi_env.get('HTTP_HOST', None) + if domain is None: + domain = wsgi_env['SERVER_NAME'] + else: + domain = domain.partition(':')[0] # strip port info + + params = self.match_pattern(ctx, + wsgi_env.get('REQUEST_METHOD', ''), + wsgi_env.get('PATH_INFO', ''), + domain, + ) + + if ctx.method_request_string is None: + ctx.method_request_string = '{%s}%s' % ( + prot.app.interface.get_tns(), + wsgi_env['PATH_INFO'].split('/')[-1]) + + logger.debug("%sMethod name: %r%s" % (LIGHT_GREEN, + ctx.method_request_string, END_COLOR)) + + ctx.in_header_doc = ctx.transport.headers + ctx.in_body_doc = _parse_qs(wsgi_env['QUERY_STRING']) + + for k, v in params.items(): + if k in ctx.in_body_doc: + ctx.in_body_doc[k].extend(v) + else: + ctx.in_body_doc[k] = list(v) + + verb = wsgi_env['REQUEST_METHOD'].upper() + if verb in ('POST', 'PUT', 'PATCH'): + stream, form, files = parse_form_data(wsgi_env, + stream_factory=prot.stream_factory) + + for k, v in form.lists(): + val = ctx.in_body_doc.get(k, []) + val.extend(v) + ctx.in_body_doc[k] = val + + for k, v in files.items(): + val = ctx.in_body_doc.get(k, []) + + mime_type = v.headers.get('Content-Type', + 'application/octet-stream') + + path = getattr(v.stream, 'name', None) + if path is None: + val.append(File.Value(name=v.filename, type=mime_type, + data=[v.stream.getvalue()])) + else: + v.stream.seek(0) + val.append(File.Value(name=v.filename, type=mime_type, + path=path, handle=v.stream)) + + ctx.in_body_doc[k] = val + + for k, v in ctx.in_body_doc.items(): + if v == ['']: + ctx.in_body_doc[k] = [None] diff --git a/pym/calculate/contrib/spyne/server/wsgi.pyc b/pym/calculate/contrib/spyne/server/wsgi.pyc new file mode 100644 index 0000000..8c7a652 Binary files /dev/null and b/pym/calculate/contrib/spyne/server/wsgi.pyc differ diff --git a/pym/calculate/contrib/spyne/server/zeromq.py b/pym/calculate/contrib/spyne/server/zeromq.py new file mode 100644 index 0000000..f897d55 --- /dev/null +++ b/pym/calculate/contrib/spyne/server/zeromq.py @@ -0,0 +1,164 @@ +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""The ``spyne.server.zeromq`` module contains a server implementation that +uses ZeroMQ (zmq.REP) as transport. +""" +import threading + +import zmq + +from spyne.auxproc import process_contexts +from spyne.context import MethodContext +from spyne.server import ServerBase + + +class ZmqMethodContext(MethodContext): + def __init__(self, app): + super(ZmqMethodContext, self).__init__(app, MethodContext.SERVER) + self.transport.type = 'zmq' + + +class ZeroMQServer(ServerBase): + """The ZeroMQ server transport.""" + transport = 'http://rfc.zeromq.org/' + + def __init__(self, app, app_url, wsdl_url=None, ctx=None, socket=None): + if ctx and socket and ctx is not socket.context: + raise ValueError("ctx should be the same as socket.context") + super(ZeroMQServer, self).__init__(app) + + self.app_url = app_url + self.wsdl_url = wsdl_url + + if ctx: + self.ctx = ctx + elif socket: + self.ctx = socket.context + else: + self.ctx = zmq.Context() + + if socket: + self.zmq_socket = socket + else: + self.zmq_socket = self.ctx.socket(zmq.REP) + self.zmq_socket.bind(app_url) + + def __handle_wsdl_request(self): + return self.app.get_interface_document(self.url) + + # FIXME: Add suport for binary-only transports + def generate_contexts(self, ctx, in_string_charset='utf8'): + return super(ZeroMQServer, self).generate_contexts(ctx, + in_string_charset=in_string_charset) + + def serve_forever(self): + """Runs the ZeroMQ server.""" + + while True: + error = None + + initial_ctx = ZmqMethodContext(self) + initial_ctx.in_string = [self.zmq_socket.recv()] + + contexts = self.generate_contexts(initial_ctx) + p_ctx, others = contexts[0], contexts[1:] + + # TODO: Rate limiting + p_ctx.active = True + + if p_ctx.in_error: + p_ctx.out_object = p_ctx.in_error + error = p_ctx.in_error + + else: + self.get_in_object(p_ctx) + + if p_ctx.in_error: + p_ctx.out_object = p_ctx.in_error + error = p_ctx.in_error + else: + self.get_out_object(p_ctx) + if p_ctx.out_error: + p_ctx.out_object = p_ctx.out_error + error = p_ctx.out_error + + self.get_out_string(p_ctx) + + process_contexts(self, others, error) + + self.zmq_socket.send(b''.join(p_ctx.out_string)) + + p_ctx.close() + + +class ZeroMQThreadPoolServer(object): + """Create a ZeroMQ server transport with several background workers, + allowing asynchronous calls. + + More details on the pattern http://zguide.zeromq.org/page:all#Shared-Queue-DEALER-and-ROUTER-sockets""" + + def __init__(self, app, app_url, pool_size, wsdl_url=None, ctx=None, socket=None): + if ctx and socket and ctx is not socket.context: + raise ValueError("ctx should be the same as socket.context") + + self.app = app + + if ctx: + self.ctx = ctx + elif socket: + self.ctx = socket.context + else: + self.ctx = zmq.Context() + + if socket: + self.frontend = socket + else: + self.frontend = self.ctx.socket(zmq.ROUTER) + self.frontend.bind(app_url) + + be_url = 'inproc://{tns}.{name}'.format(tns=self.app.tns, name=self.app.name) + self.pool = [] + self.background_jobs = [] + for i in range(pool_size): + worker, job = self.create_worker(i, be_url) + self.pool.append(worker) + self.background_jobs.append(job) + + self.backend = self.ctx.socket(zmq.DEALER) + self.backend.bind(be_url) + + def create_worker(self, i, be_url): + socket = self.ctx.socket(zmq.REP) + socket.connect(be_url) + worker = ZeroMQServer(self.app, be_url, socket=socket) + job = threading.Thread(target=worker.serve_forever) + job.daemon = True + return worker, job + + def serve_forever(self): + """Runs the ZeroMQ server.""" + + for job in self.background_jobs: + job.start() + + zmq.device(zmq.QUEUE, self.frontend, self.backend) + + # We never get here... + self.frontend.close() + self.backend.close() diff --git a/pym/calculate/contrib/spyne/server/zeromq.pyc b/pym/calculate/contrib/spyne/server/zeromq.pyc new file mode 100644 index 0000000..01186b6 Binary files /dev/null and b/pym/calculate/contrib/spyne/server/zeromq.pyc differ diff --git a/pym/calculate/contrib/spyne/service.py b/pym/calculate/contrib/spyne/service.py new file mode 100644 index 0000000..5b02c3a --- /dev/null +++ b/pym/calculate/contrib/spyne/service.py @@ -0,0 +1,251 @@ + +# +# 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:`Service` class and its helper objects. +""" + +import logging +logger = logging.getLogger(__name__) + +from spyne.util.six.moves.collections_abc import Sequence + +from spyne.evmgr import EventManager +from spyne.util import six +from spyne.util.oset import oset + + +class ServiceBaseMeta(type): + """Adds event managers.""" + + def __init__(self, cls_name, cls_bases, cls_dict): + super(ServiceBaseMeta, self).__init__(cls_name, cls_bases, cls_dict) + + self.public_methods = {} + self.event_manager = EventManager(self, + self.__get_base_event_handlers(cls_bases)) + + 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 + +class ServiceMeta(ServiceBaseMeta): + """Creates the :class:`spyne.MethodDescriptor` objects by iterating over + tagged methods. + """ + + def __init__(self, cls_name, cls_bases, cls_dict): + super(ServiceMeta, self).__init__(cls_name, cls_bases, cls_dict) + + self.__has_aux_methods = self.__aux__ is not None + has_nonaux_methods = None + + for k, v in cls_dict.items(): + if not hasattr(v, '_is_rpc'): + continue + + descriptor = v(_default_function_name=k, _service_class=self) + + # 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: + pass + # FIXME: this fails with builtins. Temporary hack while we + # investigate whether we really need this or not + + self.public_methods[k] = descriptor + if descriptor.aux is None and self.__aux__ is None: + has_nonaux_methods = True + else: + self.__has_aux_methods = True + + if self.__has_aux_methods and has_nonaux_methods: + raise Exception("You can't mix primary and " + "auxiliary methods in a single service definition.") + + def is_auxiliary(self): + return self.__has_aux_methods + + +# FIXME: To be renamed to ServiceBase in Spyne 3 +@six.add_metaclass(ServiceBaseMeta) +class ServiceBaseBase(object): + __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.""" + + __service_module__ = None + """This is used for internal idenfitication of the service class, + to override the ``__module__`` attribute.""" + + __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_name(cls): + if cls.__service_name__ is None: + return cls.__name__ + else: + return cls.__service_name__ + + @classmethod + def get_service_module(cls): + if cls.__service_module__ is None: + return cls.__module__ + else: + return cls.__service_module__ + + @classmethod + def get_internal_key(cls): + return "%s.%s" % (cls.get_service_module(), cls.get_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 get_context(cls): + """Returns a user defined context. Override this in your ServiceBase + subclass to customize context generation.""" + return None + + @classmethod + def call_wrapper(cls, ctx, args=None): + """Called in place of the original method call. You can override this to + do your own exception handling. + + :param ctx: The method context. + + The overriding function must call this function by convention. + """ + + if ctx.function is not None: + if args is None: + args = ctx.in_object + + assert not isinstance(args, six.string_types) + + # python3 wants a proper sequence as *args + if not isinstance(args, Sequence): + args = tuple(args) + + if not ctx.descriptor.no_ctx: + args = (ctx,) + tuple(args) + + return ctx.function(*args) + + @classmethod + def initialize(cls, app): + pass + + +@six.add_metaclass(ServiceMeta) +class Service(ServiceBaseBase): + """The ``Service`` 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 Service 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. + """ + + +# FIXME: To be deleted in Spyne 3 +ServiceBase = Service diff --git a/pym/calculate/contrib/spyne/service.pyc b/pym/calculate/contrib/spyne/service.pyc new file mode 100644 index 0000000..68c46cb Binary files /dev/null and b/pym/calculate/contrib/spyne/service.pyc differ diff --git a/pym/calculate/contrib/spyne/store/__init__.py b/pym/calculate/contrib/spyne/store/__init__.py new file mode 100644 index 0000000..c597ee3 --- /dev/null +++ b/pym/calculate/contrib/spyne/store/__init__.py @@ -0,0 +1,20 @@ + +# +# 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 is the spyne storage package.""" diff --git a/pym/calculate/contrib/spyne/store/__init__.pyc b/pym/calculate/contrib/spyne/store/__init__.pyc new file mode 100644 index 0000000..e4fc23e Binary files /dev/null and b/pym/calculate/contrib/spyne/store/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/store/relational/__init__.py b/pym/calculate/contrib/spyne/store/relational/__init__.py new file mode 100644 index 0000000..642d3f3 --- /dev/null +++ b/pym/calculate/contrib/spyne/store/relational/__init__.py @@ -0,0 +1,35 @@ + +# +# 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 Postgresql serializer for Spyne objects. + +Uses SQLAlchemy for mapping objects to relations. +""" + +from spyne.store.relational._base import add_column +from spyne.store.relational._base import gen_sqla_info +from spyne.store.relational._base import gen_spyne_info +from spyne.store.relational._base import get_pk_columns + +from spyne.store.relational.document import PGXml, PGObjectXml, PGHtml, \ + PGJson, PGJsonB, PGObjectJson, PGFileJson +from spyne.store.relational.simple import PGLTree, PGLQuery, PGLTxtQuery +from spyne.store.relational.spatial import PGGeometry + +from spyne.store.relational import override diff --git a/pym/calculate/contrib/spyne/store/relational/__init__.pyc b/pym/calculate/contrib/spyne/store/relational/__init__.pyc new file mode 100644 index 0000000..42f359a Binary files /dev/null and b/pym/calculate/contrib/spyne/store/relational/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/store/relational/_base.py b/pym/calculate/contrib/spyne/store/relational/_base.py new file mode 100644 index 0000000..8c2178f --- /dev/null +++ b/pym/calculate/contrib/spyne/store/relational/_base.py @@ -0,0 +1,1203 @@ + +# +# 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, print_function + +import logging +logger = logging.getLogger(__name__) + +import sqlalchemy + +try: + import simplejson as json +except ImportError: + import json + +from os.path import isabs +from inspect import isclass + +from sqlalchemy import event +from sqlalchemy.schema import Column +from sqlalchemy.schema import Index +from sqlalchemy.schema import Table +from sqlalchemy.schema import ForeignKey +from sqlalchemy.orm import _mapper_registry + +from sqlalchemy.dialects.postgresql import FLOAT +from sqlalchemy.dialects.postgresql import DOUBLE_PRECISION +from sqlalchemy.dialects.postgresql.base import PGUuid, PGInet + +from sqlalchemy.orm import relationship +from sqlalchemy.orm import mapper +from sqlalchemy.ext.associationproxy import association_proxy + +from spyne.store.relational.simple import PGLTree +from spyne.store.relational.document import PGXml, PGObjectXml, PGObjectJson, \ + PGFileJson, PGJsonB, PGHtml, PGJson +from spyne.store.relational.spatial import PGGeometry + +# internal types +from spyne.model.enum import EnumBase +from spyne.model.complex import XmlModifier + +# Config types +from spyne.model import xml as c_xml +from spyne.model import json as c_json +from spyne.model import jsonb as c_jsonb +from spyne.model import table as c_table +from spyne.model import msgpack as c_msgpack +from spyne.model.binary import HybridFileStore + +# public types +from spyne.model import SimpleModel, Enum, Array, ComplexModelBase, \ + Any, AnyDict, AnyXml, AnyHtml, \ + Date, Time, DateTime, Duration, \ + ByteArray, String, Unicode, Uuid, Boolean, \ + Point, Line, Polygon, MultiPoint, MultiLine, MultiPolygon, \ + Float, Double, Decimal, \ + Integer, Integer8, Integer16, Integer32, Integer64, \ + UnsignedInteger, UnsignedInteger8, UnsignedInteger16, UnsignedInteger32, \ + UnsignedInteger64, \ + Ipv6Address, Ipv4Address, IpAddress, \ + File, Ltree + +from spyne.util import sanitize_args + + +# Inheritance type constants. +class _SINGLE: + pass + +class _JOINED: + pass + + +_sq2sp_type_map = { + # we map float => double because sqla doesn't + # distinguish between floats and doubles. + sqlalchemy.Float: Double, + sqlalchemy.FLOAT: Double, + + sqlalchemy.Numeric: Decimal, + sqlalchemy.NUMERIC: Decimal, + + sqlalchemy.BigInteger: Integer64, + sqlalchemy.BIGINT: Integer64, + + sqlalchemy.Integer: Integer32, + sqlalchemy.INTEGER: Integer32, + + sqlalchemy.SmallInteger: Integer16, + sqlalchemy.SMALLINT: Integer16, + + sqlalchemy.Binary: ByteArray, + sqlalchemy.LargeBinary: ByteArray, + + sqlalchemy.Boolean: Boolean, + sqlalchemy.BOOLEAN: Boolean, + + sqlalchemy.DateTime: DateTime, + sqlalchemy.TIMESTAMP: DateTime, + sqlalchemy.dialects.postgresql.base.TIMESTAMP: DateTime, + sqlalchemy.DATETIME: DateTime, + sqlalchemy.dialects.postgresql.base.INTERVAL: Duration, + + sqlalchemy.Date: Date, + sqlalchemy.DATE: Date, + + sqlalchemy.Time: Time, + sqlalchemy.TIME: Time, + + PGUuid: Uuid, + PGLTree: Ltree, + PGInet: IpAddress, +} + + +# this needs to be called whenever a new column is instantiated. +def _sp_attrs_to_sqla_constraints(cls, subcls, col_kwargs=None, col=None): + # cls is the parent class of v + if subcls.Attributes.nullable == False and cls.__extends__ is None: + if col is None: + col_kwargs['nullable'] = False + else: + col.nullable = False + + if subcls.Attributes.db_default is not None: + if col is None: + col_kwargs['default'] = subcls.Attributes.db_default + else: + col.default = subcls.Attributes.db_default + + +def _get_sqlalchemy_type(cls): + db_type = cls.Attributes.db_type + if db_type is not None: + return db_type + + # must be above Unicode, because Ltree is Unicode's subclass + if issubclass(cls, Ltree): + return PGLTree + + # must be above Unicode, because Ip*Address is Unicode's subclass + if issubclass(cls, (IpAddress, Ipv4Address, Ipv6Address)): + return PGInet + + # must be above Unicode, because Uuid is Unicode's subclass + if issubclass(cls, Uuid): + return PGUuid(as_uuid=True) + + # must be above Unicode, because Point is Unicode's subclass + if issubclass(cls, Point): + return PGGeometry("POINT", dimension=cls.Attributes.dim) + + # must be above Unicode, because Line is Unicode's subclass + if issubclass(cls, Line): + return PGGeometry("LINESTRING", dimension=cls.Attributes.dim) + + # must be above Unicode, because Polygon is Unicode's subclass + if issubclass(cls, Polygon): + return PGGeometry("POLYGON", dimension=cls.Attributes.dim) + + # must be above Unicode, because MultiPoint is Unicode's subclass + if issubclass(cls, MultiPoint): + return PGGeometry("MULTIPOINT", dimension=cls.Attributes.dim) + + # must be above Unicode, because MultiLine is Unicode's subclass + if issubclass(cls, MultiLine): + return PGGeometry("MULTILINESTRING", dimension=cls.Attributes.dim) + + # must be above Unicode, because MultiPolygon is Unicode's subclass + if issubclass(cls, MultiPolygon): + return PGGeometry("MULTIPOLYGON", dimension=cls.Attributes.dim) + + # must be above Unicode, because String is Unicode's subclass + if issubclass(cls, String): + if cls.Attributes.max_len == String.Attributes.max_len: # Default is arbitrary-length + return sqlalchemy.Text + else: + return sqlalchemy.String(cls.Attributes.max_len) + + if issubclass(cls, Unicode): + if cls.Attributes.max_len == Unicode.Attributes.max_len: # Default is arbitrary-length + return sqlalchemy.UnicodeText + else: + return sqlalchemy.Unicode(cls.Attributes.max_len) + + if issubclass(cls, EnumBase): + return sqlalchemy.Enum(*cls.__values__, name=cls.__type_name__) + + if issubclass(cls, AnyXml): + return PGXml + + if issubclass(cls, AnyHtml): + return PGHtml + + if issubclass(cls, (Any, AnyDict)): + sa = cls.Attributes.store_as + if sa is None: + return None + if isinstance(sa, c_json): + return PGJson + if isinstance(sa, c_jsonb): + return PGJsonB + raise NotImplementedError(dict(cls=cls, store_as=sa)) + + if issubclass(cls, ByteArray): + return sqlalchemy.LargeBinary + + if issubclass(cls, (Integer64, UnsignedInteger64)): + return sqlalchemy.BigInteger + + if issubclass(cls, (Integer32, UnsignedInteger32)): + return sqlalchemy.Integer + + if issubclass(cls, (Integer16, UnsignedInteger16)): + return sqlalchemy.SmallInteger + + if issubclass(cls, (Integer8, UnsignedInteger8)): + return sqlalchemy.SmallInteger + + if issubclass(cls, Float): + return FLOAT + + if issubclass(cls, Double): + return DOUBLE_PRECISION + + if issubclass(cls, (Integer, UnsignedInteger)): + return sqlalchemy.DECIMAL + + if issubclass(cls, Decimal): + return sqlalchemy.DECIMAL + + if issubclass(cls, Boolean): + if cls.Attributes.store_as is bool: + return sqlalchemy.Boolean + if cls.Attributes.store_as is int: + return sqlalchemy.SmallInteger + + raise ValueError("Boolean.store_as has invalid value %r" % + cls.Attributes.store_as) + + if issubclass(cls, Date): + return sqlalchemy.Date + + if issubclass(cls, DateTime): + if cls.Attributes.timezone is None: + if cls.Attributes.as_timezone is None: + return sqlalchemy.DateTime(timezone=True) + else: + return sqlalchemy.DateTime(timezone=False) + else: + return sqlalchemy.DateTime(timezone=cls.Attributes.timezone) + + if issubclass(cls, Time): + return sqlalchemy.Time + + if issubclass(cls, Duration): + return sqlalchemy.dialects.postgresql.base.INTERVAL + + if issubclass(cls, XmlModifier): + retval = _get_sqlalchemy_type(cls.type) + return retval + + +def _get_col_o2o(parent, subname, subcls, fk_col_name, deferrable=None, + initially=None, ondelete=None, onupdate=None): + """Gets key and child type and returns a column that points to the primary + key of the child. + """ + + assert subcls.Attributes.table_name is not None, \ + "%r has no table name." % subcls + + col_args, col_kwargs = sanitize_args(subcls.Attributes.sqla_column_args) + _sp_attrs_to_sqla_constraints(parent, subcls, col_kwargs) + + # get pkeys from child class + pk_column, = get_pk_columns(subcls) # FIXME: Support multi-col keys + + pk_key, pk_spyne_type = pk_column + pk_sqla_type = _get_sqlalchemy_type(pk_spyne_type) + + # generate a fk to it from the current object (cls) + if 'name' in col_kwargs: + colname = col_kwargs.pop('name') + else: + colname = subname + + if fk_col_name is None: + fk_col_name = colname + "_" + pk_key + + assert fk_col_name != colname, \ + "The column name for the foreign key must be different from the " \ + "column name for the object itself." + + fk = ForeignKey( + '%s.%s' % (subcls.Attributes.table_name, pk_key), + use_alter=True, + name='%s_%s_fkey' % (subcls.Attributes.table_name, fk_col_name), + deferrable=deferrable, initially=initially, + ondelete=ondelete, onupdate=onupdate, + ) + + return Column(fk_col_name, pk_sqla_type, fk, **col_kwargs) + + +def _get_col_o2m(cls, fk_col_name, deferrable=None, initially=None, + ondelete=None, onupdate=None): + """Gets the parent class and returns a column that points to the primary key + of the parent. + """ + + assert cls.Attributes.table_name is not None, "%r has no table name." % cls + col_args, col_kwargs = sanitize_args(cls.Attributes.sqla_column_args) + + # get pkeys from current class + pk_column, = get_pk_columns(cls) # FIXME: Support multi-col keys + + pk_key, pk_spyne_type = pk_column + pk_sqla_type = _get_sqlalchemy_type(pk_spyne_type) + + # generate a fk from child to the current class + if fk_col_name is None: + fk_col_name = '_'.join([cls.Attributes.table_name, pk_key]) + + # we jump through all these hoops because we must instantiate the Column + # only after we're sure that it doesn't already exist and also because + # tinkering with functors is always fun :) + yield [(fk_col_name, pk_sqla_type)] + + fk = ForeignKey('%s.%s' % (cls.Attributes.table_name, pk_key), + deferrable=deferrable, initially=initially, + ondelete=ondelete, onupdate=onupdate) + col = Column(fk_col_name, pk_sqla_type, fk, **col_kwargs) + + yield col + + +def _get_cols_m2m(cls, k, child, fk_left_col_name, fk_right_col_name, + fk_left_deferrable, fk_left_initially, + fk_right_deferrable, fk_right_initially, + fk_left_ondelete, fk_left_onupdate, + fk_right_ondelete, fk_right_onupdate): + """Gets the parent and child classes and returns foreign keys to both + tables. These columns can be used to create a relation table.""" + + col_info, left_col = _get_col_o2m(cls, fk_left_col_name, + ondelete=fk_left_ondelete, onupdate=fk_left_onupdate, + deferrable=fk_left_deferrable, initially=fk_left_initially) + right_col = _get_col_o2o(cls, k, child, fk_right_col_name, + ondelete=fk_right_ondelete, onupdate=fk_right_onupdate, + deferrable=fk_right_deferrable, initially=fk_right_initially) + left_col.primary_key = right_col.primary_key = True + return left_col, right_col + + +class _FakeTable(object): + def __init__(self, name): + self.name = name + self.c = {} + self.columns = [] + self.indexes = [] + + def append_column(self, col): + self.columns.append(col) + self.c[col.name] = col + + +def _gen_index_info(table, col, k, v): + """ + :param table: sqla table + :param col: sqla col + :param k: field name (not necessarily == k) + :param v: spyne type + """ + + unique = v.Attributes.unique + index = v.Attributes.index + if unique and not index: + index = True + + try: + index_name, index_method = index + + except (TypeError, ValueError): + index_name = "%s_%s%s" % (table.name, k, '_unique' if unique else '') + index_method = index + + if index in (False, None): + return + + if index is True: + index_args = (index_name, col), dict(unique=unique) + else: + index_args = (index_name, col), dict(unique=unique, + postgresql_using=index_method) + + if isinstance(table, _FakeTable): + table.indexes.append(index_args) + + else: + indexes = dict([(idx.name, idx) for idx in col.table.indexes]) + existing_idx = indexes.get(index_name, None) + if existing_idx is None: + Index(*index_args[0], **index_args[1]) + + else: + assert existing_idx.unique == unique, \ + "Uniqueness flag differ between existing and current values. " \ + "Existing: {!r}, New: {!r}".format(existing_idx.unique, unique) + + existing_val = existing_idx.kwargs.get('postgresql_using') + + assert existing_val == index_method, \ + "Indexing methods differ between existing and current index " \ + "directives. Existing: {!r}, New: {!r}".format( + existing_val, index_method) + +def _check_inheritance(cls, cls_bases): + table_name = cls.Attributes.table_name + + inc = [] + inheritance = None + base_class = getattr(cls, '__extends__', None) + + if base_class is None: + for b in cls_bases: + if getattr(b, '_type_info', None) is not None and b.__mixin__: + base_class = b + + if base_class is not None: + base_table_name = base_class.Attributes.table_name + if base_table_name is not None: + if base_table_name == table_name: + inheritance = _SINGLE + else: + inheritance = _JOINED + raise NotImplementedError("Joined table inheritance is not yet " + "implemented.") + + # check whether the base classes are already mapped + base_mapper = None + if base_class is not None: + base_mapper = base_class.Attributes.sqla_mapper + + if base_mapper is None: + for b in cls_bases: + bm = _mapper_registry.get(b, None) + if bm is not None: + assert base_mapper is None, "There can be only one base mapper." + base_mapper = bm + inheritance = _SINGLE + + return inheritance, base_class, base_mapper, inc + + +def _check_table(cls): + table_name = cls.Attributes.table_name + metadata = cls.Attributes.sqla_metadata + + # check whether the object already has a table + table = None + if table_name in metadata.tables: + table = metadata.tables[table_name] + else: + # We need FakeTable because table_args can contain all sorts of stuff + # that can require a fully-constructed table, and we don't have that + # information here yet. + table = _FakeTable(table_name) + + return table + + +def _add_simple_type(cls, props, table, subname, subcls, sqla_type): + col_args, col_kwargs = sanitize_args(subcls.Attributes.sqla_column_args) + _sp_attrs_to_sqla_constraints(cls, subcls, col_kwargs) + + mp = getattr(subcls.Attributes, 'mapper_property', None) + + if 'name' in col_kwargs: + colname = col_kwargs.pop('name') + else: + colname = subname + + if not subcls.Attributes.exc_db: + if colname in table.c: + col = table.c[colname] + + else: + col = Column(colname, sqla_type, *col_args, **col_kwargs) + table.append_column(col) + _gen_index_info(table, col, subname, subcls) + + if not subcls.Attributes.exc_mapper: + props[subname] = col + + elif mp is not None: + props[subname] = mp + + +def _gen_array_m2m(cls, props, subname, arrser, storage): + """Generates a relational many-to-many array. + + :param cls: The class that owns the field + :param props: SQLAlchemy Mapper properties + :param subname: Field name + :param arrser: Array serializer, ie the __orig__ of the class inside the + Array object + :param storage: The storage configuration object passed to the store_as + attribute. + """ + + metadata = cls.Attributes.sqla_metadata + + col_own, col_child = _get_cols_m2m(cls, subname, arrser, + storage.left, storage.right, + storage.fk_left_deferrable, storage.fk_left_initially, + storage.fk_right_deferrable, storage.fk_right_initially, + storage.fk_left_ondelete, storage.fk_left_onupdate, + storage.fk_right_ondelete, storage.fk_right_onupdate) + + storage.left = col_own.key + storage.right = col_child.key + + # noinspection PySimplifyBooleanCheck because literal True means + # "generate table name automatically" here + if storage.multi is True: + rel_table_name = '_'.join([cls.Attributes.table_name, subname]) + else: + rel_table_name = storage.multi + + if rel_table_name in metadata.tables: + rel_t = metadata.tables[rel_table_name] + + col_own_existing = rel_t.c.get(col_own.key, None) + assert col_own_existing is not None + if col_own_existing is not None: + assert col_own.type.__class__ == col_own_existing.type.__class__ + + col_child_existing = rel_t.c.get(col_child.key, None) + if col_child_existing is None: + rel_t.append_column(col_child) + + else: + assert col_child.type.__class__ == col_child_existing.type.__class__ + + else: + rel_t = Table(rel_table_name, metadata, *(col_own, col_child)) + + own_t = cls.Attributes.sqla_table + + rel_kwargs = dict( + lazy=storage.lazy, + backref=storage.backref, + cascade=storage.cascade, + order_by=storage.order_by, + back_populates=storage.back_populates, + ) + + if storage.explicit_join: + # Specify primaryjoin and secondaryjoin when requested. + # There are special cases when sqlalchemy can't figure it out by itself. + # this is where we help it when we can. + # e.g.: http://sqlalchemy.readthedocs.org/en/rel_1_0/orm/join_conditions.html#self-referential-many-to-many-relationship + + assert own_t is not None and len(get_pk_columns(cls)) > 0 + + # FIXME: support more than one pk + (col_pk_key, _), = get_pk_columns(cls) + col_pk = own_t.c[col_pk_key] + + rel_kwargs.update(dict( + secondary=rel_t, + primaryjoin=(col_pk == rel_t.c[col_own.key]), + secondaryjoin=(col_pk == rel_t.c[col_child.key]), + )) + + if storage.single_parent is not None: + rel_kwargs['single_parent'] = storage.single_parent + + props[subname] = relationship(arrser, **rel_kwargs) + + else: + rel_kwargs.update(dict( + secondary=rel_t, + )) + + if storage.single_parent is not None: + rel_kwargs['single_parent'] = storage.single_parent + + props[subname] = relationship(arrser, **rel_kwargs) + + +def _gen_array_simple(cls, props, subname, arrser_cust, storage): + """Generate an array of simple objects. + + :param cls: The class that owns this field + :param props: SQLAlchemy Mapper properties + :param subname: Field name + :param arrser_cust: Array serializer, ie the class itself inside the Array + object + :param storage: The storage configuration object passed to the store_as + """ + + table_name = cls.Attributes.table_name + metadata = cls.Attributes.sqla_metadata + + # get left (fk) column info + _gen_col = _get_col_o2m(cls, storage.left, + ondelete=storage.fk_left_ondelete, onupdate=storage.fk_left_onupdate, + deferrable=storage.fk_left_deferrable, + initially=storage.fk_left_initially) + + col_info = next(_gen_col) # gets the column name + # FIXME: Add support for multi-column primary keys. + storage.left, child_left_col_type = col_info[0] + child_left_col_name = storage.left + + # get right(data) column info + child_right_col_type = _get_sqlalchemy_type(arrser_cust) + child_right_col_name = storage.right # this is the data column + if child_right_col_name is None: + child_right_col_name = subname + + # get table name + child_table_name = arrser_cust.Attributes.table_name + if child_table_name is None: + child_table_name = '_'.join([table_name, subname]) + + if child_table_name in metadata.tables: + child_t = metadata.tables[child_table_name] + + # if we have the table, make sure have the right column (data column) + assert child_right_col_type.__class__ is \ + child_t.c[child_right_col_name].type.__class__, "%s.%s: %r != %r" % \ + (cls, child_right_col_name, child_right_col_type.__class__, + child_t.c[child_right_col_name].type.__class__) + + if child_left_col_name in child_t.c: + assert child_left_col_type is \ + child_t.c[child_left_col_name].type.__class__, "%r != %r" % \ + (child_left_col_type, + child_t.c[child_left_col_name].type.__class__) + else: + # Table exists but our own foreign key doesn't. + child_left_col = next(_gen_col) + _sp_attrs_to_sqla_constraints(cls, arrser_cust, col=child_left_col) + child_t.append_column(child_left_col) + + else: + # table does not exist, generate table + child_right_col = Column(child_right_col_name, child_right_col_type) + _sp_attrs_to_sqla_constraints(cls, arrser_cust, col=child_right_col) + + child_left_col = next(_gen_col) + _sp_attrs_to_sqla_constraints(cls, arrser_cust, col=child_left_col) + + child_t = Table(child_table_name , metadata, + Column('id', sqlalchemy.Integer, primary_key=True), + child_left_col, + child_right_col, + ) + _gen_index_info(child_t, child_right_col, child_right_col_name, + arrser_cust) + + # generate temporary class for association proxy + cls_name = ''.join(x.capitalize() or '_' for x in + child_table_name.split('_')) + # generates camelcase class name. + + def _i(self, *args): + setattr(self, child_right_col_name, args[0]) + + cls_ = type("_" + cls_name, (object,), {'__init__': _i}) + mapper(cls_, child_t) + props["_" + subname] = relationship(cls_) + + # generate association proxy + setattr(cls, subname, + association_proxy("_" + subname, child_right_col_name)) + + +def _gen_array_o2m(cls, props, subname, arrser, arrser_cust, storage): + _gen_col = _get_col_o2m(cls, storage.right, + ondelete=storage.fk_right_ondelete, onupdate=storage.fk_right_onupdate, + deferrable=storage.fk_right_deferrable, + initially=storage.fk_right_initially) + + col_info = next(_gen_col) # gets the column name + storage.right, col_type = col_info[0] # FIXME: Add support for multi-column primary keys. + + assert storage.left is None, \ + "'left' is ignored in one-to-many relationships " \ + "with complex types (because they already have a " \ + "table). You probably meant to use 'right'." + + child_t = arrser.__table__ + + if storage.right in child_t.c: + # TODO: This branch MUST be tested. + new_col_type = child_t.c[storage.right].type.__class__ + assert col_type is child_t.c[storage.right].type.__class__, \ + "Existing column type %r disagrees with new column type %r" % \ + (col_type, new_col_type) + + # if the column is already there, the decision about whether + # it should be in child's mapper or not should also have been + # made. + # + # so, not adding the child column to to child mapper + # here. + col = child_t.c[storage.right] + + else: + col = next(_gen_col) + + _sp_attrs_to_sqla_constraints(cls, arrser_cust, col=col) + + child_t.append_column(col) + arrser.__mapper__.add_property(col.name, col) + + + rel_kwargs = dict( + lazy=storage.lazy, + backref=storage.backref, + cascade=storage.cascade, + order_by=storage.order_by, + foreign_keys=[col], + back_populates=storage.back_populates, + ) + + if storage.single_parent is not None: + rel_kwargs['single_parent'] = storage.single_parent + + props[subname] = relationship(arrser, **rel_kwargs) + + +def _is_array(v): + return v.Attributes.max_occurs > 1 or issubclass(v, Array) + + +def _add_array_to_complex(cls, props, subname, subcls, storage): + arrser_cust = subcls + if issubclass(subcls, Array): + arrser_cust, = subcls._type_info.values() + + arrser = arrser_cust + if arrser_cust.__orig__ is not None: + arrser = arrser_cust.__orig__ + + if storage.multi != False: # many to many + _gen_array_m2m(cls, props, subname, arrser, storage) + + elif issubclass(arrser, SimpleModel): # one to many simple type + _gen_array_simple(cls, props, subname, arrser_cust, storage) + + else: # one to many complex type + _gen_array_o2m(cls, props, subname, arrser, arrser_cust, storage) + + +def _add_simple_type_to_complex(cls, props, table, subname, subcls, storage, + col_kwargs): + # v has the Attribute values we need whereas real_v is what the + # user instantiates (thus what sqlalchemy needs) + if subcls.__orig__ is None: # vanilla class + real_v = subcls + else: # customized class + real_v = subcls.__orig__ + + assert not getattr(storage, 'multi', False), \ + 'Storing a single element-type using a relation table is pointless.' + + assert storage.right is None, \ + "'right' is ignored in a one-to-one relationship" + + col = _get_col_o2o(cls, subname, subcls, storage.left, + ondelete=storage.fk_left_ondelete, onupdate=storage.fk_left_onupdate, + deferrable=storage.fk_left_deferrable, + initially=storage.fk_left_initially) + + storage.left = col.name + + if col.name in table.c: + col = table.c[col.name] + if col_kwargs.get('nullable') is False: + col.nullable = False + else: + table.append_column(col) + + rel_kwargs = dict( + lazy=storage.lazy, + backref=storage.backref, + order_by=storage.order_by, + back_populates=storage.back_populates, + ) + + if storage.single_parent is not None: + rel_kwargs['single_parent'] = storage.single_parent + + if real_v is (cls.__orig__ or cls): + (pk_col_name, pk_col_type), = get_pk_columns(cls) + rel_kwargs['remote_side'] = [table.c[pk_col_name]] + + rel = relationship(real_v, uselist=False, foreign_keys=[col], + **rel_kwargs) + + _gen_index_info(table, col, subname, subcls) + + props[subname] = rel + props[col.name] = col + + +def _add_complex_type_as_table(cls, props, table, subname, subcls, storage, + col_args, col_kwargs): + # add one to many relation + if _is_array(subcls): + _add_array_to_complex(cls, props, subname, subcls, storage) + + # add one to one relation + else: + _add_simple_type_to_complex(cls, props, table, subname, subcls, + storage, col_kwargs) + + +def _add_complex_type_as_xml(cls, props, table, subname, subcls, storage, + col_args, col_kwargs): + if 'name' in col_kwargs: + colname = col_kwargs.pop('name') + else: + colname = subname + + if colname in table.c: + col = table.c[colname] + else: + t = PGObjectXml(subcls, storage.root_tag, storage.no_ns, + storage.pretty_print) + col = Column(colname, t, **col_kwargs) + + props[subname] = col + if not subname in table.c: + table.append_column(col) + + +def _add_complex_type_as_json(cls, props, table, subname, subcls, storage, + col_args, col_kwargs, dbt): + if 'name' in col_kwargs: + colname = col_kwargs.pop('name') + else: + colname = subname + + if colname in table.c: + col = table.c[colname] + + else: + t = PGObjectJson(subcls, ignore_wrappers=storage.ignore_wrappers, + complex_as=storage.complex_as, dbt=dbt) + col = Column(colname, t, **col_kwargs) + + props[subname] = col + if not subname in table.c: + table.append_column(col) + + +def _add_complex_type(cls, props, table, subname, subcls): + if issubclass(subcls, File): + return _add_file_type(cls, props, table, subname, subcls) + + storage = getattr(subcls.Attributes, 'store_as', None) + col_args, col_kwargs = sanitize_args(subcls.Attributes.sqla_column_args) + _sp_attrs_to_sqla_constraints(cls, subcls, col_kwargs) + + if isinstance(storage, c_table): + return _add_complex_type_as_table(cls, props, table, subname, subcls, + storage, col_args, col_kwargs) + if isinstance(storage, c_xml): + return _add_complex_type_as_xml(cls, props, table, subname, subcls, + storage, col_args, col_kwargs) + if isinstance(storage, c_json): + return _add_complex_type_as_json(cls, props, table, subname, subcls, + storage, col_args, col_kwargs, 'json') + if isinstance(storage, c_jsonb): + return _add_complex_type_as_json(cls, props, table, subname, subcls, + storage, col_args, col_kwargs, 'jsonb') + if isinstance(storage, c_msgpack): + raise NotImplementedError(c_msgpack) + + if storage is None: + return + + raise ValueError(storage) + + +def _convert_fake_table(cls, table): + metadata = cls.Attributes.sqla_metadata + table_name = cls.Attributes.table_name + + _table = table + table_args, table_kwargs = sanitize_args(cls.Attributes.sqla_table_args) + table = Table(table_name, metadata, + *(tuple(table.columns) + table_args), **table_kwargs) + + for index_args, index_kwargs in _table.indexes: + Index(*index_args, **index_kwargs) + + return table + + +def _gen_mapper(cls, props, table, cls_bases): + """Generate SQLAlchemy mapper from Spyne definition data. + + :param cls: La Class. + :param props: Dict of properties for SQLAlchemt'y Mapper call. + :param table: A Table instance. Not a `_FakeTable` or anything. + :param cls_bases: Sequence of class bases. + """ + + inheritance, base_class, base_mapper, inc = _check_inheritance(cls, cls_bases) + mapper_args, mapper_kwargs = sanitize_args(cls.Attributes.sqla_mapper_args) + + _props = mapper_kwargs.get('properties', None) + if _props is None: + mapper_kwargs['properties'] = props + else: + props.update(_props) + mapper_kwargs['properties'] = props + + po = mapper_kwargs.get('polymorphic_on', None) + if po is not None: + if not isinstance(po, Column): + mapper_kwargs['polymorphic_on'] = table.c[po] + else: + logger.warning("Deleted invalid 'polymorphic_on' value %r for %r.", + po, cls) + del mapper_kwargs['polymorphic_on'] + + if base_mapper is not None: + mapper_kwargs['inherits'] = base_mapper + + if inheritance is not _SINGLE: + mapper_args = (table,) + mapper_args + + cls_mapper = mapper(cls, *mapper_args, **mapper_kwargs) + + def on_load(target, context): + d = target.__dict__ + + for k, v in cls.get_flat_type_info(cls).items(): + if not k in d: + if isclass(v) and issubclass(v, ComplexModelBase): + pass + else: + d[k] = None + + event.listen(cls, 'load', on_load) + + return cls_mapper + + +def _add_file_type(cls, props, table, subname, subcls): + storage = getattr(subcls.Attributes, 'store_as', None) + col_args, col_kwargs = sanitize_args(subcls.Attributes.sqla_column_args) + _sp_attrs_to_sqla_constraints(cls, subcls, col_kwargs) + + if isinstance(storage, HybridFileStore): + if subname in table.c: + col = table.c[subname] + + else: + assert isabs(storage.store) + #FIXME: Add support for storage markers from spyne.model.complex + if storage.db_format == 'json': + t = PGFileJson(storage.store, storage.type) + + elif storage.db_format == 'jsonb': + t = PGFileJson(storage.store, storage.type, dbt='jsonb') + + else: + raise NotImplementedError(storage.db_format) + + col = Column(subname, t, **col_kwargs) + + props[subname] = col + if not subname in table.c: + table.append_column(col) + + else: + raise NotImplementedError(storage) + + +def add_column(cls, subname, subcls): + """Add field to the given Spyne object also mapped as a SQLAlchemy object + to a SQLAlchemy table + + :param cls: The class to add the column to. + :param subname: The column name + :param subcls: The column type, a ModelBase subclass. + """ + + table = cls.__table__ + mapper_props = {} + + # Add to table + sqla_type = _get_sqlalchemy_type(subcls) + if sqla_type is None: # complex model + _add_complex_type(cls, mapper_props, table, subname, subcls) + else: + _add_simple_type(cls, mapper_props, table, subname, subcls, sqla_type) + + # Add to mapper + sqla_mapper = cls.Attributes.sqla_mapper + for subname, subcls in mapper_props.items(): + if not sqla_mapper.has_property(subname): + sqla_mapper.add_property(subname, subcls) + + +def _parent_mapper_has_property(cls, cls_bases, k): + if len(cls_bases) == 0 and cls.__orig__ is cls_bases[0]: + return False + + for b in cls_bases: + if not hasattr(b, 'Attributes'): + continue + + mapper = b.Attributes.sqla_mapper + if mapper is not None and mapper.has_property(k): + # print(" Skipping mapping field", "%s.%s" % (cls.__name__, k), + # "because parent mapper from", b.__name__, "already has it") + return True + + # print("NOT skipping mapping field", "%s.%s" % (cls.__name__, k)) + return False + + +def gen_sqla_info(cls, cls_bases=()): + """Return SQLAlchemy table object corresponding to the passed Spyne object. + Also maps given class to the returned table. + """ + + table = _check_table(cls) + mapper_props = {} + + ancestors = cls.ancestors() + if len(ancestors) > 0: + anc_mapper = ancestors[0].Attributes.sqla_mapper + if anc_mapper is None: + # no mapper in parent, use all fields + fields = cls.get_flat_type_info(cls).items() + + elif anc_mapper.concrete: + # there is mapper in parent and it's concrete, so use all fields + fields = cls.get_flat_type_info(cls).items() + + else: + # there is a mapper in parent and it's not concrete, so parent + # columns are already mapped, so use only own fields. + fields = cls._type_info.items() + + else: + # when no parents, use all fields anyway. + assert set(cls._type_info.items()) == \ + set(cls.get_flat_type_info(cls).items()) + + fields = cls.get_flat_type_info(cls).items() + + for k, v in fields: + if _parent_mapper_has_property(cls, cls_bases, k): + continue + + t = _get_sqlalchemy_type(v) + + if t is None: # complex model + p = getattr(v.Attributes, 'store_as', None) + if p is None: + logger.debug("Skipping %s.%s.%s: %r, store_as: %r" % ( + cls.get_namespace(), + cls.get_type_name(), k, v, p)) + else: + _add_complex_type(cls, mapper_props, table, k, v) + else: + _add_simple_type(cls, mapper_props, table, k, v, t) + + if isinstance(table, _FakeTable): + table = _convert_fake_table(cls, table) + + cls_mapper = _gen_mapper(cls, mapper_props, table, cls_bases) + + cls.__tablename__ = cls.Attributes.table_name + cls.Attributes.sqla_mapper = cls.__mapper__ = cls_mapper + cls.Attributes.sqla_table = cls.__table__ = table + + return table + + +def _get_spyne_type(v): + """Map sqlalchemy types to spyne types.""" + + cust = {} + if v.primary_key: + cust['primary_key'] = True + + if not v.nullable: + cust['nullable'] = False + cust['min_occurs'] = 1 + + if isinstance(v.type, sqlalchemy.Enum): + if v.type.convert_unicode: + return Unicode(values=v.type.enums, **cust) + else: + cust['type_name'] = v.type.name + return Enum(*v.type.enums, **cust) + + if isinstance(v.type, (sqlalchemy.UnicodeText, sqlalchemy.Text)): + return Unicode(**cust) + + if isinstance(v.type, (sqlalchemy.Unicode, sqlalchemy.String, + sqlalchemy.VARCHAR)): + return Unicode(v.type.length, **cust) + + if isinstance(v.type, sqlalchemy.Numeric): + return Decimal(v.type.precision, v.type.scale, **cust) + + if isinstance(v.type, PGXml): + if len(cust) > 0: + return AnyXml(**cust) + else: + return AnyXml + + if isinstance(v.type, PGHtml): + if len(cust) > 0: + return AnyHtml(**cust) + else: + return AnyHtml + + if type(v.type) in _sq2sp_type_map: + retval = _sq2sp_type_map[type(v.type)] + if len(cust) > 0: + return retval.customize(**cust) + else: + return retval + + if isinstance(v.type, (PGObjectJson, PGObjectXml)): + retval = v.type.cls + if len(cust) > 0: + return retval.customize(**cust) + else: + return retval + + if isinstance(v.type, PGFileJson): + retval = v.FileData + if len(cust) > 0: + return v.FileData.customize(**cust) + else: + return retval + + raise Exception("Spyne type was not found. Probably _sq2sp_type_map " + "needs a new entry. %r" % v) + + +def gen_spyne_info(cls): + table = cls.Attributes.sqla_table + _type_info = cls._type_info + mapper_args, mapper_kwargs = sanitize_args(cls.Attributes.sqla_mapper_args) + + if len(_type_info) == 0: + for c in table.c: + _type_info[c.name] = _get_spyne_type(c) + else: + mapper_kwargs['include_properties'] = _type_info.keys() + + # Map the table to the object + cls_mapper = mapper(cls, table, *mapper_args, **mapper_kwargs) + + cls.Attributes.table_name = cls.__tablename__ = table.name + cls.Attributes.sqla_mapper = cls.__mapper__ = cls_mapper + + +def get_pk_columns(cls): + """Return primary key fields of a Spyne object.""" + + retval = [] + for k, v in cls.get_flat_type_info(cls).items(): + if v.Attributes.sqla_column_args is not None and \ + v.Attributes.sqla_column_args[-1].get('primary_key', False): + retval.append((k, v)) + + return tuple(retval) if len(retval) > 0 else None diff --git a/pym/calculate/contrib/spyne/store/relational/_base.pyc b/pym/calculate/contrib/spyne/store/relational/_base.pyc new file mode 100644 index 0000000..cb09222 Binary files /dev/null and b/pym/calculate/contrib/spyne/store/relational/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/store/relational/document.py b/pym/calculate/contrib/spyne/store/relational/document.py new file mode 100644 index 0000000..0383f7a --- /dev/null +++ b/pym/calculate/contrib/spyne/store/relational/document.py @@ -0,0 +1,353 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logger = logging.getLogger(__name__) + +import os +import json +import shutil + +import sqlalchemy.dialects + +from uuid import uuid1 +from mmap import mmap, ACCESS_READ +from contextlib import closing +from os.path import join, abspath, dirname, basename, isfile + +try: + from lxml import etree + from lxml import html + from spyne.util.xml import get_object_as_xml, get_xml_as_object + +except ImportError as _import_error: + etree = None + html = None + + _local_import_error = _import_error + def get_object_as_xml(*_, **__): + raise _local_import_error + def get_xml_as_object(*_, **__): + raise _local_import_error + +from sqlalchemy.sql.type_api import UserDefinedType + +from spyne import ValidationError +from spyne.model.relational import FileData + +from spyne.util import six +from spyne.util.six import binary_type, text_type, BytesIO, StringIO +from spyne.util.fileproxy import SeekableFileProxy + + +class PGXml(UserDefinedType): + def __init__(self, pretty_print=False, xml_declaration=False, + encoding='UTF-8'): + super(PGXml, self).__init__() + self.xml_declaration = xml_declaration + self.pretty_print = pretty_print + self.encoding = encoding + + def get_col_spec(self, **_): + return "xml" + + def bind_processor(self, dialect): + def process(value): + if value is None or \ + isinstance(value, (six.text_type, six.binary_type)): + return value + + if six.PY2: + return etree.tostring(value, pretty_print=self.pretty_print, + encoding=self.encoding, xml_declaration=False) + + return etree.tostring(value, pretty_print=self.pretty_print, + encoding="unicode", xml_declaration=False) + + return process + + def result_processor(self, dialect, col_type): + def process(value): + if value is not None: + return etree.fromstring(value) + else: + return value + return process + +sqlalchemy.dialects.postgresql.base.ischema_names['xml'] = PGXml + + +class PGHtml(UserDefinedType): + def __init__(self, pretty_print=False, encoding='UTF-8'): + super(PGHtml, self).__init__() + + self.pretty_print = pretty_print + self.encoding = encoding + + def get_col_spec(self, **_): + return "text" + + def bind_processor(self, dialect): + def process(value): + if isinstance(value, (six.text_type, six.binary_type)) \ + or value is None: + return value + else: + return html.tostring(value, pretty_print=self.pretty_print, + encoding=self.encoding) + return process + + def result_processor(self, dialect, col_type): + def process(value): + if value is not None and len(value) > 0: + return html.fromstring(value) + else: + return None + return process + + +class PGJson(UserDefinedType): + def __init__(self, encoding='UTF-8'): + self.encoding = encoding + + def get_col_spec(self, **_): + return "json" + + def bind_processor(self, dialect): + def process(value): + if isinstance(value, (text_type, binary_type)) or value is None: + return value + else: + if six.PY2: + return json.dumps(value, encoding=self.encoding) + else: + return json.dumps(value) + return process + + def result_processor(self, dialect, col_type): + def process(value): + if isinstance(value, (text_type, binary_type)): + return json.loads(value) + else: + return value + return process + +sqlalchemy.dialects.postgresql.base.ischema_names['json'] = PGJson + + +class PGJsonB(PGJson): + def get_col_spec(self, **_): + return "jsonb" + + +sqlalchemy.dialects.postgresql.base.ischema_names['jsonb'] = PGJsonB + + +class PGObjectXml(UserDefinedType): + def __init__(self, cls, root_tag_name=None, no_namespace=False, + pretty_print=False): + self.cls = cls + self.root_tag_name = root_tag_name + self.no_namespace = no_namespace + self.pretty_print = pretty_print + + def get_col_spec(self, **_): + return "xml" + + def bind_processor(self, dialect): + def process(value): + if value is not None: + return etree.tostring(get_object_as_xml(value, self.cls, + self.root_tag_name, self.no_namespace), encoding='utf8', + pretty_print=self.pretty_print, xml_declaration=False) + return process + + def result_processor(self, dialect, col_type): + def process(value): + if value is not None: + return get_xml_as_object(etree.fromstring(value), self.cls) + return process + + +class PGObjectJson(UserDefinedType): + def __init__(self, cls, ignore_wrappers=True, complex_as=dict, dbt='json', + encoding='utf8'): + self.cls = cls + self.ignore_wrappers = ignore_wrappers + self.complex_as = complex_as + self.dbt = dbt + self.encoding = encoding + + from spyne.util.dictdoc import get_dict_as_object + from spyne.util.dictdoc import get_object_as_json + self.get_object_as_json = get_object_as_json + self.get_dict_as_object = get_dict_as_object + + def get_col_spec(self, **_): + return self.dbt + + def bind_processor(self, dialect): + def process(value): + if value is not None: + try: + return self.get_object_as_json(value, self.cls, + ignore_wrappers=self.ignore_wrappers, + complex_as=self.complex_as, + ).decode(self.encoding) + + except Exception as e: + logger.debug("Failed to serialize %r to json: %r", value, e) + raise + + return process + + def result_processor(self, dialect, col_type): + from spyne.util.dictdoc import JsonDocument + + def process(value): + if value is None: + return None + + if isinstance(value, six.binary_type): + value = value.decode(self.encoding) + + if isinstance(value, six.text_type): + return self.get_dict_as_object(json.loads(value), self.cls, + ignore_wrappers=self.ignore_wrappers, + complex_as=self.complex_as, + protocol=JsonDocument, + ) + + return self.get_dict_as_object(value, self.cls, + ignore_wrappers=self.ignore_wrappers, + complex_as=self.complex_as, + protocol=JsonDocument, + ) + + return process + + +class PGFileJson(PGObjectJson): + def __init__(self, store, type=None, dbt='json'): + if type is None: + type = FileData + + super(PGFileJson, self).__init__(type, ignore_wrappers=True, + complex_as=list, dbt=dbt) + self.store = store + + def bind_processor(self, dialect): + def process(value): + if value is not None: + if value.data is not None: + value.path = uuid1().hex + fp = join(self.store, value.path) + if not abspath(fp).startswith(self.store): + raise ValidationError(value.path, "Path %r contains " + "relative path operators (e.g. '..')") + + with open(fp, 'wb') as file: + for d in value.data: + file.write(d) + + elif value.handle is not None: + value.path = uuid1().hex + fp = join(self.store, value.path) + if not abspath(fp).startswith(self.store): + raise ValidationError(value.path, "Path %r contains " + "relative path operators (e.g. '..')") + + if isinstance(value.handle, (StringIO, BytesIO)): + with open(fp, 'wb') as out_file: + out_file.write(value.handle.getvalue()) + else: + with closing(mmap(value.handle.fileno(), 0, + access=ACCESS_READ)) as data: + with open(fp, 'wb') as out_file: + out_file.write(data) + + elif value.path is not None: + in_file_path = value.path + + if not isfile(in_file_path): + logger.error("File path in %r not found" % value) + + if dirname(abspath(in_file_path)) != self.store: + dest = join(self.store, uuid1().get_hex()) + + if value.move: + shutil.move(in_file_path, dest) + logger.debug("move '%s' => '%s'", + in_file_path, dest) + + else: + shutil.copy(in_file_path, dest) + logger.debug("copy '%s' => '%s'", + in_file_path, dest) + + value.path = basename(dest) + value.abspath = dest + + else: + raise ValueError("Invalid file object passed in. All of " + ".data, .handle and .path are None.") + + value.store = self.store + value.abspath = join(self.store, value.path) + + return self.get_object_as_json(value, self.cls, + ignore_wrappers=self.ignore_wrappers, + complex_as=self.complex_as, + ) + + return process + + def result_processor(self, dialect, col_type): + def process(value): + if value is None: + return None + + if isinstance(value, six.text_type): + value = json.loads(value) + + elif isinstance(value, six.binary_type): + value = json.loads(value.decode('utf8')) + + retval = self.get_dict_as_object(value, self.cls, + ignore_wrappers=self.ignore_wrappers, + complex_as=self.complex_as) + + retval.store = self.store + retval.abspath = path = join(self.store, retval.path) + retval.handle = None + retval.data = [b''] + + if not os.access(path, os.R_OK): + logger.error("File %r is not readable", path) + return retval + + h = retval.handle = SeekableFileProxy(open(path, 'rb')) + if os.fstat(retval.handle.fileno()).st_size > 0: + h.mmap = mmap(h.fileno(), 0, access=ACCESS_READ) + retval.data = (h.mmap,) + # FIXME: Where do we close this mmap? + + return retval + + return process diff --git a/pym/calculate/contrib/spyne/store/relational/document.pyc b/pym/calculate/contrib/spyne/store/relational/document.pyc new file mode 100644 index 0000000..9eb0f94 Binary files /dev/null and b/pym/calculate/contrib/spyne/store/relational/document.pyc differ diff --git a/pym/calculate/contrib/spyne/store/relational/override.py b/pym/calculate/contrib/spyne/store/relational/override.py new file mode 100644 index 0000000..e04f2fb --- /dev/null +++ b/pym/calculate/contrib/spyne/store/relational/override.py @@ -0,0 +1,98 @@ + +# +# 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 sqlalchemy.ext.compiler import compiles + +from sqlalchemy.dialects.postgresql import INET +from spyne.store.relational import PGXml, PGJson, PGHtml, PGJsonB, \ + PGObjectJson, PGFileJson + + +@compiles(PGXml) +def compile_xml(type_, compiler, **kw): + return "xml" + + +@compiles(PGHtml) +def compile_html(type_, compiler, **kw): + return "text" + + +@compiles(PGJson) +def compile_json(type_, compiler, **kw): + return type_.get_col_spec() + + +@compiles(PGJsonB) +def compile_jsonb(type_, compiler, **kw): + return type_.get_col_spec() + + +@compiles(PGObjectJson) +def compile_ojson(type_, compiler, **kw): + return type_.get_col_spec() + + +@compiles(PGFileJson) +def compile_fjson(type_, compiler, **kw): + return type_.get_col_spec() + + +@compiles(INET) +def compile_inet(type_, compiler, **kw): + return "inet" + + + +@compiles(PGXml, "firebird") +def compile_xml_firebird(type_, compiler, **kw): + return "blob" + + +@compiles(PGHtml, "firebird") +def compile_html_firebird(type_, compiler, **kw): + return "blob" + + +@compiles(PGJson, "firebird") +def compile_json_firebird(type_, compiler, **kw): + return "blob" + + +@compiles(PGJsonB, "firebird") +def compile_jsonb_firebird(type_, compiler, **kw): + return "blob" + + +@compiles(PGObjectJson, "firebird") +def compile_ojson_firebird(type_, compiler, **kw): + return "blob" + + +@compiles(PGFileJson, "firebird") +def compile_fjson_firebird(type_, compiler, **kw): + return "blob" + + +@compiles(INET, "firebird") +def compile_inet_firebird(type_, compiler, **kw): + # http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/netinet_in.h.html + # INET6_ADDRSTRLEN + return "varchar(45)" diff --git a/pym/calculate/contrib/spyne/store/relational/override.pyc b/pym/calculate/contrib/spyne/store/relational/override.pyc new file mode 100644 index 0000000..010cfad Binary files /dev/null and b/pym/calculate/contrib/spyne/store/relational/override.pyc differ diff --git a/pym/calculate/contrib/spyne/store/relational/simple.py b/pym/calculate/contrib/spyne/store/relational/simple.py new file mode 100644 index 0000000..0b2aeba --- /dev/null +++ b/pym/calculate/contrib/spyne/store/relational/simple.py @@ -0,0 +1,96 @@ + +# +# 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 sqlalchemy import sql +from sqlalchemy.ext.compiler import compiles +from sqlalchemy.dialects.postgresql.base import ischema_names, PGTypeCompiler +from sqlalchemy.dialects.postgresql import ARRAY as PGArray, UUID as PGUuid + +from sqlalchemy.sql.sqltypes import Concatenable +from sqlalchemy.sql.type_api import UserDefinedType + + +@compiles(PGUuid, "sqlite") +def compile_uuid_sqlite(type_, compiler, **kw): + return "BLOB" + + + +class PGLTree(Concatenable, UserDefinedType): + """Postgresql `ltree` type.""" + + class Comparator(Concatenable.Comparator): + def ancestor_of(self, other): + if isinstance(other, list): + return self.op('@>')(sql.cast(other, PGArray(PGLTree))) + else: + return self.op('@>')(other) + + def descendant_of(self, other): + if isinstance(other, list): + return self.op('<@')(sql.cast(other, PGArray(PGLTree))) + else: + return self.op('<@')(other) + + def lquery(self, other): + if isinstance(other, list): + return self.op('?')(sql.cast(other, PGArray(PGLQuery))) + else: + return self.op('~')(other) + + def ltxtquery(self, other): + return self.op('@')(other) + + comparator_factory = Comparator + + __visit_name__ = 'LTREE' + + +class PGLQuery(UserDefinedType): + """Postresql `lquery` type.""" + + __visit_name__ = 'LQUERY' + + +class PGLTxtQuery(UserDefinedType): + """Postresql `ltxtquery` type.""" + + __visit_name__ = 'LTXTQUERY' + + +ischema_names['ltree'] = PGLTree +ischema_names['lquery'] = PGLQuery +ischema_names['ltxtquery'] = PGLTxtQuery + + +def visit_LTREE(self, type_, **kw): + return 'LTREE' + + +def visit_LQUERY(self, type_, **kw): + return 'LQUERY' + + +def visit_LTXTQUERY(self, type_, **kw): + return 'LTXTQUERY' + + +PGTypeCompiler.visit_LTREE = visit_LTREE +PGTypeCompiler.visit_LQUERY = visit_LQUERY +PGTypeCompiler.visit_LTXTQUERY = visit_LTXTQUERY diff --git a/pym/calculate/contrib/spyne/store/relational/simple.pyc b/pym/calculate/contrib/spyne/store/relational/simple.pyc new file mode 100644 index 0000000..b995782 Binary files /dev/null and b/pym/calculate/contrib/spyne/store/relational/simple.pyc differ diff --git a/pym/calculate/contrib/spyne/store/relational/spatial.py b/pym/calculate/contrib/spyne/store/relational/spatial.py new file mode 100644 index 0000000..152362b --- /dev/null +++ b/pym/calculate/contrib/spyne/store/relational/spatial.py @@ -0,0 +1,84 @@ + +# +# 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 sqlalchemy import sql +from sqlalchemy.ext.compiler import compiles +from sqlalchemy.sql.type_api import UserDefinedType + + +class PGGeometry(UserDefinedType): + """Geometry type for Postgis 2""" + + class PlainWkt: + pass + + class PlainWkb: + pass + + def __init__(self, geometry_type='GEOMETRY', srid=4326, dimension=2, + format='wkt'): + self.geometry_type = geometry_type.upper() + self.name = 'geometry' + self.srid = int(srid) + self.dimension = dimension + self.format = format + + if self.format == 'wkt': + self.format = PGGeometry.PlainWkt + elif self.format == 'wkb': + self.format = PGGeometry.PlainWkb + + def get_col_spec(self): + return '%s(%s,%d)' % (self.name, self.geometry_type, self.srid) + + def column_expression(self, col): + if self.format is PGGeometry.PlainWkb: + return sql.func.ST_AsBinary(col, type_=self) + if self.format is PGGeometry.PlainWkt: + return sql.func.ST_AsText(col, type_=self) + + def result_processor(self, dialect, coltype): + if self.format is PGGeometry.PlainWkt: + def process(value): + if value is not None: + return value + + if self.format is PGGeometry.PlainWkb: + def process(value): + if value is not None: + return sql.func.ST_AsBinary(value, self.srid) + + return process + + def bind_expression(self, bindvalue): + if self.format is PGGeometry.PlainWkt: + return sql.func.ST_GeomFromText(bindvalue, self.srid) + + +Geometry = PGGeometry + + +@compiles(PGGeometry) +def compile_geometry(type_, compiler, **kw): + return '%s(%s,%d)' % (type_.name, type_.geometry_type, type_.srid) + + +@compiles(PGGeometry, "sqlite") +def compile_geometry_sqlite(type_, compiler, **kw): + return "BLOB" diff --git a/pym/calculate/contrib/spyne/store/relational/spatial.pyc b/pym/calculate/contrib/spyne/store/relational/spatial.pyc new file mode 100644 index 0000000..2971cd4 Binary files /dev/null and b/pym/calculate/contrib/spyne/store/relational/spatial.pyc differ diff --git a/pym/calculate/contrib/spyne/store/relational/util.py b/pym/calculate/contrib/spyne/store/relational/util.py new file mode 100644 index 0000000..e30633f --- /dev/null +++ b/pym/calculate/contrib/spyne/store/relational/util.py @@ -0,0 +1,279 @@ +# +# retrieved from https://github.com/kvesteri/sqlalchemy-utils +# commit 99e1ea0eb288bc50ddb4a4aed5c50772d915ca73 +# +# Copyright (c) 2012, Konsta Vesterinen +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * The names of the contributors may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, +# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +import os +import cProfile + +from copy import copy + +from sqlalchemy.engine import Dialect, create_engine +from sqlalchemy.engine.url import make_url +from sqlalchemy.exc import OperationalError, ProgrammingError +from sqlalchemy.orm import object_session +from sqlalchemy.orm.exc import UnmappedInstanceError + + +def get_bind(obj): + """ + Return the bind for given SQLAlchemy Engine / Connection / declarative + model object. + :param obj: SQLAlchemy Engine / Connection / declarative model object + :: + from sqlalchemy_utils import get_bind + get_bind(session) # Connection object + get_bind(user) + """ + if hasattr(obj, 'bind'): + conn = obj.bind + else: + try: + conn = object_session(obj).bind + except UnmappedInstanceError: + conn = obj + + if not hasattr(conn, 'execute'): + raise TypeError( + 'This method accepts only Session, Engine, Connection and ' + 'declarative model objects.' + ) + return conn + + +def quote(mixed, ident): + """ + Conditionally quote an identifier. + :: + from sqlalchemy_utils import quote + engine = create_engine('sqlite:///:memory:') + quote(engine, 'order') + # '"order"' + quote(engine, 'some_other_identifier') + # 'some_other_identifier' + :param mixed: SQLAlchemy Session / Connection / Engine / Dialect object. + :param ident: identifier to conditionally quote + """ + if isinstance(mixed, Dialect): + dialect = mixed + else: + dialect = get_bind(mixed).dialect + return dialect.preparer(dialect).quote(ident) + + +def database_exists(url): + """Check if a database exists. + + :param url: A SQLAlchemy engine URL. + + Performs backend-specific testing to quickly determine if a database + exists on the server. :: + + database_exists('postgresql://postgres@localhost/name') #=> False + create_database('postgresql://postgres@localhost/name') + database_exists('postgresql://postgres@localhost/name') #=> True + + Supports checking against a constructed URL as well. :: + + engine = create_engine('postgresql://postgres@localhost/name') + database_exists(engine.url) #=> False + create_database(engine.url) + database_exists(engine.url) #=> True + + """ + + url = copy(make_url(url)) + database = url.database + if url.drivername.startswith('postgres'): + url.database = 'postgres' + elif not url.drivername.startswith('sqlite'): + url.database = None + + engine = create_engine(url) + + if engine.dialect.name == 'postgresql': + text = "SELECT 1 FROM pg_database WHERE datname='%s'" % database + return bool(engine.execute(text).scalar()) + + elif engine.dialect.name == 'mysql': + text = ("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA " + "WHERE SCHEMA_NAME = '%s'" % database) + return bool(engine.execute(text).scalar()) + + elif engine.dialect.name == 'sqlite': + return database == ':memory:' or os.path.exists(database) + + else: + text = 'SELECT 1' + try: + url.database = database + engine = create_engine(url) + engine.execute(text) + return True + + except (ProgrammingError, OperationalError): + return False + + +def create_database(url, encoding='utf8', psql_template='template1'): + """Issue the appropriate CREATE DATABASE statement. + + :param url: A SQLAlchemy engine URL. + :param encoding: The encoding to create the database as. + :param psql_template: + The name of the template from which to create the new database, + only supported by PostgreSQL driver. As per Postgresql docs, defaults to + "template1". + + To create a database, you can pass a simple URL that would have + been passed to ``create_engine``. :: + + create_database('postgresql://postgres@localhost/name') + + You may also pass the url from an existing engine. :: + + create_database(engine.url) + + Has full support for mysql, postgres, and sqlite. In theory, + other database engines should be supported. + """ + + url = copy(make_url(url)) + + database = url.database + + if url.drivername.startswith('postgres'): + url.database = 'postgres' + elif not url.drivername.startswith('sqlite'): + url.database = None + + engine = create_engine(url) + + if engine.dialect.name == 'postgresql': + if engine.driver == 'psycopg2': + from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT + engine.raw_connection().set_isolation_level( + ISOLATION_LEVEL_AUTOCOMMIT + ) + + text = "CREATE DATABASE {0} ENCODING '{1}' TEMPLATE {2}".format( + quote(engine, database), + encoding, + quote(engine, psql_template) + ) + engine.execute(text) + + elif engine.dialect.name == 'mysql': + text = "CREATE DATABASE {0} CHARACTER SET = '{1}'".format( + quote(engine, database), + encoding + ) + engine.execute(text) + + elif engine.dialect.name == 'sqlite' and database != ':memory:': + open(database, 'w').close() + + else: + text = 'CREATE DATABASE {0}'.format(quote(engine, database)) + engine.execute(text) + + +def drop_database(url): + """Issue the appropriate DROP DATABASE statement. + + :param url: A SQLAlchemy engine URL. + + Works similar to the :ref:`create_database` method in that both url text + and a constructed url are accepted. :: + + drop_database('postgresql://postgres@localhost/name') + drop_database(engine.url) + + """ + + url = copy(make_url(url)) + + database = url.database + + if url.drivername.startswith('postgresql'): + url.database = 'template1' + elif not url.drivername.startswith('sqlite'): + url.database = None + + engine = create_engine(url) + + if engine.dialect.name == 'sqlite' and url.database != ':memory:': + os.remove(url.database) + + elif engine.dialect.name == 'postgresql' and engine.driver == 'psycopg2': + from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT + engine.raw_connection().set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) + + # Disconnect all users from the database we are dropping. + version = list( + map( + int, + engine.execute('SHOW server_version').first()[0].split('.') + ) + ) + pid_column = ( + 'pid' if (version[0] >= 9 and version[1] >= 2) else 'procpid' + ) + text = ''' + SELECT pg_terminate_backend(pg_stat_activity.%(pid_column)s) + FROM pg_stat_activity + WHERE pg_stat_activity.datname = '%(database)s' + AND %(pid_column)s <> pg_backend_pid(); + ''' % {'pid_column': pid_column, 'database': database} + engine.execute(text) + + # Drop the database. + text = 'DROP DATABASE {0}'.format(quote(engine, database)) + engine.execute(text) + + else: + text = 'DROP DATABASE {0}'.format(quote(engine, database)) + engine.execute(text) + + +# https://zapier.com/engineering/profiling-python-boss/ +def do_cprofile(func): + def profiled_func(*args, **kwargs): + profile = cProfile.Profile() + try: + profile.enable() + result = func(*args, **kwargs) + profile.disable() + return result + finally: + profile.print_stats(sort='time') + + return profiled_func diff --git a/pym/calculate/contrib/spyne/store/relational/util.pyc b/pym/calculate/contrib/spyne/store/relational/util.pyc new file mode 100644 index 0000000..0ada8c5 Binary files /dev/null and b/pym/calculate/contrib/spyne/store/relational/util.pyc differ diff --git a/pym/calculate/contrib/spyne/test/__init__.py b/pym/calculate/contrib/spyne/test/__init__.py new file mode 100644 index 0000000..51fda37 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/__init__.py @@ -0,0 +1,31 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +class FakeApp(object): + transport = 'transport' + tns = 'tns' + name = 'name' + services = [] + classes = () + +import logging +logging.basicConfig(level=logging.DEBUG) +logging.getLogger('spyne.util.appreg').setLevel(logging.INFO) + +from spyne.context import FakeContext diff --git a/pym/calculate/contrib/spyne/test/__init__.pyc b/pym/calculate/contrib/spyne/test/__init__.pyc new file mode 100644 index 0000000..05b99ac Binary files /dev/null and b/pym/calculate/contrib/spyne/test/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interface/__init__.py b/pym/calculate/contrib/spyne/test/interface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pym/calculate/contrib/spyne/test/interface/__init__.pyc b/pym/calculate/contrib/spyne/test/interface/__init__.pyc new file mode 100644 index 0000000..7b1c7d0 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interface/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interface/test_interface.py b/pym/calculate/contrib/spyne/test/interface/test_interface.py new file mode 100644 index 0000000..a47c7c5 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interface/test_interface.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + + +import unittest + +from spyne import Application, Service, rpc +from spyne.model import Array, ComplexModel, AnyXml, UnsignedLong, \ + UnsignedInteger16, Integer, DateTime, Unicode +from spyne.protocol.http import HttpRpc +from spyne.protocol.soap import Soap11 + + +class TestInterface(unittest.TestCase): + def test_imports(self): + import logging + logging.basicConfig(level=logging.DEBUG) + + class KeyValuePair(ComplexModel): + __namespace__ = "1" + key = Unicode + value = Unicode + + class Something(ComplexModel): + __namespace__ = "2" + d = DateTime + i = Integer + + class SomethingElse(ComplexModel): + __namespace__ = "3" + a = AnyXml + b = UnsignedLong + s = Something + + class BetterSomething(Something): + __namespace__ = "4" + k = UnsignedInteger16 + + class Service1(Service): + @rpc(SomethingElse, _returns=Array(KeyValuePair)) + def some_call(ctx, sth): + pass + + class Service2(Service): + @rpc(BetterSomething, _returns=Array(KeyValuePair)) + def some_other_call(ctx, sth): + pass + + application = Application([Service1, Service2], + in_protocol=HttpRpc(), + out_protocol=Soap11(), + name='Service', tns='target_namespace' + ) + + imports = application.interface.imports + tns = application.interface.get_tns() + smm = application.interface.service_method_map + print(imports) + + assert imports[tns] == set(['1', '3', '4']) + assert imports['3'] == set(['2']) + assert imports['4'] == set(['2']) + + assert smm['{%s}some_call' % tns] + assert smm['{%s}some_call' % tns][0].service_class == Service1 + assert smm['{%s}some_call' % tns][0].function == Service1.some_call + + assert smm['{%s}some_other_call' % tns] + assert smm['{%s}some_other_call' % tns][0].service_class == Service2 + assert smm['{%s}some_other_call' % tns][0].function == Service2.some_other_call + + def test_custom_primitive_in_array(self): + RequestStatus = Unicode(values=['new', 'processed'], zonta='bonta') + + class DataRequest(ComplexModel): + status = Array(RequestStatus) + + class HelloWorldService(Service): + @rpc(DataRequest) + def some_call(ctx, dgrntcl): + pass + + Application([HelloWorldService], 'spyne.examples.hello.soap', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11()) + + # test passes if instantiating Application doesn't fail + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/interface/test_interface.pyc b/pym/calculate/contrib/spyne/test/interface/test_interface.pyc new file mode 100644 index 0000000..2b25deb Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interface/test_interface.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interface/test_wsgi.py b/pym/calculate/contrib/spyne/test/interface/test_wsgi.py new file mode 100644 index 0000000..e27f3c8 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interface/test_wsgi.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import unittest + +from spyne.util import six +from spyne.util.six import StringIO + +from spyne.protocol.soap.soap11 import Soap11 +from spyne.server.wsgi import WsgiApplication +from spyne.application import Application +from spyne.model.primitive import Unicode +from spyne.decorator import rpc +from spyne.const.xml import WSDL11 +from spyne.service import Service + + +def start_response(code, headers): + print(code, headers) + + +class Test(unittest.TestCase): + def setUp(self): + class SomeService(Service): + @rpc(Unicode) + def some_call(ctx, some_str): + print(some_str) + + + app = Application([SomeService], "some_tns", in_protocol=Soap11(), + out_protocol=Soap11()) + self.wsgi_app = WsgiApplication(app) + + def test_document_built(self): + self.h = 0 + + def on_wsdl_document_built(doc): + self.h += 1 + + self.wsgi_app.doc.wsdl11.event_manager.add_listener( + "wsdl_document_built", on_wsdl_document_built) + self.wsgi_app.doc.wsdl11.build_interface_document("http://some_url/") + + assert self.h == 1 + + def test_document_manipulation(self): + def on_wsdl_document_built(doc): + doc.root_elt.tag = 'ehe' + + self.wsgi_app.doc.wsdl11.event_manager.add_listener( + "wsdl_document_built", on_wsdl_document_built) + self.wsgi_app.doc.wsdl11.build_interface_document("http://some_url/") + d = self.wsgi_app.doc.wsdl11.get_interface_document() + + from lxml import etree + + assert etree.fromstring(d).tag == 'ehe' + + def test_wsgi(self): + retval = b''.join(self.wsgi_app({ + 'PATH_INFO': '/', + 'QUERY_STRING': 'wsdl', + 'SERVER_NAME': 'localhost', + 'SERVER_PORT': '7000', + 'REQUEST_METHOD': 'GET', + 'wsgi.url_scheme': 'http', + 'wsgi.input': StringIO(), + }, start_response)) + + from lxml import etree + + assert etree.fromstring(retval).tag == WSDL11('definitions') + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/interface/test_wsgi.pyc b/pym/calculate/contrib/spyne/test/interface/test_wsgi.pyc new file mode 100644 index 0000000..8dc2328 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interface/test_wsgi.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interface/test_xml_schema.py b/pym/calculate/contrib/spyne/test/interface/test_xml_schema.py new file mode 100644 index 0000000..4163539 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interface/test_xml_schema.py @@ -0,0 +1,551 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +import unittest + +from pprint import pprint +from lxml import etree + +from spyne import Application +from spyne import rpc +from spyne.const import xml as ns +from spyne.const.xml import NS_XSD +from spyne.model import ByteArray +from spyne.model import ComplexModel +from spyne.model import XmlAttribute +from spyne.model import XmlData +from spyne.model import AnyXml +from spyne.model import Integer +from spyne.model import Mandatory as M +from spyne.model import Unicode +from spyne.model import Uuid +from spyne.model import Boolean +from spyne.protocol.soap import Soap11, Soap12 +from spyne.service import Service +from spyne.util.xml import get_schema_documents +from spyne.util.xml import parse_schema_element +from spyne.util.xml import parse_schema_string + +from spyne.interface.xml_schema import XmlSchema +from spyne.interface.xml_schema.genpy import CodeGenerator + + +class TestXmlSchema(unittest.TestCase): + def test_choice_tag(self): + class SomeObject(ComplexModel): + __namespace__ = "badass_ns" + + one = Integer(xml_choice_group="numbers") + two = Integer(xml_choice_group="numbers") + punk = Unicode + + class KickassService(Service): + @rpc(_returns=SomeObject) + def wooo(ctx): + return SomeObject() + + Application([KickassService], + tns='kickass.ns', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11() + ) + + docs = get_schema_documents([SomeObject]) + doc = docs['tns'] + print(etree.tostring(doc, pretty_print=True)) + assert len(doc.xpath('/xs:schema/xs:complexType[@name="SomeObject"]' + '/xs:sequence/xs:element[@name="punk"]', + namespaces={'xs': NS_XSD})) > 0 + assert len(doc.xpath('/xs:schema/xs:complexType[@name="SomeObject"]' + '/xs:sequence/xs:choice/xs:element[@name="one"]', + namespaces={'xs': NS_XSD})) > 0 + + def test_customized_class_with_empty_subclass(self): + class SummaryStatsOfDouble(ComplexModel): + _type_info = [('Min', XmlAttribute(Integer, use='required')), + ('Max', XmlAttribute(Integer, use='required')), + ('Avg', XmlAttribute(Integer, use='required'))] + + class SummaryStats(SummaryStatsOfDouble): + ''' this is an empty base class ''' + + class Payload(ComplexModel): + _type_info = [('Stat1', SummaryStats.customize(nillable=False)), + ('Stat2', SummaryStats), + ('Stat3', SummaryStats), + ('Dummy', Unicode)] + + class JackedUpService(Service): + @rpc(_returns=Payload) + def GetPayload(ctx): + return Payload() + + Application([JackedUpService], + tns='kickass.ns', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11() + ) + + # if no exceptions while building the schema, no problem. + # see: https://github.com/arskom/spyne/issues/226 + + + def test_namespaced_xml_attribute(self): + class Release(ComplexModel): + __namespace__ = "http://usefulinc.com/ns/doap#" + + _type_info = [ + ('about', XmlAttribute(Unicode, + ns="http://www.w3.org/1999/02/22-rdf-syntax-ns#")), + ] + + class Project(ComplexModel): + __namespace__ = "http://usefulinc.com/ns/doap#" + + _type_info = [ + ('about', XmlAttribute(Unicode, + ns="http://www.w3.org/1999/02/22-rdf-syntax-ns#")), + ('release', Release.customize(max_occurs=float('inf'))), + ] + + class RdfService(Service): + @rpc(Unicode, Unicode, _returns=Project) + def some_call(ctx, a, b): + pass + + Application([RdfService], + tns='spynepi', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11() + ) + + # if no exceptions while building the schema, no problem. + + def test_customized_simple_type_in_xml_attribute(self): + class Product(ComplexModel): + __namespace__ = 'some_ns' + + id = XmlAttribute(Uuid) + edition = Unicode + + class SomeService(Service): + @rpc(Product, _returns=Product) + def echo_product(ctx, product): + logging.info('edition_id: %r', product.edition_id) + return product + + Application([SomeService], tns='some_ns', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11() + ) + + # if no exceptions while building the schema, no problem. + + def test_binary_encodings(self): + class Product(ComplexModel): + __namespace__ = 'some_ns' + + hex = ByteArray(encoding='hex') + base64_1 = ByteArray(encoding='base64') + base64_2 = ByteArray + + class SomeService(Service): + @rpc(Product, _returns=Product) + def echo_product(ctx, product): + logging.info('edition_id: %r', product.edition_id) + return product + + app = Application([SomeService], + tns='some_ns', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11() + ) + + _ns = {'xs': NS_XSD} + pref_xs = ns.PREFMAP[NS_XSD] + xs = XmlSchema(app.interface) + xs.build_interface_document() + elt = xs.get_interface_document()['tns'].xpath( + '//xs:complexType[@name="Product"]', + namespaces=_ns)[0] + + assert elt.xpath('//xs:element[@name="base64_1"]/@type', + namespaces=_ns)[0] == '%s:base64Binary' % pref_xs + assert elt.xpath('//xs:element[@name="base64_2"]/@type', + namespaces=_ns)[0] == '%s:base64Binary' % pref_xs + assert elt.xpath('//xs:element[@name="hex"]/@type', + namespaces=_ns)[0] == '%s:hexBinary' % pref_xs + + def test_multilevel_customized_simple_type(self): + class ExampleService(Service): + __tns__ = 'http://xml.company.com/ns/example/' + + @rpc(M(Uuid), _returns=Unicode) + def say_my_uuid(ctx, uuid): + return 'Your UUID: %s' % uuid + + Application([ExampleService], + tns='kickass.ns', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11() + ) + + # if no exceptions while building the schema, no problem. + # see: http://stackoverflow.com/questions/16042132/cannot-use-mandatory-uuid-or-other-pattern-related-must-be-type-as-rpc-argumen + + def test_any_tag(self): + logging.basicConfig(level=logging.DEBUG) + + class SomeType(ComplexModel): + __namespace__ = "zo" + + anything = AnyXml(schema_tag='{%s}any' % NS_XSD, namespace='##other', + process_contents='lax') + + docs = get_schema_documents([SomeType]) + print(etree.tostring(docs['tns'], pretty_print=True)) + _any = docs['tns'].xpath('//xsd:any', namespaces={'xsd': NS_XSD}) + + assert len(_any) == 1 + assert _any[0].attrib['namespace'] == '##other' + assert _any[0].attrib['processContents'] == 'lax' + + def _build_xml_data_test_schema(self, custom_root): + tns = 'kickass.ns' + + class ProductEdition(ComplexModel): + __namespace__ = tns + id = XmlAttribute(Uuid) + if custom_root: + name = XmlData(Uuid) + else: + name = XmlData(Unicode) + + class Product(ComplexModel): + __namespace__ = tns + id = XmlAttribute(Uuid) + edition = ProductEdition + + class ExampleService(Service): + @rpc(Product, _returns=Product) + def say_my_uuid(ctx, product): + pass + + app = Application([ExampleService], + tns='kickass.ns', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11() + ) + + schema = XmlSchema(app.interface) + schema.build_interface_document() + schema.build_validation_schema() + + doc = schema.get_interface_document()['tns'] + print(etree.tostring(doc, pretty_print=True)) + return schema + + def test_xml_data_schema_doc(self): + schema = self._build_xml_data_test_schema(custom_root=False) + + assert len(schema.get_interface_document()['tns'].xpath( + '/xs:schema/xs:complexType[@name="ProductEdition"]' + '/xs:simpleContent/xs:extension/xs:attribute[@name="id"]' + ,namespaces={'xs': NS_XSD})) == 1 + + def _test_xml_data_validation(self): + schema = self._build_xml_data_test_schema(custom_root=False) + + assert schema.validation_schema.validate(etree.fromstring(""" + + punk + + """)), schema.validation_schema.error_log.last_error + + def _test_xml_data_validation_custom_root(self): + schema = self._build_xml_data_test_schema(custom_root=True) + + assert schema.validation_schema.validate(etree.fromstring(""" + + + 00000000-0000-0000-0000-000000000002 + + + """)), schema.validation_schema.error_log.last_error + + + def test_subs(self): + from lxml import etree + from spyne.util.xml import get_schema_documents + xpath = lambda o, x: o.xpath(x, namespaces={"xs": NS_XSD}) + + m = { + "s0": "aa", + "s2": "cc", + "s3": "dd", + } + + class C(ComplexModel): + __namespace__ = "aa" + a = Integer + b = Integer(sub_name="bb") + c = Integer(sub_ns="cc") + d = Integer(sub_ns="dd", sub_name="dd") + + elt = get_schema_documents([C], "aa")['tns'] + print(etree.tostring(elt, pretty_print=True)) + + seq, = xpath(elt, "xs:complexType/xs:sequence") + + assert len(seq) == 4 + assert len(xpath(seq, 'xs:element[@name="a"]')) == 1 + assert len(xpath(seq, 'xs:element[@name="bb"]')) == 1 + + # FIXME: this doesn't feel right. + # check the spec to see whether it should it be prefixed. + # + #assert len(xpath(seq, 'xs:element[@name="{cc}c"]')) == 1 + #assert len(xpath(seq, 'xs:element[@name="{dd}dd"]')) == 1 + + def test_mandatory(self): + xpath = lambda o, x: o.xpath(x, namespaces={"xs": NS_XSD}) + + class C(ComplexModel): + __namespace__ = "aa" + foo = XmlAttribute(M(Unicode)) + + elt = get_schema_documents([C])['tns'] + print(etree.tostring(elt, pretty_print=True)) + foo, = xpath(elt, 'xs:complexType/xs:attribute[@name="foo"]') + attrs = foo.attrib + assert 'use' in attrs and attrs['use'] == 'required' + + def test_annotation(self): + tns = 'some_ns' + doc = "Some Doc" + + class SomeClass(ComplexModel): + __namespace__ = tns + some_attr = Unicode(doc=doc) + + schema = get_schema_documents([SomeClass], tns)['tns'] + print(etree.tostring(schema, pretty_print=True)) + assert schema.xpath("//xs:documentation/text()", + namespaces={'xs': NS_XSD}) == [doc] + + +class TestParseOwnXmlSchema(unittest.TestCase): + def test_simple(self): + tns = 'some_ns' + class SomeGuy(ComplexModel): + __namespace__ = 'some_ns' + + id = Integer + + schema = get_schema_documents([SomeGuy], tns)['tns'] + print(etree.tostring(schema, pretty_print=True)) + + objects = parse_schema_element(schema) + pprint(objects[tns].types) + + NewGuy = objects[tns].types["SomeGuy"] + assert NewGuy.get_type_name() == SomeGuy.get_type_name() + assert NewGuy.get_namespace() == SomeGuy.get_namespace() + assert dict(NewGuy._type_info) == dict(SomeGuy._type_info) + + def test_customized_unicode(self): + tns = 'some_ns' + class SomeGuy(ComplexModel): + __namespace__ = tns + name = Unicode(max_len=10, pattern="a", min_len=5, default="aa") + + schema = get_schema_documents([SomeGuy], tns)['tns'] + print(etree.tostring(schema, pretty_print=True)) + + objects = parse_schema_element(schema) + pprint(objects[tns].types) + + NewGuy = objects['some_ns'].types["SomeGuy"] + assert NewGuy._type_info['name'].Attributes.max_len == 10 + assert NewGuy._type_info['name'].Attributes.min_len == 5 + assert NewGuy._type_info['name'].Attributes.pattern == "a" + assert NewGuy._type_info['name'].Attributes.default == "aa" + + def test_boolean_default(self): + tns = 'some_ns' + class SomeGuy(ComplexModel): + __namespace__ = tns + bald = Boolean(default=True) + + schema = get_schema_documents([SomeGuy], tns)['tns'] + print(etree.tostring(schema, pretty_print=True)) + + objects = parse_schema_element(schema) + pprint(objects[tns].types) + + NewGuy = objects['some_ns'].types["SomeGuy"] + assert NewGuy._type_info['bald'].Attributes.default == True + + def test_boolean_attribute_default(self): + tns = 'some_ns' + class SomeGuy(ComplexModel): + __namespace__ = tns + + bald = XmlAttribute(Boolean(default=True)) + + schema = get_schema_documents([SomeGuy], tns)['tns'] + print(etree.tostring(schema, pretty_print=True)) + + objects = parse_schema_element(schema) + pprint(objects[tns].types) + + NewGuy = objects['some_ns'].types["SomeGuy"] + assert NewGuy._type_info['bald'].Attributes.default == True + + def test_attribute(self): + tns = 'some_ns' + class SomeGuy(ComplexModel): + __namespace__ = tns + + name = XmlAttribute(Unicode) + + schema = get_schema_documents([SomeGuy], tns)['tns'] + print(etree.tostring(schema, pretty_print=True)) + + objects = parse_schema_element(schema) + pprint(objects) + pprint(objects[tns].types) + + NewGuy = objects['some_ns'].types["SomeGuy"] + assert NewGuy._type_info['name'].type is Unicode + + def test_attribute_with_customized_type(self): + tns = 'some_ns' + class SomeGuy(ComplexModel): + __namespace__ = tns + + name = XmlAttribute(Unicode(default="aa")) + + schema = get_schema_documents([SomeGuy], tns)['tns'] + print(etree.tostring(schema, pretty_print=True)) + + objects = parse_schema_element(schema) + pprint(objects[tns].types) + + NewGuy = objects['some_ns'].types["SomeGuy"] + assert NewGuy._type_info['name'].type.__orig__ is Unicode + assert NewGuy._type_info['name'].type.Attributes.default == "aa" + + def test_inherited_attribute(self): + class DeviceEntity(ComplexModel): + token = XmlAttribute(Unicode, use='required') + + class DigitalInput(DeviceEntity): + IdleState = XmlAttribute(Unicode) + + class SomeService(Service): + @rpc(_returns=DigitalInput, _body_style='bare') + def GetDigitalInput(ctx): + return DigitalInput() + + Application([SomeService], 'some_tns', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11()) + + def test_simple_type_explicit_customization(self): + class Header(ComplexModel): + test = Boolean(min_occurs=0, nillable=False) + pw = Unicode.customize(min_occurs=0, nillable=False, min_len=6) + + class Params(ComplexModel): + sendHeader = Header.customize(nillable=False, min_occurs=1) + + class DummyService(Service): + @rpc(Params, _returns=Unicode) + def loadServices(ctx, serviceParams): + return '42' + + Application([DummyService], + tns='dummy', + name='DummyService', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11() + ) + # if instantiation doesn't fail, test is green. + + +class TestParseForeignXmlSchema(unittest.TestCase): + def test_simple_content(self): + tns = 'some_ns' + + schema = """ + + + + + + + +""" + + objects = parse_schema_string(schema) + pprint(objects[tns].types) + + NewGuy = objects[tns].types['SomeGuy'] + ti = NewGuy._type_info + pprint(dict(ti)) + assert issubclass(ti['_data'], XmlData) + assert ti['_data'].type is Unicode + + assert issubclass(ti['attr'], XmlAttribute) + assert ti['attr'].type is Unicode + + +class TestCodeGeneration(unittest.TestCase): + def _get_schema(self, *args): + schema_doc = get_schema_documents(args)['tns'] + return parse_schema_element(schema_doc) + + def test_simple(self): + ns = 'some_ns' + + class SomeObject(ComplexModel): + __namespace__ = ns + _type_info = [ + ('i', Integer), + ('s', Unicode), + ] + + s = self._get_schema(SomeObject)[ns] + code = CodeGenerator().genpy(ns, s) + + # FIXME: Properly parse it + assert """class SomeObject(_ComplexBase): + _type_info = [ + ('i', Integer), + ('s', Unicode), + ]""" in code + + +if __name__ == '__main__': + unittest.main() + diff --git a/pym/calculate/contrib/spyne/test/interface/test_xml_schema.pyc b/pym/calculate/contrib/spyne/test/interface/test_xml_schema.pyc new file mode 100644 index 0000000..6448a53 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interface/test_xml_schema.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interface/wsdl/__init__.py b/pym/calculate/contrib/spyne/test/interface/wsdl/__init__.py new file mode 100644 index 0000000..d23660d --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interface/wsdl/__init__.py @@ -0,0 +1,68 @@ + +# +# spyne - Copyright (C) spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from spyne.application import Application +from spyne.interface.wsdl import Wsdl11 +from spyne.protocol.soap import Soap11 +import spyne.const.xml as ns + +def build_app(service_list, tns, name): + app = Application(service_list, tns, name=name, + in_protocol=Soap11(), out_protocol=Soap11()) + app.transport = 'http://schemas.xmlsoap.org/soap/http' + return app + +class AppTestWrapper(): + def __init__(self, application): + + self.url = 'http:/localhost:7789/wsdl' + self.service_string = ns.WSDL11('service') + self.port_string = ns.WSDL11('port') + self.soap_binding_string = ns.WSDL11_SOAP('binding') + self.operation_string = ns.WSDL11('operation') + self.port_type_string = ns.WSDL11('portType') + self.binding_string = ns.WSDL11('binding') + + self.app = application + self.interface_doc = Wsdl11(self.app.interface) + self.interface_doc.build_interface_document(self.url) + self.wsdl = self.interface_doc.get_interface_document() + + def get_service_list(self): + return self.interface_doc.root_elt.findall(self.service_string) + + def get_port_list(self, service): + from lxml import etree + print((etree.tostring(service, pretty_print=True))) + return service.findall(self.port_string) + + def get_soap_bindings(self, binding): + return binding.findall(self.soap_binding_string) + + def get_port_types(self): + return self.interface_doc.root_elt.findall(self.port_type_string) + + def get_port_operations(self, port_type): + return port_type.findall(self.operation_string) + + def get_bindings(self): + return self.interface_doc.root_elt.findall(self.binding_string) + + def get_binding_operations(self, binding): + return [o for o in binding.iterfind(self.operation_string)] diff --git a/pym/calculate/contrib/spyne/test/interface/wsdl/__init__.pyc b/pym/calculate/contrib/spyne/test/interface/wsdl/__init__.pyc new file mode 100644 index 0000000..0713baa Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interface/wsdl/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interface/wsdl/defult_services.py b/pym/calculate/contrib/spyne/test/interface/wsdl/defult_services.py new file mode 100644 index 0000000..5d54a7f --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interface/wsdl/defult_services.py @@ -0,0 +1,49 @@ + +# +# spyne - Copyright (C) spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + + +from spyne.model.primitive import String +from spyne.service import Service +from spyne.decorator import rpc + + +def TDefaultPortService(): + class DefaultPortService(Service): + @rpc(String, _returns=String) + def echo_default_port_service(self, string): + return string + + return DefaultPortService + + +def TDefaultPortServiceMultipleMethods(): + class DefaultPortServiceMultipleMethods(Service): + @rpc(String, _returns=String) + def echo_one(self, string): + return string + + @rpc(String, _returns=String) + def echo_two(self, string): + return string + + @rpc(String, _returns=String) + def echo_three(self, string): + return string + + return DefaultPortServiceMultipleMethods diff --git a/pym/calculate/contrib/spyne/test/interface/wsdl/defult_services.pyc b/pym/calculate/contrib/spyne/test/interface/wsdl/defult_services.pyc new file mode 100644 index 0000000..05c143e Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interface/wsdl/defult_services.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interface/wsdl/port_service_services.py b/pym/calculate/contrib/spyne/test/interface/wsdl/port_service_services.py new file mode 100644 index 0000000..365bb62 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interface/wsdl/port_service_services.py @@ -0,0 +1,127 @@ + +# +# spyne - Copyright (C) spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from spyne.model.primitive import String +from spyne.service import Service +from spyne.decorator import rpc + +def TS1(): + class S1(Service): + name = 'S1Fools' + __namespace__ = 'Hippity' + + @rpc(String, _returns=String) + def echo_string_s1(self, string): + return string + return S1 + +def TS2(): + class S2(Service): + name = 'S2Fools' + __namespace__ = 'Hoppity' + + @rpc(String, _returns=String) + def bobs(self, string): + return string + + return S2 + +def TS3(): + class S3(Service): + name = 'S3Fools' + __namespace__ = 'Hoppity' + __service_name__ = 'BlahService' + __port_types__ = ['bobhope', 'larry'] + + @rpc(String, _returns=String) + def echo(self, string): + return string + + @rpc(String, _port_type='bobhope', _returns=String) + def echo_bob_hope(self, string): + return 'Bob Hope' + + return S3 + +def TMissingRPCPortService(): + class MissingRPCPortService(Service): + name = 'MissingRPCPortService' + __namespace__ = 'MissingRPCPortService' + __service_name__ = 'MissingRPCPortService' + __port_types__ = ['existing'] + + @rpc(String, _returns=String) + def raise_exception(self, string): + return string + return MissingRPCPortService + +def TBadRPCPortService(): + class BadRPCPortService(Service): + name = 'MissingRPCPortService' + __namespace__ = 'MissingRPCPortService' + __service_name__ = 'MissingRPCPortService' + __port_types__ = ['existing'] + + @rpc(String, _port_type='existingss', _returns=String) + def raise_exception(self, string): + return string + + return BadRPCPortService + +def TMissingServicePortService(): + class MissingServicePortService(Service): + name = 'MissingRPCPortService' + __namespace__ = 'MissingRPCPortService' + __service_name__ = 'MissingRPCPortService' + __port_types__ = ['existing'] + + @rpc(String, _port_type='existingss', _returns=String) + def raise_exception(self, string): + return string + + return MissingServicePortService + +def TSinglePortService(): + class SinglePortService(Service): + name = 'SinglePort' + __service_name__ = 'SinglePortService_ServiceInterface' + __namespace__ = 'SinglePortNS' + __port_types__ = ['FirstPortType'] + + @rpc(String, _port_type='FirstPortType', _returns=String) + def echo_default_port_service(self, string): + return string + + return SinglePortService + +def TDoublePortService(): + class DoublePortService(Service): + name = 'DoublePort' + __namespace__ = 'DoublePort' + __port_types__ = ['FirstPort', 'SecondPort'] + + @rpc(String, _port_type='FirstPort', _returns=String) + def echo_first_port(self, string): + return string + + @rpc(String, _port_type='SecondPort', _returns=String) + def echo_second_port(self, string): + return string + + return DoublePortService diff --git a/pym/calculate/contrib/spyne/test/interface/wsdl/port_service_services.pyc b/pym/calculate/contrib/spyne/test/interface/wsdl/port_service_services.pyc new file mode 100644 index 0000000..4dc3059 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interface/wsdl/port_service_services.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interface/wsdl/test_bindings.py b/pym/calculate/contrib/spyne/test/interface/wsdl/test_bindings.py new file mode 100644 index 0000000..e82bcf9 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interface/wsdl/test_bindings.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +#encoding: utf8 +# +# spyne - Copyright (C) spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logging.basicConfig(level=logging.DEBUG) + +import unittest + +import spyne.const.xml as ns + + +from spyne.interface.wsdl.wsdl11 import Wsdl11 +from . import build_app +from .port_service_services import TS1 +from .port_service_services import TSinglePortService +from .port_service_services import TDoublePortService + +class TestWSDLBindingBehavior(unittest.TestCase): + def setUp(self): + self.transport = 'http://schemas.xmlsoap.org/soap/http' + self.url = 'http:/localhost:7789/wsdl' + self.port_type_string = ns.WSDL11('portType') + self.service_string = ns.WSDL11('service') + self.binding_string = ns.WSDL11('binding') + self.operation_string = ns.WSDL11('operation') + self.port_string = ns.WSDL11('port') + + def test_binding_simple(self): + sa = build_app([TS1()], 'S1Port', 'TestServiceName') + + interface_doc = Wsdl11(sa.interface) + interface_doc.build_interface_document(self.url) + + + services = interface_doc.root_elt.xpath( + '/wsdl:definitions/wsdl:service', + namespaces = { + 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) + self.assertEqual(len(services), 1) + + portTypes = interface_doc.root_elt.xpath( + '/wsdl:definitions/wsdl:portType', + namespaces = { + 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) + self.assertEqual(len(portTypes), 1) + + ports = interface_doc.root_elt.xpath( + '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' % + "S1", + namespaces = { + 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) + self.assertEqual(len(ports), 1) + + + def test_binding_multiple(self): + SinglePortService, DoublePortService = TSinglePortService(), TDoublePortService() + + sa = build_app( + [SinglePortService, DoublePortService], + 'MultiServiceTns', + 'AppName' + ) + interface_doc = Wsdl11(sa.interface) + interface_doc.build_interface_document(self.url) + + + # 2 Service, + # First has 1 port + # Second has 2 + + # => need 2 service, 3 port and 3 bindings + + services = interface_doc.root_elt.xpath( + '/wsdl:definitions/wsdl:service', + namespaces = { + 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) + self.assertEqual(len(services), 2) + + portTypes = interface_doc.root_elt.xpath( + '/wsdl:definitions/wsdl:portType', + namespaces = { + 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) + self.assertEqual(len(portTypes), 3) + + + bindings = interface_doc.root_elt.xpath( + '/wsdl:definitions/wsdl:binding', + namespaces = { + 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) + + self.assertEqual(len(bindings), 3) + + ports = interface_doc.root_elt.xpath( + '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' % + SinglePortService.__service_name__, + namespaces = { + 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) + self.assertEqual(len(ports), 1) + + ports = interface_doc.root_elt.xpath( + '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' % + "DoublePortService", + namespaces = { + 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) + self.assertEqual(len(ports), 2) + + # checking name and type + #service SinglePortService + for srv in (SinglePortService, DoublePortService): + for port in srv.__port_types__: + bindings = interface_doc.root_elt.xpath( + '/wsdl:definitions/wsdl:binding[@name="%s"]' % + port, + namespaces = { + 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) + self.assertEqual(bindings[0].get('type'), "tns:%s" % port) diff --git a/pym/calculate/contrib/spyne/test/interface/wsdl/test_bindings.pyc b/pym/calculate/contrib/spyne/test/interface/wsdl/test_bindings.pyc new file mode 100644 index 0000000..0974f23 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interface/wsdl/test_bindings.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interface/wsdl/test_default_wsdl.py b/pym/calculate/contrib/spyne/test/interface/wsdl/test_default_wsdl.py new file mode 100644 index 0000000..c81046e --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interface/wsdl/test_default_wsdl.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + + +import logging +logging.basicConfig(level=logging.DEBUG) + +import unittest + +from lxml import etree + +from spyne.application import Application + +from spyne.test.interface.wsdl import AppTestWrapper +from spyne.test.interface.wsdl import build_app +from spyne.test.interface.wsdl.defult_services import TDefaultPortService +from spyne.test.interface.wsdl.defult_services import \ + TDefaultPortServiceMultipleMethods + +from spyne.const import REQUEST_SUFFIX +from spyne.const import RESPONSE_SUFFIX +from spyne.const import ARRAY_SUFFIX + +from spyne.decorator import srpc +from spyne.service import Service +from spyne.interface.wsdl import Wsdl11 +from spyne.model.complex import Array +from spyne.model.primitive import String + +ns = { + 'wsdl': 'http://schemas.xmlsoap.org/wsdl/', + 'xs': 'http://www.w3.org/2001/XMLSchema', +} + + +class TestDefaultWSDLBehavior(unittest.TestCase): + def _default_service(self, app_wrapper, service_name): + self.assertEqual(1, len(app_wrapper.get_service_list())) + + services = app_wrapper.get_service_list() + service = services[0] + + # the default behavior requires that there be only a single service + self.assertEqual(1, len(services)) + self.assertEqual(service_name, service.get('name')) + + # Test the default service has the correct number of ports + # the default behavior requires that there be only a single port + ports = app_wrapper.get_port_list(service) + self.assertEqual(len(ports), 1) + + def _default_port_type(self, app_wrapper, portType_name, op_count): + # Verify the portType Count + portTypes = app_wrapper.get_port_types() + + # there should be only one portType + self.assertEqual(1, len(portTypes)) + + # Verify the portType name + portType = portTypes[0] + # Check the name of the port + self.assertEqual(portType_name, portType.get('name')) + + # verify that the portType definition has the correct + # number of operations + ops = app_wrapper.get_port_operations(portType) + self.assertEqual(op_count, len(ops)) + + def _default_binding(self, wrapper, binding_name, opp_count): + # the default behavior is only single binding + bindings = wrapper.get_bindings() + self.assertEqual(1, len(bindings)) + + # check for the correct binding name + binding = bindings[0] + name = binding.get('name') + self.assertEqual(binding_name, name) + + # Test that the default service contains the soap binding + sb = wrapper.get_soap_bindings(binding) + self.assertEqual(1, len(sb)) + + # verify the correct number of operations + ops = wrapper.get_binding_operations(binding) + self.assertEqual(opp_count, len(ops)) + + def _default_binding_methods(self, wrapper, op_count, op_names): + binding = wrapper.get_bindings()[0] + operations = wrapper.get_binding_operations(binding) + + # Check the number of operations bound to the port + self.assertEqual(op_count, len(operations)) + + # Check the operation names are correct + for op in operations: + self.assertTrue(op.get('name') in op_names) + + def test_default_port_type(self): + # Test the default port is created + # Test the default port has the correct name + app = build_app( + [TDefaultPortService()], + 'DefaultPortTest', + 'DefaultPortName' + ) + + wrapper = AppTestWrapper(app) + self._default_port_type(wrapper, 'DefaultPortName', 1) + + def test_default_port_type_multiple(self): + app = build_app( + [TDefaultPortServiceMultipleMethods()], + 'DefaultServiceTns', + 'MultipleDefaultPortServiceApp' + ) + + wrapper = AppTestWrapper(app) + + self._default_port_type(wrapper, "MultipleDefaultPortServiceApp", 3) + + def test_default_binding(self): + app = build_app( + [TDefaultPortService()], + 'DefaultPortTest', + 'DefaultBindingName' + ) + + wrapper = AppTestWrapper(app) + + self._default_binding(wrapper, "DefaultBindingName", 1) + + def test_default_binding_multiple(self): + app = build_app( + [TDefaultPortServiceMultipleMethods()], + 'DefaultPortTest', + 'MultipleDefaultBindingNameApp' + ) + + wrapper = AppTestWrapper(app) + + self._default_binding(wrapper, 'MultipleDefaultBindingNameApp', 3) + + def test_default_binding_methods(self): + app = build_app( + [TDefaultPortService()], + 'DefaultPortTest', + 'DefaultPortMethods' + ) + + wrapper = AppTestWrapper(app) + + self._default_binding_methods( + wrapper, + 1, + ['echo_default_port_service'] + ) + + def test_bare_simple(self): + class SomeService(Service): + @srpc(String, _returns=String, _body_style='bare') + def whatever(ss): + return ss + + app = Application([SomeService], tns='tns') + app.transport = 'None' + + wsdl = Wsdl11(app.interface) + wsdl.build_interface_document('url') + wsdl = etree.fromstring(wsdl.get_interface_document()) + + schema = wsdl.xpath( + '/wsdl:definitions/wsdl:types/xs:schema[@targetNamespace="tns"]', + namespaces=ns, + ) + assert len(schema) == 1 + + print(etree.tostring(wsdl, pretty_print=True)) + + elts = schema[0].xpath( + 'xs:element[@name="whatever%s"]' % REQUEST_SUFFIX, namespaces=ns) + assert len(elts) > 0 + assert elts[0].attrib['type'] == 'xs:string' + + elts = schema[0].xpath( + 'xs:element[@name="whatever%s"]' % RESPONSE_SUFFIX, namespaces=ns) + assert len(elts) > 0 + assert elts[0].attrib['type'] == 'xs:string' + + def test_bare_with_conflicting_types(self): + class SomeService(Service): + @srpc(Array(String), _returns=Array(String)) + def whatever(sa): + return sa + + @srpc(Array(String), _returns=Array(String), _body_style='bare') + def whatever_bare(sa): + return sa + + app = Application([SomeService], tns='tns') + app.transport = 'None' + + wsdl = Wsdl11(app.interface) + wsdl.build_interface_document('url') + wsdl = etree.fromstring(wsdl.get_interface_document()) + schema, = wsdl.xpath( + '/wsdl:definitions/wsdl:types/xs:schema[@targetNamespace="tns"]', + namespaces=ns, + ) + + print(etree.tostring(schema, pretty_print=True)) + + assert len(schema.xpath( + 'xs:complexType[@name="string%s"]' % ARRAY_SUFFIX, + namespaces=ns)) > 0 + + elts = schema.xpath( + 'xs:element[@name="whatever_bare%s"]' % REQUEST_SUFFIX, + namespaces=ns) + + assert len(elts) > 0 + assert elts[0].attrib['type'] == 'tns:string%s' % ARRAY_SUFFIX + + elts = schema.xpath( + 'xs:element[@name="whatever_bare%s"]' % RESPONSE_SUFFIX, + namespaces=ns) + + assert len(elts) > 0 + assert elts[0].attrib['type'] == 'tns:string%s' % ARRAY_SUFFIX + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/interface/wsdl/test_default_wsdl.pyc b/pym/calculate/contrib/spyne/test/interface/wsdl/test_default_wsdl.pyc new file mode 100644 index 0000000..147eeb2 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interface/wsdl/test_default_wsdl.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interface/wsdl/test_op_req_suffix.py b/pym/calculate/contrib/spyne/test/interface/wsdl/test_op_req_suffix.py new file mode 100644 index 0000000..141395b --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interface/wsdl/test_op_req_suffix.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + +import unittest + +from webtest import TestApp as _TestApp # avoid confusing py.test + +from spyne.application import Application +from spyne.decorator import srpc +from spyne.service import Service +from spyne.model.primitive import Integer, Unicode +from spyne.model.complex import Iterable +from spyne.protocol.soap import Soap11 +from spyne.protocol.http import HttpRpc +from spyne.protocol.json import JsonDocument +from spyne.server.wsgi import WsgiApplication + +from spyne.const.xml import PREFMAP, NS_WSDL11_SOAP + +def strip_whitespace(string): + return ''.join(string.split()) + + +class TestOperationRequestSuffix(unittest.TestCase): + """ + test different protocols with REQUEST_SUFFIX and _operation_name + _in_message_name is a concern, will test that as well + """ + + default_function_name = 'echo' + + # output is not affected, will use soap output for all tests + result_body = ''' + + + + Echo, test + Echo, test + + + ''' + + def get_function_names(self, suffix, _operation_name=None, + _in_message_name=None): + """This tests the logic of how names are produced. + Its logic should match expected behavior of the decorator. + returns operation name, in message name, service name depending on + args""" + function_name = self.default_function_name + + if _operation_name is None: + operation_name = function_name + else: + operation_name = _operation_name + + if _in_message_name is None: + request_name = operation_name + suffix + else: + request_name = _in_message_name + + return function_name, operation_name, request_name + + def get_app(self, in_protocol, suffix, _operation_name=None, + _in_message_name=None): + """setup testapp dependent on suffix and _in_message_name""" + + import spyne.const + spyne.const.REQUEST_SUFFIX = suffix + + class EchoService(Service): + + srpc_kparams = {'_returns': Iterable(Unicode)} + if _in_message_name: + srpc_kparams['_in_message_name'] = _in_message_name + if _operation_name: + srpc_kparams['_operation_name'] = _operation_name + + @srpc(Unicode, Integer, **srpc_kparams) + def echo(string, times): + for i in range(times): + yield 'Echo, %s' % string + + application = Application([EchoService], + tns='spyne.examples.echo', + in_protocol=in_protocol, + out_protocol=Soap11() + ) + app = WsgiApplication(application) + + testapp = _TestApp(app) + + # so that it doesn't interfere with other tests. + spyne.const.REQUEST_SUFFIX = '' + + return testapp + + def assert_response_ok(self, resp): + """check the default response""" + self.assertEqual(resp.status_int, 200, resp) + self.assertTrue( + strip_whitespace(self.result_body) in strip_whitespace(str(resp)), + '{0} not in {1}'.format(self.result_body, resp)) + + ### application error tests ### + def assert_application_error(self, suffix, _operation_name=None, + _in_message_name=None): + self.assertRaises(ValueError, + self.get_app, Soap11(validator='lxml'), suffix, + _operation_name, _in_message_name) + + def test_assert_application_error(self): + """check error when op namd and in name are both used""" + self.assert_application_error(suffix='', + _operation_name='TestOperationName', + _in_message_name='TestMessageName') + + ### soap tests ### + def assert_soap_ok(self, suffix, _operation_name=None, + _in_message_name=None): + """helper to test soap requests""" + + # setup + app = self.get_app(Soap11(validator='lxml'), suffix, _operation_name, + _in_message_name) + + function_name, operation_name, request_name = self.get_function_names( + suffix, _operation_name, _in_message_name) + + soap_input_body = """ + + + + + test + 2 + + + """.format(request_name) + + # check wsdl + wsdl = app.get('/?wsdl') + self.assertEqual(wsdl.status_int, 200, wsdl) + self.assertTrue(request_name in wsdl, + '{0} not found in wsdl'.format(request_name)) + + soap_strings = [ + ''.format(request_name), + ''.format(request_name), + ] + for soap_string in soap_strings: + self.assertTrue(soap_string in wsdl, + '{0} not in {1}'.format(soap_string, wsdl)) + if request_name != operation_name: + wrong_string = '= 2.5") diff --git a/pym/calculate/contrib/spyne/test/interop/server/httprpc_csv_basic.pyc b/pym/calculate/contrib/spyne/test/interop/server/httprpc_csv_basic.pyc new file mode 100644 index 0000000..89af038 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/httprpc_csv_basic.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/httprpc_pod_basic.py b/pym/calculate/contrib/spyne/test/interop/server/httprpc_pod_basic.py new file mode 100644 index 0000000..bcf657f --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/server/httprpc_pod_basic.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""pod being plain old data""" + +import logging + + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger('spyne.protocol.xml') +logger.setLevel(logging.DEBUG) + +from spyne.test.interop.server import get_open_port +from spyne.application import Application +from spyne.test.interop.server._service import services +from spyne.protocol.http import HttpRpc +from spyne.server.wsgi import WsgiApplication + +httprpc_soap_application = Application(services, + 'spyne.test.interop.server.httprpc.pod', in_protocol=HttpRpc(), + out_protocol=HttpRpc()) +host = "127.0.0.1" +port = [0] + + +def main(): + try: + from wsgiref.simple_server import make_server + from wsgiref.validate import validator + if port[0] == 0: + port[0] = get_open_port() + + wsgi_application = WsgiApplication(httprpc_soap_application) + server = make_server(host, port[0], validator(wsgi_application)) + + logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', port[0])) + logger.info('WSDL is at: /?wsdl') + server.serve_forever() + + except ImportError: + print("Error: example server code requires Python >= 2.5") + + +if __name__ == '__main__': + main() diff --git a/pym/calculate/contrib/spyne/test/interop/server/httprpc_pod_basic.pyc b/pym/calculate/contrib/spyne/test/interop/server/httprpc_pod_basic.pyc new file mode 100644 index 0000000..d196eec Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/httprpc_pod_basic.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/httprpc_pod_basic_twisted.py b/pym/calculate/contrib/spyne/test/interop/server/httprpc_pod_basic_twisted.py new file mode 100644 index 0000000..6e824f4 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/server/httprpc_pod_basic_twisted.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""pod being plain old data""" + +import logging +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger('spyne.protocol.xml') +logger.setLevel(logging.DEBUG) + +from spyne.test.interop.server import get_open_port +from spyne.application import Application +from spyne.test.interop.server._service import services +from spyne.protocol.http import HttpRpc +from spyne.server.twisted import TwistedWebResource + +httprpc_soap_application = Application(services, + 'spyne.test.interop.server.httprpc.pod', + in_protocol=HttpRpc(), out_protocol=HttpRpc()) + +host = '127.0.0.1' +port = [0] + +def main(argv): + from twisted.web.server import Site + from twisted.internet import reactor + from twisted.python import log + observer = log.PythonLoggingObserver('twisted') + log.startLoggingWithObserver(observer.emit, setStdout=False) + + if port[0] == 0: + port[0] = get_open_port() + + wr = TwistedWebResource(httprpc_soap_application) + site = Site(wr) + + reactor.listenTCP(port[0], site) + logging.info("listening on: %s:%d" % (host,port[0])) + + return reactor.run() + + +if __name__ == '__main__': + import sys + sys.exit(main(sys.argv)) diff --git a/pym/calculate/contrib/spyne/test/interop/server/httprpc_pod_basic_twisted.pyc b/pym/calculate/contrib/spyne/test/interop/server/httprpc_pod_basic_twisted.pyc new file mode 100644 index 0000000..899ac1d Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/httprpc_pod_basic_twisted.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/msgpackrpc_http_basic.py b/pym/calculate/contrib/spyne/test/interop/server/msgpackrpc_http_basic.py new file mode 100644 index 0000000..d19e869 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/server/msgpackrpc_http_basic.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logging.basicConfig(level=logging.DEBUG) +logging.getLogger('spyne.protocol.msgpack').setLevel(logging.DEBUG) +logger = logging.getLogger('spyne.test.interop.server.msgpackrpc_http_basic') + +from spyne.test.interop.server import get_open_port +from spyne.server.wsgi import WsgiApplication +from spyne.test.interop.server._service import services +from spyne.application import Application +from spyne.protocol.msgpack import MessagePackRpc + +msgpackrpc_application = Application(services, 'spyne.test.interop.server', + in_protocol=MessagePackRpc(validator='soft'), + out_protocol=MessagePackRpc()) + +host = '127.0.0.1' +port = [0] + +def main(): + try: + from wsgiref.simple_server import make_server + from wsgiref.validate import validator + if port[0] == 0: + port[0] = get_open_port() + + wsgi_application = WsgiApplication(msgpackrpc_application) + server = make_server(host, port[0], validator(wsgi_application)) + + logger.info('Starting interop server at %s:%s.' % (host, port[0])) + logger.info('WSDL is at: /?wsdl') + server.serve_forever() + + except ImportError: + print("Error: example server code requires Python >= 2.5") + +if __name__ == '__main__': + main() diff --git a/pym/calculate/contrib/spyne/test/interop/server/msgpackrpc_http_basic.pyc b/pym/calculate/contrib/spyne/test/interop/server/msgpackrpc_http_basic.pyc new file mode 100644 index 0000000..352d090 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/msgpackrpc_http_basic.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap11/__init__.py b/pym/calculate/contrib/spyne/test/interop/server/soap11/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap11/__init__.pyc b/pym/calculate/contrib/spyne/test/interop/server/soap11/__init__.pyc new file mode 100644 index 0000000..a850fea Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/soap11/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap11/httprpc_soap_basic.py b/pym/calculate/contrib/spyne/test/interop/server/soap11/httprpc_soap_basic.py new file mode 100644 index 0000000..3969598 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/server/soap11/httprpc_soap_basic.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger('spyne.protocol.xml') +logger.setLevel(logging.DEBUG) + +from spyne.application import Application +from spyne.test.interop.server._service import services +from spyne.protocol.http import HttpRpc +from spyne.protocol.soap import Soap11 +from spyne.server.wsgi import WsgiApplication +from spyne.test.interop.server import get_open_port + + +httprpc_soap_application = Application(services, + 'spyne.test.interop.server.httprpc.soap', in_protocol=HttpRpc(), out_protocol=Soap11()) + + +host = '127.0.0.1' +port = [0] + + +if __name__ == '__main__': + try: + from wsgiref.simple_server import make_server + from wsgiref.validate import validator + if port[0] == 0: + port[0] = get_open_port() + + wsgi_application = WsgiApplication(httprpc_soap_application) + server = make_server(host, port[0], validator(wsgi_application)) + + logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', port[0])) + logger.info('WSDL is at: /?wsdl') + server.serve_forever() + + except ImportError: + print("Error: example server code requires Python >= 2.5") diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap11/httprpc_soap_basic.pyc b/pym/calculate/contrib/spyne/test/interop/server/soap11/httprpc_soap_basic.pyc new file mode 100644 index 0000000..e175e9f Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/soap11/httprpc_soap_basic.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_basic.py b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_basic.py new file mode 100644 index 0000000..65a8605 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_basic.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging + +logging.basicConfig(level=logging.DEBUG) +logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) +logger = logging.getLogger('spyne.test.interop.server.soap_http_basic') + + +from spyne.test.interop.server import get_open_port +from spyne.server.wsgi import WsgiApplication +from spyne.test.interop.server._service import services +from spyne.application import Application +from spyne.protocol.soap import Soap11 + + +soap11_application = Application(services, 'spyne.test.interop.server', + in_protocol=Soap11(validator='lxml', cleanup_namespaces=True), + out_protocol=Soap11()) + + +host = '127.0.0.1' +port = [0] + + +def main(): + try: + from wsgiref.simple_server import make_server + from wsgiref.validate import validator + if port[0] == 0: + port[0] = get_open_port() + + wsgi_application = WsgiApplication(soap11_application) + server = make_server(host, port[0], validator(wsgi_application)) + + logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', port[0])) + logger.info('WSDL is at: /?wsdl') + server.serve_forever() + + except ImportError: + print("Error: example server code requires Python >= 2.5") + + +if __name__ == '__main__': + main() diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_basic.pyc b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_basic.pyc new file mode 100644 index 0000000..95048ef Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_basic.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_basic_twisted.py b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_basic_twisted.py new file mode 100644 index 0000000..b9d502b --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_basic_twisted.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger('spyne.wsgi') +logger.setLevel(logging.DEBUG) + +from spyne.test.interop.server import get_open_port +from spyne.test.interop.server.soap_http_basic import soap11_application +from spyne.server.twisted import TwistedWebResource + + +host = '127.0.0.1' +port = [0] + + +def main(argv): + from twisted.web.server import Site + from twisted.internet import reactor + from twisted.python import log + + observer = log.PythonLoggingObserver('twisted') + log.startLoggingWithObserver(observer.emit, setStdout=False) + + wr = TwistedWebResource(soap11_application) + site = Site(wr) + + if port[0] == 0: + port[0] = get_open_port() + reactor.listenTCP(port[0], site) + logging.info("listening on: %s:%d" % (host,port[0])) + + return reactor.run() + +if __name__ == '__main__': + import sys + sys.exit(main(sys.argv)) diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_basic_twisted.pyc b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_basic_twisted.pyc new file mode 100644 index 0000000..eacdd55 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_basic_twisted.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_static.py b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_static.py new file mode 100644 index 0000000..0e8e26e --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_static.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger('spyne.wsgi') +logger.setLevel(logging.DEBUG) + +import os + +from spyne.test.interop.server import get_open_port +from spyne.test.interop.server.soap_http_basic import soap11_application +from spyne.server.twisted import TwistedWebResource + + +host = '127.0.0.1' +port = [0] +url = 'app' + + +def main(argv): + from twisted.python import log + from twisted.web.server import Site + from twisted.web.static import File + from twisted.internet import reactor + from twisted.python import log + + observer = log.PythonLoggingObserver('twisted') + log.startLoggingWithObserver(observer.emit, setStdout=False) + + static_dir = os.path.abspath('.') + logging.info("registering static folder %r on /" % static_dir) + root = File(static_dir) + + wr = TwistedWebResource(soap11_application) + logging.info("registering %r on /%s" % (wr, url)) + root.putChild(url, wr) + + site = Site(root) + + if port[0] == 0: + port[0] = get_open_port() + reactor.listenTCP(port[0], site) + logging.info("listening on: %s:%d" % (host,port)) + + return reactor.run() + + +if __name__ == '__main__': + import sys + sys.exit(main(sys.argv)) diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_static.pyc b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_static.pyc new file mode 100644 index 0000000..cbbfbd3 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_http_static.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_zeromq.py b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_zeromq.py new file mode 100644 index 0000000..5cb7f4d --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_zeromq.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging + +from spyne.test.interop.server import get_open_port +from spyne.test.interop.server.soap11.soap_http_basic import soap11_application + +from spyne.server.zeromq import ZeroMQServer + + +host = '127.0.0.1' +port = [0] + + +def main(): + if port[0] == 0: + port[0] = get_open_port() + url = "tcp://%s:%d" % (host, port[0]) + + logging.basicConfig(level=logging.DEBUG) + logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) + + server = ZeroMQServer(soap11_application, url) + logging.info("************************") + logging.info("Use Ctrl+\\ to exit if Ctrl-C does not work.") + logging.info("See the 'I can't Ctrl-C my Python/Ruby application. Help!' " + "question in http://www.zeromq.org/area:faq for more info.") + logging.info("listening on %r" % url) + logging.info("************************") + + server.serve_forever() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_zeromq.pyc b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_zeromq.pyc new file mode 100644 index 0000000..5eb9f26 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/soap11/soap_zeromq.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap12/__init__.py b/pym/calculate/contrib/spyne/test/interop/server/soap12/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap12/__init__.pyc b/pym/calculate/contrib/spyne/test/interop/server/soap12/__init__.pyc new file mode 100644 index 0000000..7ca14cb Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/soap12/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap12/httprpc_soap_basic.py b/pym/calculate/contrib/spyne/test/interop/server/soap12/httprpc_soap_basic.py new file mode 100644 index 0000000..c3bc5b1 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/server/soap12/httprpc_soap_basic.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger('spyne.protocol.xml') +logger.setLevel(logging.DEBUG) + +from spyne.application import Application +from spyne.test.interop.server._service import services +from spyne.protocol.http import HttpRpc +from spyne.protocol.soap import Soap12 +from spyne.server.wsgi import WsgiApplication + +httprpc_soap_application = Application(services, + 'spyne.test.interop.server.httprpc.soap', in_protocol=HttpRpc(), out_protocol=Soap12()) + +host = '127.0.0.1' +port = 9753 + +if __name__ == '__main__': + try: + from wsgiref.simple_server import make_server + from wsgiref.validate import validator + + wsgi_application = WsgiApplication(httprpc_soap_application) + server = make_server(host, port, validator(wsgi_application)) + + logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', 9753)) + logger.info('WSDL is at: /?wsdl') + server.serve_forever() + + except ImportError: + print("Error: example server code requires Python >= 2.5") diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap12/httprpc_soap_basic.pyc b/pym/calculate/contrib/spyne/test/interop/server/soap12/httprpc_soap_basic.pyc new file mode 100644 index 0000000..5d29640 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/soap12/httprpc_soap_basic.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_basic.py b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_basic.py new file mode 100644 index 0000000..27fe6f0 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_basic.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging + +logging.basicConfig(level=logging.DEBUG) +logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) +logger = logging.getLogger('spyne.test.interop.server.soap_http_basic') + +from spyne.server.wsgi import WsgiApplication +from spyne.test.interop.server._service import services +from spyne.application import Application +from spyne.protocol.soap import Soap12 + + +soap12_application = Application(services, 'spyne.test.interop.server', + in_protocol=Soap12(validator='lxml', cleanup_namespaces=True), + out_protocol=Soap12()) + +host = '127.0.0.1' +port = 9754 + +def main(): + try: + from wsgiref.simple_server import make_server + from wsgiref.validate import validator + + wsgi_application = WsgiApplication(soap12_application) + server = make_server(host, port, validator(wsgi_application)) + + logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', 9754)) + logger.info('WSDL is at: /?wsdl') + server.serve_forever() + + except ImportError: + print("Error: example server code requires Python >= 2.5") + +if __name__ == '__main__': + main() diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_basic.pyc b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_basic.pyc new file mode 100644 index 0000000..d62b14a Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_basic.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_basic_twisted.py b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_basic_twisted.py new file mode 100644 index 0000000..d115e2f --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_basic_twisted.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger('spyne.wsgi') +logger.setLevel(logging.DEBUG) + +from spyne.test.interop.server.soap12.soap_http_basic import soap12_application +from spyne.server.twisted import TwistedWebResource + +host = '127.0.0.1' +port = 9755 + +def main(argv): + from twisted.web.server import Site + from twisted.internet import reactor + from twisted.python import log + + observer = log.PythonLoggingObserver('twisted') + log.startLoggingWithObserver(observer.emit, setStdout=False) + + wr = TwistedWebResource(soap12_application) + site = Site(wr) + + reactor.listenTCP(port, site) + logging.info("listening on: %s:%d" % (host,port)) + + return reactor.run() + +if __name__ == '__main__': + import sys + sys.exit(main(sys.argv)) diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_basic_twisted.pyc b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_basic_twisted.pyc new file mode 100644 index 0000000..0821bac Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_basic_twisted.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_static.py b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_static.py new file mode 100644 index 0000000..6cc742f --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_static.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger('spyne.wsgi') +logger.setLevel(logging.DEBUG) + +import os + +from spyne.test.interop.server.soap12.soap_http_basic import soap12_application +from spyne.server.twisted import TwistedWebResource + +host = '127.0.0.1' +port = 9756 +url = 'app' + +def main(argv): + from twisted.python import log + from twisted.web.server import Site + from twisted.web.static import File + from twisted.internet import reactor + from twisted.python import log + + observer = log.PythonLoggingObserver('twisted') + log.startLoggingWithObserver(observer.emit, setStdout=False) + + static_dir = os.path.abspath('.') + logging.info("registering static folder %r on /" % static_dir) + root = File(static_dir) + + wr = TwistedWebResource(soap12_application) + logging.info("registering %r on /%s" % (wr, url)) + root.putChild(url, wr) + + site = Site(root) + + reactor.listenTCP(port, site) + logging.info("listening on: %s:%d" % (host,port)) + + return reactor.run() + + +if __name__ == '__main__': + import sys + sys.exit(main(sys.argv)) diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_static.pyc b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_static.pyc new file mode 100644 index 0000000..980bc1b Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_http_static.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_zeromq.py b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_zeromq.py new file mode 100644 index 0000000..62c2b73 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_zeromq.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging + +from spyne.test.interop.server.soap12.soap_http_basic import soap12_application + +from spyne.server.zeromq import ZeroMQServer + +host = '127.0.0.1' +port = 55555 + +def main(): + url = "tcp://%s:%d" % (host,port) + + logging.basicConfig(level=logging.DEBUG) + logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) + + server = ZeroMQServer(soap12_application, url) + logging.info("************************") + logging.info("Use Ctrl+\\ to exit if Ctrl-C does not work.") + logging.info("See the 'I can't Ctrl-C my Python/Ruby application. Help!' " + "question in http://www.zeromq.org/area:faq for more info.") + logging.info("listening on %r" % url) + logging.info("************************") + + server.serve_forever() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_zeromq.pyc b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_zeromq.pyc new file mode 100644 index 0000000..d8bf4a9 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/server/soap12/soap_zeromq.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/test_django.py b/pym/calculate/contrib/spyne/test/interop/test_django.py new file mode 100644 index 0000000..b5df8b5 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/test_django.py @@ -0,0 +1,305 @@ +# coding: utf-8 +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + + +from __future__ import absolute_import + +import datetime +import re +from django.core.exceptions import ImproperlyConfigured +from django.test import TestCase, TransactionTestCase, Client + +from spyne.client.django import DjangoTestClient +from spyne.model.fault import Fault +from spyne.model.complex import ComplexModelBase +from spyne.util.django import (DjangoComplexModel, DjangoComplexModelMeta, + email_re) +from spyne.util.six import add_metaclass + +from rpctest.core.models import (FieldContainer, RelatedFieldContainer, + UserProfile as DjUserProfile) +from rpctest.core.views import app, hello_world_service, Container + + +class SpyneTestCase(TransactionTestCase): + def setUp(self): + self.client = DjangoTestClient('/hello_world/', hello_world_service.app) + + def _test_say_hello(self): + resp = self.client.service.say_hello('Joe', 5) + list_resp = list(resp) + self.assertEqual(len(list_resp), 5) + self.assertEqual(list_resp, ['Hello, Joe'] * 5) + + +class DjangoViewTestCase(TestCase): + def test_say_hello(self): + client = DjangoTestClient('/say_hello/', app) + resp = client.service.say_hello('Joe', 5) + list_resp = list(resp) + self.assertEqual(len(list_resp), 5) + self.assertEqual(list_resp, ['Hello, Joe'] * 5) + + def test_response_encoding(self): + client = DjangoTestClient('/say_hello/', app) + response = client.service.say_hello.get_django_response('Joe', 5) + self.assertTrue('Content-Type' in response) + self.assertTrue(response['Content-Type'].startswith('text/xml')) + + def test_error(self): + client = Client() + response = client.post('/say_hello/', {}) + self.assertContains(response, 'faultstring', status_code=500) + + def test_cached_wsdl(self): + """Test if wsdl is cached.""" + client = Client() + response = client.get('/say_hello/') + self.assertContains(response, + 'location="http://testserver/say_hello/"') + response = client.get('/say_hello/', HTTP_HOST='newtestserver') + self.assertNotContains(response, + 'location="http://newtestserver/say_hello/"') + + def test_not_cached_wsdl(self): + """Test if wsdl is not cached.""" + client = Client() + response = client.get('/say_hello_not_cached/') + self.assertContains( + response, 'location="http://testserver/say_hello_not_cached/"') + response = client.get('/say_hello_not_cached/', + HTTP_HOST='newtestserver') + + self.assertContains( + response, 'location="http://newtestserver/say_hello_not_cached/"') + + +class ModelTestCase(TestCase): + + """Test mapping between django and spyne models.""" + + def setUp(self): + self.client = DjangoTestClient('/api/', app) + + def test_exclude(self): + """Test if excluded field is not mapped.""" + type_info = Container.get_flat_type_info(Container) + self.assertIn('id', type_info) + self.assertNotIn('excluded_field', type_info) + + def test_pk_mapping(self): + """Test if primary key is mapped as optional but not nillable.""" + type_info = Container.get_flat_type_info(Container) + pk_field = type_info['id'] + self.assertEqual(pk_field.Attributes.min_occurs, 0) + self.assertFalse(pk_field.Attributes.nullable) + + def test_regex_pattern_mapping(self): + """Test if regex pattern is mapped from django model.""" + type_info = Container.get_flat_type_info(Container) + email_field = type_info['email_field'] + self.assertEqual(email_field.__name__, 'Unicode') + self.assertIsNotNone(email_field.Attributes.pattern) + self.assertEqual(email_field.Attributes.min_occurs, 1) + self.assertFalse(email_field.Attributes.nullable) + + def test_blank_field(self): + """Test if blank fields are optional but not null.""" + type_info = Container.get_flat_type_info(Container) + blank_field = type_info['blank_field'] + self.assertEqual(blank_field.__name__, 'NormalizedString') + self.assertEqual(blank_field.Attributes.min_occurs, 0) + self.assertFalse(blank_field.Attributes.nullable) + + def test_blank_as_dict(self): + """Test if blank field is omitted in as_dict representation.""" + container = Container() + container_dict = container.as_dict() + self.assertNotIn('blank_field', container_dict) + + def test_length_validators_field(self): + """Test if length validators are correctly mapped.""" + type_info = Container.get_flat_type_info(Container) + length_validators_field = type_info['length_validators_field'] + self.assertEqual(length_validators_field.__name__, 'NormalizedString') + self.assertEqual(length_validators_field.Attributes.min_occurs, 1) + self.assertTrue(length_validators_field.Attributes.nullable) + self.assertEqual(length_validators_field.Attributes.min_len, 3) + self.assertEqual(length_validators_field.Attributes.max_len, 10) + + def test_get_container(self): + """Test mapping from Django model to spyne model.""" + get_container = lambda: self.client.service.get_container(2) + self.assertRaises(Fault, get_container) + container = FieldContainer.objects.create(slug_field='container') + FieldContainer.objects.create(slug_field='container2', + foreign_key=container, + one_to_one_field=container, + email_field='email@example.com', + char_field='yo') + c = get_container() + self.assertIsInstance(c, Container) + + def test_create_container(self): + """Test complex input to create Django model.""" + related_container = RelatedFieldContainer(id='related') + new_container = FieldContainer(slug_field='container', + date_field=datetime.date.today(), + datetime_field=datetime.datetime.now(), + email_field='email@example.com', + time_field=datetime.time(), + custom_foreign_key=related_container, + custom_one_to_one_field=related_container) + create_container = (lambda: self.client.service.create_container( + new_container)) + c = create_container() + + self.assertIsInstance(c, Container) + self.assertEqual(c.custom_one_to_one_field_id, 'related') + self.assertEqual(c.custom_foreign_key_id, 'related') + self.assertRaises(Fault, create_container) + + def test_create_container_unicode(self): + """Test complex unicode input to create Django model.""" + new_container = FieldContainer( + char_field=u'спайн', + text_field=u'спайн', + slug_field='spyne', + email_field='email@example.com', + date_field=datetime.date.today(), + datetime_field=datetime.datetime.now(), + time_field=datetime.time() + ) + create_container = (lambda: self.client.service.create_container( + new_container)) + c = create_container() + self.assertIsInstance(c, Container) + self.assertRaises(Fault, create_container) + + def test_optional_relation_fields(self): + """Test if optional_relations flag makes fields optional.""" + class UserProfile(DjangoComplexModel): + class Attributes(DjangoComplexModel.Attributes): + django_model = DjUserProfile + + self.assertFalse(UserProfile._type_info['user_id'].Attributes.nullable) + + class UserProfile(DjangoComplexModel): + class Attributes(DjangoComplexModel.Attributes): + django_model = DjUserProfile + django_optional_relations = True + + self.assertEqual( + UserProfile._type_info['user_id'].Attributes.min_occurs, 0) + + def test_abstract_custom_djangomodel(self): + """Test if can create custom DjangoComplexModel.""" + @add_metaclass(DjangoComplexModelMeta) + class OrderedDjangoComplexModel(ComplexModelBase): + __abstract__ = True + + class Attributes(ComplexModelBase.Attributes): + declare_order = 'declared' + + class OrderedFieldContainer(OrderedDjangoComplexModel): + class Attributes(OrderedDjangoComplexModel.Attributes): + django_model = FieldContainer + + field_container = OrderedFieldContainer() + type_info_fields = field_container._type_info.keys() + django_field_names = [field.get_attname() for field in + FieldContainer._meta.fields] + # file field is not mapped + django_field_names.remove('file_field') + # check if ordering is the same as defined in Django model + self.assertEqual(type_info_fields, django_field_names) + + def test_nonabstract_custom_djangomodel(self): + """Test if can't create non abstract custom model.""" + with self.assertRaises( + ImproperlyConfigured, msg='Can create non abstract custom model' + ): + @add_metaclass(DjangoComplexModelMeta) + class CustomNotAbstractDjangoComplexModel(ComplexModelBase): + class Attributes(ComplexModelBase.Attributes): + declare_order = 'declared' + + +# in XmlSchema ^ and $ are set implicitly +python_email_re = '^' + email_re.pattern + '$' + + +class EmailRegexTestCase(TestCase): + + """Tests for email_re.""" + + def test_empty(self): + """Empty string is invalid email.""" + self.assertIsNone(re.match(python_email_re, '')) + + def test_valid(self): + """Test valid email.""" + self.assertIsNotNone( + re.match(python_email_re, 'valid.email@example.com') + ) + + def test_valid_single_letter_domain(self): + """Test valid email.""" + self.assertIsNotNone(re.match(python_email_re, 'valid.email@e.x.com')) + + def test_invalid(self): + """Test invalid email.""" + self.assertIsNone(re.match(python_email_re, '@example.com')) + + def test_invalid_tld(self): + """Test if email from Top Level Domain is invalid.""" + self.assertIsNone(re.match(python_email_re, 'babushka@email')) + self.assertIsNone(re.match(python_email_re, 'babushka@domain.email-')) + + +class DjangoServiceTestCase(TestCase): + + """Tests for Django specific service.""" + + def test_handle_does_not_exist(self): + """Test if Django service handles `ObjectDoesNotExist` exceptions.""" + client = DjangoTestClient('/api/', app) + with self.assertRaisesRegexp(Fault, 'Client.FieldContainerNotFound'): + client.service.raise_does_not_exist() + + def test_handle_validation_error(self): + """Test if Django service handles `ValidationError` exceptions.""" + client = DjangoTestClient('/api/', app) + with self.assertRaisesRegexp(Fault, 'Client.ValidationError'): + client.service.raise_validation_error() + + +class FromUnicodeAssertionTestCase(TestCase): + + def test_from_unicode_does_not_assert(self): + client = Client() + url = '/synchro/1/' + msg = b"""TestModel + 2015-09-23T13:54:51.796366+00:00 + """ + hdrs = {'SOAPAction': b'"sync"', 'Content-Type': 'text/xml; charset=utf-8'} + client.post(url, msg, 'text/xml', True, **hdrs) diff --git a/pym/calculate/contrib/spyne/test/interop/test_django.pyc b/pym/calculate/contrib/spyne/test/interop/test_django.pyc new file mode 100644 index 0000000..ff2289a Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/test_django.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/test_httprpc.py b/pym/calculate/contrib/spyne/test/interop/test_httprpc.py new file mode 100644 index 0000000..4d72bc4 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/test_httprpc.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import unittest + +import time + +import pytz + +from datetime import datetime + +from spyne.test.interop._test_soap_client_base import server_started +from spyne.util import thread, urlencode, urlopen, Request, HTTPError + + +_server_started = False + + +class TestHttpRpc(unittest.TestCase): + def setUp(self): + global _server_started + from spyne.test.interop.server.httprpc_pod_basic import main, port + + if not _server_started: + def run_server(): + main() + + thread.start_new_thread(run_server, ()) + + # FIXME: Does anybody have a better idea? + time.sleep(2) + + _server_started = True + + self.base_url = 'http://localhost:%d' % port[0] + + def test_404(self): + url = '%s/404' % self.base_url + try: + data = urlopen(url).read() + except HTTPError as e: + assert e.code == 404 + + def test_413(self): + url = self.base_url + try: + data = Request(url,("foo"*3*1024*1024)) + except HTTPError as e: + assert e.code == 413 + + def test_500(self): + url = '%s/python_exception' % self.base_url + try: + data = urlopen(url).read() + except HTTPError as e: + assert e.code == 500 + + def test_500_2(self): + url = '%s/soap_exception' % self.base_url + try: + data = urlopen(url).read() + except HTTPError as e: + assert e.code == 500 + + def test_echo_string(self): + url = '%s/echo_string?s=punk' % self.base_url + data = urlopen(url).read() + + assert data == b'punk' + + def test_echo_integer(self): + url = '%s/echo_integer?i=444' % self.base_url + data = urlopen(url).read() + + assert data == b'444' + + def test_echo_datetime(self): + dt = datetime.now(pytz.utc).isoformat().encode('ascii') + params = urlencode({ + 'dt': dt, + }) + + print(params) + url = '%s/echo_datetime?%s' % (self.base_url, str(params)) + data = urlopen(url).read() + + assert dt == data + + def test_echo_datetime_tz(self): + dt = datetime.now(pytz.utc).isoformat().encode('ascii') + params = urlencode({ + 'dt': dt, + }) + + print(params) + url = '%s/echo_datetime?%s' % (self.base_url, str(params)) + data = urlopen(url).read() + + assert dt == data + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/interop/test_httprpc.pyc b/pym/calculate/contrib/spyne/test/interop/test_httprpc.pyc new file mode 100644 index 0000000..eac24b6 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/test_httprpc.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/test_msgpackrpc_client_http.py b/pym/calculate/contrib/spyne/test/interop/test_msgpackrpc_client_http.py new file mode 100644 index 0000000..ab39ff6 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/test_msgpackrpc_client_http.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import unittest + +from spyne.client.http import HttpClient +from spyne.test.interop._test_soap_client_base import SpyneClientTestBase +from spyne.test.interop.server.msgpackrpc_http_basic import msgpackrpc_application, port +from spyne.util.etreeconv import root_dict_to_etree + +class TestSpyneHttpClient(SpyneClientTestBase, unittest.TestCase): + def setUp(self): + SpyneClientTestBase.setUp(self, 'msgpack_rpc_http') + + self.client = HttpClient('http://localhost:%d/' % port[0], + msgpackrpc_application) + self.ns = "spyne.test.interop.server" + + @unittest.skip("MessagePackRpc does not support header") + def test_echo_in_header(self): + pass + + @unittest.skip("MessagePackRpc does not support header") + def test_send_out_header(self): + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/interop/test_msgpackrpc_client_http.pyc b/pym/calculate/contrib/spyne/test/interop/test_msgpackrpc_client_http.pyc new file mode 100644 index 0000000..fec7e41 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/test_msgpackrpc_client_http.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/test_pyramid.py b/pym/calculate/contrib/spyne/test/interop/test_pyramid.py new file mode 100644 index 0000000..2dd3b04 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/test_pyramid.py @@ -0,0 +1,76 @@ +# coding: utf-8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# +import unittest +from wsgiref.util import setup_testing_defaults +from wsgiref.validate import validator + +from lxml import etree +from pyramid import testing +from pyramid.config import Configurator +from pyramid.request import Request + +from spyne.protocol.soap import Soap11 +from spyne.service import Service +from spyne.decorator import srpc +from spyne import Application +from spyne.model import Unicode, Integer, Iterable +from spyne.server.pyramid import PyramidApplication + + +class SpyneIntegrationTest(unittest.TestCase): + """Tests for integration of Spyne into Pyramid view callable""" + class HelloWorldService(Service): + @srpc(Unicode, Integer, _returns=Iterable(Unicode)) + def say_hello(name, times): + for i in range(times): + yield 'Hello, %s' % name + + def setUp(self): + request = testing.DummyRequest() + self.config = testing.setUp(request=request) + + def tearDown(self): + testing.tearDown() + + def testGetWsdl(self): + """Simple test for serving of WSDL by spyne through pyramid route""" + application = PyramidApplication( + Application([self.HelloWorldService], + tns='spyne.examples.hello', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11())) + + config = Configurator(settings={'debug_all': True}) + config.add_route('home', '/') + config.add_view(application, route_name='home') + wsgi_app = validator(config.make_wsgi_app()) + + env = { + 'SCRIPT_NAME': '', + 'REQUEST_METHOD': 'GET', + 'PATH_INFO': '/', + 'QUERY_STRING': 'wsdl', + } + setup_testing_defaults(env) + + request = Request(env) + resp = request.get_response(wsgi_app) + self.assert_(resp.status.startswith("200 ")) + node = etree.XML(resp.body) # will throw exception if non well formed + diff --git a/pym/calculate/contrib/spyne/test/interop/test_pyramid.pyc b/pym/calculate/contrib/spyne/test/interop/test_pyramid.pyc new file mode 100644 index 0000000..e25d28e Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/test_pyramid.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/test_soap_client_http.py b/pym/calculate/contrib/spyne/test/interop/test_soap_client_http.py new file mode 100644 index 0000000..b1e88e2 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/test_soap_client_http.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import unittest + +from spyne.client.http import HttpClient +from spyne.test.interop._test_soap_client_base import SpyneClientTestBase, \ + server_started +from spyne.test.interop.server.soap11.soap_http_basic import soap11_application + +class TestSpyneHttpClient(SpyneClientTestBase, unittest.TestCase): + def setUp(self): + SpyneClientTestBase.setUp(self, 'http') + + port, = server_started.keys() + + self.client = HttpClient('http://localhost:%d/' % port, + soap11_application) + self.ns = "spyne.test.interop.server" + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/interop/test_soap_client_http.pyc b/pym/calculate/contrib/spyne/test/interop/test_soap_client_http.pyc new file mode 100644 index 0000000..6fd866d Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/test_soap_client_http.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/test_soap_client_http_twisted.py b/pym/calculate/contrib/spyne/test/interop/test_soap_client_http_twisted.py new file mode 100644 index 0000000..84022e9 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/test_soap_client_http_twisted.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from twisted.trial import unittest +from spyne.test.interop._test_soap_client_base import run_server, server_started + +from spyne.client.twisted import TwistedHttpClient +from spyne.test.interop.server.soap11.soap_http_basic import soap11_application + +class TestSpyneHttpClient(unittest.TestCase): + def setUp(self): + run_server('http') + + port, = server_started.keys() + + self.ns = b"spyne.test.interop.server._service" + self.client = TwistedHttpClient(b'http://localhost:%d/' % port, + soap11_application) + + def test_echo_boolean(self): + def eb(ret): + raise ret + + def cb(ret): + assert ret == True + + return self.client.service.echo_boolean(True).addCallbacks(cb, eb) + + def test_python_exception(self): + def eb(ret): + print(ret) + + def cb(ret): + assert False, "must fail: %r" % ret + + return self.client.service.python_exception().addCallbacks(cb, eb) + + def test_soap_exception(self): + def eb(ret): + print(type(ret)) + + def cb(ret): + assert False, "must fail: %r" % ret + + return self.client.service.soap_exception().addCallbacks(cb, eb) + + def test_documented_exception(self): + def eb(ret): + print(ret) + + def cb(ret): + assert False, "must fail: %r" % ret + + return self.client.service.python_exception().addCallbacks(cb, eb) diff --git a/pym/calculate/contrib/spyne/test/interop/test_soap_client_http_twisted.pyc b/pym/calculate/contrib/spyne/test/interop/test_soap_client_http_twisted.pyc new file mode 100644 index 0000000..29775eb Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/test_soap_client_http_twisted.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/test_soap_client_zeromq.py b/pym/calculate/contrib/spyne/test/interop/test_soap_client_zeromq.py new file mode 100644 index 0000000..944c2b0 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/test_soap_client_zeromq.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import unittest + +from spyne.client.zeromq import ZeroMQClient + +from spyne.test.interop._test_soap_client_base import SpyneClientTestBase, \ + server_started +from spyne.test.interop.server.soap11.soap_http_basic import soap11_application + + +class TestSpyneZmqClient(SpyneClientTestBase, unittest.TestCase): + def setUp(self): + SpyneClientTestBase.setUp(self, 'zeromq') + + port, = server_started.keys() + + self.client = ZeroMQClient('tcp://localhost:%d' % port, + soap11_application) + self.ns = "spyne.test.interop.server._service" + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/interop/test_soap_client_zeromq.pyc b/pym/calculate/contrib/spyne/test/interop/test_soap_client_zeromq.pyc new file mode 100644 index 0000000..ef9a62d Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/test_soap_client_zeromq.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/test_suds.py b/pym/calculate/contrib/spyne/test/interop/test_suds.py new file mode 100644 index 0000000..87fdb44 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/test_suds.py @@ -0,0 +1,428 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +suds_logger = logging.getLogger('suds') +suds_logger.setLevel(logging.INFO) + +import unittest + +from datetime import datetime +from base64 import b64encode, b64decode + +from suds.sax.parser import Parser +from suds.client import Client +from suds.plugin import MessagePlugin +from suds import WebFault + +from spyne.util import six + +from spyne.test.interop._test_soap_client_base import SpyneClientTestBase, \ + server_started + + +class LastReceivedPlugin(MessagePlugin): + def received(self, context): + sax = Parser() + self.reply = sax.parse(string=context.reply) + + +class TestSuds(SpyneClientTestBase, unittest.TestCase): + def setUp(self): + SpyneClientTestBase.setUp(self, 'http') + + port, = server_started.keys() + + self.client = Client("http://localhost:%d/?wsdl" % port, cache=None, + plugins=[LastReceivedPlugin()]) + self.ns = "spyne.test.interop.server" + + def test_echo_datetime(self): + val = datetime.now() + ret = self.client.service.echo_datetime(val) + + assert val == ret + + def test_echo_datetime_with_invalid_format(self): + val = datetime.now() + ret = self.client.service.echo_datetime_with_invalid_format(val) + + assert val == ret + + def test_echo_date(self): + val = datetime.now().date() + ret = self.client.service.echo_date(val) + + assert val == ret + + def test_echo_date_with_invalid_format(self): + val = datetime.now().date() + ret = self.client.service.echo_date_with_invalid_format(val) + + assert val == ret + + def test_echo_time(self): + val = datetime.now().time() + ret = self.client.service.echo_time(val) + + assert val == ret + + def test_echo_time_with_invalid_format(self): + val = datetime.now().time() + ret = self.client.service.echo_time_with_invalid_format(val) + + assert val == ret + + def test_echo_simple_boolean_array(self): + val = [False, False, False, True] + ret = self.client.service.echo_simple_boolean_array(val) + + assert val == ret + + def test_echo_boolean(self): + val = True + ret = self.client.service.echo_boolean(val) + self.assertEqual(val, ret) + + val = False + ret = self.client.service.echo_boolean(val) + self.assertEqual(val, ret) + + def test_enum(self): + DaysOfWeekEnum = self.client.factory.create("DaysOfWeekEnum") + + val = DaysOfWeekEnum.Monday + ret = self.client.service.echo_enum(val) + + assert val == ret + + def test_bytearray(self): + val = b"\x00\x01\x02\x03\x04" + # suds doesn't support base64 encoding, so we do it manually + ret = self.client.service.echo_bytearray(b64encode(val).decode()) + + assert val == b64decode(ret) + + def test_validation(self): + non_nillable_class = self.client.factory.create( + "{hunk.sunk}NonNillableClass") + non_nillable_class.i = 6 + non_nillable_class.s = None + + try: + self.client.service.non_nillable(non_nillable_class) + except WebFault as e: + pass + else: + raise Exception("must fail") + + def test_echo_integer_array(self): + ia = self.client.factory.create('integerArray') + ia.integer.extend([1, 2, 3, 4, 5]) + self.client.service.echo_integer_array(ia) + + def test_echo_in_header(self): + in_header = self.client.factory.create('InHeader') + in_header.s = 'a' + in_header.i = 3 + + self.client.set_options(soapheaders=in_header) + ret = self.client.service.echo_in_header() + self.client.set_options(soapheaders=None) + + print(ret) + + self.assertEqual(in_header.s, ret.s) + self.assertEqual(in_header.i, ret.i) + + def test_echo_in_complex_header(self): + in_header = self.client.factory.create('InHeader') + in_header.s = 'a' + in_header.i = 3 + in_trace_header = self.client.factory.create('InTraceHeader') + in_trace_header.client = 'suds' + in_trace_header.callDate = datetime(year=2000, month=1, day=1, hour=0, + minute=0, second=0, microsecond=0) + + self.client.set_options(soapheaders=(in_header, in_trace_header)) + ret = self.client.service.echo_in_complex_header() + self.client.set_options(soapheaders=None) + + print(ret) + + self.assertEqual(in_header.s, ret[0].s) + self.assertEqual(in_header.i, ret[0].i) + self.assertEqual(in_trace_header.client, ret[1].client) + self.assertEqual(in_trace_header.callDate, ret[1].callDate) + + def test_send_out_header(self): + out_header = self.client.factory.create('OutHeader') + out_header.dt = datetime(year=2000, month=1, day=1) + out_header.f = 3.141592653 + + ret = self.client.service.send_out_header() + + self.assertTrue(isinstance(ret, type(out_header))) + self.assertEqual(ret.dt, out_header.dt) + self.assertEqual(ret.f, out_header.f) + + def test_send_out_complex_header(self): + out_header = self.client.factory.create('OutHeader') + out_header.dt = datetime(year=2000, month=1, day=1) + out_header.f = 3.141592653 + out_trace_header = self.client.factory.create('OutTraceHeader') + out_trace_header.receiptDate = datetime(year=2000, month=1, day=1, + hour=1, minute=1, second=1, microsecond=1) + out_trace_header.returnDate = datetime(year=2000, month=1, day=1, + hour=1, minute=1, second=1, microsecond=100) + + ret = self.client.service.send_out_complex_header() + + self.assertTrue(isinstance(ret[0], type(out_header))) + self.assertEqual(ret[0].dt, out_header.dt) + self.assertEqual(ret[0].f, out_header.f) + self.assertTrue(isinstance(ret[1], type(out_trace_header))) + self.assertEqual(ret[1].receiptDate, out_trace_header.receiptDate) + self.assertEqual(ret[1].returnDate, out_trace_header.returnDate) + # Control the reply soap header (in an unelegant way but this is the + # only way with suds) + soapheaders = self.client.options.plugins[0].reply.getChild("Envelope").getChild("Header") + soap_out_header = soapheaders.getChild('OutHeader') + self.assertEqual('T'.join((out_header.dt.date().isoformat(), + out_header.dt.time().isoformat())), + soap_out_header.getChild('dt').getText()) + self.assertEqual(six.text_type(out_header.f), soap_out_header.getChild('f').getText()) + soap_out_trace_header = soapheaders.getChild('OutTraceHeader') + self.assertEqual('T'.join((out_trace_header.receiptDate.date().isoformat(), + out_trace_header.receiptDate.time().isoformat())), + soap_out_trace_header.getChild('receiptDate').getText()) + self.assertEqual('T'.join((out_trace_header.returnDate.date().isoformat(), + out_trace_header.returnDate.time().isoformat())), + soap_out_trace_header.getChild('returnDate').getText()) + + def test_echo_string(self): + test_string = "OK" + ret = self.client.service.echo_string(test_string) + + self.assertEqual(ret, test_string) + + def __get_xml_test_val(self): + return { + "test_sub": { + "test_subsub1": { + "test_subsubsub1": ["subsubsub1 value"] + }, + "test_subsub2": ["subsub2 value 1", "subsub2 value 2"], + "test_subsub3": [ + { + "test_subsub3sub1": ["subsub3sub1 value"] + }, + { + "test_subsub3sub2": ["subsub3sub2 value"] + }, + ], + "test_subsub4": [], + "test_subsub5": ["x"], + } + } + + + def test_echo_simple_class(self): + val = self.client.factory.create("{spyne.test.interop.server}SimpleClass") + + val.i = 45 + val.s = "asd" + + ret = self.client.service.echo_simple_class(val) + + assert ret.i == val.i + assert ret.s == val.s + + def test_echo_class_with_self_reference(self): + val = self.client.factory.create("{spyne.test.interop.server}ClassWithSelfReference") + + val.i = 45 + val.sr = self.client.factory.create("{spyne.test.interop.server}ClassWithSelfReference") + val.sr.i = 50 + val.sr.sr = None + + ret = self.client.service.echo_class_with_self_reference(val) + + assert ret.i == val.i + assert ret.sr.i == val.sr.i + + def test_echo_nested_class(self): + val = self.client.factory.create("{punk.tunk}NestedClass"); + + val.i = 45 + val.s = "asd" + val.f = 12.34 + val.ai = self.client.factory.create("integerArray") + val.ai.integer.extend([1, 2, 3, 45, 5, 3, 2, 1, 4]) + + val.simple = self.client.factory.create("{spyne.test.interop.server}SimpleClassArray") + + val.simple.SimpleClass.append(self.client.factory.create("{spyne.test.interop.server}SimpleClass")) + val.simple.SimpleClass.append(self.client.factory.create("{spyne.test.interop.server}SimpleClass")) + + val.simple.SimpleClass[0].i = 45 + val.simple.SimpleClass[0].s = "asd" + val.simple.SimpleClass[1].i = 12 + val.simple.SimpleClass[1].s = "qwe" + + val.other = self.client.factory.create("{spyne.test.interop.server}OtherClass"); + val.other.dt = datetime.now() + val.other.d = 123.456 + val.other.b = True + + ret = self.client.service.echo_nested_class(val) + + self.assertEqual(ret.i, val.i) + self.assertEqual(ret.ai[0], val.ai[0]) + self.assertEqual(ret.simple.SimpleClass[0].s, val.simple.SimpleClass[0].s) + self.assertEqual(ret.other.dt, val.other.dt) + + def test_huge_number(self): + self.assertEqual(self.client.service.huge_number(), 2 ** int(1e5)) + + def test_long_string(self): + self.assertEqual(self.client.service.long_string(), + ('0123456789abcdef' * 16384)) + + def test_empty(self): + self.client.service.test_empty() + + def test_echo_extension_class(self): + val = self.client.factory.create("{bar}ExtensionClass") + + val.i = 45 + val.s = "asd" + val.f = 12.34 + + val.simple = self.client.factory.create("{spyne.test.interop.server}SimpleClassArray") + + val.simple.SimpleClass.append(self.client.factory.create("{spyne.test.interop.server}SimpleClass")) + val.simple.SimpleClass.append(self.client.factory.create("{spyne.test.interop.server}SimpleClass")) + + val.simple.SimpleClass[0].i = 45 + val.simple.SimpleClass[0].s = "asd" + val.simple.SimpleClass[1].i = 12 + val.simple.SimpleClass[1].s = "qwe" + + val.other = self.client.factory.create("{spyne.test.interop.server}OtherClass"); + val.other.dt = datetime.now() + val.other.d = 123.456 + val.other.b = True + + val.p = self.client.factory.create("{hunk.sunk}NonNillableClass"); + val.p.dt = datetime(2010, 6, 2) + val.p.i = 123 + val.p.s = "punk" + + val.l = datetime(2010, 7, 2) + val.q = 5 + + ret = self.client.service.echo_extension_class(val) + print(ret) + + self.assertEqual(ret.i, val.i) + self.assertEqual(ret.s, val.s) + self.assertEqual(ret.f, val.f) + self.assertEqual(ret.simple.SimpleClass[0].i, val.simple.SimpleClass[0].i) + self.assertEqual(ret.other.dt, val.other.dt) + self.assertEqual(ret.p.s, val.p.s) + + + def test_python_exception(self): + try: + self.client.service.python_exception() + raise Exception("must fail") + except WebFault as e: + pass + + def test_soap_exception(self): + try: + self.client.service.soap_exception() + raise Exception("must fail") + except WebFault as e: + pass + + def test_complex_return(self): + ret = self.client.service.complex_return() + + self.assertEqual(ret.resultCode, 1) + self.assertEqual(ret.resultDescription, "Test") + self.assertEqual(ret.transactionId, 123) + self.assertEqual(ret.roles.RoleEnum[0], "MEMBER") + + def test_return_invalid_data(self): + try: + self.client.service.return_invalid_data() + raise Exception("must fail") + except: + pass + + def test_custom_messages(self): + ret = self.client.service.custom_messages("test") + + assert ret == 'test' + + def test_echo_simple_bare(self): + ret = self.client.service.echo_simple_bare("test") + + assert ret == 'test' + + # + # This test is disabled because suds does not create the right request + # object. Opening the first tag below is wrong. + # + # + # + # + # + # + # abc + # def + # + # + # + # + # + # The right request looks like this: + # + # + # abc + # def + # + # + def _test_echo_complex_bare(self): + val = ['abc','def'] + ia = self.client.factory.create('stringArray') + ia.string.extend(val) + ret = self.client.service.echo_complex_bare(ia) + + assert ret == val + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/interop/test_suds.pyc b/pym/calculate/contrib/spyne/test/interop/test_suds.pyc new file mode 100644 index 0000000..45f7c31 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/test_suds.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/test_wsi.py b/pym/calculate/contrib/spyne/test/interop/test_wsi.py new file mode 100644 index 0000000..cb0aefe --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/test_wsi.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python +# +# WS-I interoperability test http://www.ws-i.org/deliverables/workinggroup.aspx?wg=testingtools +# latest download: http://www.ws-i.org/Testing/Tools/2005/06/WSI_Test_Java_Final_1.1.zip +# +# Before launching this test, you should download the zip file and unpack it in this +# directory this should create the wsi-test-tools directory. +# +# Adapted from http://thestewscope.wordpress.com/2008/08/19/ruby-wrapper-for-ws-i-analyzer-tools/ +# from Luca Dariz +# + +import os +import string +from lxml import etree + +CONFIG_FILE = 'config.xml' +SPYNE_TEST_NS = 'spyne.test.interop.server' +SPYNE_TEST_PORT = 'Application' +SPYNE_REPORT_FILE = 'wsi-report-spyne.xml' + +WSI_ANALYZER_CONFIG_TEMPLATE=string.Template(""" + + + false + + + + + + ${ASSERTIONS_FILE} + + + + ${PORT_NAME} + + + ${PORT_NAME} + + + ${PORT_NAME} + + + ${PORT_NAME} + + + ${PORT_NAME} + + + ${PORT_NAME} + + + ${PORT_NAME} + + ${WSDL_URI} + + +""") + +#This must be changed to point to the physical root of the wsi-installation +WSI_HOME_TAG = "WSI_HOME" +WSI_HOME_VAL = "wsi-test-tools" +WSI_JAVA_HOME_TAG = "WSI_JAVA_HOME" +WSI_JAVA_HOME_VAL = WSI_HOME_VAL+"/java" +WSI_JAVA_OPTS_TAG = "WSI_JAVA_OPTS" +WSI_JAVA_OPTS_VAL = " -Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser" +WSI_TEST_ASSERTIONS_FILE = WSI_HOME_VAL+"/common/profiles/SSBP10_BP11_TAD.xml" +WSI_STYLESHEET_FILE = WSI_HOME_VAL+"/common/xsl/report.xsl" +WSI_EXECUTION_COMMAND = "java ${WSI_JAVA_OPTS} -Dwsi.home=${WSI_HOME} -cp ${WSI_CP}\ + org.wsi.test.analyzer.BasicProfileAnalyzer -config " + +WSIClasspath=[ + WSI_JAVA_HOME_VAL+"/lib/wsi-test-tools.jar", + WSI_JAVA_HOME_VAL+"/lib", + WSI_JAVA_HOME_VAL+"/lib/xercesImpl.jar", + WSI_JAVA_HOME_VAL+"/lib/xmlParserAPIs.jar", + WSI_JAVA_HOME_VAL+"/lib/wsdl4j.jar", + WSI_JAVA_HOME_VAL+"/lib/uddi4j.jar", + WSI_JAVA_HOME_VAL+"/lib/axis.jar", + WSI_JAVA_HOME_VAL+"/lib/jaxrpc.jar", + WSI_JAVA_HOME_VAL+"/lib/saaj.jar", + WSI_JAVA_HOME_VAL+"/lib/commons-discovery.jar", + WSI_JAVA_HOME_VAL+"/lib/commons-logging.jar" +] +WSI_CLASSPATH_TAG = "WSI_CP" +WSI_CLASSPATH_VAL = ':'.join(WSIClasspath) + + +def configure_env(): + os.environ[WSI_HOME_TAG] = WSI_HOME_VAL + os.environ[WSI_JAVA_HOME_TAG] = WSI_JAVA_HOME_VAL + os.environ[WSI_JAVA_OPTS_TAG] = WSI_JAVA_OPTS_VAL + os.environ[WSI_CLASSPATH_TAG] = WSI_CLASSPATH_VAL + +def create_config(wsdl_uri, config_file): + print(("Creating config for wsdl at %s ...\n" %wsdl_uri)) + # extract target elements + service = 'ValidatingApplication' + port = 'ValidatingApplication' + # for wsdl service declarations: + # create config(service, port) + vars = {'REPORT_FILE':SPYNE_REPORT_FILE, + 'STYLESHEET_FILE':WSI_STYLESHEET_FILE, + 'ASSERTIONS_FILE':WSI_TEST_ASSERTIONS_FILE, + 'WSDL_NAMESPACE':SPYNE_TEST_NS, + 'PORT_NAME':SPYNE_TEST_PORT, + 'WSDL_URI':wsdl_uri} + config = WSI_ANALYZER_CONFIG_TEMPLATE.substitute(vars) + f = open(config_file, 'w') + f.write(config) + f.close() + +def analyze_wsdl(config_file): + # execute ws-i tests + # don't execute Analyzer.sh directly since it needs bash + os.system(WSI_EXECUTION_COMMAND + config_file) + + # parse result + e = etree.parse(SPYNE_REPORT_FILE).getroot() + summary = etree.ETXPath('{%s}summary' %e.nsmap['wsi-report'])(e) + if summary: + # retrieve overall result of the test + result = summary[0].get('result') + if result == 'failed': + outs = etree.ETXPath('{%s}artifact' %(e.nsmap['wsi-report'],))(e) + + # filter for the object describing the wsdl test + desc = [o for o in outs if o.get('type') == 'description'][0] + + # loop over every group test + for entry in desc.iterchildren(): + # loop over every single test + for test in entry.iterchildren(): + # simply print the error if there is one + # an html can be generated using files in wsi-test-tools/common/xsl + if test.get('result') == 'failed': + fail_msg = etree.ETXPath('{%s}failureMessage' %e.nsmap['wsi-report'])(test) + fail_det = etree.ETXPath('{%s}failureDetail' %e.nsmap['wsi-report'])(test) + if fail_msg: + print(('\nFAILURE in test %s\n' %test.get('id'))) + print((fail_msg[0].text)) + if fail_det: + print('\nFAILURE MSG\n') + print((fail_det[0].text)) + +from spyne.test.interop._test_soap_client_base import run_server + +if __name__ == '__main__': + run_server('http') + configure_env() + create_config('http://localhost:9754/?wsdl', CONFIG_FILE) + analyze_wsdl(CONFIG_FILE) diff --git a/pym/calculate/contrib/spyne/test/interop/test_wsi.pyc b/pym/calculate/contrib/spyne/test/interop/test_wsi.pyc new file mode 100644 index 0000000..a9d0e10 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/test_wsi.pyc differ diff --git a/pym/calculate/contrib/spyne/test/interop/test_zeep.py b/pym/calculate/contrib/spyne/test/interop/test_zeep.py new file mode 100644 index 0000000..d2df5f7 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/interop/test_zeep.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# +import logging + + +zeep_logger = logging.getLogger('zeep') +zeep_logger.setLevel(logging.INFO) + +import unittest + +from datetime import datetime +from base64 import b64encode, b64decode + +from spyne.test.interop._test_soap_client_base import server_started +from spyne.util import six + +from zeep import Client +from zeep.transports import Transport +from zeep.exceptions import Error as ZeepError + + +class TestZeep(unittest.TestCase): + def setUp(self): + from spyne.test.interop._test_soap_client_base import run_server + run_server('http') + + port, = server_started.keys() + + transport = Transport(cache=False) + self.client = Client("http://localhost:%d/?wsdl" % port, + transport=transport) + self.ns = "spyne.test.interop.server" + + def get_inst(self, what): + return self.client.get_type(what)() + + def test_echo_datetime(self): + val = datetime.now() + ret = self.client.service.echo_datetime(val) + + assert val == ret + + def test_echo_datetime_with_invalid_format(self): + val = datetime.now() + ret = self.client.service.echo_datetime_with_invalid_format(val) + + assert val == ret + + def test_echo_date(self): + val = datetime.now().date() + ret = self.client.service.echo_date(val) + + assert val == ret + + def test_echo_date_with_invalid_format(self): + val = datetime.now().date() + ret = self.client.service.echo_date_with_invalid_format(val) + + assert val == ret + + def test_echo_time(self): + val = datetime.now().time() + ret = self.client.service.echo_time(val) + + assert val == ret + + def test_echo_time_with_invalid_format(self): + val = datetime.now().time() + ret = self.client.service.echo_time_with_invalid_format(val) + + assert val == ret + + def test_echo_simple_boolean_array(self): + val = [False, False, False, True] + ret = self.client.service.echo_simple_boolean_array(val) + + assert val == ret + + def test_echo_boolean(self): + val = True + ret = self.client.service.echo_boolean(val) + self.assertEqual(val, ret) + + val = False + ret = self.client.service.echo_boolean(val) + self.assertEqual(val, ret) + + def test_enum(self): + val = self.client.get_type("{%s}DaysOfWeekEnum" % self.ns)('Monday') + + ret = self.client.service.echo_enum(val) + + assert val == ret + + def test_bytearray(self): + val = b"\x00\x01\x02\x03\x04" + ret = self.client.service.echo_bytearray(val) + + assert val == ret + + def test_validation(self): + non_nillable_class = self.client.get_type("{hunk.sunk}NonNillableClass")() + non_nillable_class.i = 6 + non_nillable_class.s = None + + try: + self.client.service.non_nillable(non_nillable_class) + except ZeepError as e: + pass + else: + raise Exception("must fail") + + def test_echo_integer_array(self): + ia = self.client.get_type('{%s}integerArray' % self.ns)() + ia.integer.extend([1, 2, 3, 4, 5]) + self.client.service.echo_integer_array(ia) + + def test_echo_in_header(self): + in_header = self.client.get_type('{%s}InHeader' % self.ns)() + in_header.s = 'a' + in_header.i = 3 + + ret = self.client.service.echo_in_header(_soapheaders={ + 'InHeader': in_header, + }) + + print(ret) + + out_header = ret.body.echo_in_headerResult + self.assertEqual(in_header.s, out_header.s) + self.assertEqual(in_header.i, out_header.i) + + def test_echo_in_complex_header(self): + in_header = self.client.get_type('{%s}InHeader' % self.ns)() + in_header.s = 'a' + in_header.i = 3 + in_trace_header = self.client.get_type('{%s}InTraceHeader' % self.ns)() + in_trace_header.client = 'suds' + in_trace_header.callDate = datetime(year=2000, month=1, day=1, hour=0, + minute=0, second=0, microsecond=0) + + ret = self.client.service.echo_in_complex_header(_soapheaders={ + 'InHeader': in_header, + 'InTraceHeader': in_trace_header + }) + + print(ret) + + out_header = ret.body.echo_in_complex_headerResult0 + out_trace_header = ret.body.echo_in_complex_headerResult1 + + self.assertEqual(in_header.s, out_header.s) + self.assertEqual(in_header.i, out_header.i) + self.assertEqual(in_trace_header.client, out_trace_header.client) + self.assertEqual(in_trace_header.callDate, out_trace_header.callDate) + + def test_send_out_header(self): + out_header = self.client.get_type('{%s}OutHeader' % self.ns)() + out_header.dt = datetime(year=2000, month=1, day=1) + out_header.f = 3.141592653 + + ret = self.client.service.send_out_header() + + self.assertEqual(ret.header.OutHeader.dt, out_header.dt) + self.assertEqual(ret.header.OutHeader.f, out_header.f) + + def test_send_out_complex_header(self): + out_header = self.client.get_type('{%s}OutHeader' % self.ns)() + out_header.dt = datetime(year=2000, month=1, day=1) + out_header.f = 3.141592653 + out_trace_header = self.client.get_type('{%s}OutTraceHeader' % self.ns)() + out_trace_header.receiptDate = datetime(year=2000, month=1, day=1, + hour=1, minute=1, second=1, microsecond=1) + out_trace_header.returnDate = datetime(year=2000, month=1, day=1, + hour=1, minute=1, second=1, microsecond=100) + + ret = self.client.service.send_out_complex_header() + + self.assertEqual(ret.header.OutHeader.dt, out_header.dt) + self.assertEqual(ret.header.OutHeader.f, out_header.f) + self.assertEqual(ret.header.OutTraceHeader.receiptDate, out_trace_header.receiptDate) + self.assertEqual(ret.header.OutTraceHeader.returnDate, out_trace_header.returnDate) + + def test_echo_string(self): + test_string = "OK" + ret = self.client.service.echo_string(test_string) + + self.assertEqual(ret, test_string) + + def __get_xml_test_val(self): + return { + "test_sub": { + "test_subsub1": { + "test_subsubsub1": ["subsubsub1 value"] + }, + "test_subsub2": ["subsub2 value 1", "subsub2 value 2"], + "test_subsub3": [ + { + "test_subsub3sub1": ["subsub3sub1 value"] + }, + { + "test_subsub3sub2": ["subsub3sub2 value"] + }, + ], + "test_subsub4": [], + "test_subsub5": ["x"], + } + } + + + def test_echo_simple_class(self): + val = self.client.get_type("{%s}SimpleClass" % self.ns)() + + val.i = 45 + val.s = "asd" + + ret = self.client.service.echo_simple_class(val) + + assert ret.i == val.i + assert ret.s == val.s + + def test_echo_class_with_self_reference(self): + val = self.client.get_type("{%s}ClassWithSelfReference" % self.ns)() + + val.i = 45 + val.sr = self.client.get_type("{%s}ClassWithSelfReference" % self.ns)() + val.sr.i = 50 + val.sr.sr = None + + ret = self.client.service.echo_class_with_self_reference(val) + + assert ret.i == val.i + assert ret.sr.i == val.sr.i + + def test_echo_nested_class(self): + val = self.client.get_type("{punk.tunk}NestedClass")() + + val.i = 45 + val.s = "asd" + val.f = 12.34 + val.ai = self.client.get_type("{%s}integerArray" % self.ns)() + val.ai.integer.extend([1, 2, 3, 45, 5, 3, 2, 1, 4]) + + val.simple = self.client.get_type("{%s}SimpleClassArray" % self.ns)() + + val.simple.SimpleClass.append(self.client.get_type("{%s}SimpleClass" % self.ns)()) + val.simple.SimpleClass.append(self.client.get_type("{%s}SimpleClass" % self.ns)()) + + val.simple.SimpleClass[0].i = 45 + val.simple.SimpleClass[0].s = "asd" + val.simple.SimpleClass[1].i = 12 + val.simple.SimpleClass[1].s = "qwe" + + val.other = self.client.get_type("{%s}OtherClass" % self.ns)() + val.other.dt = datetime.now() + val.other.d = 123.456 + val.other.b = True + + ret = self.client.service.echo_nested_class(val) + + self.assertEqual(ret.i, val.i) + self.assertEqual(ret.ai.integer, val.ai.integer) + self.assertEqual(ret.ai.integer[0], val.ai.integer[0]) + self.assertEqual(ret.simple.SimpleClass[0].s, val.simple.SimpleClass[0].s) + self.assertEqual(ret.other.dt, val.other.dt) + + def test_huge_number(self): + self.assertEqual(self.client.service.huge_number(), 2 ** int(1e5)) + + def test_long_string(self): + self.assertEqual(self.client.service.long_string(), + ('0123456789abcdef' * 16384)) + + def test_empty(self): + self.client.service.test_empty() + + def test_echo_extension_class(self): + val = self.client.get_type("{bar}ExtensionClass")() + + val.i = 45 + val.s = "asd" + val.f = 12.34 + + val.simple = self.client.get_type("{%s}SimpleClassArray" % self.ns)() + + val.simple.SimpleClass.append(self.client.get_type("{%s}SimpleClass" % self.ns)()) + val.simple.SimpleClass.append(self.client.get_type("{%s}SimpleClass" % self.ns)()) + + val.simple.SimpleClass[0].i = 45 + val.simple.SimpleClass[0].s = "asd" + val.simple.SimpleClass[1].i = 12 + val.simple.SimpleClass[1].s = "qwe" + + val.other = self.client.get_type("{%s}OtherClass" % self.ns)() + val.other.dt = datetime.now() + val.other.d = 123.456 + val.other.b = True + + val.p = self.client.get_type("{hunk.sunk}NonNillableClass")() + val.p.dt = datetime(2010, 6, 2) + val.p.i = 123 + val.p.s = "punk" + + val.l = datetime(2010, 7, 2) + val.q = 5 + + ret = self.client.service.echo_extension_class(val) + print(ret) + + self.assertEqual(ret.i, val.i) + self.assertEqual(ret.s, val.s) + self.assertEqual(ret.f, val.f) + self.assertEqual(ret.simple.SimpleClass[0].i, val.simple.SimpleClass[0].i) + self.assertEqual(ret.other.dt, val.other.dt) + self.assertEqual(ret.p.s, val.p.s) + + + def test_python_exception(self): + try: + self.client.service.python_exception() + raise Exception("must fail") + except ZeepError as e: + pass + + def test_soap_exception(self): + try: + self.client.service.soap_exception() + raise Exception("must fail") + except ZeepError as e: + pass + + def test_complex_return(self): + ret = self.client.service.complex_return() + + self.assertEqual(ret.resultCode, 1) + self.assertEqual(ret.resultDescription, "Test") + self.assertEqual(ret.transactionId, 123) + self.assertEqual(ret.roles.RoleEnum[0], "MEMBER") + + def test_return_invalid_data(self): + try: + self.client.service.return_invalid_data() + raise Exception("must fail") + except: + pass + + def test_custom_messages(self): + ret = self.client.service.custom_messages("test") + + assert ret == 'test' + + def test_echo_simple_bare(self): + ret = self.client.service.echo_simple_bare("test") + + assert ret == 'test' + + def test_echo_complex_bare(self): + val = ['abc','def'] + ret = self.client.service.echo_complex_bare(val) + + assert ret == val + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/interop/test_zeep.pyc b/pym/calculate/contrib/spyne/test/interop/test_zeep.pyc new file mode 100644 index 0000000..397f7b8 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/interop/test_zeep.pyc differ diff --git a/pym/calculate/contrib/spyne/test/model/__init__.py b/pym/calculate/contrib/spyne/test/model/__init__.py new file mode 100644 index 0000000..7b899d5 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/model/__init__.py @@ -0,0 +1,18 @@ + +# +# 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 +# diff --git a/pym/calculate/contrib/spyne/test/model/__init__.pyc b/pym/calculate/contrib/spyne/test/model/__init__.pyc new file mode 100644 index 0000000..a71e1f4 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/model/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/test/model/test_binary.py b/pym/calculate/contrib/spyne/test/model/test_binary.py new file mode 100644 index 0000000..06a4841 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/model/test_binary.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import unittest +from lxml import etree + +from spyne.protocol.soap import Soap11 +from spyne.model.binary import ByteArray +from spyne.model.binary import _bytes_join +import spyne.const.xml + +ns_xsd = spyne.const.xml.NS_XSD +ns_test = 'test_namespace' + + +class TestBinary(unittest.TestCase): + def setUp(self): + self.data = bytes(bytearray(range(0xff))) + + def test_data(self): + element = etree.Element('test') + Soap11().to_parent(None, ByteArray, [self.data], element, ns_test) + print(etree.tostring(element, pretty_print=True)) + element = element[0] + + a2 = Soap11().from_element(None, ByteArray, element) + self.assertEqual(self.data, _bytes_join(a2)) + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/model/test_binary.pyc b/pym/calculate/contrib/spyne/test/model/test_binary.pyc new file mode 100644 index 0000000..5a928ba Binary files /dev/null and b/pym/calculate/contrib/spyne/test/model/test_binary.pyc differ diff --git a/pym/calculate/contrib/spyne/test/model/test_complex.py b/pym/calculate/contrib/spyne/test/model/test_complex.py new file mode 100644 index 0000000..0bc56e0 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/model/test_complex.py @@ -0,0 +1,1146 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from __future__ import print_function + +import pytz +import datetime +import unittest + +from pprint import pprint + +from lxml import etree + +from base64 import b64encode +from decimal import Decimal as D + +from spyne import Application, rpc, mrpc, Service, ByteArray, Array, \ + ComplexModel, SelfReference, XmlData, XmlAttribute, Unicode, DateTime, \ + Float, Integer, String +from spyne.const import xml +from spyne.error import ResourceNotFoundError +from spyne.interface import Interface +from spyne.interface.wsdl import Wsdl11 +from spyne.model.addtl import TimeSegment, DateSegment, DateTimeSegment +from spyne.protocol import ProtocolBase +from spyne.protocol.soap import Soap11 +from spyne.server.null import NullServer + +from spyne.protocol.dictdoc import SimpleDictDocument +from spyne.protocol.xml import XmlDocument + +from spyne.test import FakeApp + +ns_test = 'test_namespace' + + +class Address(ComplexModel): + street = String + city = String + zip = Integer + since = DateTime + lattitude = Float + longitude = Float + +Address.resolve_namespace(Address, __name__) + + +class Person(ComplexModel): + name = String + birthdate = DateTime + age = Integer + addresses = Array(Address) + titles = Array(String) + +Person.resolve_namespace(Person, __name__) + + +class Employee(Person): + employee_id = Integer + salary = Float + +Employee.resolve_namespace(Employee, __name__) + +class Level2(ComplexModel): + arg1 = String + arg2 = Float + +Level2.resolve_namespace(Level2, __name__) + + +class Level3(ComplexModel): + arg1 = Integer + +Level3.resolve_namespace(Level3, __name__) + + +class Level4(ComplexModel): + arg1 = String + +Level4.resolve_namespace(Level4, __name__) + + +class Level1(ComplexModel): + level2 = Level2 + level3 = Array(Level3) + level4 = Array(Level4) + +Level1.resolve_namespace(Level1, __name__) + + +class TestComplexModel(unittest.TestCase): + def test_validate_on_assignment_fail(self): + class C(ComplexModel): + i = Integer(voa=True) + + try: + C().i = 'a' + except ValueError: + pass + else: + raise Exception('must fail with ValueError') + + def test_validate_on_assignment_success(self): + class C(ComplexModel): + i = Integer(voa=True) + + c = C() + + c.i = None + assert c.i is None + + c.i = 5 + assert c.i == 5 + + def test_simple_class(self): + a = Address() + a.street = '123 happy way' + a.city = 'badtown' + a.zip = 32 + a.lattitude = 4.3 + a.longitude = 88.0 + + element = etree.Element('test') + XmlDocument().to_parent(None, Address, a, element, ns_test) + element = element[0] + self.assertEqual(5, len(element.getchildren())) + + a.since = datetime.datetime(year=2011, month=12, day=31, tzinfo=pytz.utc) + element = etree.Element('test') + XmlDocument().to_parent(None, Address, a, element, ns_test) + element = element[0] + self.assertEqual(6, len(element.getchildren())) + + r = XmlDocument().from_element(None, Address, element) + + self.assertEqual(a.street, r.street) + self.assertEqual(a.city, r.city) + self.assertEqual(a.zip, r.zip) + self.assertEqual(a.lattitude, r.lattitude) + self.assertEqual(a.longitude, r.longitude) + self.assertEqual(a.since, r.since) + + def test_nested_class(self): # FIXME: this test is incomplete + p = Person() + element = etree.Element('test') + XmlDocument().to_parent(None, Person, p, element, ns_test) + element = element[0] + + self.assertEqual(None, p.name) + self.assertEqual(None, p.birthdate) + self.assertEqual(None, p.age) + self.assertEqual(None, p.addresses) + + def test_class_array(self): + peeps = [] + names = ['bob', 'jim', 'peabody', 'mumblesleeves'] + dob = datetime.datetime(1979, 1, 1, tzinfo=pytz.utc) + for name in names: + a = Person() + a.name = name + a.birthdate = dob + a.age = 27 + peeps.append(a) + + type = Array(Person) + type.resolve_namespace(type, __name__) + + element = etree.Element('test') + + XmlDocument().to_parent(None, type, peeps, element, ns_test) + element = element[0] + + self.assertEqual(4, len(element.getchildren())) + + peeps2 = XmlDocument().from_element(None, type, element) + for i in range(0, 4): + self.assertEqual(peeps2[i].name, names[i]) + self.assertEqual(peeps2[i].birthdate, dob) + + def test_class_nested_array(self): + peeps = [] + names = ['bob', 'jim', 'peabody', 'mumblesleves'] + + for name in names: + a = Person() + a.name = name + a.birthdate = datetime.datetime(1979, 1, 1) + a.age = 27 + a.addresses = [] + + for i in range(0, 25): + addr = Address() + addr.street = '555 downtown' + addr.city = 'funkytown' + a.addresses.append(addr) + peeps.append(a) + + arr = Array(Person) + arr.resolve_namespace(arr, __name__) + element = etree.Element('test') + XmlDocument().to_parent(None, arr, peeps, element, ns_test) + element = element[0] + + self.assertEqual(4, len(element.getchildren())) + + peeps2 = XmlDocument().from_element(None, arr, element) + for peep in peeps2: + self.assertEqual(27, peep.age) + self.assertEqual(25, len(peep.addresses)) + self.assertEqual('funkytown', peep.addresses[18].city) + + def test_complex_class(self): + l = Level1() + l.level2 = Level2() + l.level2.arg1 = 'abcd' + l.level2.arg2 = 1.444 + l.level3 = [] + l.level4 = [] + + for i in range(0, 100): + a = Level3() + a.arg1 = i + l.level3.append(a) + + for i in range(0, 4): + a = Level4() + a.arg1 = str(i) + l.level4.append(a) + + element = etree.Element('test') + XmlDocument().to_parent(None, Level1, l, element, ns_test) + element = element[0] + l1 = XmlDocument().from_element(None, Level1, element) + + self.assertEqual(l1.level2.arg1, l.level2.arg1) + self.assertEqual(l1.level2.arg2, l.level2.arg2) + self.assertEqual(len(l1.level4), len(l.level4)) + self.assertEqual(100, len(l.level3)) + + +class X(ComplexModel): + __namespace__ = 'tns' + x = Integer(nillable=True, max_occurs='unbounded') + + +class Y(X): + __namespace__ = 'tns' + y = Integer + + +class TestIncompleteInput(unittest.TestCase): + def test_x(self): + x = X() + x.x = [1, 2] + element = etree.Element('test') + XmlDocument().to_parent(None, X, x, element, 'tns') + msg = element[0] + r = XmlDocument().from_element(None, X, msg) + self.assertEqual(r.x, [1, 2]) + + def test_y_fromxml(self): + x = X() + x.x = [1, 2] + element = etree.Element('test') + XmlDocument().to_parent(None, X, x, element, 'tns') + msg = element[0] + r = XmlDocument().from_element(None, Y, msg) + self.assertEqual(r.x, [1, 2]) + + def test_y_toxml(self): + y = Y() + y.x = [1, 2] + y.y = 38 + element = etree.Element('test') + XmlDocument().to_parent(None, Y, y, element, 'tns') + msg = element[0] + r = XmlDocument().from_element(None, Y, msg) + + def test_serialization_instance_on_subclass(self): + test_values = { + 'x': [1, 2], + 'y': 38 + } + instance = Y.get_serialization_instance(test_values) + + self.assertEqual(instance.x, [1, 2]) + self.assertEqual(instance.y, 38) + + +class SisMsg(ComplexModel): + data_source = String(nillable=False, min_occurs=1, max_occurs=1, max_len=50) + direction = String(nillable=False, min_occurs=1, max_occurs=1, max_len=50) + interface_name = String(nillable=False, min_occurs=1, max_occurs=1, max_len=50) + crt_dt = DateTime(nillable=False) + + +class EncExtractXs(ComplexModel): + __min_occurs__ = 1 + __max_occurs__ = 1 + mbr_idn = Integer(nillable=False, min_occurs=1, max_occurs=1, max_len=18) + enc_idn = Integer(nillable=False, min_occurs=1, max_occurs=1, max_len=18) + hist_idn = Integer(nillable=False, min_occurs=1, max_occurs=1, max_len=18) + +class TestComplex(unittest.TestCase): + def test_array_type_name(self): + assert Array(String, type_name='punk').__type_name__ == 'punk' + + def test_ctor_kwargs(self): + class Category(ComplexModel): + id = Integer(min_occurs=1, max_occurs=1, nillable=False) + children = Array(Unicode) + + v = Category(id=5, children=['a','b']) + + assert v.id == 5 + assert v.children == ['a', 'b'] + + def test_ctor_args(self): + class Category(ComplexModel): + id = XmlData(Integer(min_occurs=1, max_occurs=1, nillable=False)) + children = Array(Unicode) + + v = Category(id=5, children=['a','b']) + + assert v.id == 5 + assert v.children == ['a', 'b'] + + v = Category(5, children=['a','b']) + + assert v.id == 5 + assert v.children == ['a', 'b'] + + def test_ctor_args_2(self): + class Category(ComplexModel): + children = Array(Unicode) + + class BetterCategory(Category): + sub_category = Unicode + + v = BetterCategory(children=['a','b'], sub_category='aaa') + + assert v.children == ['a', 'b'] + assert v.sub_category == 'aaa' + + def test_flat_type_info(self): + class A(ComplexModel): + i = Integer + + class B(A): + s = String + + assert 's' in B.get_flat_type_info(B) + assert 'i' in B.get_flat_type_info(B) + + def test_flat_type_info_attr(self): + class A(ComplexModel): + i = Integer + ia = XmlAttribute(Integer) + + class B(A): + s = String + sa = XmlAttribute(String) + + assert 's' in B.get_flat_type_info(B) + assert 'i' in B.get_flat_type_info(B) + assert 'sa' in B.get_flat_type_info(B) + assert 'ia' in B.get_flat_type_info(B) + assert 'sa' in B.get_flat_type_info(B).attrs + assert 'ia' in B.get_flat_type_info(B).attrs + + +class TestXmlAttribute(unittest.TestCase): + def assertIsNotNone(self, obj, msg=None): + """Stolen from Python 2.7 stdlib.""" + + if obj is None: + standardMsg = 'unexpectedly None' + self.fail(self._formatMessage(msg, standardMsg)) + + def test_add_to_schema(self): + class CM(ComplexModel): + i = Integer + s = String + a = XmlAttribute(String) + + app = FakeApp() + app.tns = 'tns' + CM.resolve_namespace(CM, app.tns) + interface = Interface(app) + interface.add_class(CM) + + wsdl = Wsdl11(interface) + wsdl.build_interface_document('http://a-aaaa.com') + pref = CM.get_namespace_prefix(interface) + type_def = wsdl.get_schema_info(pref).types[CM.get_type_name()] + attribute_def = type_def.find(xml.XSD('attribute')) + print(etree.tostring(type_def, pretty_print=True)) + + self.assertIsNotNone(attribute_def) + self.assertEqual(attribute_def.get('name'), 'a') + self.assertEqual(attribute_def.get('type'), CM.a.type.get_type_name_ns(interface)) + + def test_b64_non_attribute(self): + class PacketNonAttribute(ComplexModel): + __namespace__ = 'myns' + Data = ByteArray + + test_string = b'yo test data' + b64string = b64encode(test_string) + + gg = PacketNonAttribute(Data=[test_string]) + + element = etree.Element('test') + Soap11().to_parent(None, PacketNonAttribute, gg, element, gg.get_namespace()) + + element = element[0] + #print etree.tostring(element, pretty_print=True) + data = element.find('{%s}Data' % gg.get_namespace()).text + self.assertEqual(data, b64string.decode('ascii')) + s1 = Soap11().from_element(None, PacketNonAttribute, element) + assert s1.Data[0] == test_string + + def test_b64_attribute(self): + class PacketAttribute(ComplexModel): + __namespace__ = 'myns' + Data = XmlAttribute(ByteArray, use='required') + + test_string = b'yo test data' + b64string = b64encode(test_string) + gg = PacketAttribute(Data=[test_string]) + + element = etree.Element('test') + Soap11().to_parent(None, PacketAttribute, gg, element, gg.get_namespace()) + + element = element[0] + print(etree.tostring(element, pretty_print=True)) + print(element.attrib) + self.assertEqual(element.attrib['Data'], b64string.decode('ascii')) + + s1 = Soap11().from_element(None, PacketAttribute, element) + assert s1.Data[0] == test_string + + def test_customized_type(self): + class SomeClass(ComplexModel): + a = XmlAttribute(Integer(ge=4)) + class SomeService(Service): + @rpc(SomeClass) + def some_call(ctx, some_class): + pass + app = Application([SomeService], 'some_tns') + + +class TestSimpleTypeRestrictions(unittest.TestCase): + def test_simple_type_info(self): + class CM(ComplexModel): + i = Integer + s = String + + class CCM(ComplexModel): + c = CM + i = Integer + s = String + + sti = CCM.get_simple_type_info(CCM) + + pprint(sti) + assert "i" in sti + assert sti["i"].path == ('i',) + assert sti["i"].type is Integer + assert sti["s"].parent is CCM + assert "s" in sti + assert sti["s"].path == ('s',) + assert sti["s"].type is String + assert sti["s"].parent is CCM + + assert "c.i" in sti + assert sti["c.i"].path == ('c','i') + assert sti["c.i"].type is Integer + assert sti["c.i"].parent is CM + assert "c.s" in sti + assert sti["c.s"].path == ('c','s') + assert sti["c.s"].type is String + assert sti["c.s"].parent is CM + + def test_simple_type_info_conflicts(self): + class CM(ComplexModel): + i = Integer + s = String + + class CCM(ComplexModel): + c = CM + c_i = Float + + try: + CCM.get_simple_type_info(CCM, hier_delim='_') + except ValueError: + pass + else: + raise Exception("must fail") + +class TestFlatDict(unittest.TestCase): + def test_basic(self): + class CM(ComplexModel): + i = Integer + s = String + + class CCM(ComplexModel): + c = CM + i = Integer + s = String + + val = CCM(i=5, s='a', c=CM(i=7, s='b')) + + d = SimpleDictDocument().object_to_simple_dict(CCM, val) + + assert d['i'] == 5 + assert d['s'] == 'a' + assert d['c.i'] == 7 + assert d['c.s'] == 'b' + + assert len(d) == 4 + + def test_sub_name_ser(self): + class CM(ComplexModel): + integer = Integer(sub_name='i') + string = String(sub_name='s') + + val = CM(integer=7, string='b') + + d = SimpleDictDocument().object_to_simple_dict(CM, val) + + pprint(d) + + assert d['i'] == 7 + assert d['s'] == 'b' + + assert len(d) == 2 + + def test_sub_name_deser(self): + class CM(ComplexModel): + integer = Integer(sub_name='i') + string = String(sub_name='s') + + d = {'i': [7], 's': ['b']} + + val = SimpleDictDocument().simple_dict_to_object(None, d, CM) + + pprint(d) + + assert val.integer == 7 + assert val.string == 'b' + + def test_array_not_none(self): + class CM(ComplexModel): + i = Integer + s = String + + class CCM(ComplexModel): + c = Array(CM) + + val = CCM(c=[CM(i=i, s='b'*(i+1)) for i in range(2)]) + + d = SimpleDictDocument().object_to_simple_dict(CCM, val) + print(d) + + assert d['c[0].i'] == 0 + assert d['c[0].s'] == 'b' + assert d['c[1].i'] == 1 + assert d['c[1].s'] == 'bb' + + assert len(d) == 4 + + def test_array_none(self): + class CM(ComplexModel): + i = Integer + s = String + + class CCM(ComplexModel): + c = Array(CM) + + val = CCM() + + d = SimpleDictDocument().object_to_simple_dict(CCM, val) + print(d) + + assert len(d) == 0 + + def test_array_nested(self): + class CM(ComplexModel): + i = Array(Integer) + + class CCM(ComplexModel): + c = Array(CM) + + val = CCM(c=[CM(i=range(i)) for i in range(2, 4)]) + + d = SimpleDictDocument().object_to_simple_dict(CCM, val) + pprint(d) + + assert d['c[0].i'] == [0, 1] + assert d['c[1].i'] == [0, 1, 2] + + assert len(d) == 2 + + def test_array_nonwrapped(self): + i = Array(Integer, wrapped=False) + + assert issubclass(i, Integer), i + assert i.Attributes.max_occurs == D('infinity') + + +class TestSelfRefence(unittest.TestCase): + def test_canonical_case(self): + class TestSelfReference(ComplexModel): + self_reference = SelfReference + + c = TestSelfReference._type_info['self_reference'] + c = c.__orig__ or c + + assert c is TestSelfReference + + class SoapService(Service): + @rpc(_returns=TestSelfReference) + def view_categories(ctx): + pass + + Application([SoapService], 'service.soap') + + def test_self_referential_array_workaround(self): + from spyne.util.dictdoc import get_object_as_dict + class Category(ComplexModel): + id = Integer(min_occurs=1, max_occurs=1, nillable=False) + + Category._type_info['children'] = Array(Category) + + parent = Category() + parent.children = [Category(id=0), Category(id=1)] + + d = get_object_as_dict(parent, Category) + pprint(d) + assert d['children'][0]['id'] == 0 + assert d['children'][1]['id'] == 1 + + class SoapService(Service): + @rpc(_returns=Category) + def view_categories(ctx): + pass + + Application([SoapService], 'service.soap', + in_protocol=ProtocolBase(), + out_protocol=ProtocolBase()) + + def test_canonical_array(self): + class Category(ComplexModel): + id = Integer(min_occurs=1, max_occurs=1, nillable=False) + children = Array(SelfReference) + + parent = Category() + parent.children = [Category(id=1), Category(id=2)] + + sr, = Category._type_info['children']._type_info.values() + assert issubclass(sr, Category) + + +class TestMemberRpc(unittest.TestCase): + def test_simple(self): + class SomeComplexModel(ComplexModel): + @mrpc() + def put(self, ctx): + return "PUNK!!!" + + methods = SomeComplexModel.Attributes.methods + print(methods) + assert 'put' in methods + + def test_simple_customize(self): + class SomeComplexModel(ComplexModel): + @mrpc() + def put(self, ctx): + return "PUNK!!!" + + methods = SomeComplexModel.customize(zart='zurt').Attributes.methods + print(methods) + assert 'put' in methods + + def test_simple_with_fields(self): + class SomeComplexModel(ComplexModel): + a = Integer + @mrpc() + def put(self, ctx): + return "PUNK!!!" + + methods = SomeComplexModel.Attributes.methods + print(methods) + assert 'put' in methods + + def test_simple_with_explicit_fields(self): + class SomeComplexModel(ComplexModel): + _type_info = [('a', Integer)] + @mrpc() + def put(self, ctx): + return "PUNK!!!" + + methods = SomeComplexModel.Attributes.methods + print(methods) + assert 'put' in methods + + def test_native_call(self): + v = 'whatever' + + class SomeComplexModel(ComplexModel): + @mrpc() + def put(self, ctx): + return v + + assert SomeComplexModel().put(None) == v + + def test_interface(self): + class SomeComplexModel(ComplexModel): + @mrpc() + def member_method(self, ctx): + pass + + methods = SomeComplexModel.Attributes.methods + print(methods) + assert 'member_method' in methods + + class SomeService(Service): + @rpc(_returns=SomeComplexModel) + def service_method(ctx): + return SomeComplexModel() + + app = Application([SomeService], 'some_ns') + + mmm = __name__ + '.SomeComplexModel.member_method' + assert mmm in app.interface.method_id_map + + def test_interface_mult(self): + class SomeComplexModel(ComplexModel): + @mrpc() + def member_method(self, ctx): + pass + + methods = SomeComplexModel.Attributes.methods + print(methods) + assert 'member_method' in methods + + class SomeService(Service): + @rpc(_returns=SomeComplexModel) + def service_method(ctx): + return SomeComplexModel() + + @rpc(_returns=SomeComplexModel.customize(type_name='zon')) + def service_method_2(ctx): + return SomeComplexModel() + + app = Application([SomeService], 'some_ns') + + mmm = __name__ + '.SomeComplexModel.member_method' + assert mmm in app.interface.method_id_map + + def test_remote_call_error(self): + from spyne import mrpc + v = 'deger' + + class SomeComplexModel(ComplexModel): + @mrpc(_returns=SelfReference) + def put(self, ctx): + return v + + class SomeService(Service): + @rpc(_returns=SomeComplexModel) + def get(ctx): + return SomeComplexModel() + + null = NullServer(Application([SomeService], tns='some_tns')) + + try: + null.service.put() + except ResourceNotFoundError: + pass + else: + raise Exception("Must fail with: \"Requested resource " + "'{spyne.test.model.test_complex}SomeComplexModel' not found\"") + + def test_signature(self): + class SomeComplexModel(ComplexModel): + @mrpc() + def member_method(self, ctx): + pass + + methods = SomeComplexModel.Attributes.methods + + # we use __orig__ because implicit classes are .customize(validate_freq=False)'d + assert methods['member_method'].in_message._type_info[0].__orig__ is SomeComplexModel + + def test_self_reference(self): + from spyne import mrpc + + class SomeComplexModel(ComplexModel): + @mrpc(_returns=SelfReference) + def method(self, ctx): + pass + + methods = SomeComplexModel.Attributes.methods + assert methods['method'].out_message._type_info[0] is SomeComplexModel + + def test_remote_call_success(self): + from spyne import mrpc + + class SomeComplexModel(ComplexModel): + i = Integer + @mrpc(_returns=SelfReference) + def echo(self, ctx): + return self + + class SomeService(Service): + @rpc(_returns=SomeComplexModel) + def get(ctx): + return SomeComplexModel() + + null = NullServer(Application([SomeService], tns='some_tns')) + + v = SomeComplexModel(i=5) + assert null.service['SomeComplexModel.echo'](v) is v + + def test_order(self): + class CM(ComplexModel): + _type_info = [ + ('a', Integer), + ('c', Integer(order=0)) + ] + + assert CM._type_info.keys() == ['c', 'a'] + + +class TestDoc(unittest.TestCase): + def test_parent_doc(self): + class SomeComplexModel(ComplexModel): + """Some docstring""" + some_field = Unicode + class Annotations(ComplexModel.Annotations): + __use_parent_doc__ = True + assert "Some docstring" == SomeComplexModel.get_documentation() + + def test_annotation(self): + class SomeComplexModel(ComplexModel): + """Some docstring""" + class Annotations(ComplexModel.Annotations): + doc = "Some annotations" + + some_field = Unicode + assert "Some annotations" == SomeComplexModel.get_documentation() + + def test_no_parent_doc(self): + class SomeComplexModel(ComplexModel): + """Some docstring""" + class Annotations(ComplexModel.Annotations): + __use_parent_doc__ = False + + some_field = Unicode + assert "" == SomeComplexModel.get_documentation() + + def test_parent_doc_customize(self): + """Check that we keep the documentation when we use customize""" + class SomeComplexModel(ComplexModel): + """Some docstring""" + some_field = Unicode + class Annotations(ComplexModel.Annotations): + __use_parent_doc__ = True + assert "Some docstring" == SomeComplexModel.customize().get_documentation() + + +class TestCustomize(unittest.TestCase): + def test_base_class(self): + class A(ComplexModel): + s = Unicode + + assert A.customize().__extends__ is None + + class B(A): + i = Integer + + assert B.__orig__ is None + + B2 = B.customize() + + assert B2.__orig__ is B + assert B2.__extends__ is A + + B3 = B2.customize() + + assert B3.__orig__ is B + assert B3.__extends__ is A + + def test_noop(self): + class A(ComplexModel): + s = Unicode + + assert A.get_flat_type_info(A)['s'].Attributes.max_len == D('inf') + + def test_cust_simple(self): + # simple types are different from complex ones for __extends__ handling. + # simple types set __orig__ and __extends__ on customization. + # complex types set __orig__ but not extend. + # for complex types, __extend__ is set only on explicit inheritance + + t = Unicode(max_len=10) + + assert t.Attributes.max_len == 10 + assert t.__extends__ is Unicode + assert t.__orig__ is Unicode + + def test_cust_simple_again(self): + t = Unicode(max_len=10) + t2 = t(min_len=5) + + assert t2.Attributes.max_len == 10 + assert t2.Attributes.min_len == 5 + assert t2.__extends__ is t + assert t2.__orig__ is Unicode + + def test_cust_complex(self): + class A(ComplexModel): + s = Unicode + + A2 = A.customize( + child_attrs=dict( + s=dict( + max_len=10 + ) + ) + ) + + assert A2.get_flat_type_info(A2)['s'].Attributes.max_len == 10 + + def test_cust_base_class(self): + class A(ComplexModel): + s = Unicode + + class B(A): + i = Integer + + B2 = B.customize( + child_attrs=dict( + s=dict( + max_len=10, + ), + ), + ) + + assert B2.get_flat_type_info(B2)['s'].Attributes.max_len == 10 + + def test_cust_again_base_class(self): + class A(ComplexModel): + s = Unicode + + A2 = A.customize() + try: + class B(A2): + i = Integer + except AssertionError: + pass + else: + raise Exception("must fail") + + def test_cust_array(self): + A = Array(Unicode) + + assert A.__orig__ is Array + assert A.__extends__ is None + assert issubclass(A, Array) + + def test_cust_array_again(self): + A = Array(Unicode) + + A = A.customize(foo='bar') + + assert A.Attributes.foo == 'bar' + assert A.__orig__ is Array + assert A.__extends__ is None + assert issubclass(A, Array) + + def test_cust_array_serializer(self): + A = Array(Unicode) + + A = A.customize( + serializer_attrs=dict( + max_len=10, + ), + ) + + serializer, = A._type_info.values() + + assert serializer.Attributes.max_len == 10 + assert serializer.__orig__ is Unicode + assert issubclass(serializer, Unicode) + + def test_cust_sub_array(self): + """vanilla class is passed as base""" + class A(ComplexModel): + s = Array(Unicode) + + d = dict( + child_attrs=dict( + s=dict( + serializer_attrs=dict( + max_len=10, + ), + ), + ), + ) + + A2 = A.customize(**d) + + ser, = A2._type_info['s']._type_info.values() + assert ser.Attributes.max_len == 10 + + class B(A): + i = Integer + + B2 = B.customize(**d) + + b2_fti = B2.get_flat_type_info(B2) + ser, = b2_fti['s']._type_info.values() + + assert ser.Attributes.max_len == 10 + + def test_cust_side_effect(self): + class A(ComplexModel): + s = Unicode + i = Integer + + class B(A): + d = DateTime + + B2 = B.customize(child_attrs=dict(s=dict(max_len=10))) + assert B2.get_flat_type_info(B2)['s'].Attributes.max_len == 10 + + B3 = B2.customize(child_attrs=dict(d=dict(dt_format="%y"))) + assert B3.get_flat_type_info(B3)['s'].Attributes.max_len == 10 + + def test_cust_all(self): + class A(ComplexModel): + s = Unicode + i = Unicode + + class B(A): + d = DateTime + + B2 = B.customize(child_attrs_all=dict(max_len=10)) + assert B2.get_flat_type_info(B2)['s'].Attributes.max_len == 10 + assert B2.get_flat_type_info(B2)['i'].Attributes.max_len == 10 + + def test_cust_noexc(self): + class A(ComplexModel): + s = Unicode + i = Integer + + class B(A): + d = DateTime + + B2 = B.customize(child_attrs_noexc=dict(s=dict(max_len=10))) + assert B2.get_flat_type_info(B2)['s'].Attributes.max_len == 10 + assert B2.get_flat_type_info(B2)['s'].Attributes.exc == False + assert B2.get_flat_type_info(B2)['i'].Attributes.exc == True + + + def test_complex_type_name_clashes(self): + class TestComplexModel(ComplexModel): + attr1 = String + + TestComplexModel1 = TestComplexModel + + class TestComplexModel(ComplexModel): + attr2 = String + + TestComplexModel2 = TestComplexModel + + class TestService(Service): + @rpc(TestComplexModel1) + def test1(ctx, obj): + pass + + @rpc(TestComplexModel2) + def test2(ctx, obj): + pass + + try: + Application([TestService], 'tns') + except Exception as e: + print(e) + else: + raise Exception("must fail with: " + "ValueError: classes " + " " + "and " + " " + "have conflicting names.") + + +class TestAdditional(unittest.TestCase): + def test_time_segment(self): + data = TimeSegment.from_string("[11:12:13.123456,14:15:16.789012]") + + assert data.start_inclusive + assert data.start == datetime.time(11, 12, 13, 123456) + assert data.end == datetime.time(14, 15, 16, 789012) + assert data.end_inclusive + + def test_date_segment(self): + data = DateSegment.from_string("[2016-03-03,2016-05-07[") + + assert data.start_inclusive == True + assert data.start == datetime.date(2016, 3, 3) + assert data.end == datetime.date(2016, 5, 7) + assert data.end_inclusive == False + + def test_datetime_segment(self): + data = DateTimeSegment.from_string("]2016-03-03T10:20:30.405060," + "2016-05-07T00:01:02.030405]") + + assert data.start_inclusive == False + assert data.start == datetime.datetime(2016, 3, 3, 10, 20, 30, 405060) + assert data.end == datetime.datetime(2016, 5, 7, 0, 1, 2, 30405) + assert data.end_inclusive == True + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/model/test_complex.pyc b/pym/calculate/contrib/spyne/test/model/test_complex.pyc new file mode 100644 index 0000000..6993a4e Binary files /dev/null and b/pym/calculate/contrib/spyne/test/model/test_complex.pyc differ diff --git a/pym/calculate/contrib/spyne/test/model/test_enum.py b/pym/calculate/contrib/spyne/test/model/test_enum.py new file mode 100644 index 0000000..7d8fd4f --- /dev/null +++ b/pym/calculate/contrib/spyne/test/model/test_enum.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import unittest + +from pprint import pprint + +from spyne.application import Application +from spyne.const.xml import XSD +from spyne.interface.wsdl.wsdl11 import Wsdl11 +from spyne.model.complex import Array +from spyne.model.complex import ComplexModel +from spyne.protocol.xml import XmlDocument +from spyne.protocol.soap.soap11 import Soap11 + +from spyne.server.wsgi import WsgiApplication +from spyne.service import Service +from spyne.decorator import rpc + +from spyne.model.enum import Enum + +from lxml import etree + +vals = [ + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday', +] + +DaysOfWeekEnum = Enum( + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday', + type_name = 'DaysOfWeekEnum', +) + +class SomeService(Service): + @rpc(DaysOfWeekEnum, _returns=DaysOfWeekEnum) + def get_the_day(self, day): + return DaysOfWeekEnum.Sunday + + +class SomeClass(ComplexModel): + days = DaysOfWeekEnum(max_occurs=7) + + +class TestEnum(unittest.TestCase): + def setUp(self): + self.app = Application([SomeService], 'tns', + in_protocol=Soap11(), out_protocol=Soap11()) + self.app.transport = 'test' + + self.server = WsgiApplication(self.app) + self.wsdl = Wsdl11(self.app.interface) + self.wsdl.build_interface_document('prot://url') + + def test_wsdl(self): + wsdl = self.wsdl.get_interface_document() + + elt = etree.fromstring(wsdl) + simple_type = elt.xpath('//xs:simpleType', namespaces=self.app.interface.nsmap)[0] + + print((etree.tostring(elt, pretty_print=True))) + print(simple_type) + + self.assertEqual(simple_type.attrib['name'], 'DaysOfWeekEnum') + self.assertEqual(simple_type[0].tag, XSD("restriction")) + self.assertEqual([e.attrib['value'] for e in simple_type[0]], vals) + + def test_serialize(self): + mo = DaysOfWeekEnum.Monday + print((repr(mo))) + + elt = etree.Element('test') + XmlDocument().to_parent(None, DaysOfWeekEnum, mo, elt, 'test_namespace') + elt = elt[0] + ret = XmlDocument().from_element(None, DaysOfWeekEnum, elt) + + self.assertEqual(mo, ret) + + def test_serialize_complex_array(self): + days = [ + DaysOfWeekEnum.Monday, + DaysOfWeekEnum.Tuesday, + DaysOfWeekEnum.Wednesday, + DaysOfWeekEnum.Thursday, + DaysOfWeekEnum.Friday, + DaysOfWeekEnum.Saturday, + DaysOfWeekEnum.Sunday, + ] + + days_xml = [ + ('{tns}DaysOfWeekEnum', 'Monday'), + ('{tns}DaysOfWeekEnum', 'Tuesday'), + ('{tns}DaysOfWeekEnum', 'Wednesday'), + ('{tns}DaysOfWeekEnum', 'Thursday'), + ('{tns}DaysOfWeekEnum', 'Friday'), + ('{tns}DaysOfWeekEnum', 'Saturday'), + ('{tns}DaysOfWeekEnum', 'Sunday'), + ] + + DaysOfWeekEnumArray = Array(DaysOfWeekEnum) + DaysOfWeekEnumArray.__namespace__ = 'tns' + + elt = etree.Element('test') + XmlDocument().to_parent(None, DaysOfWeekEnumArray, days, + elt, 'test_namespace') + + elt = elt[0] + ret = XmlDocument().from_element(None, Array(DaysOfWeekEnum), elt) + assert days == ret + + print((etree.tostring(elt, pretty_print=True))) + + pprint(self.app.interface.nsmap) + assert days_xml == [ (e.tag, e.text) for e in + elt.xpath('//tns:DaysOfWeekEnum', namespaces=self.app.interface.nsmap)] + + def test_serialize_simple_array(self): + t = SomeClass(days=[ + DaysOfWeekEnum.Monday, + DaysOfWeekEnum.Tuesday, + DaysOfWeekEnum.Wednesday, + DaysOfWeekEnum.Thursday, + DaysOfWeekEnum.Friday, + DaysOfWeekEnum.Saturday, + DaysOfWeekEnum.Sunday, + ]) + + SomeClass.resolve_namespace(SomeClass, 'tns') + + elt = etree.Element('test') + XmlDocument().to_parent(None, SomeClass, t, elt, 'test_namespace') + elt = elt[0] + + print((etree.tostring(elt, pretty_print=True))) + + ret = XmlDocument().from_element(None, SomeClass, elt) + self.assertEqual(t.days, ret.days) + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/model/test_enum.pyc b/pym/calculate/contrib/spyne/test/model/test_enum.pyc new file mode 100644 index 0000000..0de8ce5 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/model/test_enum.pyc differ diff --git a/pym/calculate/contrib/spyne/test/model/test_exception.py b/pym/calculate/contrib/spyne/test/model/test_exception.py new file mode 100644 index 0000000..1b436a3 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/model/test_exception.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import unittest +from spyne.test import FakeApp +from spyne.interface import Interface +from spyne.interface.wsdl import Wsdl11 +from spyne.protocol.xml import XmlDocument +from spyne.model.fault import Fault + +class FaultTests(unittest.TestCase): + def test_ctor_defaults(self): + fault = Fault() + self.assertEqual(fault.faultcode, 'Server') + self.assertEqual(fault.faultstring, 'Fault') + self.assertEqual(fault.faultactor, '') + self.assertEqual(fault.detail, None) + self.assertEqual(repr(fault), "Fault(Server: 'Fault')") + + def test_ctor_faultcode_w_senv_prefix(self): + fault = Fault(faultcode='Other') + self.assertEqual(fault.faultcode, 'Other') + self.assertEqual(repr(fault), "Fault(Other: 'Fault')") + + def test_ctor_explicit_faultstring(self): + fault = Fault(faultstring='Testing') + self.assertEqual(fault.faultstring, 'Testing') + self.assertEqual(repr(fault), "Fault(Server: 'Testing')") + + def test_to_parent_wo_detail(self): + from lxml.etree import Element + import spyne.const.xml + ns_soap_env = spyne.const.xml.NS_SOAP11_ENV + soap_env = spyne.const.xml.PREFMAP[spyne.const.xml.NS_SOAP11_ENV] + + element = Element('testing') + fault = Fault() + cls = Fault + + XmlDocument().to_parent(None, cls, fault, element, 'urn:ignored') + + (child,) = element.getchildren() + self.assertEqual(child.tag, '{%s}Fault' % ns_soap_env) + self.assertEqual(child.find('faultcode').text, '%s:Server' % soap_env) + self.assertEqual(child.find('faultstring').text, 'Fault') + self.assertEqual(child.find('faultactor').text, '') + self.assertFalse(child.findall('detail')) + + def test_to_parent_w_detail(self): + from lxml.etree import Element + element = Element('testing') + detail = Element('something') + fault = Fault(detail=detail) + cls = Fault + + XmlDocument().to_parent(None, cls, fault, element, 'urn:ignored') + + (child,) = element.getchildren() + self.assertTrue(child.find('detail').find('something') is detail) + + def test_from_xml_wo_detail(self): + from lxml.etree import Element + from lxml.etree import SubElement + from spyne.const.xml import PREFMAP, SOAP11_ENV, NS_SOAP11_ENV + + soap_env = PREFMAP[NS_SOAP11_ENV] + element = Element(SOAP11_ENV('Fault')) + + fcode = SubElement(element, 'faultcode') + fcode.text = '%s:other' % soap_env + fstr = SubElement(element, 'faultstring') + fstr.text = 'Testing' + actor = SubElement(element, 'faultactor') + actor.text = 'phreddy' + + fault = XmlDocument().from_element(None, Fault, element) + + self.assertEqual(fault.faultcode, '%s:other' % soap_env) + self.assertEqual(fault.faultstring, 'Testing') + self.assertEqual(fault.faultactor, 'phreddy') + self.assertEqual(fault.detail, None) + + def test_from_xml_w_detail(self): + from lxml.etree import Element + from lxml.etree import SubElement + from spyne.const.xml import SOAP11_ENV + + element = Element(SOAP11_ENV('Fault')) + fcode = SubElement(element, 'faultcode') + fcode.text = 'soap11env:other' + fstr = SubElement(element, 'faultstring') + fstr.text = 'Testing' + actor = SubElement(element, 'faultactor') + actor.text = 'phreddy' + detail = SubElement(element, 'detail') + + fault = XmlDocument().from_element(None, Fault, element) + + self.assertTrue(fault.detail is detail) + + def test_add_to_schema_no_extends(self): + from spyne.const.xml import XSD + + class cls(Fault): + __namespace__='ns' + @classmethod + def get_type_name_ns(self, app): + return 'testing:My' + + interface = Interface(FakeApp()) + interface.add_class(cls) + + pref = cls.get_namespace_prefix(interface) + wsdl = Wsdl11(interface) + wsdl.build_interface_document('prot://addr') + schema = wsdl.get_schema_info(pref) + + self.assertEqual(len(schema.types), 1) + c_cls = interface.classes['{ns}cls'] + c_elt = schema.types[0] + self.assertTrue(c_cls is cls) + self.assertEqual(c_elt.tag, XSD('complexType')) + self.assertEqual(c_elt.get('name'), 'cls') + + self.assertEqual(len(schema.elements), 1) + e_elt = schema.elements.values()[0] + self.assertEqual(e_elt.tag, XSD('element')) + self.assertEqual(e_elt.get('name'), 'cls') + self.assertEqual(e_elt.get('type'), 'testing:My') + self.assertEqual(len(e_elt), 0) + + def test_add_to_schema_w_extends(self): + from spyne.const.xml import XSD + + class base(Fault): + __namespace__ = 'ns' + + @classmethod + def get_type_name_ns(self, app): + return 'testing:Base' + + class cls(Fault): + __namespace__ = 'ns' + @classmethod + def get_type_name_ns(self, app): + return 'testing:My' + + interface = Interface(FakeApp()) + interface.add_class(cls) + + pref = cls.get_namespace_prefix(interface) + wsdl = Wsdl11(interface) + wsdl.build_interface_document('prot://addr') + schema = wsdl.get_schema_info(pref) + + self.assertEqual(len(schema.types), 1) + self.assertEqual(len(interface.classes), 1) + + c_cls = next(iter(interface.classes.values())) + c_elt = next(iter(schema.types.values())) + + self.assertTrue(c_cls is cls) + self.assertEqual(c_elt.tag, XSD('complexType')) + self.assertEqual(c_elt.get('name'), 'cls') + + from lxml import etree + print(etree.tostring(c_elt, pretty_print=True)) + self.assertEqual(len(c_elt), 0) + +class DummySchemaEntries: + def __init__(self, app): + self.app = app + self._complex_types = [] + self._elements = [] + + def add_complex_type(self, cls, ct): + self._complex_types.append((cls, ct)) + + def add_element(self, cls, elt): + self._elements.append((cls, elt)) + + +if __name__ == '__main__': #pragma NO COVERAGE + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/model/test_exception.pyc b/pym/calculate/contrib/spyne/test/model/test_exception.pyc new file mode 100644 index 0000000..448410d Binary files /dev/null and b/pym/calculate/contrib/spyne/test/model/test_exception.pyc differ diff --git a/pym/calculate/contrib/spyne/test/model/test_primitive.py b/pym/calculate/contrib/spyne/test/model/test_primitive.py new file mode 100644 index 0000000..cc6c8c2 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/model/test_primitive.py @@ -0,0 +1,978 @@ +#!/usr/bin/env python +# 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 +# + +from __future__ import print_function + +import re +import uuid +import datetime +import unittest +import warnings + +import pytz +import spyne + +from datetime import timedelta + +from lxml import etree +from spyne.model.primitive._base import re_match_with_span as rmws + +from spyne.util import six +from spyne.const import xml as ns + +from spyne import Null, AnyDict, Uuid, Array, ComplexModel, Date, Time, \ + Boolean, DateTime, Duration, Float, Integer, NumberLimitsWarning, Unicode, \ + String, Decimal, Integer16, ModelBase, MimeType, MimeTypeStrict, MediaType + +from spyne.protocol import ProtocolBase +from spyne.protocol.xml import XmlDocument + +ns_test = 'test_namespace' + + +class TestCast(unittest.TestCase): + pass # TODO: test Unicode(cast=str) + + +class TestPrimitive(unittest.TestCase): + def test_mime_type_family(self): + mime_attr = MimeType.Attributes + mime_strict_attr = MimeTypeStrict.Attributes + assert rmws(mime_attr, u'application/foo') + assert not rmws(mime_attr, u'application/ foo') + assert not rmws(mime_attr, u'application/') + assert rmws(mime_attr, u'foo/bar') + assert not rmws(mime_attr, u'foo/bar ') + assert not rmws(mime_strict_attr, u'foo/bar') + + media_attr = MediaType.Attributes + media_strict_attr = MediaType.Attributes + + print(media_attr.pattern) + + assert rmws(media_attr, u'text/plain; charset="utf-8"') + assert rmws(media_attr, u'text/plain; charset=utf-8') + assert rmws(media_attr, u'text/plain; charset=utf-8 ') + assert rmws(media_attr, u'text/plain; charset=utf-8') + assert rmws(media_attr, u'text/plain; charset=utf-8;') + assert rmws(media_attr, u'text/plain; charset=utf-8; ') + assert not rmws(media_attr, u'text/plain; charset=utf-8; foo') + assert not rmws(media_attr, u'text/plain; charset=utf-8; foo=') + assert rmws(media_attr, u'text/plain; charset=utf-8; foo=""') + assert rmws(media_attr, u'text/plain; charset=utf-8; foo="";') + assert rmws(media_attr, u'text/plain; charset=utf-8; foo=""; ') + assert rmws(media_attr, u'text/plain; charset=utf-8; foo=""; ') + assert not rmws(media_attr, u'text/plain;; charset=utf-8; foo=""') + assert not rmws(media_attr, u'text/plain;;; charset=utf-8; foo=""') + assert not rmws(media_attr, u'text/plain; charset=utf-8;; foo=""') + assert not rmws(media_attr, u'text/plain; charset=utf-8;;; foo=""') + assert not rmws(media_attr, u'text/plain; charset=utf-8;;; foo="";') + assert not rmws(media_attr, u'text/plain; charset=utf-8;;; foo=""; ; ') + assert not rmws(media_strict_attr, u'foo/bar;') + assert not rmws(media_strict_attr, u' applicaton/json;') + assert not rmws(media_strict_attr, u'applicaton/json;') + + assert MediaType + + + def test_getitem_cust(self): + assert Unicode[dict(max_len=2)].Attributes.max_len + + def test_ancestors(self): + class A(ComplexModel): i = Integer + class B(A): i2 = Integer + class C(B): i3 = Integer + + assert C.ancestors() == [B, A] + assert B.ancestors() == [A] + assert A.ancestors() == [] + + def test_nillable_quirks(self): + assert ModelBase.Attributes.nillable == True + + class Attributes(ModelBase.Attributes): + nillable = False + nullable = False + + assert Attributes.nillable == False + assert Attributes.nullable == False + + class Attributes(ModelBase.Attributes): + nillable = True + + assert Attributes.nillable == True + assert Attributes.nullable == True + + class Attributes(ModelBase.Attributes): + nillable = False + + assert Attributes.nillable == False + assert Attributes.nullable == False + + class Attributes(ModelBase.Attributes): + nullable = True + + assert Attributes.nillable == True + assert Attributes.nullable == True + + class Attributes(ModelBase.Attributes): + nullable = False + + assert Attributes.nillable == False + assert Attributes.nullable == False + + class Attributes(ModelBase.Attributes): + nullable = False + + class Attributes(Attributes): + pass + + assert Attributes.nullable == False + + def test_nillable_inheritance_quirks(self): + class Attributes(ModelBase.Attributes): + nullable = False + + class AttrMixin: + pass + + class NewAttributes(Attributes, AttrMixin): + pass + + assert NewAttributes.nullable is False + + class AttrMixin: + pass + + class NewAttributes(AttrMixin, Attributes): + pass + + assert NewAttributes.nullable is False + + def test_decimal(self): + assert Decimal(10, 4).Attributes.total_digits == 10 + assert Decimal(10, 4).Attributes.fraction_digits == 4 + + def test_decimal_format(self): + f = 123456 + str_format = '${0}' + element = etree.Element('test') + XmlDocument().to_parent(None, Decimal(str_format=str_format), f, + element, ns_test) + element = element[0] + + self.assertEqual(element.text, '$123456') + + def test_string(self): + s = String() + element = etree.Element('test') + XmlDocument().to_parent(None, String, 'value', element, ns_test) + element = element[0] + + self.assertEqual(element.text, 'value') + value = XmlDocument().from_element(None, String, element) + self.assertEqual(value, 'value') + + def test_datetime(self): + n = datetime.datetime.now(pytz.utc) + + element = etree.Element('test') + XmlDocument().to_parent(None, DateTime, n, element, ns_test) + element = element[0] + + self.assertEqual(element.text, n.isoformat()) + dt = XmlDocument().from_element(None, DateTime, element) + self.assertEqual(n, dt) + + def test_datetime_format(self): + n = datetime.datetime.now().replace(microsecond=0) + format = "%Y %m %d %H %M %S" + + element = etree.Element('test') + XmlDocument().to_parent(None, DateTime(dt_format=format), n, element, + ns_test) + element = element[0] + + assert element.text == datetime.datetime.strftime(n, format) + dt = XmlDocument().from_element(None, DateTime(dt_format=format), + element) + assert n == dt + + def test_datetime_unicode_format(self): + n = datetime.datetime.now().replace(microsecond=0) + format = u"%Y %m %d\u00a0%H %M %S" + + element = etree.Element('test') + XmlDocument().to_parent(None, DateTime(dt_format=format), n, + element, ns_test) + element = element[0] + + if six.PY2: + assert element.text == n.strftime(format.encode('utf8')) \ + .decode('utf8') + else: + assert element.text == n.strftime(format) + + dt = XmlDocument().from_element(None, DateTime(dt_format=format), + element) + assert n == dt + + def test_date_format(self): + t = datetime.date.today() + format = "%Y-%m-%d" + + element = etree.Element('test') + XmlDocument().to_parent(None, + Date(date_format=format), t, element, ns_test) + assert element[0].text == datetime.date.strftime(t, format) + + dt = XmlDocument().from_element(None, + Date(date_format=format), element[0]) + assert t == dt + + def test_datetime_timezone(self): + import pytz + + n = datetime.datetime.now(pytz.timezone('EST')) + element = etree.Element('test') + cls = DateTime(as_timezone=pytz.utc, timezone=False) + XmlDocument().to_parent(None, cls, n, element, ns_test) + element = element[0] + + c = n.astimezone(pytz.utc).replace(tzinfo=None) + self.assertEqual(element.text, c.isoformat()) + dt = XmlDocument().from_element(None, cls, element) + assert dt.tzinfo is not None + dt = dt.replace(tzinfo=None) + self.assertEqual(c, dt) + + def test_date_timezone(self): + elt = etree.Element('wot') + elt.text = '2013-08-09+02:00' + dt = XmlDocument().from_element(None, Date, elt) + print("ok without validation.") + dt = XmlDocument(validator='soft').from_element(None, Date, elt) + print(dt) + + def test_time(self): + n = datetime.time(1, 2, 3, 4) + + ret = ProtocolBase().to_bytes(Time, n) + self.assertEqual(ret, n.isoformat()) + + dt = ProtocolBase().from_unicode(Time, ret) + self.assertEqual(n, dt) + + def test_time_usec(self): + # python's datetime and time only accept ints between [0, 1e6[ + # if the incoming data is 999999.8 microseconds, rounding it up means + # adding 1 second to time. For many reasons, we want to avoid that. (see + # http://bugs.python.org/issue1487389) That's why 999999.8 usec is + # rounded to 999999. + + # rounding 0.1 µsec down + t = ProtocolBase().from_unicode(Time, "12:12:12.0000001") + self.assertEqual(datetime.time(12, 12, 12), t) + + # rounding 1.5 µsec up. 0.5 is rounded down by python 3 and up by + # python 2 so we test with 1.5 µsec instead. frikkin' nonsense. + t = ProtocolBase().from_unicode(Time, "12:12:12.0000015") + self.assertEqual(datetime.time(12, 12, 12, 2), t) + + # rounding 999998.8 µsec up + t = ProtocolBase().from_unicode(Time, "12:12:12.9999988") + self.assertEqual(datetime.time(12, 12, 12, 999999), t) + + # rounding 999999.1 µsec down + t = ProtocolBase().from_unicode(Time, "12:12:12.9999991") + self.assertEqual(datetime.time(12, 12, 12, 999999), t) + + # rounding 999999.8 µsec down, not up. + t = ProtocolBase().from_unicode(Time, "12:12:12.9999998") + self.assertEqual(datetime.time(12, 12, 12, 999999), t) + + def test_date(self): + n = datetime.date(2011, 12, 13) + + ret = ProtocolBase().to_unicode(Date, n) + self.assertEqual(ret, n.isoformat()) + + dt = ProtocolBase().from_unicode(Date, ret) + self.assertEqual(n, dt) + + def test_utcdatetime(self): + datestring = '2007-05-15T13:40:44Z' + e = etree.Element('test') + e.text = datestring + + dt = XmlDocument().from_element(None, DateTime, e) + + self.assertEqual(dt.year, 2007) + self.assertEqual(dt.month, 5) + self.assertEqual(dt.day, 15) + + datestring = '2007-05-15T13:40:44.003Z' + e = etree.Element('test') + e.text = datestring + + dt = XmlDocument().from_element(None, DateTime, e) + + self.assertEqual(dt.year, 2007) + self.assertEqual(dt.month, 5) + self.assertEqual(dt.day, 15) + + def test_date_exclusive_boundaries(self): + test_model = Date.customize(gt=datetime.date(2016, 1, 1), + lt=datetime.date(2016, 2, 1)) + self.assertFalse( + test_model.validate_native(test_model, datetime.date(2016, 1, 1))) + self.assertFalse( + test_model.validate_native(test_model, datetime.date(2016, 2, 1))) + + def test_date_inclusive_boundaries(self): + test_model = Date.customize(ge=datetime.date(2016, 1, 1), + le=datetime.date(2016, 2, 1)) + self.assertTrue( + test_model.validate_native(test_model, datetime.date(2016, 1, 1))) + self.assertTrue( + test_model.validate_native(test_model, datetime.date(2016, 2, 1))) + + def test_datetime_exclusive_boundaries(self): + test_model = DateTime.customize( + gt=datetime.datetime(2016, 1, 1, 12, 00) + .replace(tzinfo=spyne.LOCAL_TZ), + lt=datetime.datetime(2016, 2, 1, 12, 00) + .replace(tzinfo=spyne.LOCAL_TZ), + ) + self.assertFalse(test_model.validate_native(test_model, + datetime.datetime(2016, 1, 1, 12, 00))) + self.assertFalse(test_model.validate_native(test_model, + datetime.datetime(2016, 2, 1, 12, 00))) + + def test_datetime_inclusive_boundaries(self): + test_model = DateTime.customize( + ge=datetime.datetime(2016, 1, 1, 12, 00) + .replace(tzinfo=spyne.LOCAL_TZ), + le=datetime.datetime(2016, 2, 1, 12, 00) + .replace(tzinfo=spyne.LOCAL_TZ) + ) + + self.assertTrue(test_model.validate_native(test_model, + datetime.datetime(2016, 1, 1, 12, 00))) + self.assertTrue(test_model.validate_native(test_model, + datetime.datetime(2016, 2, 1, 12, 00))) + + def test_time_exclusive_boundaries(self): + test_model = Time.customize(gt=datetime.time(12, 00), + lt=datetime.time(13, 00)) + + self.assertFalse( + test_model.validate_native(test_model, datetime.time(12, 00))) + self.assertFalse( + test_model.validate_native(test_model, datetime.time(13, 00))) + + def test_time_inclusive_boundaries(self): + test_model = Time.customize(ge=datetime.time(12, 00), + le=datetime.time(13, 00)) + + self.assertTrue( + test_model.validate_native(test_model, datetime.time(12, 00))) + self.assertTrue( + test_model.validate_native(test_model, datetime.time(13, 00))) + + def test_datetime_extreme_boundary(self): + self.assertTrue( + DateTime.validate_native(DateTime, datetime.datetime.min)) + self.assertTrue( + DateTime.validate_native(DateTime, datetime.datetime.max)) + + def test_time_extreme_boundary(self): + self.assertTrue(Time.validate_native(Time, datetime.time(0, 0, 0, 0))) + self.assertTrue( + Time.validate_native(Time, datetime.time(23, 59, 59, 999999))) + + def test_date_extreme_boundary(self): + self.assertTrue(Date.validate_native(Date, datetime.date.min)) + self.assertTrue(Date.validate_native(Date, datetime.date.max)) + + def test_integer(self): + i = 12 + integer = Integer() + + element = etree.Element('test') + XmlDocument().to_parent(None, Integer, i, element, ns_test) + element = element[0] + + self.assertEqual(element.text, '12') + value = XmlDocument().from_element(None, integer, element) + self.assertEqual(value, i) + + def test_integer_limits(self): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + + integer = Integer16(ge=-32768) + + assert len(w) == 0 + + integer = Integer16(ge=-32769) + assert len(w) == 1 + assert issubclass(w[-1].category, NumberLimitsWarning) + assert "smaller than min_bound" in str(w[-1].message) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + + integer = Integer16(le=32767) + + assert len(w) == 0 + + integer = Integer16(le=32768) + assert len(w) == 1 + assert issubclass(w[-1].category, NumberLimitsWarning) + assert "greater than max_bound" in str(w[-1].message) + + try: + Integer16(ge=32768) + except ValueError: + pass + else: + raise Exception("must fail") + + try: + Integer16(lt=-32768) + except ValueError: + pass + else: + raise Exception("must fail") + + def test_large_integer(self): + i = 128375873458473 + integer = Integer() + + element = etree.Element('test') + XmlDocument().to_parent(None, Integer, i, element, ns_test) + element = element[0] + + self.assertEqual(element.text, '128375873458473') + value = XmlDocument().from_element(None, integer, element) + self.assertEqual(value, i) + + def test_float(self): + f = 1.22255645 + + element = etree.Element('test') + XmlDocument().to_parent(None, Float, f, element, ns_test) + element = element[0] + + self.assertEqual(element.text, repr(f)) + + f2 = XmlDocument().from_element(None, Float, element) + self.assertEqual(f2, f) + + def test_array(self): + type = Array(String) + type.resolve_namespace(type, "zbank") + + values = ['a', 'b', 'c', 'd', 'e', 'f'] + + element = etree.Element('test') + XmlDocument().to_parent(None, type, values, element, ns_test) + element = element[0] + + self.assertEqual(len(values), len(element.getchildren())) + + values2 = XmlDocument().from_element(None, type, element) + self.assertEqual(values[3], values2[3]) + + def test_array_empty(self): + type = Array(String) + type.resolve_namespace(type, "zbank") + + values = [] + + element = etree.Element('test') + XmlDocument().to_parent(None, type, values, element, ns_test) + element = element[0] + + self.assertEqual(len(values), len(element.getchildren())) + + values2 = XmlDocument().from_element(None, type, element) + self.assertEqual(len(values2), 0) + + def test_unicode(self): + s = u'\x34\x55\x65\x34' + self.assertEqual(4, len(s)) + element = etree.Element('test') + XmlDocument().to_parent(None, String, s, element, 'test_ns') + element = element[0] + value = XmlDocument().from_element(None, String, element) + self.assertEqual(value, s) + + def test_unicode_pattern_mult_cust(self): + assert Unicode(pattern='a').Attributes.pattern == 'a' + assert Unicode(pattern='a')(5).Attributes.pattern == 'a' + + def test_unicode_upattern(self): + patt = r'[\w .-]+' + attr = Unicode(unicode_pattern=patt).Attributes + assert attr.pattern == patt + assert attr._pattern_re.flags & re.UNICODE + assert attr._pattern_re.match(u"Ğ Ğ ç .-") + assert attr._pattern_re.match(u"\t") is None + + def test_unicode_nullable_mult_cust_false(self): + assert Unicode(nullable=False).Attributes.nullable == False + assert Unicode(nullable=False)(5).Attributes.nullable == False + + def test_unicode_nullable_mult_cust_true(self): + assert Unicode(nullable=True).Attributes.nullable == True + assert Unicode(nullable=True)(5).Attributes.nullable == True + + def test_null(self): + element = etree.Element('test') + XmlDocument().to_parent(None, Null, None, element, ns_test) + print(etree.tostring(element)) + + element = element[0] + self.assertTrue(bool(element.attrib.get(ns.XSI('nil')))) + value = XmlDocument().from_element(None, Null, element) + self.assertEqual(None, value) + + def test_point(self): + from spyne.model.primitive.spatial import _get_point_pattern + + a = re.compile(_get_point_pattern(2)) + assert a.match('POINT (10 40)') is not None + assert a.match('POINT(10 40)') is not None + + assert a.match('POINT(10.0 40)') is not None + assert a.match('POINT(1.310e4 40)') is not None + + def test_multipoint(self): + from spyne.model.primitive.spatial import _get_multipoint_pattern + + a = re.compile(_get_multipoint_pattern(2)) + assert a.match('MULTIPOINT (10 40, 40 30, 20 20, 30 10)') is not None + # FIXME: + # assert a.match('MULTIPOINT ((10 40), (40 30), (20 20), (30 10))') is not None + + def test_linestring(self): + from spyne.model.primitive.spatial import _get_linestring_pattern + + a = re.compile(_get_linestring_pattern(2)) + assert a.match('LINESTRING (30 10, 10 30, 40 40)') is not None + + def test_multilinestring(self): + from spyne.model.primitive.spatial import _get_multilinestring_pattern + + a = re.compile(_get_multilinestring_pattern(2)) + assert a.match('''MULTILINESTRING ((10 10, 20 20, 10 40), + (40 40, 30 30, 40 20, 30 10))''') is not None + + def test_polygon(self): + from spyne.model.primitive.spatial import _get_polygon_pattern + + a = re.compile(_get_polygon_pattern(2)) + assert a.match( + 'POLYGON ((30 10, 10 20, 20 40, 40 40, 30 10))') is not None + + def test_multipolygon(self): + from spyne.model.primitive.spatial import _get_multipolygon_pattern + + a = re.compile(_get_multipolygon_pattern(2)) + assert a.match('''MULTIPOLYGON (((30 20, 10 40, 45 40, 30 20)), + ((15 5, 40 10, 10 20, 5 10, 15 5)))''') is not None + assert a.match('''MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), + ((20 35, 45 20, 30 5, 10 10, 10 30, 20 35), + (30 20, 20 25, 20 15, 30 20)))''') is not None + + def test_boolean(self): + b = etree.Element('test') + XmlDocument().to_parent(None, Boolean, True, b, ns_test) + b = b[0] + self.assertEqual('true', b.text) + + b = etree.Element('test') + XmlDocument().to_parent(None, Boolean, 0, b, ns_test) + b = b[0] + self.assertEqual('false', b.text) + + b = etree.Element('test') + XmlDocument().to_parent(None, Boolean, 1, b, ns_test) + b = b[0] + self.assertEqual('true', b.text) + + b = XmlDocument().from_element(None, Boolean, b) + self.assertEqual(b, True) + + b = etree.Element('test') + XmlDocument().to_parent(None, Boolean, False, b, ns_test) + b = b[0] + self.assertEqual('false', b.text) + + b = XmlDocument().from_element(None, Boolean, b) + self.assertEqual(b, False) + + b = etree.Element('test') + XmlDocument().to_parent(None, Boolean, None, b, ns_test) + b = b[0] + self.assertEqual('true', b.get(ns.XSI('nil'))) + + b = XmlDocument().from_element(None, Boolean, b) + self.assertEqual(b, None) + + def test_new_type(self): + """Customized primitives go into namespace based on module name.""" + custom_type = Unicode(pattern='123') + self.assertEqual(custom_type.get_namespace(), custom_type.__module__) + + def test_default_nullable(self): + """Test if default nullable changes nullable attribute.""" + try: + self.assertTrue(Unicode.Attributes.nullable) + orig_default = Unicode.Attributes.NULLABLE_DEFAULT + Unicode.Attributes.NULLABLE_DEFAULT = False + self.assertFalse(Unicode.Attributes.nullable) + self.assertFalse(Unicode.Attributes.nillable) + finally: + Unicode.Attributes.NULLABLE_DEFAULT = orig_default + self.assertEqual(Unicode.Attributes.nullable, orig_default) + + def test_simple_type_explicit_customization(self): + assert Unicode(max_len=5).__extends__ is not None + assert Unicode.customize(max_len=5).__extends__ is not None + + def test_anydict_customization(self): + from spyne.model import json + assert isinstance( + AnyDict.customize(store_as='json').Attributes.store_as, json) + + def test_uuid_serialize(self): + value = uuid.UUID('12345678123456781234567812345678') + + assert ProtocolBase().to_unicode(Uuid, value) \ + == '12345678-1234-5678-1234-567812345678' + assert ProtocolBase().to_unicode(Uuid(serialize_as='hex'), value) \ + == '12345678123456781234567812345678' + assert ProtocolBase().to_unicode(Uuid(serialize_as='urn'), value) \ + == 'urn:uuid:12345678-1234-5678-1234-567812345678' + assert ProtocolBase().to_unicode(Uuid(serialize_as='bytes'), value) \ + == b'\x124Vx\x124Vx\x124Vx\x124Vx' + assert ProtocolBase().to_unicode(Uuid(serialize_as='bytes_le'), value) \ + == b'xV4\x124\x12xV\x124Vx\x124Vx' + assert ProtocolBase().to_unicode(Uuid(serialize_as='fields'), value) \ + == (305419896, 4660, 22136, 18, 52, 95073701484152) + assert ProtocolBase().to_unicode(Uuid(serialize_as='int'), value) \ + == 24197857161011715162171839636988778104 + + def test_uuid_deserialize(self): + value = uuid.UUID('12345678123456781234567812345678') + + assert ProtocolBase().from_unicode(Uuid, + '12345678-1234-5678-1234-567812345678') == value + assert ProtocolBase().from_unicode(Uuid(serialize_as='hex'), + '12345678123456781234567812345678') == value + assert ProtocolBase().from_unicode(Uuid(serialize_as='urn'), + 'urn:uuid:12345678-1234-5678-1234-567812345678') == value + assert ProtocolBase().from_bytes(Uuid(serialize_as='bytes'), + b'\x124Vx\x124Vx\x124Vx\x124Vx') == value + assert ProtocolBase().from_bytes(Uuid(serialize_as='bytes_le'), + b'xV4\x124\x12xV\x124Vx\x124Vx') == value + assert ProtocolBase().from_unicode(Uuid(serialize_as='fields'), + (305419896, 4660, 22136, 18, 52, 95073701484152)) == value + assert ProtocolBase().from_unicode(Uuid(serialize_as='int'), + 24197857161011715162171839636988778104) == value + + def test_uuid_validate(self): + assert Uuid.validate_string(Uuid, + '12345678-1234-5678-1234-567812345678') + assert Uuid.validate_native(Uuid, + uuid.UUID('12345678-1234-5678-1234-567812345678')) + + def test_datetime_serialize_as(self): + i = 1234567890123456 + v = datetime.datetime.fromtimestamp(i / 1e6) + + assert ProtocolBase().to_unicode( + DateTime(serialize_as='sec'), v) == i//1e6 + assert ProtocolBase().to_unicode( + DateTime(serialize_as='sec_float'), v) == i/1e6 + assert ProtocolBase().to_unicode( + DateTime(serialize_as='msec'), v) == i//1e3 + assert ProtocolBase().to_unicode( + DateTime(serialize_as='msec_float'), v) == i/1e3 + assert ProtocolBase().to_unicode( + DateTime(serialize_as='usec'), v) == i + + def test_datetime_deserialize(self): + i = 1234567890123456 + v = datetime.datetime.fromtimestamp(i / 1e6) + + assert ProtocolBase().from_unicode( + DateTime(serialize_as='sec'), i//1e6) == \ + datetime.datetime.fromtimestamp(i//1e6) + assert ProtocolBase().from_unicode( + DateTime(serialize_as='sec_float'), i/1e6) == v + + assert ProtocolBase().from_unicode( + DateTime(serialize_as='msec'), i//1e3) == \ + datetime.datetime.fromtimestamp(i/1e3//1000) + assert ProtocolBase().from_unicode( + DateTime(serialize_as='msec_float'), i/1e3) == v + + assert ProtocolBase().from_unicode( + DateTime(serialize_as='usec'), i) == v + + def test_datetime_ancient(self): + t = DateTime(dt_format="%Y-%m-%d %H:%M:%S") # to trigger strftime + v = datetime.datetime(1881, 1, 1) + vs = '1881-01-01 00:00:00' + + dt = ProtocolBase().from_unicode(t, vs) + self.assertEqual(v, dt) + + dt = ProtocolBase().to_unicode(t, v) + self.assertEqual(vs, dt) + + def test_custom_strftime(self): + s = ProtocolBase.strftime(datetime.date(1800, 9, 23), + "%Y has the same days as 1980 and 2008") + if s != "1800 has the same days as 1980 and 2008": + raise AssertionError(s) + + print("Testing all day names from 0001/01/01 until 2000/08/01") + # Get the weekdays. Can't hard code them; they could be + # localized. + days = [] + for i in range(1, 10): + days.append(datetime.date(2000, 1, i).strftime("%A")) + nextday = {} + for i in range(8): + nextday[days[i]] = days[i + 1] + + startdate = datetime.date(1, 1, 1) + enddate = datetime.date(2000, 8, 1) + prevday = ProtocolBase.strftime(startdate, "%A") + one_day = datetime.timedelta(1) + + testdate = startdate + one_day + while testdate < enddate: + if (testdate.day == 1 and testdate.month == 1 and + (testdate.year % 100 == 0)): + print("Testing century", testdate.year) + day = ProtocolBase.strftime(testdate, "%A") + if nextday[prevday] != day: + raise AssertionError(str(testdate)) + prevday = day + testdate = testdate + one_day + + def test_datetime_usec(self): + # see the comments on time test for why the rounding here is weird + + # rounding 0.1 µsec down + dt = ProtocolBase().from_unicode(DateTime, + "2015-01-01 12:12:12.0000001") + self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12), dt) + + # rounding 1.5 µsec up. 0.5 is rounded down by python 3 and up by + # python 2 so we test with 1.5 µsec instead. frikkin' nonsense. + dt = ProtocolBase().from_unicode(DateTime, + "2015-01-01 12:12:12.0000015") + self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12, 2), dt) + + # rounding 999998.8 µsec up + dt = ProtocolBase().from_unicode(DateTime, + "2015-01-01 12:12:12.9999988") + self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12, 999999), dt) + + # rounding 999999.1 µsec down + dt = ProtocolBase().from_unicode(DateTime, + "2015-01-01 12:12:12.9999991") + self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12, 999999), dt) + + # rounding 999999.8 µsec down, not up. + dt = ProtocolBase().from_unicode(DateTime, + "2015-01-01 12:12:12.9999998") + self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12, 999999), dt) + + +### Duration Data Type +## http://www.w3schools.com/schema/schema_dtypes_date.asp +# Duration Data type +# The time interval is specified in the following form "PnYnMnDTnHnMnS" where: +# P indicates the period (required) +# nY indicates the number of years +# nM indicates the number of months +# nD indicates the number of days +# T indicates the start of a time section (*required* if you are going to +# specify hours, minutes, seconds or microseconds) +# nH indicates the number of hours +# nM indicates the number of minutes +# nS indicates the number of seconds + +class SomeBlob(ComplexModel): + __namespace__ = 'myns' + howlong = Duration() + + +class TestDurationPrimitive(unittest.TestCase): + def test_onehour_oneminute_onesecond(self): + answer = 'PT1H1M1S' + gg = SomeBlob() + gg.howlong = timedelta(hours=1, minutes=1, seconds=1) + + element = etree.Element('test') + XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) + element = element[0] + + print(gg.howlong) + print(etree.tostring(element, pretty_print=True)) + assert element[0].text == answer + + data = element.find('{%s}howlong' % gg.get_namespace()).text + self.assertEqual(data, answer) + s1 = XmlDocument().from_element(None, SomeBlob, element) + assert s1.howlong.total_seconds() == gg.howlong.total_seconds() + + def test_4suite(self): + # borrowed from 4Suite + tests_seconds = [ + (0, u'PT0S'), + (1, u'PT1S'), + (59, u'PT59S'), + (60, u'PT1M'), + (3599, u'PT59M59S'), + (3600, u'PT1H'), + (86399, u'PT23H59M59S'), + (86400, u'P1D'), + (86400 * 60, u'P60D'), + (86400 * 400, u'P400D') + ] + + for secs, answer in tests_seconds: + gg = SomeBlob() + gg.howlong = timedelta(seconds=secs) + + element = etree.Element('test') + XmlDocument()\ + .to_parent(None, SomeBlob, gg, element, gg.get_namespace()) + element = element[0] + + print(gg.howlong) + print(etree.tostring(element, pretty_print=True)) + assert element[0].text == answer + + data = element.find('{%s}howlong' % gg.get_namespace()).text + self.assertEqual(data, answer) + s1 = XmlDocument().from_element(None, SomeBlob, element) + assert s1.howlong.total_seconds() == secs + + for secs, answer in tests_seconds: + if secs > 0: + secs *= -1 + answer = '-' + answer + gg = SomeBlob() + gg.howlong = timedelta(seconds=secs) + + element = etree.Element('test') + XmlDocument()\ + .to_parent(None, SomeBlob, gg, element, gg.get_namespace()) + element = element[0] + + print(gg.howlong) + print(etree.tostring(element, pretty_print=True)) + assert element[0].text == answer + + data = element.find('{%s}howlong' % gg.get_namespace()).text + self.assertEqual(data, answer) + s1 = XmlDocument().from_element(None, SomeBlob, element) + assert s1.howlong.total_seconds() == secs + + def test_duration_positive_seconds_only(self): + answer = 'PT35S' + gg = SomeBlob() + gg.howlong = timedelta(seconds=35) + + element = etree.Element('test') + XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) + element = element[0] + + print(gg.howlong) + print(etree.tostring(element, pretty_print=True)) + assert element[0].text == answer + + data = element.find('{%s}howlong' % gg.get_namespace()).text + self.assertEqual(data, answer) + s1 = XmlDocument().from_element(None, SomeBlob, element) + assert s1.howlong.total_seconds() == gg.howlong.total_seconds() + + def test_duration_positive_minutes_and_seconds_only(self): + answer = 'PT5M35S' + gg = SomeBlob() + gg.howlong = timedelta(minutes=5, seconds=35) + + element = etree.Element('test') + XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) + element = element[0] + + print(gg.howlong) + print(etree.tostring(element, pretty_print=True)) + assert element[0].text == answer + + data = element.find('{%s}howlong' % gg.get_namespace()).text + self.assertEqual(data, answer) + s1 = XmlDocument().from_element(None, SomeBlob, element) + assert s1.howlong.total_seconds() == gg.howlong.total_seconds() + + def test_duration_positive_milliseconds_only(self): + answer = 'PT0.666000S' + gg = SomeBlob() + gg.howlong = timedelta(milliseconds=666) + + element = etree.Element('test') + XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) + element = element[0] + + print(gg.howlong) + print(etree.tostring(element, pretty_print=True)) + assert element[0].text == answer + + data = element.find('{%s}howlong' % gg.get_namespace()).text + self.assertEqual(data, answer) + s1 = XmlDocument().from_element(None, SomeBlob, element) + assert s1.howlong.total_seconds() == gg.howlong.total_seconds() + + def test_duration_xml_duration(self): + dur = datetime.timedelta(days=5 + 30 + 365, hours=1, minutes=1, + seconds=12, microseconds=8e5) + + str1 = 'P400DT3672.8S' + str2 = 'P1Y1M5DT1H1M12.8S' + + self.assertEqual(dur, ProtocolBase().from_unicode(Duration, str1)) + self.assertEqual(dur, ProtocolBase().from_unicode(Duration, str2)) + + self.assertEqual(dur, ProtocolBase().from_unicode(Duration, + ProtocolBase().to_unicode(Duration, dur))) + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/model/test_primitive.pyc b/pym/calculate/contrib/spyne/test/model/test_primitive.pyc new file mode 100644 index 0000000..a533136 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/model/test_primitive.pyc differ diff --git a/pym/calculate/contrib/spyne/test/multipython/__init__.py b/pym/calculate/contrib/spyne/test/multipython/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pym/calculate/contrib/spyne/test/multipython/__init__.pyc b/pym/calculate/contrib/spyne/test/multipython/__init__.pyc new file mode 100644 index 0000000..9e94b73 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/multipython/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/test/multipython/model/__init__.py b/pym/calculate/contrib/spyne/test/multipython/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pym/calculate/contrib/spyne/test/multipython/model/__init__.pyc b/pym/calculate/contrib/spyne/test/multipython/model/__init__.pyc new file mode 100644 index 0000000..5ef4091 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/multipython/model/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/test/multipython/model/test_complex.py b/pym/calculate/contrib/spyne/test/multipython/model/test_complex.py new file mode 100644 index 0000000..9aa8d1c --- /dev/null +++ b/pym/calculate/contrib/spyne/test/multipython/model/test_complex.py @@ -0,0 +1,179 @@ +# coding: utf-8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + + +"""Complex model tests runnable on different Python implementations.""" + +import unittest + +from spyne.model.complex import (ComplexModel, ComplexModelMeta, + ComplexModelBase, Array) +from spyne.model.primitive import Unicode, Integer, String +from spyne.util.six import add_metaclass + + +class DeclareOrder_declare(ComplexModel.customize(declare_order='declared')): + field3 = Integer + field1 = Integer + field2 = Integer + + +class MyComplexModelMeta(ComplexModelMeta): + """Custom complex model metaclass.""" + + def __new__(mcs, name, bases, attrs): + attrs['new_field'] = Unicode + attrs['field1'] = Unicode + new_cls = super(MyComplexModelMeta, mcs).__new__(mcs, name, bases, + attrs) + return new_cls + + +@add_metaclass(MyComplexModelMeta) +class MyComplexModel(ComplexModelBase): + """Custom complex model class.""" + class Attributes(ComplexModelBase.Attributes): + declare_order = 'declared' + + +class MyModelWithDeclaredOrder(MyComplexModel): + """Test model for complex model with custom metaclass.""" + class Attributes(MyComplexModel.Attributes): + declare_order = 'declared' + + field3 = Integer + field1 = Integer + field2 = Integer + + +class TestComplexModel(unittest.TestCase): + def test_add_field(self): + class C(ComplexModel): + u = Unicode + C.append_field('i', Integer) + assert C._type_info['i'] is Integer + + def test_insert_field(self): + class C(ComplexModel): + u = Unicode + C.insert_field(0, 'i', Integer) + assert C._type_info.keys() == ['i', 'u'] + + def test_variants(self): + class C(ComplexModel): + u = Unicode + CC = C.customize(child_attrs=dict(u=dict(min_len=5))) + print(dict(C.Attributes._variants.items())) + r, = C.Attributes._variants + assert r is CC + assert CC.Attributes.parent_variant is C + C.append_field('i', Integer) + assert C._type_info['i'] is Integer + assert CC._type_info['i'] is Integer + + def test_child_customization(self): + class C(ComplexModel): + u = Unicode + CC = C.customize(child_attrs=dict(u=dict(min_len=5))) + assert CC._type_info['u'].Attributes.min_len == 5 + assert C._type_info['u'].Attributes.min_len != 5 + + def test_array_customization(self): + CC = Array(Unicode).customize( + serializer_attrs=dict(min_len=5), punks='roll', + ) + assert CC.Attributes.punks == 'roll' + assert CC._type_info[0].Attributes.min_len == 5 + + def test_array_customization_complex(self): + class C(ComplexModel): + u = Unicode + + CC = Array(C).customize( + punks='roll', + serializer_attrs=dict(bidik=True) + ) + assert CC.Attributes.punks == 'roll' + assert CC._type_info[0].Attributes.bidik == True + + def test_delayed_child_customization_append(self): + class C(ComplexModel): + u = Unicode + CC = C.customize(child_attrs=dict(i=dict(ge=5))) + CC.append_field('i', Integer) + assert CC._type_info['i'].Attributes.ge == 5 + assert not 'i' in C._type_info + + def test_delayed_child_customization_insert(self): + class C(ComplexModel): + u = Unicode + CC = C.customize(child_attrs=dict(i=dict(ge=5))) + CC.insert_field(1, 'i', Integer) + assert CC._type_info['i'].Attributes.ge == 5 + assert not 'i' in C._type_info + + def test_array_member_name(self): + print(Array(String, member_name="punk")._type_info) + assert 'punk' in Array(String, member_name="punk")._type_info + + def test_customize(self): + class Base(ComplexModel): + class Attributes(ComplexModel.Attributes): + prop1 = 3 + prop2 = 6 + + Base2 = Base.customize(prop1=4) + + self.assertNotEquals(Base.Attributes.prop1, Base2.Attributes.prop1) + self.assertEqual(Base.Attributes.prop2, Base2.Attributes.prop2) + + class Derived(Base): + class Attributes(Base.Attributes): + prop3 = 9 + prop4 = 12 + + Derived2 = Derived.customize(prop1=5, prop3=12) + + self.assertEqual(Base.Attributes.prop1, 3) + self.assertEqual(Base2.Attributes.prop1, 4) + + self.assertEqual(Derived.Attributes.prop1, 3) + self.assertEqual(Derived2.Attributes.prop1, 5) + + self.assertNotEquals(Derived.Attributes.prop3, Derived2.Attributes.prop3) + self.assertEqual(Derived.Attributes.prop4, Derived2.Attributes.prop4) + + Derived3 = Derived.customize(prop3=12) + Base.prop1 = 4 + + # changes made to bases propagate, unless overridden + self.assertEqual(Derived.Attributes.prop1, Base.Attributes.prop1) + self.assertNotEquals(Derived2.Attributes.prop1, Base.Attributes.prop1) + self.assertEqual(Derived3.Attributes.prop1, Base.Attributes.prop1) + + def test_declare_order(self): + self.assertEqual(["field3", "field1", "field2"], + list(DeclareOrder_declare._type_info)) + self.assertEqual(["field3", "field1", "field2", "new_field"], + list(MyModelWithDeclaredOrder._type_info)) + + +if __name__ == '__main__': + import sys + sys.exit(unittest.main()) diff --git a/pym/calculate/contrib/spyne/test/multipython/model/test_complex.pyc b/pym/calculate/contrib/spyne/test/multipython/model/test_complex.pyc new file mode 100644 index 0000000..5975caa Binary files /dev/null and b/pym/calculate/contrib/spyne/test/multipython/model/test_complex.pyc differ diff --git a/pym/calculate/contrib/spyne/test/protocol/__init__.py b/pym/calculate/contrib/spyne/test/protocol/__init__.py new file mode 100644 index 0000000..7250806 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/protocol/__init__.py @@ -0,0 +1,24 @@ + +# +# 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 is Spyne's test package. + +You are not supposed to import test package from production code because tests +fiddle with global state of Spyne classes. +""" diff --git a/pym/calculate/contrib/spyne/test/protocol/__init__.pyc b/pym/calculate/contrib/spyne/test/protocol/__init__.pyc new file mode 100644 index 0000000..6e2bae8 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/protocol/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/test/protocol/_test_dictdoc.py b/pym/calculate/contrib/spyne/test/protocol/_test_dictdoc.py new file mode 100644 index 0000000..9dd8352 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/protocol/_test_dictdoc.py @@ -0,0 +1,1381 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from __future__ import unicode_literals + +import logging +logger = logging.getLogger(__name__) + +import unittest + +import uuid +import pytz +import decimal +from spyne.util import six +from spyne.util.dictdoc import get_object_as_dict + +if not six.PY2: + long = int + +from datetime import datetime +from datetime import date +from datetime import time +from datetime import timedelta + +import lxml.etree +import lxml.html + +from lxml.builder import E + +from spyne import MethodContext +from spyne.service import Service +from spyne.server import ServerBase +from spyne.application import Application +from spyne.decorator import srpc, rpc +from spyne.error import ValidationError +from spyne.model.binary import binary_encoding_handlers, File +from spyne.model.complex import ComplexModel +from spyne.model.complex import Iterable +from spyne.model.fault import Fault +from spyne.protocol import ProtocolBase +from spyne.model.binary import ByteArray +from spyne.model.primitive import Decimal +from spyne.model.primitive import Integer +from spyne.model.primitive import String +from spyne.model.primitive import DateTime +from spyne.model.primitive import Mandatory +from spyne.model.primitive import AnyXml +from spyne.model.primitive import AnyHtml +from spyne.model.primitive import AnyDict +from spyne.model.primitive import Unicode +from spyne.model.primitive import AnyUri +from spyne.model.primitive import ImageUri +from spyne.model.primitive import Double +from spyne.model.primitive import Integer8 +from spyne.model.primitive import Time +from spyne.model.primitive import Date +from spyne.model.primitive import Duration +from spyne.model.primitive import Boolean +from spyne.model.primitive import Uuid +from spyne.model.primitive import Point +from spyne.model.primitive import Line +from spyne.model.primitive import Polygon +from spyne.model.primitive import MultiPoint +from spyne.model.primitive import MultiLine +from spyne.model.primitive import MultiPolygon + + +def _unbyte(d): + if d is None: + return + + for k, v in list(d.items()): + if isinstance(k, bytes): + del d[k] + d[k.decode('utf8')] = v + + if isinstance(v, dict): + _unbyte(v) + + for k, v in d.items(): + if isinstance(v, (list, tuple)): + l = [] + for sub in v: + if isinstance(sub, dict): + l.append(_unbyte(sub)) + + elif isinstance(sub, bytes): + l.append(sub.decode("utf8")) + + else: + l.append(sub) + + d[k] = tuple(l) + + elif isinstance(v, bytes): + try: + d[k] = v.decode('utf8') + except UnicodeDecodeError: + d[k] = v + + return d + + +def TDry(serializer, _DictDocumentChild, dumps_kwargs=None): + if not dumps_kwargs: + dumps_kwargs = {} + + def _dry_me(services, d, ignore_wrappers=False, complex_as=dict, + just_ctx=False, just_in_object=False, validator=None, + polymorphic=False): + + app = Application(services, 'tns', + in_protocol=_DictDocumentChild( + ignore_wrappers=ignore_wrappers, complex_as=complex_as, + polymorphic=polymorphic, validator=validator, + ), + out_protocol=_DictDocumentChild( + ignore_wrappers=ignore_wrappers, complex_as=complex_as, + polymorphic=polymorphic), + ) + + server = ServerBase(app) + initial_ctx = MethodContext(server, MethodContext.SERVER) + in_string = serializer.dumps(d, **dumps_kwargs) + if not isinstance(in_string, bytes): + in_string = in_string.encode('utf8') + initial_ctx.in_string = [in_string] + + ctx, = server.generate_contexts(initial_ctx, in_string_charset='utf8') + if not just_ctx: + server.get_in_object(ctx) + if not just_in_object: + server.get_out_object(ctx) + server.get_out_string(ctx) + + return ctx + return _dry_me + +def TDictDocumentTest(serializer, _DictDocumentChild, dumps_kwargs=None, + loads_kwargs=None, convert_dict=None): + if not dumps_kwargs: + dumps_kwargs = {} + if not loads_kwargs: + loads_kwargs = {} + _dry_me = TDry(serializer, _DictDocumentChild, dumps_kwargs) + + if convert_dict is None: + convert_dict = lambda v: v + + class Test(unittest.TestCase): + def dumps(self, o): + print("using", dumps_kwargs, "to dump", o) + return serializer.dumps(o, **dumps_kwargs) + + def loads(self, o): + return _unbyte(serializer.loads(o, **loads_kwargs)) + + def test_complex_with_only_primitive_fields(self): + class SomeComplexModel(ComplexModel): + i = Integer + s = Unicode + + class SomeService(Service): + @srpc(SomeComplexModel, _returns=SomeComplexModel) + def some_call(scm): + return SomeComplexModel(i=5, s='5x') + + ctx = _dry_me([SomeService], {"some_call":[]}) + + s = self.loads(b''.join(ctx.out_string)) + + s = s["some_callResponse"]["some_callResult"]["SomeComplexModel"] + assert s["i"] == 5 + assert s["s"] in ("5x", b"5x") + + def test_complex(self): + class CM(ComplexModel): + i = Integer + s = Unicode + + class CCM(ComplexModel): + c = CM + i = Integer + s = Unicode + + class SomeService(Service): + @srpc(CCM, _returns=CCM) + def some_call(ccm): + return CCM(c=ccm.c, i=ccm.i, s=ccm.s) + + ctx = _dry_me([SomeService], {"some_call": + {"ccm": {"CCM":{ + "c":{"CM":{"i":3, "s": "3x"}}, + "i":4, + "s": "4x", + }}} + }) + + ret = self.loads(b''.join(ctx.out_string)) + print(ret) + + d = ret['some_callResponse']['some_callResult']['CCM'] + assert d['i'] == 4 + assert d['s'] in ('4x', b'4x') + assert d['c']['CM']['i'] == 3 + assert d['c']['CM']['s'] in ('3x', b'3x') + + def test_multiple_list(self): + class SomeService(Service): + @srpc(Unicode(max_occurs=decimal.Decimal('inf')), + _returns=Unicode(max_occurs=decimal.Decimal('inf'))) + def some_call(s): + return s + + ctx = _dry_me([SomeService], {"some_call":[["a","b"]]}) + + data = b''.join(ctx.out_string) + print(data) + + assert self.loads(data) == \ + {"some_callResponse": {"some_callResult": ("a", "b")}} + + def test_multiple_dict(self): + class SomeService(Service): + @srpc(Unicode(max_occurs=decimal.Decimal('inf')), + _returns=Unicode(max_occurs=decimal.Decimal('inf'))) + def some_call(s): + return s + + ctx = _dry_me([SomeService], {"some_call":{"s":["a","b"]}}) + + assert self.loads(b''.join(ctx.out_string)) == \ + {"some_callResponse": {"some_callResult": ("a", "b")}} + + def test_multiple_dict_array(self): + class SomeService(Service): + @srpc(Iterable(Unicode), _returns=Iterable(Unicode)) + def some_call(s): + return s + + ctx = _dry_me([SomeService], {"some_call":{"s":["a","b"]}}) + + assert self.loads(b''.join(ctx.out_string)) == \ + {"some_callResponse": {"some_callResult": ("a", "b")}} + + def test_multiple_dict_complex_array(self): + class CM(ComplexModel): + i = Integer + s = Unicode + + class CCM(ComplexModel): + c = CM + i = Integer + s = Unicode + + class ECM(CCM): + d = DateTime + + class SomeService(Service): + @srpc(Iterable(ECM), _returns=Iterable(ECM)) + def some_call(ecm): + return ecm + + ctx = _dry_me([SomeService], { + "some_call": {"ecm": [{"ECM": { + "c": {"CM":{"i":3, "s": "3x"}}, + "i":4, + "s": "4x", + "d": "2011-12-13T14:15:16Z" + }}] + }}) + + print(ctx.in_object) + + ret = self.loads(b''.join(ctx.out_string)) + print(ret) + assert ret["some_callResponse"]['some_callResult'] + assert ret["some_callResponse"]['some_callResult'][0] + assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["c"] + assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["c"]["CM"]["i"] == 3 + assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["c"]["CM"]["s"] in ("3x", b"3x") + assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["i"] == 4 + assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["s"] in ("4x", b"4x") + assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["d"] == "2011-12-13T14:15:16+00:00" + + def test_invalid_request(self): + class SomeService(Service): + @srpc(Integer, String, DateTime) + def yay(i,s,d): + print(i,s,d) + + ctx = _dry_me([SomeService], {"some_call": {"yay": []}}, + just_in_object=True) + + print(ctx.in_error) + assert ctx.in_error.faultcode == 'Client.ResourceNotFound' + + def test_invalid_string(self): + class SomeService(Service): + @srpc(Integer, String, DateTime) + def yay(i,s,d): + print(i, s, d) + + ctx = _dry_me([SomeService], {"yay": {"s": 1}}, validator='soft', + just_in_object=True) + + assert ctx.in_error.faultcode == 'Client.ValidationError' + + def test_invalid_number(self): + class SomeService(Service): + @srpc(Integer, String, DateTime) + def yay(i,s,d): + print(i,s,d) + + ctx = _dry_me([SomeService], {"yay": ["s", "B"]}, validator='soft', + just_in_object=True) + + assert ctx.in_error.faultcode == 'Client.ValidationError' + + def test_missing_value(self): + class SomeService(Service): + @srpc(Integer, Unicode, Mandatory.DateTime) + def yay(i, s, d): + print(i, s, d) + + ctx = _dry_me([SomeService], {"yay": [1, "B"]}, validator='soft', + just_in_object=True) + + print(ctx.in_error.faultstring) + assert ctx.in_error.faultcode == 'Client.ValidationError' + assert ctx.in_error.faultstring.endswith("at least 1 times.") + + def test_invalid_datetime(self): + class SomeService(Service): + @srpc(Integer, String, Mandatory.DateTime) + def yay(i,s,d): + print(i,s,d) + + ctx = _dry_me([SomeService],{"yay": {"d":"a2011"}},validator='soft', + just_in_object=True) + + assert ctx.in_error.faultcode == 'Client.ValidationError' + + def test_fault_to_dict(self): + class SomeService(Service): + @srpc(_returns=String) + def some_call(): + raise Fault() + + _dry_me([SomeService], {"some_call":[]}) + + def test_prune_none_and_optional(self): + class SomeObject(ComplexModel): + i = Integer + s = String(min_occurs=1) + + class SomeService(Service): + @srpc(_returns=SomeObject) + def some_call(): + return SomeObject() + + ctx = _dry_me([SomeService], {"some_call":[]}) + + ret = self.loads(b''.join(ctx.out_string)) + + assert ret == {"some_callResponse": {'some_callResult': + {'SomeObject': {'s': None}}}} + + def test_any_xml(self): + d = lxml.etree.tostring(E('{ns1}x', E('{ns2}Y', "some data")), + encoding='unicode') + + class SomeService(Service): + @srpc(AnyXml, _returns=AnyXml) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == lxml.etree._Element + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_any_html(self): + d = lxml.html.tostring(E('div', E('span', "something")), + encoding='unicode') + + class SomeService(Service): + @srpc(AnyHtml, _returns=AnyHtml) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == lxml.html.HtmlElement + return p + + ctx = _dry_me([SomeService], {"some_call": [d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + + print(s) + print(d) + assert s == d + + def test_any_dict(self): + d = {'helo': 213, 'data': {'nested': [12, 0.3]}} + + class SomeService(Service): + @srpc(AnyDict, _returns=AnyDict) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == dict + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = b''.join(ctx.out_string) + d = self.dumps({"some_callResponse": {"some_callResult": d}}) + + print(s) + print(d) + assert self.loads(s) == self.loads(d) + + def test_unicode(self): + d = u'some string' + + class SomeService(Service): + @srpc(Unicode, _returns=Unicode) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == six.text_type + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_any_uri(self): + d = 'http://example.com/?asd=b12&df=aa#tag' + + class SomeService(Service): + @srpc(AnyUri, _returns=AnyUri) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call": [d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_image_uri(self): + d = 'http://example.com/funny.gif' + + class SomeService(Service): + @srpc(ImageUri, _returns=ImageUri) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call": [d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_decimal(self): + d = decimal.Decimal('1e100') + if _DictDocumentChild._decimal_as_string: + d = str(d) + + class SomeService(Service): + @srpc(Decimal, _returns=Decimal) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == decimal.Decimal + return p + + ctx = _dry_me([SomeService], {"some_call": [d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_double(self): + d = 12.3467 + + class SomeService(Service): + @srpc(Double, _returns=Double) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == float + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_integer(self): + d = 5 + + class SomeService(Service): + @srpc(Integer, _returns=Integer) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == int + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_integer_way_small(self): + d = -1<<1000 + if _DictDocumentChild._huge_numbers_as_string: + d = str(d) + + class SomeService(Service): + @srpc(Integer, _returns=Integer) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == long + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + + print(s) + print(d) + assert s == d + + def test_integer_way_big(self): + d = 1<<1000 + if _DictDocumentChild._huge_numbers_as_string: + d = str(d) + + class SomeService(Service): + @srpc(Integer, _returns=Integer) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == long + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_time(self): + d = time(10, 20, 30).isoformat() + + class SomeService(Service): + @srpc(Time, _returns=Time) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == time + assert p.isoformat() == d + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_date(self): + vdt = datetime(2010, 9, 8) + d = vdt.date().isoformat() + + class SomeService(Service): + @srpc(Date, _returns=Date) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == date + assert p.isoformat() == d + return p + + @srpc(_returns=Date) + def some_call_dt(): + return vdt + + ctx = _dry_me([SomeService], {"some_call": [d]}) + s = self.loads(b''.join(ctx.out_string)) + rd = {"some_callResponse": {"some_callResult": d}} + print(s) + print(rd) + assert s == rd + + ctx = _dry_me([SomeService], {"some_call_dt": []}) + s = self.loads(b''.join(ctx.out_string)) + rd = {"some_call_dtResponse": {"some_call_dtResult": d}} + print(s) + print(rd) + assert s == rd + + def test_datetime(self): + d = datetime(2010, 9, 8, 7, 6, 5).isoformat() + + class SomeService(Service): + @srpc(DateTime, _returns=DateTime(timezone=False)) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == datetime + assert p.replace(tzinfo=None).isoformat() == d + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}, validator='soft') + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_datetime_tz(self): + d = datetime(2010, 9, 8, 7, 6, 5, tzinfo=pytz.utc).isoformat() + + class SomeService(Service): + @srpc(DateTime, _returns=DateTime(ge=datetime(2010,1,1,tzinfo=pytz.utc))) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == datetime + assert p.isoformat() == d + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}, validator='soft') + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_duration(self): + d = ProtocolBase().to_unicode(Duration, timedelta(0, 45)) + + class SomeService(Service): + @srpc(Duration, _returns=Duration) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == timedelta + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_boolean(self): + d = True + + class SomeService(Service): + @srpc(Boolean, _returns=Boolean) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == bool + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_uuid(self): + d = '7d2a6330-eb64-4900-8a10-38ebef415e9d' + + class SomeService(Service): + @srpc(Uuid, _returns=Uuid) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == uuid.UUID + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_point2d(self): + d = 'POINT(1 2)' + + class SomeService(Service): + @srpc(Point, _returns=Point) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_point3d(self): + d = 'POINT(1 2 3)' + + class SomeService(Service): + @srpc(Point, _returns=Point) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_line2d(self): + d = 'LINESTRING(1 2, 3 4)' + + class SomeService(Service): + @srpc(Line, _returns=Line) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_line3d(self): + d = 'LINESTRING(1 2 3, 4 5 6)' + + class SomeService(Service): + @srpc(Line, _returns=Line) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_polygon2d(self): + d = 'POLYGON((1 1, 1 2, 2 2, 2 1, 1 1))' + + class SomeService(Service): + @srpc(Polygon(2), _returns=Polygon(2)) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_polygon3d(self): + d = 'POLYGON((1 1 0, 1 2 0, 2 2 0, 2 1 0, 1 1 0))' + + class SomeService(Service): + @srpc(Polygon(3), _returns=Polygon(3)) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_multipoint2d(self): + d = 'MULTIPOINT ((10 40), (40 30), (20 20), (30 10))' + + class SomeService(Service): + @srpc(MultiPoint(2), _returns=MultiPoint(2)) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_multipoint3d(self): + d = 'MULTIPOINT (10 40 30, 40 30 10,)' + + class SomeService(Service): + @srpc(MultiPoint(3), _returns=MultiPoint(3)) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_multiline2d(self): + d = 'MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))' + + class SomeService(Service): + @srpc(MultiLine(2), _returns=MultiLine(2)) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_multiline3d(self): + d = 'MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))' + + class SomeService(Service): + @srpc(MultiLine(3), _returns=MultiLine(3)) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_multipolygon2d(self): + d = 'MULTIPOLYGON (((30 20, 10 40, 45 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5)))' + + class SomeService(Service): + @srpc(MultiPolygon(2), _returns=MultiPolygon(2)) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_multipolygon3d(self): + d = 'MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),' \ + '((20 35, 45 20, 30 5, 10 10, 10 30, 20 35),' \ + '(30 20, 20 25, 20 15, 30 20)))' + + class SomeService(Service): + @srpc(MultiPolygon(3), _returns=MultiPolygon(3)) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, six.string_types) + return p + + ctx = _dry_me([SomeService], {"some_call":[d]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": d}} + print(s) + print(d) + assert s == d + + def test_generator(self): + class SomeService(Service): + @srpc(_returns=Iterable(Integer)) + def some_call(): + return iter(range(1000)) + + ctx = _dry_me([SomeService], {"some_call":[]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": tuple(range(1000))}} + print(s) + print(d) + assert s == d + + def test_bytearray(self): + dbe = _DictDocumentChild.default_binary_encoding + beh = binary_encoding_handlers[dbe] + + data = bytes(bytearray(range(0xff))) + encoded_data = beh([data]) + if _DictDocumentChild.text_based: + encoded_data = encoded_data.decode('latin1') + + class SomeService(Service): + @srpc(ByteArray, _returns=ByteArray) + def some_call(ba): + print(ba) + print(type(ba)) + assert isinstance(ba, tuple) + assert ba == (data,) + return ba + + ctx = _dry_me([SomeService], {"some_call": [encoded_data]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": encoded_data}} + + print(repr(s)) + print(repr(d)) + print(repr(encoded_data)) + assert s == d + + def test_file_data(self): + # the only difference with the bytearray test is/are the types + # inside @srpc + dbe = _DictDocumentChild.default_binary_encoding + beh = binary_encoding_handlers[dbe] + + data = bytes(bytearray(range(0xff))) + encoded_data = beh([data]) + if _DictDocumentChild.text_based: + encoded_data = encoded_data.decode('latin1') + + class SomeService(Service): + @srpc(File, _returns=File) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, File.Value) + assert p.data == (data,) + return p.data + + # we put the encoded data in the list of arguments. + ctx = _dry_me([SomeService], {"some_call": [encoded_data]}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": encoded_data}} + + print(s) + print(d) + print(repr(encoded_data)) + assert s == d + + def test_file_value(self): + dbe = _DictDocumentChild.default_binary_encoding + beh = binary_encoding_handlers[dbe] + + # Prepare data + v = File.Value( + name='some_file.bin', + type='application/octet-stream', + ) + file_data = bytes(bytearray(range(0xff))) + v.data = (file_data,) + beh([file_data]) + if _DictDocumentChild.text_based: + test_data = beh(v.data).decode('latin1') + else: + test_data = beh(v.data) + + print(repr(v.data)) + + class SomeService(Service): + @srpc(File, _returns=File) + def some_call(p): + print(p) + print(type(p)) + assert isinstance(p, File.Value) + assert p.data == (file_data,) + assert p.type == v.type + assert p.name == v.name + return p + + d = get_object_as_dict(v, File, protocol=_DictDocumentChild, + ignore_wrappers=False) + ctx = _dry_me([SomeService], {"some_call": {'p': d}}) + s = b''.join(ctx.out_string) + d = self.dumps({"some_callResponse": {"some_callResult": { + 'name': v.name, + 'type': v.type, + 'data': test_data, + }}}) + + print(self.loads(s)) + print(self.loads(d)) + print(v) + assert self.loads(s) == self.loads(d) + + def test_validation_frequency(self): + class SomeService(Service): + @srpc(ByteArray(min_occurs=1), _returns=ByteArray) + def some_call(p): + pass + + try: + _dry_me([SomeService], {"some_call": []}, validator='soft') + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + def test_validation_nullable(self): + class SomeService(Service): + @srpc(ByteArray(nullable=False), _returns=ByteArray) + def some_call(p): + pass + + try: + _dry_me([SomeService], {"some_call": [None]}, + validator='soft') + except ValidationError: + pass + + else: + raise Exception("must raise ValidationError") + + def test_validation_string_pattern(self): + class SomeService(Service): + @srpc(Uuid) + def some_call(p): + pass + + try: + _dry_me([SomeService], {"some_call": ["duduk"]}, + validator='soft') + except ValidationError as e: + print(e) + pass + + else: + raise Exception("must raise ValidationError") + + def test_validation_integer_range(self): + class SomeService(Service): + @srpc(Integer(ge=0, le=5)) + def some_call(p): + pass + + try: + _dry_me([SomeService], {"some_call": [10]}, + validator='soft') + except ValidationError: + pass + + else: + raise Exception("must raise ValidationError") + + def test_validation_integer_type(self): + class SomeService(Service): + @srpc(Integer8) + def some_call(p): + pass + + try: + _dry_me([SomeService], {"some_call": [-129]}, + validator='soft') + except ValidationError: + pass + + else: + raise Exception("must raise ValidationError") + + def test_validation_integer_type_2(self): + class SomeService(Service): + @srpc(Integer8) + def some_call(p): + pass + + try: + _dry_me([SomeService], {"some_call": [1.2]}, validator='soft') + + except ValidationError: + pass + + else: + raise Exception("must raise ValidationError") + + def test_not_wrapped(self): + class SomeInnerClass(ComplexModel): + d = date + dt = datetime + + class SomeClass(ComplexModel): + a = int + b = Unicode + c = SomeInnerClass.customize(not_wrapped=True) + + class SomeService(Service): + @srpc(SomeClass.customize(not_wrapped=True), + _returns=SomeClass.customize(not_wrapped=True)) + def some_call(p): + assert p.a == 1 + assert p.b == 's' + assert p.c.d == date(2018, 11, 22) + return p + + inner = {"a": 1, "b": "s", "c": {"d": '2018-11-22'}} + doc = {"some_call": [inner]} + ctx = _dry_me([SomeService], doc, validator='soft') + + print(ctx.out_document) + + d = convert_dict({"some_callResponse": {"some_callResult": inner}}) + self.assertEquals(ctx.out_document[0], d) + + def test_validation_freq_parent(self): + class C(ComplexModel): + i = Integer(min_occurs=1) + s = Unicode + + class SomeService(Service): + @srpc(C) + def some_call(p): + pass + + try: + # must raise validation error for missing i + _dry_me([SomeService], {"some_call": {'p': {'C': {'s': 'a'}}}}, + validator='soft') + except ValidationError as e: + logger.exception(e) + pass + except BaseException as e: + logger.exception(e) + pass + else: + raise Exception("must raise ValidationError") + + # must not raise anything for missing p because C has min_occurs=0 + _dry_me([SomeService], {"some_call": {}}, validator='soft') + + def test_inheritance(self): + class P(ComplexModel): + identifier = Uuid + signature = Unicode + + class C(P): + foo = Unicode + bar = Uuid + + class SomeService(Service): + @rpc(_returns=C) + def some_call(ctx): + result = C() + result.identifier = uuid.UUID(int=0) + result.signature = 'yyyyyyyyyyy' + result.foo = 'zzzzzz' + result.bar = uuid.UUID(int=1) + return result + + ctx = _dry_me([SomeService], {"some_call": []}) + + s = self.loads(b''.join(ctx.out_string)) + d = {"some_callResponse": {"some_callResult": {"C": { + 'identifier': '00000000-0000-0000-0000-000000000000', + 'bar': '00000000-0000-0000-0000-000000000001', + 'foo': 'zzzzzz', + 'signature': 'yyyyyyyyyyy' + }}}} + + assert s == d + + def test_exclude(self): + class C(ComplexModel): + s1 = Unicode(exc=True) + s2 = Unicode + + class SomeService(Service): + @srpc(C, _returns=C) + def some_call(sc): + assert sc.s1 is None, "sc={}".format(sc) + assert sc.s2 == "s2" + return C(s1="s1", s2="s2") + + doc = [{"C": {"s1": "s1","s2": "s2"}}] + ctx = _dry_me([SomeService], {"some_call": doc}) + + self.assertEquals(ctx.out_document[0], convert_dict( + {'some_callResponse': {'some_callResult': {'C': {'s2': 's2'}}}}) + ) + + def test_polymorphic_deserialization(self): + class P(ComplexModel): + sig = Unicode + + class C(P): + foo = Unicode + + class D(P): + bar = Integer + + class SomeService(Service): + @rpc(P, _returns=Unicode) + def typeof(ctx, p): + return type(p).__name__ + + ctx = _dry_me([SomeService], + {"typeof": [{'C':{'sig':'a', 'foo': 'f'}}]}, + polymorphic=True) + + s = self.loads(b''.join(ctx.out_string)) + d = {"typeofResponse": {"typeofResult": 'C'}} + + print(s) + print(d) + assert s == d + + ctx = _dry_me([SomeService], + {"typeof": [{'D':{'sig':'b', 'bar': 5}}]}, + polymorphic=True) + + s = self.loads(b''.join(ctx.out_string)) + d = {"typeofResponse": {"typeofResult": 'D'}} + + print(s) + print(d) + assert s == d + + def test_default(self): + class SomeComplexModel(ComplexModel): + _type_info = [ + ('a', Unicode), + ('b', Unicode(default='default')), + ] + + class SomeService(Service): + @srpc(SomeComplexModel) + def some_method(s): + pass + + ctx = _dry_me([SomeService], + {"some_method": [{"s": {"a": "x", "b": None}}]}, + polymorphic=True) + + assert ctx.in_object.s.b == None + assert ctx.in_error is None + + ctx = _dry_me([SomeService], {"some_method": {"s": {"a": "x"}}}, + polymorphic=True) + + assert ctx.in_object.s.b == 'default' + assert ctx.in_error is None + + def test_nillable_default(self): + class SomeComplexModel(ComplexModel): + _type_info = [ + ('a', Unicode), + ('b', Unicode(min_occurs=1, default='default', nillable=True)), + ] + + class SomeService(Service): + @srpc(SomeComplexModel) + def some_method(s): + pass + + ctx = _dry_me([SomeService], + {"some_method": [{"s": {"a": "x", "b": None}}]}, + polymorphic=True, validator='soft') + + assert ctx.in_object.s.b == None + assert ctx.in_error is None + + ctx = _dry_me([SomeService], {"some_method": {"s": {"a": "x"}}}, + polymorphic=True) + + assert ctx.in_object.s.b == 'default' + assert ctx.in_error is None + + return Test diff --git a/pym/calculate/contrib/spyne/test/protocol/_test_dictdoc.pyc b/pym/calculate/contrib/spyne/test/protocol/_test_dictdoc.pyc new file mode 100644 index 0000000..324867b Binary files /dev/null and b/pym/calculate/contrib/spyne/test/protocol/_test_dictdoc.pyc differ diff --git a/pym/calculate/contrib/spyne/test/protocol/test_cloth.py b/pym/calculate/contrib/spyne/test/protocol/test_cloth.py new file mode 100644 index 0000000..965d852 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/protocol/test_cloth.py @@ -0,0 +1,570 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from __future__ import print_function + +import logging +logger = logging.getLogger(__name__) + +import unittest + +from lxml import etree, html +from lxml.builder import E + +from spyne import ComplexModel, XmlAttribute, Unicode, Array, Integer, \ + SelfReference, XmlData +from spyne.protocol.cloth import XmlCloth +from spyne.test import FakeContext +from spyne.util.six import BytesIO + + +class TestModelCloth(unittest.TestCase): + def test_root_html(self): + class SomeObject(ComplexModel): + class Attributes(ComplexModel.Attributes): + html_cloth = html.fromstring("") + + assert SomeObject.Attributes._html_cloth is None + assert SomeObject.Attributes._html_root_cloth is not None + + def test_html(self): + class SomeObject(ComplexModel): + class Attributes(ComplexModel.Attributes): + html_cloth = html.fromstring('') + + assert SomeObject.Attributes._html_cloth is not None + assert SomeObject.Attributes._html_root_cloth is None + + def test_root_xml(self): + class SomeObject(ComplexModel): + class Attributes(ComplexModel.Attributes): + xml_cloth = etree.fromstring('') + + assert SomeObject.Attributes._xml_cloth is None + assert SomeObject.Attributes._xml_root_cloth is not None + + def test_xml(self): + class SomeObject(ComplexModel): + class Attributes(ComplexModel.Attributes): + xml_cloth = html.fromstring('') + + assert SomeObject.Attributes._xml_cloth is not None + assert SomeObject.Attributes._xml_root_cloth is None + + +class TestXmlClothToParent(unittest.TestCase): + def setUp(self): + self.ctx = FakeContext() + self.stream = BytesIO() + logging.basicConfig(level=logging.DEBUG) + + def _run(self, inst, cls=None): + if cls is None: + cls = inst.__class__ + + with etree.xmlfile(self.stream) as parent: + XmlCloth().subserialize(self.ctx, cls, inst, parent, + name=cls.__name__) + + elt = etree.fromstring(self.stream.getvalue()) + + print(etree.tostring(elt, pretty_print=True)) + return elt + + def test_simple(self): + v = 'punk.' + elt = self._run(v, Unicode) + + assert elt.text == v + + def test_complex_primitive(self): + class SomeObject(ComplexModel): + s = Unicode + + v = 'punk.' + elt = self._run(SomeObject(s=v)) + + assert elt[0].text == v + + def test_complex_inheritance(self): + class A(ComplexModel): + i = Integer + + class B(A): + s = Unicode + + i = 42 + s = 'punk.' + elt = self._run(B(i=i, s=s)) + + # order is important + assert len(elt) == 2 + assert elt[0].text == str(i) + assert elt[1].text == s + + +### !!! WARNING !!! ### !!! WARNING !!! ### !!! WARNING !!! ### !!! WARNING !!! +# +# This test uses spyne_id and spyne_tagbag instead of spyne-id and spyne-tagbag +# for ease of testing. The attributes used here are different from what you are +# going to see in the real-world uses of this functionality. +# You have been warned !!! +# +### !!! WARNING !!! ### !!! WARNING !!! ### !!! WARNING !!! ### !!! WARNING !!! +class TestXmlCloth(unittest.TestCase): + def setUp(self): + self.ctx = FakeContext() + self.stream = BytesIO() + logging.basicConfig(level=logging.DEBUG) + + def _run(self, inst, spid=None, cloth=None): + cls = inst.__class__ + if cloth is None: + assert spid is not None + cloth = etree.fromstring("""""" % spid) + else: + assert spid is None + + with etree.xmlfile(self.stream) as parent: + XmlCloth(cloth=cloth).set_identifier_prefix('spyne_') \ + .subserialize(self.ctx, cls, inst, parent) + + elt = etree.fromstring(self.stream.getvalue()) + + print(etree.tostring(elt, pretty_print=True)) + return elt + + def test_simple_value(self): + class SomeObject(ComplexModel): + s = Unicode + + v = 'punk.' + elt = self._run(SomeObject(s=v), spid='s') + + assert elt[0].text == v + + def test_simple_empty(self): + class SomeObject(ComplexModel): + s = Unicode + + elt = self._run(SomeObject(), spid='s') + + assert len(elt) == 0 + + # FIXME: just fix it + def _test_simple_empty_nonoptional(self): + class SomeObject(ComplexModel): + s = Unicode(min_occurs=1) + + elt = self._run(SomeObject(), spid='s') + + assert elt[0].text is None + + # FIXME: just fix it + def _test_simple_empty_nonoptional_clear(self): + class SomeObject(ComplexModel): + s = Unicode(min_occurs=1) + + cloth = etree.fromstring("""oi punk!""") + + elt = self._run(SomeObject(), cloth=cloth) + + assert elt[0].text is None + + def test_xml_data_tag(self): + class SomeObject(ComplexModel): + d = XmlData(Unicode) + + cloth = etree.fromstring('') + + elt = self._run(SomeObject(d='data'), cloth=cloth) + + assert elt.text == 'data' + + def test_xml_data_attr(self): + class SomeObject(ComplexModel): + d = XmlData(Unicode) + + cloth = etree.fromstring('') + + elt = self._run(SomeObject(d='data'), cloth=cloth) + + assert elt.text == 'data' + + def test_xml_data_attr_undesignated(self): + class SomeObject(ComplexModel): + d = Unicode + + cloth = etree.fromstring('') + + elt = self._run(SomeObject(d='data'), cloth=cloth) + + assert elt.text == 'data' + + def test_simple_value_xmlattribute(self): + v = 'punk.' + + class SomeObject(ComplexModel): + s = XmlAttribute(Unicode(min_occurs=1)) + + cloth = etree.fromstring("""""") + elt = self._run(SomeObject(s=v), cloth=cloth) + + assert elt.attrib['s'] == v + + def test_simple_value_xmlattribute_subname(self): + v = 'punk.' + + class SomeObject(ComplexModel): + s = XmlAttribute(Unicode(min_occurs=1, sub_name='foo')) + + cloth = etree.fromstring("""""") + elt = self._run(SomeObject(s=v), cloth=cloth) + + assert elt.attrib['foo'] == v + + def test_simple_value_xmlattribute_non_immediate(self): + v = 'punk.' + + class SomeObject(ComplexModel): + s = XmlAttribute(Unicode(min_occurs=1, sub_name='foo')) + + cloth = etree.fromstring("""""") + elt = self._run(SomeObject(s=v), cloth=cloth) + + assert elt.attrib['foo'] == v + assert elt[0].attrib['foo'] == v + + def test_simple_value_xmlattribute_non_immediate_non_designated(self): + v = 'punk.' + + class SomeObject(ComplexModel): + s = Unicode(min_occurs=1, sub_name='foo') + + cloth = etree.fromstring("""""") + elt = self._run(SomeObject(s=v), cloth=cloth) + + assert not 'foo' in elt.attrib + assert elt[0].attrib['foo'] == v + + def test_non_tagbag(self): + cloth = E.a( + E.b( + E.c( + E.d( + spyne_id="i", + ), + spyne_id="c", + ), + spyne_id="i", + ), + spyne_tagbag='', + ) + + class C2(ComplexModel): + i = Integer + + class C1(ComplexModel): + i = Integer + c = C2 + + elt = self._run(C1(i=1, c=C2(i=2)), cloth=cloth) + assert elt.xpath('//b/text()') == ['1'] + # no order guarantee is given + assert set(elt.xpath('//d/text()')) == set(['1', '2']) + + def test_array(self): + v = range(3) + + class SomeObject(ComplexModel): + s = Array(Integer) + + cloth = E.a( + E.b( + E.c(spyne_id="integer"), + spyne_id="s", + ) + ) + + elt = self._run(SomeObject(s=v), cloth=cloth) + + assert elt.xpath('//c/text()') == [str(i) for i in v] + + def test_array_empty(self): + class SomeObject(ComplexModel): + s = Array(Integer) + + elt_str = '' + cloth = etree.fromstring(elt_str) + + elt = self._run(SomeObject(), cloth=cloth) + + assert elt.xpath('//c') == [] + + # FIXME: just fix it + def _test_array_empty_nonoptional(self): + class SomeObject(ComplexModel): + s = Array(Integer(min_occurs=1)) + + elt_str = '' + cloth = etree.fromstring(elt_str) + + elt = self._run(SomeObject(), cloth=cloth) + + assert elt.xpath('//c') == [cloth[0][0]] + + def test_simple_two_tags(self): + class SomeObject(ComplexModel): + s = Unicode + i = Integer + + v = SomeObject(s='s', i=5) + + cloth = E.a( + E.b1(), + E.b2( + E.c1(spyne_id="s"), + E.c2(), + ), + E.e( + E.g1(), + E.g2(spyne_id="i"), + E.g3(), + ), + ) + + elt = self._run(v, cloth=cloth) + + print(etree.tostring(elt, pretty_print=True)) + assert elt[0].tag == 'b1' + assert elt[1].tag == 'b2' + assert elt[1][0].tag == 'c1' + assert elt[1][0].text == 's' + assert elt[1][1].tag == 'c2' + assert elt[2].tag == 'e' + assert elt[2][0].tag == 'g1' + assert elt[2][1].tag == 'g2' + assert elt[2][1].text == '5' + assert elt[2][2].tag == 'g3' + + def test_sibling_order(self): + class SomeObject(ComplexModel): + s = Unicode + + v = SomeObject(s='s') + + cloth = E.a( + E.b1(), + E.b2( + E.c0(), + E.c1(), + E.c2(spyne_id="s"), + E.c3(), + E.c4(), + ), + ) + + elt = self._run(v, cloth=cloth) + print(etree.tostring(elt, pretty_print=True)) + assert elt[0].tag == 'b1' + assert elt[1].tag == 'b2' + assert elt[1][0].tag == 'c0' + assert elt[1][1].tag == 'c1' + assert elt[1][2].tag == 'c2' + assert elt[1][2].text == 's' + assert elt[1][3].tag == 'c3' + assert elt[1][4].tag == 'c4' + + def test_parent_text(self): + class SomeObject(ComplexModel): + s = Unicode + + v = SomeObject(s='s') + + cloth = E.a( + "text 0", + E.b1(spyne_id="s"), + ) + + print(etree.tostring(cloth, pretty_print=True)) + elt = self._run(v, cloth=cloth) + print(etree.tostring(elt, pretty_print=True)) + + assert elt.tag == 'a' + assert elt.text == 'text 0' + + assert elt[0].tag == 'b1' + assert elt[0].text == 's' + + def test_anc_text(self): + class SomeObject(ComplexModel): + s = Unicode + + v = SomeObject(s='s') + + cloth = E.a( + E.b1( + "text 1", + E.c1(spyne_id="s"), + ) + ) + + print(etree.tostring(cloth, pretty_print=True)) + elt = self._run(v, cloth=cloth) + print(etree.tostring(elt, pretty_print=True)) + + assert elt[0].tag == 'b1' + assert elt[0].text == 'text 1' + assert elt[0][0].tag == 'c1' + assert elt[0][0].text == 's' + + def test_prevsibl_tail(self): + class SomeObject(ComplexModel): + s = Unicode + + v = SomeObject(s='s') + + cloth = E.a( + E.b1( + E.c1(), + "text 2", + E.c2(spyne_id="s"), + ) + ) + + print(etree.tostring(cloth, pretty_print=True)) + elt = self._run(v, cloth=cloth) + print(etree.tostring(elt, pretty_print=True)) + + assert elt[0].tag == 'b1' + assert elt[0][0].tag == 'c1' + assert elt[0][0].tail == 'text 2' + assert elt[0][1].text == 's' + + def test_sibling_tail_close(self): + class SomeObject(ComplexModel): + s = Unicode + + v = SomeObject(s='s') + + cloth = E.a( + E.b0(spyne_id="s"), + "text 3", + ) + + print(etree.tostring(cloth, pretty_print=True)) + elt = self._run(v, cloth=cloth) + print(etree.tostring(elt, pretty_print=True)) + + assert elt[0].tag == 'b0' + assert elt[0].text == 's' + assert elt[0].tail == 'text 3' + + def test_sibling_tail_close_sibling(self): + class SomeObject(ComplexModel): + s = Unicode + i = Integer + + v = SomeObject(s='s', i=5) + + cloth = E.a( + E.b0(spyne_id="s"), + "text 3", + E.b1(spyne_id="i"), + ) + + print(etree.tostring(cloth, pretty_print=True)) + elt = self._run(v, cloth=cloth) + print(etree.tostring(elt, pretty_print=True)) + + assert elt[0].tag == 'b0' + assert elt[0].text == 's' + assert elt[0].tail == 'text 3' + + def test_sibling_tail_close_anc(self): + class SomeObject(ComplexModel): + s = Unicode + i = Integer + + v = SomeObject(s='s', i=5) + + cloth = E.a( + E.b0(), + "text 0", + E.b1( + E.c0(spyne_id="s"), + "text 1", + E.c1(), + "text 2", + ), + "text 3", + E.b2( + E.c1(spyne_id="i"), + "text 4", + ) + ) + + print(etree.tostring(cloth, pretty_print=True)) + elt = self._run(v, cloth=cloth) + print(etree.tostring(elt, pretty_print=True)) + + assert elt.xpath('/a/b1/c0')[0].tail == 'text 1' + assert elt.xpath('/a/b1/c1')[0].tail == 'text 2' + assert elt.xpath('/a/b2/c1')[0].tail == 'text 4' + + def test_nested_conflicts(self): + class SomeObject(ComplexModel): + s = Unicode + i = Integer + c = SelfReference + + v = SomeObject(s='x', i=1, c=SomeObject(s='y', i=2)) + + cloth = E.a( + E.b0(), + "text 0", + E.b1( + E.c0(spyne_id="s"), + "text 1", + E.c1( + E.d0(spyne_id="s"), + E.d1(spyne_id="i"), + spyne_id="c", + ), + "text 2", + ), + "text 3", + E.b2( + E.c2(spyne_id="i"), + "text 4", + ) + ) + + print(etree.tostring(cloth, pretty_print=True)) + elt = self._run(v, cloth=cloth) + print(etree.tostring(elt, pretty_print=True)) + + assert elt.xpath('/a/b1/c0')[0].text == str(v.s) + assert elt.xpath('/a/b1/c1/d0')[0].text == str(v.c.s) + assert elt.xpath('/a/b1/c1/d1')[0].text == str(v.c.i) + assert elt.xpath('/a/b2/c2')[0].text == str(v.i) + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/protocol/test_cloth.pyc b/pym/calculate/contrib/spyne/test/protocol/test_cloth.pyc new file mode 100644 index 0000000..1687a13 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/protocol/test_cloth.pyc differ diff --git a/pym/calculate/contrib/spyne/test/protocol/test_html_microformat.py b/pym/calculate/contrib/spyne/test/protocol/test_html_microformat.py new file mode 100644 index 0000000..8a0df49 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/protocol/test_html_microformat.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logging.basicConfig(level=logging.DEBUG) + +import unittest + +from lxml import html + +from spyne.application import Application +from spyne.decorator import srpc +from spyne.model.primitive import Integer +from spyne.model.primitive import String +from spyne.model.complex import Array +from spyne.model.complex import ComplexModel +from spyne.protocol.http import HttpRpc +from spyne.protocol.html import HtmlMicroFormat +from spyne.service import Service +from spyne.server.wsgi import WsgiMethodContext +from spyne.server.wsgi import WsgiApplication +from spyne.util.test import show, call_wsgi_app_kwargs + + +class TestHtmlMicroFormat(unittest.TestCase): + def test_simple(self): + class SomeService(Service): + @srpc(String, _returns=String) + def some_call(s): + return s + + app = Application([SomeService], 'tns', + in_protocol=HttpRpc(hier_delim='_'), + out_protocol=HtmlMicroFormat(doctype=None)) + + server = WsgiApplication(app) + + initial_ctx = WsgiMethodContext(server, { + 'QUERY_STRING': 's=s', + 'PATH_INFO': '/some_call', + 'REQUEST_METHOD': 'GET', + 'SERVER_NAME': 'localhost', + }, 'some-content-type') + + ctx, = server.generate_contexts(initial_ctx) + assert ctx.in_error is None + + server.get_in_object(ctx) + server.get_out_object(ctx) + server.get_out_string(ctx) + + assert b''.join(ctx.out_string) == b'
' \ + b'
s
' + + def test_multiple_return(self): + class SomeService(Service): + @srpc(_returns=[Integer, String]) + def some_call(): + return 1, 's' + + app = Application([SomeService], 'tns', + in_protocol=HttpRpc(hier_delim='_'), + out_protocol=HtmlMicroFormat(doctype=None)) + server = WsgiApplication(app) + + initial_ctx = WsgiMethodContext(server, { + 'QUERY_STRING': '', + 'PATH_INFO': '/some_call', + 'REQUEST_METHOD': 'GET', + 'SERVER_NAME': 'localhost', + }, 'some-content-type') + + ctx, = server.generate_contexts(initial_ctx) + server.get_in_object(ctx) + server.get_out_object(ctx) + server.get_out_string(ctx) + + assert b''.join(ctx.out_string) == b'
' \ + b'
1
' \ + b'
s
' + + + def test_complex(self): + class CM(ComplexModel): + i = Integer + s = String + + class CCM(ComplexModel): + c = CM + i = Integer + s = String + + class SomeService(Service): + @srpc(CCM, _returns=CCM) + def some_call(ccm): + return CCM(c=ccm.c,i=ccm.i, s=ccm.s) + + app = Application([SomeService], 'tns', + in_protocol=HttpRpc(hier_delim='_'), + out_protocol=HtmlMicroFormat(doctype=None)) + server = WsgiApplication(app) + + initial_ctx = WsgiMethodContext(server, { + 'QUERY_STRING': 'ccm_c_s=abc&ccm_c_i=123&ccm_i=456&ccm_s=def', + 'PATH_INFO': '/some_call', + 'REQUEST_METHOD': 'GET', + 'SERVER_NAME': 'localhost', + }, 'some-content-type') + + ctx, = server.generate_contexts(initial_ctx) + server.get_in_object(ctx) + server.get_out_object(ctx) + server.get_out_string(ctx) + + # + # Here's what this is supposed to return: + # + #
+ #
+ #
456
+ #
+ #
123
+ #
abc
+ #
+ #
def
+ #
+ #
+ # + + elt = html.fromstring(b''.join(ctx.out_string)) + print(html.tostring(elt, pretty_print=True)) + + resp = elt.find_class('some_callResponse') + assert len(resp) == 1 + res = resp[0].find_class('some_callResult') + assert len(res) == 1 + + i = res[0].findall('div[@class="i"]') + assert len(i) == 1 + assert i[0].text == '456' + + c = res[0].findall('div[@class="c"]') + assert len(c) == 1 + + c_i = c[0].findall('div[@class="i"]') + assert len(c_i) == 1 + assert c_i[0].text == '123' + + c_s = c[0].findall('div[@class="s"]') + assert len(c_s) == 1 + assert c_s[0].text == 'abc' + + s = res[0].findall('div[@class="s"]') + assert len(s) == 1 + assert s[0].text == 'def' + + def test_multiple(self): + class SomeService(Service): + @srpc(String(max_occurs='unbounded'), _returns=String) + def some_call(s): + print(s) + return '\n'.join(s) + + app = Application([SomeService], 'tns', + in_protocol=HttpRpc(hier_delim='_'), + out_protocol=HtmlMicroFormat(doctype=None)) + server = WsgiApplication(app) + + initial_ctx = WsgiMethodContext(server, { + 'QUERY_STRING': 's=1&s=2', + 'PATH_INFO': '/some_call', + 'REQUEST_METHOD': 'GET', + 'SERVER_NAME': 'localhost', + }, 'some-content-type') + + ctx, = server.generate_contexts(initial_ctx) + server.get_in_object(ctx) + server.get_out_object(ctx) + server.get_out_string(ctx) + + assert b''.join(ctx.out_string) == (b'
' + b'
1\n2
') + + ctx, = server.generate_contexts(initial_ctx) + server.get_in_object(ctx) + server.get_out_object(ctx) + server.get_out_string(ctx) + + assert b''.join(ctx.out_string) == b'
' \ + b'
1\n2
' + + def test_before_first_root(self): + class CM(ComplexModel): + i = Integer + s = String + + class CCM(ComplexModel): + c = CM + i = Integer + s = String + + class SomeService(Service): + @srpc(CCM, _returns=Array(CCM)) + def some_call(ccm): + return [CCM(c=ccm.c,i=ccm.i, s=ccm.s)] * 2 + + cb_called = [False] + def _cb(ctx, cls, inst, parent, name, **kwargs): + assert not cb_called[0] + cb_called[0] = True + + app = Application([SomeService], 'tns', + in_protocol=HttpRpc(hier_delim='_'), + out_protocol=HtmlMicroFormat( + doctype=None, before_first_root=_cb)) + server = WsgiApplication(app) + + call_wsgi_app_kwargs(server, + ccm_c_s='abc', ccm_c_i=123, ccm_i=456, ccm_s='def') + + assert cb_called[0] + + def test_complex_array(self): + class CM(ComplexModel): + i = Integer + s = String + + class CCM(ComplexModel): + c = CM + i = Integer + s = String + + class SomeService(Service): + @srpc(CCM, _returns=Array(CCM)) + def some_call(ccm): + return [CCM(c=ccm.c,i=ccm.i, s=ccm.s)] * 2 + + app = Application([SomeService], 'tns', + in_protocol=HttpRpc(hier_delim='_'), + out_protocol=HtmlMicroFormat(doctype=None)) + server = WsgiApplication(app) + + out_string = call_wsgi_app_kwargs(server, + ccm_c_s='abc', ccm_c_i=123, ccm_i=456, ccm_s='def') + + # + # Here's what this is supposed to return: + # + #
+ #
+ #
456
+ #
+ #
123
+ #
abc
+ #
+ #
def
+ #
+ #
+ #
456
+ #
+ #
123
+ #
abc
+ #
+ #
def
+ #
+ #
+ # + + print(out_string) + elt = html.fromstring(out_string) + show(elt, "TestHtmlMicroFormat.test_complex_array") + + resp = elt.find_class('some_callResponse') + assert len(resp) == 1 + res = resp[0].find_class('some_callResult') + assert len(res) == 1 + + assert len(res[0].find_class("CCM")) == 2 + + # We don't need to test the rest as the test_complex test takes care of + # that + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/protocol/test_html_microformat.pyc b/pym/calculate/contrib/spyne/test/protocol/test_html_microformat.pyc new file mode 100644 index 0000000..f18ee0b Binary files /dev/null and b/pym/calculate/contrib/spyne/test/protocol/test_html_microformat.pyc differ diff --git a/pym/calculate/contrib/spyne/test/protocol/test_html_table.py b/pym/calculate/contrib/spyne/test/protocol/test_html_table.py new file mode 100644 index 0000000..d979356 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/protocol/test_html_table.py @@ -0,0 +1,508 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logging.basicConfig(level=logging.DEBUG) + +import unittest + +from lxml import etree, html + +from spyne.application import Application +from spyne.decorator import srpc +from spyne.model.primitive import Integer, Unicode +from spyne.model.primitive import String +from spyne.model.primitive import AnyUri +from spyne.model.complex import Array +from spyne.model.complex import ComplexModel +from spyne.protocol.http import HttpRpc +from spyne.protocol.html.table import HtmlColumnTable, HtmlRowTable +from spyne.service import Service +from spyne.server.wsgi import WsgiApplication +from spyne.util.test import show, call_wsgi_app_kwargs, call_wsgi_app + + +class CM(ComplexModel): + _type_info = [ + ('i', Integer), + ('s', String), + ] + + +class CCM(ComplexModel): + _type_info = [ + ('c', CM), + ('i', Integer), + ('s', String), + ] + + +class TestHtmlColumnTable(unittest.TestCase): + def test_complex_array(self): + class SomeService(Service): + @srpc(CCM, _returns=Array(CCM)) + def some_call(ccm): + return [ccm] * 5 + + app = Application([SomeService], 'tns', in_protocol=HttpRpc(), + out_protocol=HtmlColumnTable(field_type_name_attr=None)) + server = WsgiApplication(app) + + out_string = call_wsgi_app_kwargs(server, + ccm_i='456', + ccm_s='def', + ccm_c_i='123', + ccm_c_s='abc', + ) + + elt = etree.fromstring(out_string) + show(elt, 'TestHtmlColumnTable.test_complex_array') + + elt = html.fromstring(out_string) + + row, = elt[0] # thead + cell = row.findall('th[@class="i"]') + assert len(cell) == 1 + assert cell[0].text == 'i' + + cell = row.findall('th[@class="s"]') + assert len(cell) == 1 + assert cell[0].text == 's' + + for row in elt[1]: # tbody + cell = row.xpath('td[@class="i"]') + assert len(cell) == 1 + assert cell[0].text == '456' + + cell = row.xpath('td[@class="c"]//td[@class="i"]') + assert len(cell) == 1 + assert cell[0].text == '123' + + cell = row.xpath('td[@class="c"]//td[@class="s"]') + assert len(cell) == 1 + assert cell[0].text == 'abc' + + cell = row.xpath('td[@class="s"]') + assert len(cell) == 1 + assert cell[0].text == 'def' + + def test_string_array(self): + class SomeService(Service): + @srpc(String(max_occurs='unbounded'), _returns=Array(String)) + def some_call(s): + return s + + app = Application([SomeService], 'tns', in_protocol=HttpRpc(), + out_protocol=HtmlColumnTable( + field_name_attr=None, field_type_name_attr=None)) + server = WsgiApplication(app) + + out_string = call_wsgi_app(server, body_pairs=(('s', '1'), ('s', '2'))) + elt = etree.fromstring(out_string) + show(elt, "TestHtmlColumnTable.test_string_array") + assert out_string.decode('utf8') == \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '
some_callResponse
1
2
' + + def test_anyuri_string(self): + _link = "http://arskom.com.tr/" + + class C(ComplexModel): + c = AnyUri + + class SomeService(Service): + @srpc(_returns=Array(C)) + def some_call(): + return [C(c=_link)] + + app = Application([SomeService], 'tns', in_protocol=HttpRpc(), + out_protocol=HtmlColumnTable(field_type_name_attr=None)) + server = WsgiApplication(app) + + out_string = call_wsgi_app_kwargs(server) + + elt = html.fromstring(out_string) + show(elt, "TestHtmlColumnTable.test_anyuri_string") + assert elt.xpath('//td[@class="c"]')[0][0].tag == 'a' + assert elt.xpath('//td[@class="c"]')[0][0].attrib['href'] == _link + + def test_anyuri_uri_value(self): + _link = "http://arskom.com.tr/" + _text = "Arskom" + + class C(ComplexModel): + c = AnyUri + + class SomeService(Service): + @srpc(_returns=Array(C)) + def some_call(): + return [C(c=AnyUri.Value(_link, text=_text))] + + app = Application([SomeService], 'tns', in_protocol=HttpRpc(), + out_protocol=HtmlColumnTable(field_type_name_attr=None)) + server = WsgiApplication(app) + + out_string = call_wsgi_app_kwargs(server) + + elt = html.fromstring(out_string) + print(html.tostring(elt, pretty_print=True)) + assert elt.xpath('//td[@class="c"]')[0][0].tag == 'a' + assert elt.xpath('//td[@class="c"]')[0][0].text == _text + assert elt.xpath('//td[@class="c"]')[0][0].attrib['href'] == _link + + def test_row_subprot(self): + from lxml.html.builder import E + from spyne.protocol.html import HtmlBase + from spyne.util.six.moves.urllib.parse import urlencode + from spyne.protocol.html import HtmlMicroFormat + + class SearchProtocol(HtmlBase): + def to_parent(self, ctx, cls, inst, parent, name, **kwargs): + s = self.to_unicode(cls._type_info['query'], inst.query) + q = urlencode({"q": s}) + + parent.write(E.a("Search %s" % inst.query, + href="{}?{}".format(inst.uri, q))) + + def column_table_gen_header(self, ctx, cls, parent, name): + parent.write(E.thead(E.th("Search", + **{'class': 'search-link'}))) + + def column_table_before_row(self, ctx, cls, inst, parent, name,**_): + ctxstack = getattr(ctx.protocol[self], + 'array_subprot_ctxstack', []) + + tr_ctx = parent.element('tr') + tr_ctx.__enter__() + ctxstack.append(tr_ctx) + + td_ctx = parent.element('td', **{'class': "search-link"}) + td_ctx.__enter__() + ctxstack.append(td_ctx) + + ctx.protocol[self].array_subprot_ctxstack = ctxstack + + def column_table_after_row(self, ctx, cls, inst, parent, name, + **kwargs): + ctxstack = ctx.protocol[self].array_subprot_ctxstack + + for elt_ctx in reversed(ctxstack): + elt_ctx.__exit__(None, None, None) + + del ctxstack[:] + + class Search(ComplexModel): + query = Unicode + uri = Unicode + + SearchTable = Array( + Search.customize(prot=SearchProtocol()), + prot=HtmlColumnTable(field_type_name_attr=None), + ) + + class SomeService(Service): + @srpc(_returns=SearchTable) + def some_call(): + return [ + Search(query='Arskom', uri='https://www.google.com/search'), + Search(query='Spyne', uri='https://www.bing.com/search'), + ] + + app = Application([SomeService], 'tns', in_protocol=HttpRpc(), + out_protocol=HtmlMicroFormat()) + server = WsgiApplication(app) + + out_string = call_wsgi_app_kwargs(server) + + elt = html.fromstring(out_string) + print(html.tostring(elt, pretty_print=True)) + + assert elt.xpath('//td[@class="search-link"]/a/text()') == \ + ['Search Arskom', 'Search Spyne'] + + assert elt.xpath('//td[@class="search-link"]/a/@href') == [ + 'https://www.google.com/search?q=Arskom', + 'https://www.bing.com/search?q=Spyne', + ] + + assert elt.xpath('//th[@class="search-link"]/text()') == ["Search"] + + +class TestHtmlRowTable(unittest.TestCase): + def test_anyuri_string(self): + _link = "http://arskom.com.tr/" + + class C(ComplexModel): + c = AnyUri + + class SomeService(Service): + @srpc(_returns=C) + def some_call(): + return C(c=_link) + + app = Application([SomeService], 'tns', in_protocol=HttpRpc(), + out_protocol=HtmlRowTable(field_type_name_attr=None)) + server = WsgiApplication(app) + + out_string = call_wsgi_app_kwargs(server) + + elt = html.fromstring(out_string) + print(html.tostring(elt, pretty_print=True)) + assert elt.xpath('//td[@class="c"]')[0][0].tag == 'a' + assert elt.xpath('//td[@class="c"]')[0][0].attrib['href'] == _link + + def test_anyuri_uri_value(self): + _link = "http://arskom.com.tr/" + _text = "Arskom" + + class C(ComplexModel): + c = AnyUri + + class SomeService(Service): + @srpc(_returns=C) + def some_call(): + return C(c=AnyUri.Value(_link, text=_text)) + + app = Application([SomeService], 'tns', in_protocol=HttpRpc(), + out_protocol=HtmlRowTable(field_type_name_attr=None)) + server = WsgiApplication(app) + + out_string = call_wsgi_app_kwargs(server) + + elt = html.fromstring(out_string) + print(html.tostring(elt, pretty_print=True)) + assert elt.xpath('//td[@class="c"]')[0][0].tag == 'a' + assert elt.xpath('//td[@class="c"]')[0][0].text == _text + assert elt.xpath('//td[@class="c"]')[0][0].attrib['href'] == _link + + def test_complex(self): + class SomeService(Service): + @srpc(CCM, _returns=CCM) + def some_call(ccm): + return ccm + + app = Application([SomeService], 'tns', + in_protocol=HttpRpc(hier_delim="_"), + out_protocol=HtmlRowTable(field_type_name_attr=None)) + + server = WsgiApplication(app) + + out_string = call_wsgi_app_kwargs(server, 'some_call', + ccm_c_s='abc', ccm_c_i='123', ccm_i='456', ccm_s='def') + + elt = html.fromstring(out_string) + show(elt, "TestHtmlRowTable.test_complex") + + # Here's what this is supposed to return + """ + + + + + + + + + + + + + + + +
i456
c + + + + + + + + + + + +
i123
sabc
+
sdef
+ """ + + print(html.tostring(elt, pretty_print=True)) + resp = elt.find_class('CCM') + assert len(resp) == 1 + + assert elt.xpath('tbody/tr/th[@class="i"]/text()')[0] == 'i' + assert elt.xpath('tbody/tr/td[@class="i"]/text()')[0] == '456' + + assert elt.xpath('tbody/tr/td[@class="c"]//th[@class="i"]/text()')[0] == 'i' + assert elt.xpath('tbody/tr/td[@class="c"]//td[@class="i"]/text()')[0] == '123' + + assert elt.xpath('tbody/tr/td[@class="c"]//th[@class="s"]/text()')[0] == 's' + assert elt.xpath('tbody/tr/td[@class="c"]//td[@class="s"]/text()')[0] == 'abc' + + assert elt.xpath('tbody/tr/th[@class="s"]/text()')[0] == 's' + assert elt.xpath('tbody/tr/td[@class="s"]/text()')[0] == 'def' + + def test_string_array(self): + class SomeService(Service): + @srpc(String(max_occurs='unbounded'), _returns=Array(String)) + def some_call(s): + return s + + app = Application([SomeService], 'tns', in_protocol=HttpRpc(), + out_protocol=HtmlRowTable(field_name_attr=None, + field_type_name_attr=None)) + server = WsgiApplication(app) + + out_string = call_wsgi_app(server, body_pairs=(('s', '1'), ('s', '2')) ) + show(html.fromstring(out_string), 'TestHtmlRowTable.test_string_array') + assert out_string.decode('utf8') == \ + '
' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '
string' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '
1
2
' \ + '
' \ + '
' + + def test_string_array_no_header(self): + class SomeService(Service): + @srpc(String(max_occurs='unbounded'), _returns=Array(String)) + def some_call(s): + return s + + app = Application([SomeService], 'tns', in_protocol=HttpRpc(), + out_protocol=HtmlRowTable(header=False, + field_name_attr=None, field_type_name_attr=None)) + + server = WsgiApplication(app) + + out_string = call_wsgi_app(server, body_pairs=(('s', '1'), ('s', '2')) ) + #FIXME: Needs a proper test with xpaths and all. + show(html.fromstring(out_string), 'TestHtmlRowTable.test_string_array_no_header') + assert out_string.decode('utf8') == \ + '
' \ + '' \ + '' \ + '' \ + '' \ + '
' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '
1
2
' \ + '
' \ + '
' + + + def test_complex_array(self): + v = [ + CM(i=1, s='a'), + CM(i=2, s='b'), + CM(i=3, s='c'), + CM(i=4, s='d'), + ] + class SomeService(Service): + @srpc(_returns=Array(CM)) + def some_call(): + return v + + app = Application([SomeService], 'tns', in_protocol=HttpRpc(), + out_protocol=HtmlRowTable(field_type_name_attr=None)) + server = WsgiApplication(app) + + out_string = call_wsgi_app_kwargs(server) + show(html.fromstring(out_string), 'TestHtmlRowTable.test_complex_array') + #FIXME: Needs a proper test with xpaths and all. + assert out_string.decode('utf8') == \ + '
' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '
i1
sa
' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '
i2
sb
' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '
i3
sc
' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '
i4
sd
' \ + '
' + + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/protocol/test_html_table.pyc b/pym/calculate/contrib/spyne/test/protocol/test_html_table.pyc new file mode 100644 index 0000000..cd57918 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/protocol/test_html_table.pyc differ diff --git a/pym/calculate/contrib/spyne/test/protocol/test_http.py b/pym/calculate/contrib/spyne/test/protocol/test_http.py new file mode 100644 index 0000000..3f34572 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/protocol/test_http.py @@ -0,0 +1,867 @@ +#!/usr/bin/env python +# encoding: utf8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + + +import logging +logging.basicConfig(level=logging.DEBUG) + +import unittest + +from spyne.util.six import StringIO +from spyne.util.six.moves.http_cookies import SimpleCookie + +from datetime import datetime +from wsgiref.validate import validator as wsgiref_validator + +from spyne.server.wsgi import _parse_qs +from spyne.application import Application +from spyne.error import ValidationError +from spyne.const.http import HTTP_200 +from spyne.decorator import rpc +from spyne.decorator import srpc +from spyne.model import ByteArray, DateTime, Uuid, String, Integer, Integer8, \ + ComplexModel, Array +from spyne.protocol.http import HttpRpc, HttpPattern, _parse_cookie +from spyne.service import Service +from spyne.server.wsgi import WsgiApplication, WsgiMethodContext +from spyne.server.http import HttpTransportContext +from spyne.util.test import call_wsgi_app_kwargs + + +class TestString(unittest.TestCase): + def setUp(self): + class SomeService(Service): + @srpc(String, _returns=String) + def echo_string(s): + return s + + app = Application([SomeService], 'tns', + in_protocol=HttpRpc(validator='soft'), + out_protocol=HttpRpc(), + ) + + self.app = WsgiApplication(app) + + def test_without_content_type(self): + headers = None + ret = call_wsgi_app_kwargs(self.app, 'echo_string', headers, s="string") + assert ret == b'string' + + def test_without_encoding(self): + headers = {'CONTENT_TYPE':'text/plain'} + ret = call_wsgi_app_kwargs(self.app, 'echo_string', headers, s="string") + assert ret == b'string' + + def test_with_encoding(self): + headers = {'CONTENT_TYPE':'text/plain; charset=utf8'} + ret = call_wsgi_app_kwargs(self.app, 'echo_string', headers, s="string") + assert ret == b'string' + + +class TestHttpTransportContext(unittest.TestCase): + def test_gen_header(self): + val = HttpTransportContext.gen_header("text/plain", charset="utf8") + assert val == 'text/plain; charset="utf8"' + + +class TestSimpleDictDocument(unittest.TestCase): + def test_own_parse_qs_01(self): + assert dict(_parse_qs('')) == {} + def test_own_parse_qs_02(self): + assert dict(_parse_qs('p')) == {'p': [None]} + def test_own_parse_qs_03(self): + assert dict(_parse_qs('p=')) == {'p': ['']} + def test_own_parse_qs_04(self): + assert dict(_parse_qs('p=1')) == {'p': ['1']} + def test_own_parse_qs_05(self): + assert dict(_parse_qs('p=1&')) == {'p': ['1']} + def test_own_parse_qs_06(self): + assert dict(_parse_qs('p=1&q')) == {'p': ['1'], 'q': [None]} + def test_own_parse_qs_07(self): + assert dict(_parse_qs('p=1&q=')) == {'p': ['1'], 'q': ['']} + def test_own_parse_qs_08(self): + assert dict(_parse_qs('p=1&q=2')) == {'p': ['1'], 'q': ['2']} + def test_own_parse_qs_09(self): + assert dict(_parse_qs('p=1&q=2&p')) == {'p': ['1', None], 'q': ['2']} + def test_own_parse_qs_10(self): + assert dict(_parse_qs('p=1&q=2&p=')) == {'p': ['1', ''], 'q': ['2']} + def test_own_parse_qs_11(self): + assert dict(_parse_qs('p=1&q=2&p=3')) == {'p': ['1', '3'], 'q': ['2']} + +def _test(services, qs, validator='soft', strict_arrays=False): + app = Application(services, 'tns', + in_protocol=HttpRpc(validator=validator, strict_arrays=strict_arrays), + out_protocol=HttpRpc()) + server = WsgiApplication(app) + + initial_ctx = WsgiMethodContext(server, { + 'QUERY_STRING': qs, + 'PATH_INFO': '/some_call', + 'REQUEST_METHOD': 'GET', + 'SERVER_NAME': "localhost", + }, 'some-content-type') + + ctx, = server.generate_contexts(initial_ctx) + + server.get_in_object(ctx) + if ctx.in_error is not None: + raise ctx.in_error + + server.get_out_object(ctx) + if ctx.out_error is not None: + raise ctx.out_error + + server.get_out_string(ctx) + + return ctx + +class TestValidation(unittest.TestCase): + def test_validation_frequency(self): + class SomeService(Service): + @srpc(ByteArray(min_occurs=1), _returns=ByteArray) + def some_call(p): + pass + + try: + _test([SomeService], '', validator='soft') + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + def _test_validation_frequency_simple_bare(self): + class SomeService(Service): + @srpc(ByteArray(min_occurs=1), _body_style='bare', _returns=ByteArray) + def some_call(p): + pass + + try: + _test([SomeService], '', validator='soft') + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + def test_validation_frequency_complex_bare_parent(self): + class C(ComplexModel): + i=Integer(min_occurs=1) + s=String + + class SomeService(Service): + @srpc(C, _body_style='bare') + def some_call(p): + pass + + # must not complain about missing s + _test([SomeService], 'i=5', validator='soft') + + # must raise validation error for missing i + try: + _test([SomeService], 's=a', validator='soft') + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + # must raise validation error for missing i + try: + _test([SomeService], '', validator='soft') + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + def test_validation_frequency_parent(self): + class C(ComplexModel): + i=Integer(min_occurs=1) + s=String + + class SomeService(Service): + @srpc(C) + def some_call(p): + pass + + # must not complain about missing s + _test([SomeService], 'p.i=5', validator='soft') + try: + # must raise validation error for missing i + _test([SomeService], 'p.s=a', validator='soft') + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + # must not raise anything for missing p because C has min_occurs=0 + _test([SomeService], '', validator='soft') + + def test_validation_array(self): + class C(ComplexModel): + i=Integer(min_occurs=1) + s=String + + class SomeService(Service): + @srpc(Array(C)) + def some_call(p): + pass + + # must not complain about missing s + _test([SomeService], 'p[0].i=5', validator='soft') + try: + # must raise validation error for missing i + _test([SomeService], 'p[0].s=a', validator='soft') + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + # must not raise anything for missing p because C has min_occurs=0 + _test([SomeService], '', validator='soft') + + def test_validation_array_index_jump_error(self): + class C(ComplexModel): + i=Integer + + class SomeService(Service): + @srpc(Array(C), _returns=String) + def some_call(p): + return repr(p) + + try: + # must raise validation error for index jump from 0 to 2 even without + # any validation + _test([SomeService], 'p[0].i=42&p[2].i=42&', strict_arrays=True) + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + def test_validation_array_index_jump_tolerate(self): + class C(ComplexModel): + i=Integer + + class SomeService(Service): + @srpc(Array(C), _returns=String) + def some_call(p): + return repr(p) + + # must not raise validation error for index jump from 0 to 2 and ignore + # element with index 1 + ret = _test([SomeService], 'p[0].i=0&p[2].i=2&', strict_arrays=False) + assert ret.out_object[0] == '[C(i=0), C(i=2)]' + + # even if they arrive out-of-order. + ret = _test([SomeService], 'p[2].i=2&p[0].i=0&', strict_arrays=False) + assert ret.out_object[0] == '[C(i=0), C(i=2)]' + + def test_validation_nested_array(self): + class CC(ComplexModel): + d = DateTime + + class C(ComplexModel): + i = Integer(min_occurs=1) + cc = Array(CC) + + class SomeService(Service): + @srpc(Array(C)) + def some_call(p): + print(p) + + # must not complain about missing s + _test([SomeService], 'p[0].i=5', validator='soft') + try: + # must raise validation error for missing i + _test([SomeService], 'p[0].cc[0].d=2013-01-01', validator='soft') + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + # must not raise anything for missing p because C has min_occurs=0 + _test([SomeService], '', validator='soft') + + def test_validation_nullable(self): + class SomeService(Service): + @srpc(ByteArray(nullable=False), _returns=ByteArray) + def some_call(p): + pass + + try: + _test([SomeService], 'p', validator='soft') + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + def test_validation_string_pattern(self): + class SomeService(Service): + @srpc(Uuid) + def some_call(p): + pass + + try: + _test([SomeService], "p=duduk", validator='soft') + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + def test_validation_integer_range(self): + class SomeService(Service): + @srpc(Integer(ge=0, le=5)) + def some_call(p): + pass + + try: + _test([SomeService], 'p=10', validator='soft') + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + def test_validation_integer_type(self): + class SomeService(Service): + @srpc(Integer8) + def some_call(p): + pass + + try: + _test([SomeService], "p=-129", validator='soft') + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + def test_validation_integer_type_2(self): + class SomeService(Service): + @srpc(Integer8) + def some_call(p): + pass + + try: + _test([SomeService], "p=1.2", validator='soft') + except ValidationError: + pass + else: + raise Exception("must raise ValidationError") + + +class Test(unittest.TestCase): + def test_multiple_return(self): + class SomeService(Service): + @srpc(_returns=[Integer, String]) + def some_call(): + return 1, 's' + + try: + _test([SomeService], '') + except TypeError: + pass + else: + raise Exception("Must fail with: HttpRpc does not support complex " + "return types.") + + def test_primitive_only(self): + class SomeComplexModel(ComplexModel): + i = Integer + s = String + + class SomeService(Service): + @srpc(SomeComplexModel, _returns=SomeComplexModel) + def some_call(scm): + return SomeComplexModel(i=5, s='5x') + + try: + _test([SomeService], '') + except TypeError: + pass + else: + raise Exception("Must fail with: HttpRpc does not support complex " + "return types.") + + def test_complex(self): + class CM(ComplexModel): + _type_info = [ + ("i", Integer), + ("s", String), + ] + + class CCM(ComplexModel): + _type_info = [ + ("i", Integer), + ("c", CM), + ("s", String), + ] + + class SomeService(Service): + @srpc(CCM, _returns=String) + def some_call(ccm): + return repr(CCM(c=ccm.c, i=ccm.i, s=ccm.s)) + + ctx = _test([SomeService], '&ccm.i=1&ccm.s=s&ccm.c.i=3&ccm.c.s=cs') + + assert ctx.out_string[0] == b"CCM(i=1, c=CM(i=3, s='cs'), s='s')" + + def test_simple_array(self): + class SomeService(Service): + @srpc(String(max_occurs='unbounded'), _returns=String) + def some_call(s): + return '\n'.join(s) + + ctx = _test([SomeService], '&s=1&s=2') + assert b''.join(ctx.out_string) == b'1\n2' + + def test_complex_array(self): + class CM(ComplexModel): + _type_info = [ + ("i", Integer), + ("s", String), + ] + + class SomeService(Service): + @srpc(Array(CM), _returns=String) + def some_call(cs): + return '\n'.join([repr(c) for c in cs]) + + ctx = _test([SomeService], + 'cs[0].i=1&cs[0].s=x' + '&cs[1].i=2&cs[1].s=y' + '&cs[2].i=3&cs[2].s=z') + + assert b''.join(ctx.out_string) == \ + b"CM(i=1, s='x')\n" \ + b"CM(i=2, s='y')\n" \ + b"CM(i=3, s='z')" + + def test_complex_array_empty(self): + class CM(ComplexModel): + _type_info = [ + ("i", Integer), + ("s", String), + ] + + class SomeService(Service): + @srpc(Array(CM), _returns=String) + def some_call(cs): + return repr(cs) + + ctx = _test([SomeService], 'cs=empty') + + assert b''.join(ctx.out_string) == b'[]' + + def test_complex_object_empty(self): + class CM(ComplexModel): + _type_info = [ + ("i", Integer), + ("s", String), + ] + + class SomeService(Service): + @srpc(CM, _returns=String) + def some_call(c): + return repr(c) + + ctx = _test([SomeService], 'c=empty') + + assert b''.join(ctx.out_string) == b'CM()' + + def test_nested_flatten(self): + class CM(ComplexModel): + _type_info = [ + ("i", Integer), + ("s", String), + ] + + class CCM(ComplexModel): + _type_info = [ + ("i", Integer), + ("c", CM), + ("s", String), + ] + + class SomeService(Service): + @srpc(CCM, _returns=String) + def some_call(ccm): + return repr(ccm) + + ctx = _test([SomeService], '&ccm.i=1&ccm.s=s&ccm.c.i=3&ccm.c.s=cs') + + print(ctx.out_string) + assert b''.join(ctx.out_string) == b"CCM(i=1, c=CM(i=3, s='cs'), s='s')" + + def test_nested_flatten_with_multiple_values_1(self): + class CM(ComplexModel): + _type_info = [ + ("i", Integer), + ("s", String), + ] + + class CCM(ComplexModel): + _type_info = [ + ("i", Integer), + ("c", CM), + ("s", String), + ] + + class SomeService(Service): + @srpc(CCM.customize(max_occurs=2), _returns=String) + def some_call(ccm): + return repr(ccm) + + ctx = _test([SomeService], 'ccm[0].i=1&ccm[0].s=s' + '&ccm[0].c.i=1&ccm[0].c.s=a' + '&ccm[1].c.i=2&ccm[1].c.s=b') + + s = b''.join(ctx.out_string) + + assert s == b"[CCM(i=1, c=CM(i=1, s='a'), s='s'), CCM(c=CM(i=2, s='b'))]" + + def test_nested_flatten_with_multiple_values_2(self): + class CM(ComplexModel): + _type_info = [ + ("i", Integer), + ("s", String), + ] + + class CCM(ComplexModel): + _type_info = [ + ("i", Integer), + ("c", CM.customize(max_occurs=2)), + ("s", String), + ] + + class SomeService(Service): + @srpc(CCM, _returns=String) + def some_call(ccm): + return repr(ccm) + + ctx = _test([SomeService], 'ccm.i=1&ccm.s=s' + '&ccm.c[0].i=1&ccm.c[0].s=a' + '&ccm.c[1].i=2&ccm.c[1].s=b') + + s = b''.join(list(ctx.out_string)) + assert s == b"CCM(i=1, c=[CM(i=1, s='a'), CM(i=2, s='b')], s='s')" + + def test_nested_flatten_with_complex_array(self): + class CM(ComplexModel): + _type_info = [ + ("i", Integer), + ("s", String), + ] + + class CCM(ComplexModel): + _type_info = [ + ("i", Integer), + ("c", Array(CM)), + ("s", String), + ] + + class SomeService(Service): + @srpc(CCM, _returns=String) + def some_call(ccm): + return repr(ccm) + + ctx = _test([SomeService], 'ccm.i=1&ccm.s=s' + '&ccm.c[0].i=1&ccm.c[0].s=a' + '&ccm.c[1].i=2&ccm.c[1].s=b') + + s = b''.join(list(ctx.out_string)) + assert s == b"CCM(i=1, c=[CM(i=1, s='a'), CM(i=2, s='b')], s='s')" + + def test_nested_2_flatten_with_primitive_array(self): + class CCM(ComplexModel): + _type_info = [ + ("i", Integer), + ("c", Array(String)), + ("s", String), + ] + + class SomeService(Service): + @srpc(Array(CCM), _returns=String) + def some_call(ccm): + return repr(ccm) + + ctx = _test([SomeService], 'ccm[0].i=1&ccm[0].s=s' + '&ccm[0].c=a' + '&ccm[0].c=b') + s = b''.join(list(ctx.out_string)) + assert s == b"[CCM(i=1, c=['a', 'b'], s='s')]" + + def test_default(self): + class CM(ComplexModel): + _type_info = [ + ("i", Integer), + ("s", String(default='default')), + ] + + class SomeService(Service): + @srpc(CM, _returns=String) + def some_call(cm): + return repr(cm) + + # s is missing + ctx = _test([SomeService], 'cm.i=1') + s = b''.join(ctx.out_string) + assert s == b"CM(i=1, s='default')" + + # s is None + ctx = _test([SomeService], 'cm.i=1&cm.s') + s = b''.join(ctx.out_string) + assert s == b"CM(i=1)" + + # s is empty + ctx = _test([SomeService], 'cm.i=1&cm.s=') + s = b''.join(ctx.out_string) + assert s == b"CM(i=1, s='')" + + def test_nested_flatten_with_primitive_array(self): + class CCM(ComplexModel): + _type_info = [ + ("i", Integer), + ("c", Array(String)), + ("s", String), + ] + + class SomeService(Service): + @srpc(CCM, _returns=String) + def some_call(ccm): + return repr(ccm) + + ctx = _test([SomeService], 'ccm.i=1&ccm.s=s' + '&ccm.c=a' + '&ccm.c=b') + s = b''.join(list(ctx.out_string)) + assert s == b"CCM(i=1, c=['a', 'b'], s='s')" + + ctx = _test([SomeService], 'ccm.i=1' + '&ccm.s=s' + '&ccm.c[1]=b' + '&ccm.c[0]=a') + s = b''.join(list(ctx.out_string)) + assert s == b"CCM(i=1, c=['a', 'b'], s='s')" + + ctx = _test([SomeService], 'ccm.i=1' + '&ccm.s=s' + '&ccm.c[0]=a' + '&ccm.c[1]=b') + s = b''.join(list(ctx.out_string)) + assert s == b"CCM(i=1, c=['a', 'b'], s='s')" + + def test_http_headers(self): + d = datetime(year=2013, month=1, day=1) + string = ['hey', 'yo'] + + class ResponseHeader(ComplexModel): + _type_info = { + 'Set-Cookie': String(max_occurs='unbounded'), + 'Expires': DateTime + } + + class SomeService(Service): + __out_header__ = ResponseHeader + + @rpc(String) + def some_call(ctx, s): + assert s is not None + ctx.out_header = ResponseHeader(**{'Set-Cookie': string, + 'Expires': d}) + + def start_response(code, headers): + print(headers) + assert len([s for s in string if ('Set-Cookie', s) in headers]) == len(string) + assert dict(headers)['Expires'] == 'Tue, 01 Jan 2013 00:00:00 GMT' + + app = Application([SomeService], 'tns', + in_protocol=HttpRpc(), out_protocol=HttpRpc()) + wsgi_app = WsgiApplication(app) + + req_dict = { + 'SCRIPT_NAME': '', + 'QUERY_STRING': '&s=foo', + 'PATH_INFO': '/some_call', + 'REQUEST_METHOD': 'GET', + 'SERVER_NAME': 'localhost', + 'SERVER_PORT': "9999", + 'wsgi.url_scheme': 'http', + 'wsgi.version': (1,0), + 'wsgi.input': StringIO(), + 'wsgi.errors': StringIO(), + 'wsgi.multithread': False, + 'wsgi.multiprocess': False, + 'wsgi.run_once': True, + } + + ret = wsgi_app(req_dict, start_response) + print(list(ret)) + + wsgi_app = wsgiref_validator(wsgi_app) + + ret = wsgi_app(req_dict, start_response) + + assert list(ret) == [b''] + + +class TestHttpPatterns(unittest.TestCase): + def test_rules(self): + _int = 5 + _fragment = 'some_fragment' + + class SomeService(Service): + @srpc(Integer, _returns=Integer, _patterns=[ + HttpPattern('/%s/'% _fragment)]) + def some_call(some_int): + assert some_int == _int + + app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HttpRpc()) + server = WsgiApplication(app) + + environ = { + 'QUERY_STRING': '', + 'PATH_INFO': '/%s/%d' % (_fragment, _int), + 'SERVER_PATH':"/", + 'SERVER_NAME': "localhost", + 'wsgi.url_scheme': 'http', + 'SERVER_PORT': '9000', + 'REQUEST_METHOD': 'GET', + } + + initial_ctx = WsgiMethodContext(server, environ, 'some-content-type') + + ctx, = server.generate_contexts(initial_ctx) + + foo = [] + for i in server._http_patterns: + foo.append(i) + + assert len(foo) == 1 + print(foo) + assert ctx.descriptor is not None + + server.get_in_object(ctx) + assert ctx.in_error is None + + server.get_out_object(ctx) + assert ctx.out_error is None + + +class ParseCookieTest(unittest.TestCase): + def test_cookie_parse(self): + string = 'some_string' + class RequestHeader(ComplexModel): + some_field = String + + class SomeService(Service): + __in_header__ = RequestHeader + + @rpc(String) + def some_call(ctx, s): + assert ctx.in_header.some_field == string + + def start_response(code, headers): + assert code == HTTP_200 + + c = 'some_field=%s'% (string,) + + app = Application([SomeService], 'tns', + in_protocol=HttpRpc(parse_cookie=True), out_protocol=HttpRpc()) + + wsgi_app = WsgiApplication(app) + + req_dict = { + 'SCRIPT_NAME': '', + 'QUERY_STRING': '', + 'PATH_INFO': '/some_call', + 'REQUEST_METHOD': 'GET', + 'SERVER_NAME': 'localhost', + 'SERVER_PORT': "9999", + 'HTTP_COOKIE': c, + 'wsgi.url_scheme': 'http', + 'wsgi.version': (1,0), + 'wsgi.input': StringIO(), + 'wsgi.errors': StringIO(), + 'wsgi.multithread': False, + 'wsgi.multiprocess': False, + 'wsgi.run_once': True, + } + + ret = wsgi_app(req_dict, start_response) + print(ret) + + wsgi_app = wsgiref_validator(wsgi_app) + + ret = wsgi_app(req_dict, start_response) + print(ret) + + # These tests copied from Django: + # https://github.com/django/django/pull/6277/commits/da810901ada1cae9fc1f018f879f11a7fb467b28 + def test_python_cookies(self): + """ + Test cases copied from Python's Lib/test/test_http_cookies.py + """ + self.assertEqual(_parse_cookie('chips=ahoy; vienna=finger'), {'chips': 'ahoy', 'vienna': 'finger'}) + # Here _parse_cookie() differs from Python's cookie parsing in that it + # treats all semicolons as delimiters, even within quotes. + self.assertEqual( + _parse_cookie('keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'), + {'keebler': '"E=mc2', 'L': '\\"Loves\\"', 'fudge': '\\012', '': '"'} + ) + # Illegal cookies that have an '=' char in an unquoted value. + self.assertEqual(_parse_cookie('keebler=E=mc2'), {'keebler': 'E=mc2'}) + # Cookies with ':' character in their name. + self.assertEqual(_parse_cookie('key:term=value:term'), {'key:term': 'value:term'}) + # Cookies with '[' and ']'. + self.assertEqual(_parse_cookie('a=b; c=[; d=r; f=h'), {'a': 'b', 'c': '[', 'd': 'r', 'f': 'h'}) + + def test_cookie_edgecases(self): + # Cookies that RFC6265 allows. + self.assertEqual(_parse_cookie('a=b; Domain=example.com'), {'a': 'b', 'Domain': 'example.com'}) + # _parse_cookie() has historically kept only the last cookie with the + # same name. + self.assertEqual(_parse_cookie('a=b; h=i; a=c'), {'a': 'c', 'h': 'i'}) + + def test_invalid_cookies(self): + """ + Cookie strings that go against RFC6265 but browsers will send if set + via document.cookie. + """ + # Chunks without an equals sign appear as unnamed values per + # https://bugzilla.mozilla.org/show_bug.cgi?id=169091 + self.assertIn('django_language', + _parse_cookie('abc=def; unnamed; django_language=en').keys()) + # Even a double quote may be an unamed value. + self.assertEqual( + _parse_cookie('a=b; "; c=d'), {'a': 'b', '': '"', 'c': 'd'}) + # Spaces in names and values, and an equals sign in values. + self.assertEqual(_parse_cookie('a b c=d e = f; gh=i'), + {'a b c': 'd e = f', 'gh': 'i'}) + # More characters the spec forbids. + self.assertEqual(_parse_cookie('a b,c<>@:/[]?{}=d " =e,f g'), + {'a b,c<>@:/[]?{}': 'd " =e,f g'}) + # Unicode characters. The spec only allows ASCII. + self.assertEqual(_parse_cookie(u'saint=André Bessette'), + {u'saint': u'André Bessette'}) + # Browsers don't send extra whitespace or semicolons in Cookie headers, + # but _parse_cookie() should parse whitespace the same way + # document.cookie parses whitespace. + self.assertEqual(_parse_cookie(' = b ; ; = ; c = ; '), + {'': 'b', 'c': ''}) + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/protocol/test_http.pyc b/pym/calculate/contrib/spyne/test/protocol/test_http.pyc new file mode 100644 index 0000000..7c26f04 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/protocol/test_http.pyc differ diff --git a/pym/calculate/contrib/spyne/test/protocol/test_json.py b/pym/calculate/contrib/spyne/test/protocol/test_json.py new file mode 100644 index 0000000..ac162fb --- /dev/null +++ b/pym/calculate/contrib/spyne/test/protocol/test_json.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import unittest +try: + import simplejson as json +except ImportError: + import json + + +from spyne import MethodContext +from spyne import Application +from spyne import rpc,srpc +from spyne import Service +from spyne.model import Integer, Unicode, ComplexModel +from spyne.protocol.json import JsonP +from spyne.protocol.json import JsonDocument +from spyne.protocol.json import JsonEncoder +from spyne.protocol.json import _SpyneJsonRpc1 +from spyne.server import ServerBase +from spyne.server.null import NullServer + +from spyne.test.protocol._test_dictdoc import TDictDocumentTest +from spyne.test.protocol._test_dictdoc import TDry + + +class TestDictDocument(TDictDocumentTest(json, JsonDocument, + dumps_kwargs=dict(cls=JsonEncoder))): + def dumps(self, o): + return super(TestDictDocument, self).dumps(o).encode('utf8') + + def loads(self, o): + return super(TestDictDocument, self).loads(o.decode('utf8')) + +_dry_sjrpc1 = TDry(json, _SpyneJsonRpc1) + +class TestSpyneJsonRpc1(unittest.TestCase): + def test_call(self): + class SomeService(Service): + @srpc(Integer, _returns=Integer) + def yay(i): + print(i) + return i + + ctx = _dry_sjrpc1([SomeService], + {"ver": 1, "body": {"yay": {"i":5}}}, True) + + print(ctx) + print(list(ctx.out_string)) + assert ctx.out_document == {"ver": 1, "body": 5} + + def test_call_with_header(self): + class SomeHeader(ComplexModel): + i = Integer + + class SomeService(Service): + __in_header__ = SomeHeader + @rpc(Integer, _returns=Integer) + def yay(ctx, i): + print(ctx.in_header) + return ctx.in_header.i + + ctx = _dry_sjrpc1([SomeService], + {"ver": 1, "body": {"yay": None}, "head": {"i":5}}, True) + + print(ctx) + print(list(ctx.out_string)) + assert ctx.out_document == {"ver": 1, "body": 5} + + def test_error(self): + class SomeHeader(ComplexModel): + i = Integer + + class SomeService(Service): + __in_header__ = SomeHeader + @rpc(Integer, Integer, _returns=Integer) + def div(ctx, dividend, divisor): + return dividend / divisor + + ctx = _dry_sjrpc1([SomeService], + {"ver": 1, "body": {"div": [4,0]}}, True) + + print(ctx) + print(list(ctx.out_string)) + assert ctx.out_document == {"ver": 1, "fault": { + 'faultcode': 'Server', 'faultstring': 'Internal Error'}} + + +class TestJsonDocument(unittest.TestCase): + def test_out_kwargs(self): + class SomeService(Service): + @srpc() + def yay(): + pass + + app = Application([SomeService], 'tns', + in_protocol=JsonDocument(), + out_protocol=JsonDocument()) + + assert 'cls' in app.out_protocol.kwargs + assert not ('cls' in app.in_protocol.kwargs) + + app = Application([SomeService], 'tns', + in_protocol=JsonDocument(), + out_protocol=JsonDocument(cls='hey')) + + assert app.out_protocol.kwargs['cls'] == 'hey' + assert not ('cls' in app.in_protocol.kwargs) + + def test_invalid_input(self): + class SomeService(Service): + pass + + app = Application([SomeService], 'tns', + in_protocol=JsonDocument(), + out_protocol=JsonDocument()) + + server = ServerBase(app) + + initial_ctx = MethodContext(server, MethodContext.SERVER) + initial_ctx.in_string = [b'{'] + ctx, = server.generate_contexts(initial_ctx, in_string_charset='utf8') + assert ctx.in_error.faultcode == 'Client.JsonDecodeError' + + +class TestJsonP(unittest.TestCase): + def test_callback_name(self): + callback_name = 'some_callback' + + class SomeComplexModel(ComplexModel): + i = Integer + s = Unicode + + v1 = 42 + v2 = SomeComplexModel(i=42, s='foo') + + class SomeService(Service): + @srpc(_returns=Integer) + def yay(): + return v1 + + @srpc(_returns=SomeComplexModel) + def complex(): + return v2 + + app = Application([SomeService], 'tns', + in_protocol=JsonDocument(), + out_protocol=JsonP(callback_name)) + + server = NullServer(app, ostr=True) + + ret = server.service.yay() + ret = list(ret) + print(b''.join(ret)) + assert b''.join(ret) == b''.join((callback_name.encode('utf8'), b'(', + str(v1).encode('utf8'), b');')) + + ret = server.service.complex() + ret = list(ret) + print(b''.join(ret)) + assert b''.join(ret) == b''.join((callback_name.encode('utf8'), b'(', + json.dumps({"i": 42, "s": "foo"}).encode('utf-8') , b');')) + + + def test_wrapped_array_in_wrapped_response(self): + from spyne.model.complex import ComplexModel, Array + from spyne.model.primitive import Unicode + + class Permission(ComplexModel): + _type_info = [ + ('application', Unicode), + ('feature', Unicode), + ] + + class SomeService(Service): + @srpc(_returns=Array(Permission)) + def yay(): + return [ + Permission(application='app', feature='f1'), + Permission(application='app', feature='f2') + ] + + app = Application([SomeService], 'tns', + in_protocol=JsonDocument(), + out_protocol=JsonDocument(ignore_wrappers=False)) + + server = NullServer(app, ostr=True) + retstr = b''.join(server.service.yay()).decode('utf-8') + print(retstr) + assert retstr == '{"yayResponse": {"yayResult": [' \ + '{"Permission": {"application": "app", "feature": "f1"}}, ' \ + '{"Permission": {"application": "app", "feature": "f2"}}]}}' + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/protocol/test_json.pyc b/pym/calculate/contrib/spyne/test/protocol/test_json.pyc new file mode 100644 index 0000000..8e23b46 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/protocol/test_json.pyc differ diff --git a/pym/calculate/contrib/spyne/test/protocol/test_msgpack.py b/pym/calculate/contrib/spyne/test/protocol/test_msgpack.py new file mode 100644 index 0000000..aa98892 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/protocol/test_msgpack.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging + +from spyne.util import six + +logging.basicConfig(level=logging.DEBUG) + +import unittest + +import msgpack + +from spyne import MethodContext +from spyne.application import Application +from spyne.decorator import rpc +from spyne.decorator import srpc +from spyne.service import Service +from spyne.model.complex import Array +from spyne.model.primitive import String +from spyne.model.complex import ComplexModel +from spyne.model.primitive import Unicode +from spyne.protocol.msgpack import MessagePackDocument +from spyne.protocol.msgpack import MessagePackRpc +from spyne.util.six import BytesIO +from spyne.server import ServerBase +from spyne.server.wsgi import WsgiApplication +from spyne.test.protocol._test_dictdoc import TDictDocumentTest + +from spyne.test.test_service import start_response + + +def convert_dict(d): + if isinstance(d, six.text_type): + return d.encode('utf8') + + if not isinstance(d, dict): + return d + + r = {} + + for k, v in d.items(): + r[k.encode('utf8')] = convert_dict(v) + + return r + + +# apply spyne defaults to test unpacker +TestMessagePackDocument = TDictDocumentTest(msgpack, MessagePackDocument, + loads_kwargs=dict(use_list=False), convert_dict=convert_dict) + + +class TestMessagePackRpc(unittest.TestCase): + def test_invalid_input(self): + class SomeService(Service): + @srpc() + def yay(): + pass + + app = Application([SomeService], 'tns', + in_protocol=MessagePackDocument(), + out_protocol=MessagePackDocument()) + + server = ServerBase(app) + + initial_ctx = MethodContext(server, MethodContext.SERVER) + initial_ctx.in_string = [b'\xdf'] # Invalid input + ctx, = server.generate_contexts(initial_ctx) + assert ctx.in_error.faultcode == 'Client.MessagePackDecodeError' + + def test_rpc(self): + data = {"a":"b", "c": "d"} + + class KeyValuePair(ComplexModel): + key = Unicode + value = Unicode + + class SomeService(Service): + @rpc(String(max_occurs='unbounded'), + _returns=Array(KeyValuePair), + _in_variable_names={ + 'keys': 'key' + } + ) + def get_values(ctx, keys): + for k in keys: + yield KeyValuePair(key=k, value=data[k]) + + application = Application([SomeService], + in_protocol=MessagePackRpc(), + out_protocol=MessagePackRpc(ignore_wrappers=False), + name='Service', tns='tns') + server = WsgiApplication(application) + + input_string = msgpack.packb([0, 0, "get_values", [["a", "c"]]]) + input_stream = BytesIO(input_string) + + ret = server({ + 'CONTENT_LENGTH': str(len(input_string)), + 'CONTENT_TYPE': 'application/x-msgpack', + 'HTTP_CONNECTION': 'close', + 'HTTP_CONTENT_LENGTH': str(len(input_string)), + 'HTTP_CONTENT_TYPE': 'application/x-msgpack', + 'PATH_INFO': '/', + 'QUERY_STRING': '', + 'SERVER_NAME': 'localhost', + 'SERVER_PORT': '7000', + 'REQUEST_METHOD': 'POST', + 'wsgi.url_scheme': 'http', + 'wsgi.input': input_stream, + }, start_response) + + ret = b''.join(ret) + print(repr(ret)) + ret = msgpack.unpackb(ret) + print(repr(ret)) + + s = [1, 0, None, {b'get_valuesResponse': { + b'get_valuesResult': [ + {b"KeyValuePair": {b'key': b'a', b'value': b'b'}}, + {b"KeyValuePair": {b'key': b'c', b'value': b'd'}}, + ] + }} + ] + print(s) + assert ret == s + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/protocol/test_msgpack.pyc b/pym/calculate/contrib/spyne/test/protocol/test_msgpack.pyc new file mode 100644 index 0000000..42e2062 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/protocol/test_msgpack.pyc differ diff --git a/pym/calculate/contrib/spyne/test/protocol/test_soap11.py b/pym/calculate/contrib/spyne/test/protocol/test_soap11.py new file mode 100644 index 0000000..01d8346 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/protocol/test_soap11.py @@ -0,0 +1,500 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +# +# Most of the service tests are performed through the interop tests. +# + +import datetime +import unittest + +from lxml import etree +import pytz + +from spyne import MethodContext +from spyne.application import Application +from spyne.decorator import rpc +from spyne.interface.wsdl import Wsdl11 +from spyne.model.complex import Array +from spyne.model.complex import ComplexModel +from spyne.model.primitive import Unicode +from spyne.model.primitive import DateTime, Date +from spyne.model.primitive import Float +from spyne.model.primitive import Integer +from spyne.model.primitive import String +from spyne.model.fault import Fault +from spyne.protocol.soap import Soap11 +from spyne.service import Service +from spyne.server import ServerBase + +from spyne.protocol.soap import _from_soap +from spyne.protocol.soap import _parse_xml_string + +Application.transport = 'test' + + +def start_response(code, headers): + print(code, headers) + + +class Address(ComplexModel): + __namespace__ = "TestService" + + street = String + city = String + zip = Integer + since = DateTime + laditude = Float + longitude = Float + +class Person(ComplexModel): + __namespace__ = "TestService" + + name = String + birthdate = DateTime + age = Integer + addresses = Array(Address) + titles = Array(String) + +class Request(ComplexModel): + __namespace__ = "TestService" + + param1 = String + param2 = Integer + +class Response(ComplexModel): + __namespace__ = "TestService" + + param1 = Float + +class TypeNS1(ComplexModel): + __namespace__ = "TestService.NS1" + + s = String + i = Integer + +class TypeNS2(ComplexModel): + __namespace__ = "TestService.NS2" + + d = DateTime + f = Float + +class MultipleNamespaceService(Service): + @rpc(TypeNS1, TypeNS2) + def a(ctx, t1, t2): + return "OK" + +class TestService(Service): + @rpc(String, _returns=String) + def aa(ctx, s): + return s + + @rpc(String, Integer, _returns=DateTime) + def a(ctx, s, i): + return datetime.datetime.now() + + @rpc(Person, String, Address, _returns=Address) + def b(ctx, p, s, a): + return Address() + + @rpc(Person) + def d(ctx, Person): + pass + + @rpc(Person) + def e(ctx, Person): + pass + + @rpc(String, String, String, _returns=String, + _in_variable_names={'_from': 'from', '_self': 'self', + '_import': 'import'}, + _out_variable_name="return") + def f(ctx, _from, _self, _import): + return '1234' + + +class MultipleReturnService(Service): + @rpc(String, _returns=(String, String, String)) + def multi(ctx, s): + return s, 'a', 'b' + + +class TestSingle(unittest.TestCase): + def setUp(self): + self.app = Application([TestService], 'tns', + in_protocol=Soap11(), out_protocol=Soap11()) + self.app.transport = 'null.spyne' + self.srv = TestService() + + wsdl = Wsdl11(self.app.interface) + wsdl.build_interface_document('URL') + self.wsdl_str = wsdl.get_interface_document() + self.wsdl_doc = etree.fromstring(self.wsdl_str) + + def test_portypes(self): + porttype = self.wsdl_doc.find('{http://schemas.xmlsoap.org/wsdl/}portType') + self.assertEqual( + len(self.srv.public_methods), len(porttype.getchildren())) + + def test_override_param_names(self): + for n in [b'self', b'import', b'return', b'from']: + assert n in self.wsdl_str, '"%s" not in self.wsdl_str' + +class TestMultiple(unittest.TestCase): + def setUp(self): + self.app = Application([MultipleReturnService], 'tns', in_protocol=Soap11(), out_protocol=Soap11()) + self.app.transport = 'none' + self.wsdl = Wsdl11(self.app.interface) + self.wsdl.build_interface_document('URL') + + def test_multiple_return(self): + message_class = list(MultipleReturnService.public_methods.values())[0].out_message + message = message_class() + + self.assertEqual(len(message._type_info), 3) + + sent_xml = etree.Element('test') + self.app.out_protocol.to_parent(None, message_class, ('a', 'b', 'c'), + sent_xml, self.app.tns) + sent_xml = sent_xml[0] + + print((etree.tostring(sent_xml, pretty_print=True))) + response_data = self.app.out_protocol.from_element(None, message_class, sent_xml) + + self.assertEqual(len(response_data), 3) + self.assertEqual(response_data[0], 'a') + self.assertEqual(response_data[1], 'b') + self.assertEqual(response_data[2], 'c') + + +class TestSoap11(unittest.TestCase): + def test_simple_message(self): + m = ComplexModel.produce( + namespace=None, + type_name='myMessage', + members={'s': String, 'i': Integer} + ) + m.resolve_namespace(m, 'test') + + m_inst = m(s="a", i=43) + + e = etree.Element('test') + Soap11().to_parent(None, m, m_inst, e, m.get_namespace()) + e=e[0] + + self.assertEqual(e.tag, '{%s}myMessage' % m.get_namespace()) + + self.assertEqual(e.find('{%s}s' % m.get_namespace()).text, 'a') + self.assertEqual(e.find('{%s}i' % m.get_namespace()).text, '43') + + values = Soap11().from_element(None, m, e) + + self.assertEqual('a', values.s) + self.assertEqual(43, values.i) + + def test_href(self): + # the template. Start at pos 0, some servers complain if + # xml tag is not in the first line. + envelope_string = [ +b''' + + + + + + + + + + somemachine + someuser + + + machine2 + user2 + + +'''] + + root, xmlids = _parse_xml_string(envelope_string, + etree.XMLParser(), 'utf8') + header, payload = _from_soap(root, xmlids) + + # quick and dirty test href reconstruction + self.assertEqual(len(payload[0]), 2) + + def test_namespaces(self): + m = ComplexModel.produce( + namespace="some_namespace", + type_name='myMessage', + members={'s': String, 'i': Integer}, + ) + + mi = m() + mi.s = 'a' + + e = etree.Element('test') + Soap11().to_parent(None, m, mi, e, m.get_namespace()) + e=e[0] + + self.assertEqual(e.tag, '{some_namespace}myMessage') + + def test_class_to_parent(self): + m = ComplexModel.produce( + namespace=None, + type_name='myMessage', + members={'p': Person} + ) + + m.resolve_namespace(m, "punk") + + m_inst = m() + m_inst.p = Person() + m_inst.p.name = 'steve-o' + m_inst.p.age = 2 + m_inst.p.addresses = [] + + element=etree.Element('test') + Soap11().to_parent(None, m, m_inst, element, m.get_namespace()) + element=element[0] + + self.assertEqual(element.tag, '{%s}myMessage' % m.get_namespace()) + self.assertEqual(element[0].find('{%s}name' % Person.get_namespace()).text, + 'steve-o') + self.assertEqual(element[0].find('{%s}age' % Person.get_namespace()).text, '2') + self.assertEqual( + len(element[0].find('{%s}addresses' % Person.get_namespace())), 0) + + p1 = Soap11().from_element(None, m, element)[0] + + self.assertEqual(p1.name, m_inst.p.name) + self.assertEqual(p1.age, m_inst.p.age) + self.assertEqual(p1.addresses, []) + + def test_datetime_fixed_format(self): + # Soap should ignore formats + n = datetime.datetime.now(pytz.utc).replace(microsecond=0) + format = "%Y %m %d %H %M %S" + + element = etree.Element('test') + Soap11().to_parent(None, DateTime(dt_format=format), n, + element, 'some_namespace') + assert element[0].text == n.isoformat() + + dt = Soap11().from_element(None, DateTime(dt_format=format), element[0]) + assert n == dt + + def test_date_with_tzoffset(self): + for iso_d in ('2013-04-05', '2013-04-05+02:00', '2013-04-05-02:00', '2013-04-05Z'): + d = Soap11().from_unicode(Date, iso_d) + assert isinstance(d, datetime.date) == True + assert d.year == 2013 + assert d.month == 4 + assert d.day == 5 + + def test_to_parent_nested(self): + m = ComplexModel.produce( + namespace=None, + type_name='myMessage', + members={'p':Person} + ) + + m.resolve_namespace(m, "m") + + p = Person() + p.name = 'steve-o' + p.age = 2 + p.addresses = [] + + for i in range(0, 100): + a = Address() + a.street = '123 happy way' + a.zip = i + a.laditude = '45.22' + a.longitude = '444.234' + p.addresses.append(a) + + m_inst = m(p=p) + + element=etree.Element('test') + Soap11().to_parent(None, m, m_inst, element, m.get_namespace()) + element=element[0] + + self.assertEqual('{%s}myMessage' % m.get_namespace(), element.tag) + + addresses = element[0].find('{%s}addresses' % Person.get_namespace()) + self.assertEqual(100, len(addresses)) + self.assertEqual('0', addresses[0].find('{%s}zip' % + Address.get_namespace()).text) + + def test_fault_deserialization_missing_fault_actor(self): + element = etree.fromstring(b""" + + + + soap:Client + Some String + + + Some_Policy + + + + + """) + + ret = Soap11().from_element(None, Fault, element[0][0]) + assert ret.faultcode == "soap:Client" + + +# TestSoapHeader supporting classes. +# SOAP Header Elements defined by WS-Addressing. + +NAMESPACE_ADDRESSING = 'http://www.w3.org/2005/08/addressing' + +class Action (Unicode): + __type_name__ = "Action" + __namespace__ = NAMESPACE_ADDRESSING + +class MessageID (Unicode): + __type_name__ = "MessageID" + __namespace__ = NAMESPACE_ADDRESSING + +class RelatesTo (Unicode): + __type_name__ = "RelatesTo" + __namespace__ = NAMESPACE_ADDRESSING + +class SOAPServiceWithHeader(Service): + @rpc(Unicode, + _in_header=(Action, + MessageID, + RelatesTo), + _out_variable_name= 'status', + _returns=Unicode + ) + def someRequest(ctx, response): + print (response) + return 'OK' + +class TestSoapHeader(unittest.TestCase): + + def setUp(self): + self.app = Application([SOAPServiceWithHeader], + 'tns', + in_protocol=Soap11(), + out_protocol=Soap11()) + + def test_soap_input_header(self): + server = ServerBase(self.app) + initial_ctx = MethodContext(server, MethodContext.SERVER) + initial_ctx.in_string = [ + b''' + + /SomeAction + SomeMessageID + SomeRelatesToID + + + + OK + + + ''' + ] + + ctx, = server.generate_contexts(initial_ctx, in_string_charset='utf8') + server.get_in_object(ctx) + + self.assertEqual(ctx.in_header[0], '/SomeAction') + self.assertEqual(ctx.in_header[1], 'SomeMessageID') + self.assertEqual(ctx.in_header[2], 'SomeRelatesToID') + + def test_soap_input_header_order(self): + """ + Tests supports for input headers whose elements are provided in + different order than that defined in rpc declaration _in_header parameter. + """ + server = ServerBase(self.app) + initial_ctx = MethodContext(server, MethodContext.SERVER) + initial_ctx.in_string = [ + b''' + + SomeMessageID + SomeRelatesToID + /SomeAction + + + + OK + + + ''' + ] + + ctx, = server.generate_contexts(initial_ctx, in_string_charset='utf8') + server.get_in_object(ctx) + + self.assertEqual(ctx.in_header[0], '/SomeAction') + self.assertEqual(ctx.in_header[1], 'SomeMessageID') + self.assertEqual(ctx.in_header[2], 'SomeRelatesToID') + + + def test_soap_input_header_order_and_missing(self): + """ + Test that header ordering logic also works when an input header + element is missing. Confirm that it returns None for the missing + parameter. + """ + server = ServerBase(self.app) + initial_ctx = MethodContext(server, MethodContext.SERVER) + initial_ctx.in_string = [ + b''' + + SomeMessageID + /SomeAction + + + + OK + + + ''' + ] + + ctx, = server.generate_contexts(initial_ctx, in_string_charset='utf8') + server.get_in_object(ctx) + + self.assertEqual(ctx.in_header[0], '/SomeAction') + self.assertEqual(ctx.in_header[1], 'SomeMessageID') + self.assertEqual(ctx.in_header[2], None) + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/protocol/test_soap11.pyc b/pym/calculate/contrib/spyne/test/protocol/test_soap11.pyc new file mode 100644 index 0000000..5ea83f1 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/protocol/test_soap11.pyc differ diff --git a/pym/calculate/contrib/spyne/test/protocol/test_soap12.py b/pym/calculate/contrib/spyne/test/protocol/test_soap12.py new file mode 100644 index 0000000..30e3b18 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/protocol/test_soap12.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python + +from __future__ import unicode_literals + +import unittest + +from lxml import etree +from lxml.doctestcompare import LXMLOutputChecker, PARSE_XML + +from spyne import Fault, Unicode, ByteArray +from spyne.application import Application +from spyne.const import xml as ns +from spyne.const.xml import NS_SOAP11_ENV +from spyne.decorator import srpc, rpc +from spyne.interface import Wsdl11 +from spyne.model.complex import ComplexModel +from spyne.model.primitive import Integer, String +from spyne.protocol.soap.mime import _join_attachment +from spyne.protocol.soap.soap12 import Soap12 +from spyne.protocol.xml import XmlDocument +from spyne.server.wsgi import WsgiApplication +from spyne.service import Service +from spyne.test.protocol.test_soap11 import TestService, TestSingle, \ + TestMultiple, MultipleReturnService +from spyne.util.six import BytesIO + + +def start_response(code, headers): + print(code, headers) + + +MTOM_REQUEST = b""" +--uuid:2e53e161-b47f-444a-b594-eb6b72e76997 +Content-Type: application/xop+xml; charset=UTF-8; + type="application/soap+xml"; action="sendDocument"; +Content-Transfer-Encoding: binary +Content-ID: + + + + + EA055406-5881-4F02-A3DC-9A5A7510D018.dat + + + + 26981FCD51C95FA47780400B7A45132F + + + + +--uuid:2e53e161-b47f-444a-b594-eb6b72e76997 +Content-Type: application/octet-stream +Content-Transfer-Encoding: binary +Content-ID: <04dfbca1-54b8-4631-a556-4addea6716ed-223384@cxf.apache.org> + +sample data +--uuid:2e53e161-b47f-444a-b594-eb6b72e76997-- +""" + + +# Service Classes +class DownloadPartFileResult(ComplexModel): + ErrorCode = Integer + ErrorMessage = String + Data = String + + +class TestSingleSoap12(TestSingle): + def setUp(self): + self.app = Application([TestService], 'tns', in_protocol=Soap12(), out_protocol=Soap12()) + self.app.transport = 'null.spyne' + self.srv = TestService() + + wsdl = Wsdl11(self.app.interface) + wsdl.build_interface_document('URL') + self.wsdl_str = wsdl.get_interface_document() + self.wsdl_doc = etree.fromstring(self.wsdl_str) + + +class TestMultipleSoap12(TestMultiple): + def setUp(self): + self.app = Application([MultipleReturnService], 'tns', in_protocol=Soap12(), out_protocol=Soap12()) + self.app.transport = 'none' + self.wsdl = Wsdl11(self.app.interface) + self.wsdl.build_interface_document('URL') + + +class TestSoap12(unittest.TestCase): + + def test_soap12(self): + element = etree.fromstring(b""" + + + + + env:Sender + + st:SomeDomainProblem + + + + + Some_Policy + + + + + """) + + so = Soap12() + ret = so.from_element(None, Fault, element[0][0]) + assert ret.faultcode == "env:Sender.st:SomeDomainProblem" + + def test_fault_generation(self): + class SoapException(Service): + @srpc() + def soap_exception(): + raise Fault( + "Client.Plausible.issue", "A plausible fault", 'http://faultactor.example.com', + detail={'some':'extra info'}) + app = Application([SoapException], 'tns', in_protocol=Soap12(), out_protocol=Soap12()) + + req = b""" + + + + + + """ + + server = WsgiApplication(app) + response = etree.fromstring(b''.join(server({ + 'QUERY_STRING': '', + 'PATH_INFO': '/call', + 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'text/xml; charset=utf8', + 'wsgi.input': BytesIO(req) + }, start_response, "http://null"))) + + response_str = etree.tostring(response, pretty_print=True) + print(response_str) + + expected = b""" + + + + + A plausible fault + + http://faultactor.example.com + + soap12env:Sender + + Plausible + + issue + + + + + extra info + + + + """ + if not LXMLOutputChecker().check_output(expected, response_str, PARSE_XML): + raise Exception("Got: %s but expected: %s" % (response_str, expected)) + + def test_gen_fault_codes(self): + fault_string = "Server.Plausible.error" + value, faultstrings = Soap12().gen_fault_codes(faultstring=fault_string) + self.assertEqual(value, "%s:Receiver" %(Soap12.soap_env)) + self.assertEqual(faultstrings[0], "Plausible") + self.assertEqual(faultstrings[1], "error") + + fault_string = "UnknownFaultCode.Plausible.error" + with self.assertRaises(TypeError): + value, faultstrings = Soap12().gen_fault_codes(faultstring=fault_string) + + def test_mtom(self): + FILE_NAME = 'EA055406-5881-4F02-A3DC-9A5A7510D018.dat' + TNS = 'http://gib.gov.tr/vedop3/eFatura' + class SomeService(Service): + @rpc(Unicode(sub_name="fileName"), ByteArray(sub_name='binaryData'), + ByteArray(sub_name="hash"), _returns=Unicode) + def documentRequest(ctx, file_name, file_data, data_hash): + assert file_name == FILE_NAME + assert file_data == (b'sample data',) + + return file_name + + app = Application([SomeService], tns=TNS, + in_protocol=Soap12(), out_protocol=Soap12()) + + server = WsgiApplication(app) + response = etree.fromstring(b''.join(server({ + 'QUERY_STRING': '', + 'PATH_INFO': '/call', + 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'Content-Type: multipart/related; ' + 'type="application/xop+xml"; ' + 'boundary="uuid:2e53e161-b47f-444a-b594-eb6b72e76997"; ' + 'start=""; ' + 'start-info="application/soap+xml"; action="sendDocument"', + 'wsgi.input': BytesIO(MTOM_REQUEST.replace(b"\n", b"\r\n")) + }, start_response, "http://null"))) + + response_str = etree.tostring(response, pretty_print=True) + print(response_str) + + nsdict = dict(tns=TNS) + + assert etree.fromstring(response_str) \ + .xpath(".//tns:documentRequestResult/text()", namespaces=nsdict) \ + == [FILE_NAME] + + def test_mtom_join_envelope_chunks(self): + FILE_NAME = 'EA055406-5881-4F02-A3DC-9A5A7510D018.dat' + TNS = 'http://gib.gov.tr/vedop3/eFatura' + + # large enough payload to be chunked + PAYLOAD = b"sample data " * 1024 + class SomeService(Service): + @rpc(Unicode(sub_name="fileName"), ByteArray(sub_name='binaryData'), + ByteArray(sub_name="hash"), _returns=Unicode) + def documentRequest(ctx, file_name, file_data, data_hash): + assert file_name == FILE_NAME + assert file_data == (PAYLOAD,) + + return file_name + + app = Application([SomeService], tns=TNS, + in_protocol=Soap12(), out_protocol=Soap12()) + + server = WsgiApplication(app, block_length=1024) + response = etree.fromstring(b''.join(server({ + 'QUERY_STRING': '', + 'PATH_INFO': '/call', + 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'Content-Type: multipart/related; ' + 'type="application/xop+xml"; ' + 'boundary="uuid:2e53e161-b47f-444a-b594-eb6b72e76997"; ' + 'start=""; ' + 'start-info="application/soap+xml"; action="sendDocument"', + 'wsgi.input': BytesIO(MTOM_REQUEST + .replace(b"\n", b"\r\n") + .replace(b"sample data", PAYLOAD)), + }, start_response, "http://null"))) + + response_str = etree.tostring(response, pretty_print=True) + print(response_str) + + nsdict = dict(tns=TNS) + + assert etree.fromstring(response_str) \ + .xpath(".//tns:documentRequestResult/text()", namespaces=nsdict) \ + == [FILE_NAME] + + def test_bytes_join_attachment(self): + href_id = "http://tempuri.org/1/634133419330914808" + payload = "ANJNSLJNDYBC SFDJNIREMX:CMKSAJN" + envelope = ''' + + + + + 0 + + + + + + + + + ''' % href_id + + (joinedmsg, numreplaces) = _join_attachment(NS_SOAP11_ENV, + href_id, envelope, payload) + + soaptree = etree.fromstring(joinedmsg) + + body = soaptree.find(ns.SOAP11_ENV("Body")) + response = body.getchildren()[0] + result = response.getchildren()[0] + r = XmlDocument().from_element(None, DownloadPartFileResult, result) + + self.assertEqual(payload, r.Data) + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/protocol/test_soap12.pyc b/pym/calculate/contrib/spyne/test/protocol/test_soap12.pyc new file mode 100644 index 0000000..6030638 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/protocol/test_soap12.pyc differ diff --git a/pym/calculate/contrib/spyne/test/protocol/test_xml.py b/pym/calculate/contrib/spyne/test/protocol/test_xml.py new file mode 100644 index 0000000..2ae3218 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/protocol/test_xml.py @@ -0,0 +1,628 @@ +#!/usr/bin/env python +# encoding: utf-8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from __future__ import print_function + +import logging +logging.basicConfig(level=logging.DEBUG) + +import sys +import unittest +import decimal +import datetime + +from pprint import pprint +from base64 import b64encode + +from lxml import etree +from lxml.builder import E + +from spyne import MethodContext, rpc, ByteArray, File, AnyXml +from spyne.context import FakeContext +from spyne.const import RESULT_SUFFIX +from spyne.service import Service +from spyne.server import ServerBase +from spyne.application import Application +from spyne.decorator import srpc +from spyne.util.six import BytesIO +from spyne.model import Fault, Integer, Decimal, Unicode, Date, DateTime, \ + XmlData, Array, ComplexModel, XmlAttribute, Mandatory as M +from spyne.protocol.xml import XmlDocument, SchemaValidationError + +from spyne.util import six +from spyne.util.xml import get_xml_as_object, get_object_as_xml, \ + get_object_as_xml_polymorphic, get_xml_as_object_polymorphic +from spyne.server.wsgi import WsgiApplication +from spyne.const.xml import NS_XSI + + +class TestXml(unittest.TestCase): + def test_empty_string(self): + class a(ComplexModel): + b = Unicode + + elt = etree.fromstring('') + o = get_xml_as_object(elt, a) + + assert o.b == '' + + def test_xml_data(self): + class C(ComplexModel): + a = XmlData(Unicode) + b = XmlAttribute(Unicode) + + class SomeService(Service): + @srpc(C, _returns=C) + def some_call(c): + assert c.a == 'a' + assert c.b == 'b' + return c + + app = Application([SomeService], "tns", name="test_xml_data", + in_protocol=XmlDocument(), out_protocol=XmlDocument()) + server = ServerBase(app) + initial_ctx = MethodContext(server, MethodContext.SERVER) + initial_ctx.in_string = [ + b'' + b'a' + b'' + ] + + ctx, = server.generate_contexts(initial_ctx) + server.get_in_object(ctx) + server.get_out_object(ctx) + server.get_out_string(ctx) + + print(ctx.out_string) + pprint(app.interface.nsmap) + + ret = etree.fromstring(b''.join(ctx.out_string)).xpath( + '//tns:some_call' + RESULT_SUFFIX, namespaces=app.interface.nsmap)[0] + + print(etree.tostring(ret, pretty_print=True)) + + assert ret.text == "a" + assert ret.attrib['b'] == "b" + + def test_wrapped_array(self): + parent = etree.Element('parent') + val = ['a', 'b'] + cls = Array(Unicode, namespace='tns') + XmlDocument().to_parent(None, cls, val, parent, 'tns') + print(etree.tostring(parent, pretty_print=True)) + xpath = parent.xpath('//x:stringArray/x:string/text()', + namespaces={'x': 'tns'}) + assert xpath == val + + def test_simple_array(self): + class cls(ComplexModel): + __namespace__ = 'tns' + s = Unicode(max_occurs='unbounded') + val = cls(s=['a', 'b']) + + parent = etree.Element('parent') + XmlDocument().to_parent(None, cls, val, parent, 'tns') + print(etree.tostring(parent, pretty_print=True)) + xpath = parent.xpath('//x:cls/x:s/text()', namespaces={'x': 'tns'}) + assert xpath == val.s + + def test_decimal(self): + d = decimal.Decimal('1e100') + + class SomeService(Service): + @srpc(Decimal(120,4), _returns=Decimal) + def some_call(p): + print(p) + print(type(p)) + assert type(p) == decimal.Decimal + assert d == p + return p + + app = Application([SomeService], "tns", in_protocol=XmlDocument(), + out_protocol=XmlDocument()) + server = ServerBase(app) + initial_ctx = MethodContext(server, MethodContext.SERVER) + initial_ctx.in_string = [ + b'

', + str(d).encode('ascii'), + b'

' + ] + + ctx, = server.generate_contexts(initial_ctx) + server.get_in_object(ctx) + server.get_out_object(ctx) + server.get_out_string(ctx) + + elt = etree.fromstring(b''.join(ctx.out_string)) + + print(etree.tostring(elt, pretty_print=True)) + target = elt.xpath('//tns:some_callResult/text()', + namespaces=app.interface.nsmap)[0] + assert target == str(d) + + def test_subs(self): + from lxml import etree + from spyne.util.xml import get_xml_as_object + from spyne.util.xml import get_object_as_xml + + m = { + "s0": "aa", + "s2": "cc", + "s3": "dd", + } + + class C(ComplexModel): + __namespace__ = "aa" + a = Integer + b = Integer(sub_name="bb") + c = Integer(sub_ns="cc") + d = Integer(sub_ns="dd", sub_name="dd") + + elt = get_object_as_xml(C(a=1, b=2, c=3, d=4), C) + print(etree.tostring(elt, pretty_print=True)) + + assert elt.xpath("s0:a/text()", namespaces=m) == ["1"] + assert elt.xpath("s0:bb/text()", namespaces=m) == ["2"] + assert elt.xpath("s2:c/text()", namespaces=m) == ["3"] + assert elt.xpath("s3:dd/text()", namespaces=m) == ["4"] + + c = get_xml_as_object(elt, C) + print(c) + assert c.a == 1 + assert c.b == 2 + assert c.c == 3 + assert c.d == 4 + + def test_sub_attributes(self): + from lxml import etree + from spyne.util.xml import get_xml_as_object + from spyne.util.xml import get_object_as_xml + + m = { + "s0": "aa", + "s2": "cc", + "s3": "dd", + } + + class C(ComplexModel): + __namespace__ = "aa" + a = XmlAttribute(Integer) + b = XmlAttribute(Integer(sub_name="bb")) + c = XmlAttribute(Integer(sub_ns="cc")) + d = XmlAttribute(Integer(sub_ns="dd", sub_name="dd")) + + elt = get_object_as_xml(C(a=1, b=2, c=3, d=4), C) + print(etree.tostring(elt, pretty_print=True)) + + assert elt.xpath("//*/@a") == ["1"] + assert elt.xpath("//*/@bb") == ["2"] + assert elt.xpath("//*/@s2:c", namespaces=m) == ["3"] + assert elt.xpath("//*/@s3:dd", namespaces=m) == ["4"] + + c = get_xml_as_object(elt, C) + print(c) + assert c.a == 1 + assert c.b == 2 + assert c.c == 3 + assert c.d == 4 + + def test_dates(self): + d = Date + xml_dates = [ + etree.fromstring(b'2013-04-05'), + etree.fromstring(b'2013-04-05+02:00'), + etree.fromstring(b'2013-04-05-02:00'), + etree.fromstring(b'2013-04-05Z'), + ] + + for xml_date in xml_dates: + c = get_xml_as_object(xml_date, d) + assert isinstance(c, datetime.date) == True + assert c.year == 2013 + assert c.month == 4 + assert c.day == 5 + + def test_datetime_usec(self): + fs = etree.fromstring + d = get_xml_as_object(fs('2013-04-05T06:07:08.123456'), DateTime) + assert d.microsecond == 123456 + + # rounds up + d = get_xml_as_object(fs('2013-04-05T06:07:08.1234567'), DateTime) + assert d.microsecond == 123457 + + # rounds down + d = get_xml_as_object(fs('2013-04-05T06:07:08.1234564'), DateTime) + assert d.microsecond == 123456 + + # rounds up as well + d = get_xml_as_object(fs('2013-04-05T06:07:08.1234565'), DateTime) + # FIXME: this is very interesting. why? + if not six.PY2: + assert d.microsecond == 123456 + else: + assert d.microsecond == 123457 + + def _get_ctx(self, server, in_string): + initial_ctx = MethodContext(server, MethodContext.SERVER) + initial_ctx.in_string = in_string + ctx, = server.generate_contexts(initial_ctx) + server.get_in_object(ctx) + return ctx + + def test_mandatory_elements(self): + class SomeService(Service): + @srpc(M(Unicode), _returns=Unicode) + def some_call(s): + assert s == 'hello' + return s + + app = Application([SomeService], "tns", name="test_mandatory_elements", + in_protocol=XmlDocument(validator='lxml'), + out_protocol=XmlDocument()) + server = ServerBase(app) + + # Valid call with all mandatory elements in + ctx = self._get_ctx(server, [ + b'' + b'hello' + b'' + ]) + server.get_out_object(ctx) + server.get_out_string(ctx) + ret = etree.fromstring(b''.join(ctx.out_string)).xpath( + '//tns:some_call%s/text()' % RESULT_SUFFIX, + namespaces=app.interface.nsmap)[0] + assert ret == 'hello' + + # Invalid call + ctx = self._get_ctx(server, [ + b'' + # no mandatory elements here... + b'' + ]) + self.assertRaises(SchemaValidationError, server.get_out_object, ctx) + + def test_unicode_chars_in_exception(self): + class SomeService(Service): + @srpc(Unicode(pattern=u'x'), _returns=Unicode) + def some_call(s): + test(should, never, reach, here) + + app = Application([SomeService], "tns", name="test_mandatory_elements", + in_protocol=XmlDocument(validator='lxml'), + out_protocol=XmlDocument()) + server = WsgiApplication(app) + + req = ( + u'' + u'Ğ' + u'' + ).encode('utf8') + + print("AAA") + resp = server({ + 'QUERY_STRING': '', + 'PATH_INFO': '/', + 'REQUEST_METHOD': 'POST', + 'SERVER_NAME': 'localhost', + 'SERVER_PORT': '80', + 'wsgi.input': BytesIO(req), + "wsgi.url_scheme": 'http', + }, lambda x, y: print(x,y)) + print("AAA") + + assert u'Ğ'.encode('utf8') in b''.join(resp) + + def test_mandatory_subelements(self): + class C(ComplexModel): + foo = M(Unicode) + + class SomeService(Service): + @srpc(C.customize(min_occurs=1), _returns=Unicode) + def some_call(c): + assert c is not None + assert c.foo == 'hello' + return c.foo + + app = Application( + [SomeService], "tns", name="test_mandatory_subelements", + in_protocol=XmlDocument(validator='lxml'), + out_protocol=XmlDocument()) + server = ServerBase(app) + + ctx = self._get_ctx(server, [ + b'' + # no mandatory elements at all... + b'' + ]) + self.assertRaises(SchemaValidationError, server.get_out_object, ctx) + + ctx = self._get_ctx(server, [ + b'' + b'' + # no mandatory elements here... + b'' + b'' + ]) + self.assertRaises(SchemaValidationError, server.get_out_object, ctx) + + def test_mandatory_element_attributes(self): + class C(ComplexModel): + bar = XmlAttribute(M(Unicode)) + + class SomeService(Service): + @srpc(C.customize(min_occurs=1), _returns=Unicode) + def some_call(c): + assert c is not None + assert hasattr(c, 'foo') + assert c.foo == 'hello' + return c.foo + + app = Application( + [SomeService], "tns", name="test_mandatory_element_attributes", + in_protocol=XmlDocument(validator='lxml'), + out_protocol=XmlDocument()) + server = ServerBase(app) + + ctx = self._get_ctx(server, [ + b'' + # no mandatory elements at all... + b'' + ]) + self.assertRaises(SchemaValidationError, server.get_out_object, ctx) + + ctx = self._get_ctx(server, [ + b'' + b'' + # no mandatory elements here... + b'' + b'' + ]) + self.assertRaises(SchemaValidationError, server.get_out_object, ctx) + + def test_bare_sub_name_ns(self): + class Action(ComplexModel): + class Attributes(ComplexModel.Attributes): + sub_ns = "SOME_NS" + sub_name = "Action" + data = XmlData(Unicode) + must_understand = XmlAttribute(Unicode) + + elt = get_object_as_xml(Action("x", must_understand="y"), Action) + eltstr = etree.tostring(elt) + print(eltstr) + assert eltstr == b'x' + + def test_null_mandatory_attribute(self): + class Action (ComplexModel): + data = XmlAttribute(M(Unicode)) + + elt = get_object_as_xml(Action(), Action) + eltstr = etree.tostring(elt) + print(eltstr) + assert eltstr == b'' + + def test_bytearray(self): + v = b'aaaa' + elt = get_object_as_xml([v], ByteArray, 'B') + eltstr = etree.tostring(elt) + print(eltstr) + assert elt.text == b64encode(v).decode('ascii') + + def test_any_xml_text(self): + v = u"" + elt = get_object_as_xml(v, AnyXml, 'B', no_namespace=True) + eltstr = etree.tostring(elt) + print(eltstr) + assert etree.tostring(elt[0], encoding="unicode") == v + + def test_any_xml_bytes(self): + v = b"" + + elt = get_object_as_xml(v, AnyXml, 'B', no_namespace=True) + eltstr = etree.tostring(elt) + print(eltstr) + assert etree.tostring(elt[0]) == v + + def test_any_xml_elt(self): + v = E.roots(E.bloody(E.roots())) + elt = get_object_as_xml(v, AnyXml, 'B') + eltstr = etree.tostring(elt) + print(eltstr) + assert etree.tostring(elt[0]) == etree.tostring(v) + + def test_file(self): + v = b'aaaa' + f = BytesIO(v) + elt = get_object_as_xml(File.Value(handle=f), File, 'B') + eltstr = etree.tostring(elt) + print(eltstr) + assert elt.text == b64encode(v).decode('ascii') + + def test_fault_detail_as_dict(self): + elt = get_object_as_xml(Fault(detail={"this": "that"}), Fault) + eltstr = etree.tostring(elt) + print(eltstr) + assert b'that' in eltstr + + def test_xml_encoding(self): + ctx = FakeContext(out_document=E.rain(u"yağmur")) + XmlDocument(encoding='iso-8859-9').create_out_string(ctx) + s = b''.join(ctx.out_string) + assert u"ğ".encode('iso-8859-9') in s + + def test_default(self): + class SomeComplexModel(ComplexModel): + _type_info = [ + ('a', Unicode), + ('b', Unicode(default='default')), + ] + + obj = XmlDocument().from_element( + None, SomeComplexModel, + etree.fromstring(""" + + string + + """) + ) + + # xml schema says it should be None + assert obj.b == 'default' + + obj = XmlDocument().from_element( + None, SomeComplexModel, + etree.fromstring(""" + + string + + + """ % NS_XSI) + ) + + # xml schema says it should be 'default' + assert obj.b == 'default' + + obj = XmlDocument(replace_null_with_default=False).from_element( + None, SomeComplexModel, + etree.fromstring(""" + + string + + + """ % NS_XSI) + ) + + # xml schema says it should be 'default' + assert obj.b is None + + def test_polymorphic_roundtrip(self): + + class B(ComplexModel): + __namespace__ = 'some_ns' + _type_info = { + '_b': Unicode, + } + + def __init__(self): + super(B, self).__init__() + self._b = "b" + + class C(B): + __namespace__ = 'some_ns' + _type_info = { + '_c': Unicode, + } + + def __init__(self): + super(C, self).__init__() + self._c = "c" + + class A(ComplexModel): + __namespace__ = 'some_ns' + _type_info = { + '_a': Unicode, + '_b': B, + } + + def __init__(self, b=None): + super(A, self).__init__() + self._a = 'a' + self._b = b + + a = A(b=C()) + elt = get_object_as_xml_polymorphic(a, A) + xml_string = etree.tostring(elt, pretty_print=True) + if six.PY2: + print(xml_string, end="") + else: + sys.stdout.buffer.write(xml_string) + + element_tree = etree.fromstring(xml_string) + new_a = get_xml_as_object_polymorphic(elt, A) + + assert new_a._a == a._a, (a._a, new_a._a) + assert new_a._b._b == a._b._b, (a._b._b, new_a._b._b) + assert new_a._b._c == a._b._c, (a._b._c, new_a._b._c) + + +class TestIncremental(unittest.TestCase): + def test_one(self): + class SomeComplexModel(ComplexModel): + s = Unicode + i = Integer + + v = SomeComplexModel(s='a', i=1), + + class SomeService(Service): + @rpc(_returns=SomeComplexModel) + def get(ctx): + return v + + desc = SomeService.public_methods['get'] + ctx = FakeContext(out_object=v, descriptor=desc) + ostr = ctx.out_stream = BytesIO() + XmlDocument(Application([SomeService], __name__)) \ + .serialize(ctx, XmlDocument.RESPONSE) + + elt = etree.fromstring(ostr.getvalue()) + print(etree.tostring(elt, pretty_print=True)) + + assert elt.xpath('x:getResult/x:i/text()', + namespaces={'x':__name__}) == ['1'] + assert elt.xpath('x:getResult/x:s/text()', + namespaces={'x':__name__}) == ['a'] + + def test_many(self): + class SomeComplexModel(ComplexModel): + s = Unicode + i = Integer + + v = [ + SomeComplexModel(s='a', i=1), + SomeComplexModel(s='b', i=2), + SomeComplexModel(s='c', i=3), + SomeComplexModel(s='d', i=4), + SomeComplexModel(s='e', i=5), + ] + + class SomeService(Service): + @rpc(_returns=Array(SomeComplexModel)) + def get(ctx): + return v + + desc = SomeService.public_methods['get'] + ctx = FakeContext(out_object=[v], descriptor=desc) + ostr = ctx.out_stream = BytesIO() + XmlDocument(Application([SomeService], __name__)) \ + .serialize(ctx, XmlDocument.RESPONSE) + + elt = etree.fromstring(ostr.getvalue()) + print(etree.tostring(elt, pretty_print=True)) + + assert elt.xpath('x:getResult/x:SomeComplexModel/x:i/text()', + namespaces={'x': __name__}) == ['1', '2', '3', '4', '5'] + assert elt.xpath('x:getResult/x:SomeComplexModel/x:s/text()', + namespaces={'x': __name__}) == ['a', 'b', 'c', 'd', 'e'] + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/protocol/test_xml.pyc b/pym/calculate/contrib/spyne/test/protocol/test_xml.pyc new file mode 100644 index 0000000..20be48b Binary files /dev/null and b/pym/calculate/contrib/spyne/test/protocol/test_xml.pyc differ diff --git a/pym/calculate/contrib/spyne/test/protocol/test_yaml.py b/pym/calculate/contrib/spyne/test/protocol/test_yaml.py new file mode 100644 index 0000000..2cb224c --- /dev/null +++ b/pym/calculate/contrib/spyne/test/protocol/test_yaml.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import unittest + +from spyne.test.protocol._test_dictdoc import TDictDocumentTest +from spyne.protocol.yaml import YamlDocument + +from spyne import MethodContext +from spyne.application import Application +from spyne.decorator import srpc +from spyne.service import Service +from spyne.server import ServerBase + +from spyne.protocol.yaml import yaml +yaml.dumps = yaml.dump +yaml.loads = yaml.load + +TestYamlDocument = TDictDocumentTest(yaml, YamlDocument, YamlDocument().out_kwargs) + + +class Test(unittest.TestCase): + def test_invalid_input(self): + class SomeService(Service): + @srpc() + def yay(): + pass + + app = Application([SomeService], 'tns', + in_protocol=YamlDocument(), + out_protocol=YamlDocument()) + + server = ServerBase(app) + + initial_ctx = MethodContext(server, MethodContext.SERVER) + initial_ctx.in_string = [b'{'] + ctx, = server.generate_contexts(initial_ctx) + assert ctx.in_error.faultcode == 'Client.YamlDecodeError' + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/protocol/test_yaml.pyc b/pym/calculate/contrib/spyne/test/protocol/test_yaml.pyc new file mode 100644 index 0000000..c8ba5b1 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/protocol/test_yaml.pyc differ diff --git a/pym/calculate/contrib/spyne/test/regen_wsdl.py b/pym/calculate/contrib/spyne/test/regen_wsdl.py new file mode 100644 index 0000000..2ad5f75 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/regen_wsdl.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +from lxml import etree +from spyne.test.sort_wsdl import sort_wsdl +from spyne.interface.wsdl import Wsdl11 + +from spyne.test.interop.server._service import services +from spyne.application import Application + +app = Application(services, 'spyne.test.interop.server') +app.transport = 'http://schemas.xmlsoap.org/soap/http' +wsdl = Wsdl11(app.interface) +wsdl.build_interface_document('http://localhost:9754/') +elt = etree.ElementTree(etree.fromstring(wsdl.get_interface_document())) +sort_wsdl(elt) +s = etree.tostring(elt) + +# minidom's serialization seems to put attributes in alphabetic order. +# this is exactly what we want here. +from xml.dom.minidom import parseString +doc = parseString(s) +s = doc.toprettyxml(indent=' ', newl='\n', encoding='utf8') +s = s.replace(" xmlns:","\n xmlns:") + +open('wsdl.xml', 'w').write(s) +print('wsdl.xml written') diff --git a/pym/calculate/contrib/spyne/test/regen_wsdl.pyc b/pym/calculate/contrib/spyne/test/regen_wsdl.pyc new file mode 100644 index 0000000..f8074d8 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/regen_wsdl.pyc differ diff --git a/pym/calculate/contrib/spyne/test/sort_wsdl.py b/pym/calculate/contrib/spyne/test/sort_wsdl.py new file mode 100644 index 0000000..96a4581 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/sort_wsdl.py @@ -0,0 +1,104 @@ +#!/usr/bin/python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +"""Quick hack to sort the wsdl. it's helpful when comparing the wsdl output +from two spyne versions. +""" + +ns_wsdl = "http://schemas.xmlsoap.org/wsdl/" +ns_schema = "http://www.w3.org/2001/XMLSchema" + +import sys + +from lxml import etree + + +def cache_order(l, ns): + return dict([ ("{%s}%s" % (ns, a), l.index(a)) for a in l]) + + +wsdl_order = ('types', 'message', 'service', 'portType', 'binding') +wsdl_order = cache_order(wsdl_order, ns_wsdl) + +schema_order = ('import', 'element', 'simpleType', 'complexType', 'attribute') +schema_order = cache_order(schema_order, ns_schema) + +parser = etree.XMLParser(remove_blank_text=True) + + +def main(): + tree = etree.parse(sys.stdin, parser=parser) + sort_wsdl(tree) + tree.write(sys.stdout, encoding="UTF-8", xml_declaration=True) + return 0 + + +def sort_wsdl(tree): + l0 = [] + type_node = None + + for e in tree.getroot(): + if e.tag == "{%s}types" % ns_wsdl: + assert type_node is None + type_node = e + + else: + l0.append(e) + e.getparent().remove(e) + + l0.sort(key=lambda e: (wsdl_order[e.tag], e.attrib['name'])) + for e in l0: + tree.getroot().append(e) + + for e in tree.getroot(): + if e.tag in ("{%s}portType" % ns_wsdl, "{%s}binding" % ns_wsdl, "{%s}operation" % ns_wsdl): + nodes = [] + for p in e.getchildren(): + nodes.append(p) + + nodes.sort(key=lambda e: e.attrib.get('name', '0')) + + for p in nodes: + e.append(p) + + schemas = [] + + for e in type_node: + schemas.append(e) + e.getparent().remove(e) + + schemas.sort(key=lambda e: e.attrib["targetNamespace"]) + + for s in schemas: + type_node.append(s) + + for s in schemas: + nodes = [] + for e in s: + nodes.append(e) + e.getparent().remove(e) + + nodes.sort(key=lambda e: (schema_order[e.tag], e.attrib.get('name', '\0'))) + + for e in nodes: + s.append(e) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/pym/calculate/contrib/spyne/test/sort_wsdl.pyc b/pym/calculate/contrib/spyne/test/sort_wsdl.pyc new file mode 100644 index 0000000..5813a7a Binary files /dev/null and b/pym/calculate/contrib/spyne/test/sort_wsdl.pyc differ diff --git a/pym/calculate/contrib/spyne/test/test_null_server.py b/pym/calculate/contrib/spyne/test/test_null_server.py new file mode 100644 index 0000000..26307fb --- /dev/null +++ b/pym/calculate/contrib/spyne/test/test_null_server.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import gc +import unittest + +from lxml import etree + +from spyne import const +from spyne.interface.wsdl import Wsdl11 +from spyne.protocol.xml import XmlDocument + +from spyne.model.complex import Array +from spyne.model.primitive import Boolean +from spyne.model.primitive import String +from spyne.application import Application +from spyne.decorator import srpc +from spyne.service import Service +from spyne.server.null import NullServer + +class TestNullServer(unittest.TestCase): + def test_call_one_arg(self): + queue = set() + + class MessageService(Service): + @srpc(String) + def send_message(s): + queue.add(s) + + application = Application([MessageService], 'some_tns', + in_protocol=XmlDocument(), out_protocol=XmlDocument()) + + server = NullServer(application) + server.service.send_message("zabaaa") + + assert set(["zabaaa"]) == queue + + def test_call_two_args(self): + queue = set() + + class MessageService(Service): + @srpc(String, String) + def send_message(s, k): + queue.add((s,k)) + + application = Application([MessageService], 'some_tns', + in_protocol=XmlDocument(), out_protocol=XmlDocument()) + + server = NullServer(application) + + queue.clear() + server.service.send_message("zabaaa", k="hobaa") + assert set([("zabaaa","hobaa")]) == queue + + queue.clear() + server.service.send_message(k="hobaa") + assert set([(None,"hobaa")]) == queue + + queue.clear() + server.service.send_message("zobaaa", s="hobaa") + assert set([("hobaa", None)]) == queue + + def test_ostr(self): + queue = set() + + class MessageService(Service): + @srpc(String, String, _returns=Array(String)) + def send_message(s, k): + queue.add((s, k)) + return [s, k] + + application = Application([MessageService], 'some_tns', + in_protocol=XmlDocument(), out_protocol=XmlDocument()) + + ostr_server = NullServer(application, ostr=True) + + queue.clear() + ret = ostr_server.service.send_message("zabaaa", k="hobaa") + assert set([("zabaaa","hobaa")]) == queue + assert etree.fromstring(b''.join(ret)).xpath('//tns:string/text()', + namespaces=application.interface.nsmap) == ['zabaaa', 'hobaa'] + + queue.clear() + ostr_server.service.send_message(k="hobaa") + assert set([(None,"hobaa")]) == queue + + queue.clear() + ostr_server.service.send_message("zobaaa", s="hobaa") + assert set([("hobaa", None)]) == queue + + def test_no_gc_collect(self): + class PingService(Service): + @srpc(_returns=Boolean) + def ping(): + return True + + application = Application( + [PingService], 'some_tns', + in_protocol=XmlDocument(), out_protocol=XmlDocument()) + + server = NullServer(application) + origin_collect = gc.collect + origin_MIN_GC_INTERVAL = const.MIN_GC_INTERVAL + try: + gc.collect = lambda : 1/0 + with self.assertRaises(ZeroDivisionError): + const.MIN_GC_INTERVAL = 0 + server.service.ping() + # No raise + const.MIN_GC_INTERVAL = float('inf') + server.service.ping() + finally: + gc.collect = origin_collect + const.MIN_GC_INTERVAL = origin_MIN_GC_INTERVAL + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/test_null_server.pyc b/pym/calculate/contrib/spyne/test/test_null_server.pyc new file mode 100644 index 0000000..daf0b21 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/test_null_server.pyc differ diff --git a/pym/calculate/contrib/spyne/test/test_service.py b/pym/calculate/contrib/spyne/test/test_service.py new file mode 100644 index 0000000..b8d8acb --- /dev/null +++ b/pym/calculate/contrib/spyne/test/test_service.py @@ -0,0 +1,511 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +# +# Most of the service tests are performed through the interop tests. +# + +import logging +logging.basicConfig(level=logging.DEBUG) + +import unittest + +from spyne.util.six import BytesIO + +from lxml import etree + +from spyne import LogicError +from spyne.const import RESPONSE_SUFFIX +from spyne.model.primitive import NATIVE_MAP + +from spyne.service import Service +from spyne.decorator import rpc, srpc +from spyne.application import Application +from spyne.auxproc.sync import SyncAuxProc +from spyne.auxproc.thread import ThreadAuxProc +from spyne.protocol.http import HttpRpc +from spyne.protocol.soap import Soap11 +from spyne.server.null import NullServer +from spyne.server.wsgi import WsgiApplication +from spyne.model import Array, SelfReference, Iterable, ComplexModel, String, \ + Unicode + +Application.transport = 'test' + + +def start_response(code, headers): + print(code, headers) + + +class MultipleMethods1(Service): + @srpc(String) + def multi(s): + return "%r multi 1" % s + + +class MultipleMethods2(Service): + @srpc(String) + def multi(s): + return "%r multi 2" % s + + +class TestEvents(unittest.TestCase): + def test_method_exception(self): + from spyne.protocol.xml import XmlDocument + + h = [0] + + def on_method_exception_object(ctx): + assert ctx.out_error is not None + from spyne.protocol.xml import SchemaValidationError + assert isinstance(ctx.out_error, SchemaValidationError) + logging.error("method_exception_object: %r", repr(ctx.out_error)) + h[0] += 1 + + def on_method_exception_document(ctx): + assert ctx.out_error is not None + from spyne.protocol.xml import SchemaValidationError + assert isinstance(ctx.out_error, SchemaValidationError) + logging.error("method_exception_document: %r", + etree.tostring(ctx.out_document)) + h[0] += 1 + + class SomeService(Service): + @rpc(Unicode(5)) + def some_call(ctx, some_str): + print(some_str) + + app = Application([SomeService], "some_tns", + in_protocol=XmlDocument(validator='lxml'), out_protocol=Soap11()) + + app.event_manager.add_listener( + "method_exception_object", on_method_exception_object) + + app.event_manager.add_listener( + "method_exception_document", on_method_exception_document) + + # this shouldn't be called because: + # 1. document isn't validated + # 2. hence; document can't be parsed + # 3. hence; document can't be mapped to a function + # 4. hence; document can't be mapped to a service class + # 5. hence; no handlers from the service class is invoked. + # 6. hence; the h[0] == 2 check (instead of 3) + SomeService.event_manager.add_listener( + "method_exception_object", on_method_exception_object) + + wsgi_app = WsgiApplication(app) + + xml_request = b""" + + 123456 + + """ + + _ = b''.join(wsgi_app({ + 'PATH_INFO': '/', + 'SERVER_NAME': 'localhost', + 'SERVER_PORT': '7000', + 'REQUEST_METHOD': 'POST', + 'wsgi.url_scheme': 'http', + 'wsgi.input': BytesIO(xml_request), + }, start_response)) + + assert h[0] == 2 + + +class TestMultipleMethods(unittest.TestCase): + def test_single_method(self): + try: + Application([MultipleMethods1, MultipleMethods2], 'tns', + in_protocol=Soap11(), out_protocol=Soap11()) + + except ValueError: + pass + + else: + raise Exception('must fail.') + + def test_simple_aux_nullserver(self): + data = [] + + class SomeService(Service): + @srpc(String) + def call(s): + data.append(s) + + class AuxService(Service): + __aux__ = SyncAuxProc() + + @srpc(String) + def call(s): + data.append(s) + + app = Application([SomeService, AuxService], 'tns', 'name', Soap11(), + Soap11()) + server = NullServer(app) + server.service.call("hey") + + assert data == ['hey', 'hey'] + + def test_namespace_in_message_name(self): + class S(Service): + @srpc(String, _in_message_name='{tns}inMessageName') + def call(s): + pass + + app = Application([S], 'tns', 'name', Soap11(), Soap11()) + + def test_simple_aux_wsgi(self): + data = [] + + class SomeService(Service): + @srpc(String, _returns=String) + def call(s): + data.append(s) + + class AuxService(Service): + __aux__ = SyncAuxProc() + + @srpc(String, _returns=String) + def call(s): + data.append(s) + + app = Application([SomeService, AuxService], 'tns', + in_protocol=HttpRpc(), out_protocol=HttpRpc()) + + server = WsgiApplication(app) + server({ + 'QUERY_STRING': 's=hey', + 'PATH_INFO': '/call', + 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'text/xml; charset=utf8', + 'SERVER_NAME': 'localhost', + 'wsgi.input': BytesIO(), + }, start_response, "http://null") + + assert data == ['hey', 'hey'] + + def test_thread_aux_wsgi(self): + import logging + logging.basicConfig(level=logging.DEBUG) + + data = set() + + class SomeService(Service): + @srpc(String, _returns=String) + def call(s): + data.add(s) + + class AuxService(Service): + __aux__ = ThreadAuxProc() + + @srpc(String, _returns=String) + def call(s): + data.add(s + "aux") + + app = Application([SomeService, AuxService], 'tns', + in_protocol=HttpRpc(), out_protocol=HttpRpc()) + server = WsgiApplication(app) + server({ + 'QUERY_STRING': 's=hey', + 'PATH_INFO': '/call', + 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'text/xml; charset=utf8', + 'SERVER_NAME': 'localhost', + 'wsgi.input': BytesIO(), + }, start_response, "http://null") + + import time + time.sleep(1) + + assert data == set(['hey', 'heyaux']) + + def test_mixing_primary_and_aux_methods(self): + try: + class SomeService(Service): + @srpc(String, _returns=String, _aux=ThreadAuxProc()) + def call(s): + pass + + @srpc(String, _returns=String) + def mall(s): + pass + except Exception: + pass + else: + raise Exception("must fail with 'Exception: you can't mix aux and " + "non-aux methods in a single service definition.'") + + def __run_service(self, service): + app = Application([service], 'tns', in_protocol=HttpRpc(), + out_protocol=Soap11()) + server = WsgiApplication(app) + + return_string = b''.join(server({ + 'QUERY_STRING': '', + 'PATH_INFO': '/some_call', + 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'text/xml; charset=utf8', + 'SERVER_NAME': 'localhost', + 'wsgi.input': BytesIO(b""), + }, start_response, "http://null")) + + elt = etree.fromstring(return_string) + print(etree.tostring(elt, pretty_print=True)) + + return elt, app.interface.nsmap + + def test_settings_headers_from_user_code(self): + class RespHeader(ComplexModel): + __namespace__ = 'tns' + Elem1 = String + + # test header in service definition + class SomeService(Service): + __out_header__ = RespHeader + + @rpc() + def some_call(ctx): + ctx.out_header = RespHeader() + ctx.out_header.Elem1 = 'Test1' + + elt, nsmap = self.__run_service(SomeService) + query = '/soap11env:Envelope/soap11env:Header/tns:RespHeader' \ + '/tns:Elem1/text()' + + assert elt.xpath(query, namespaces=nsmap)[0] == 'Test1' + + # test header in decorator + class SomeService(Service): + @rpc(_out_header=RespHeader) + def some_call(ctx): + ctx.out_header = RespHeader() + ctx.out_header.Elem1 = 'Test1' + + elt, nsmap = self.__run_service(SomeService) + query = '/soap11env:Envelope/soap11env:Header/tns:RespHeader/tns' \ + ':Elem1/text()' + assert elt.xpath(query, namespaces=nsmap)[0] == 'Test1' + + # test no header + class SomeService(Service): + @rpc() + def some_call(ctx): + ctx.out_header = RespHeader() + ctx.out_header.Elem1 = 'Test1' + + elt, nsmap = self.__run_service(SomeService) + query = '/soap11env:Envelope/soap11env:Header/tns:RespHeader' \ + '/tns:Elem1/text()' + assert len(elt.xpath(query, namespaces=nsmap)) == 0 + + +class TestNativeTypes(unittest.TestCase): + def test_native_types(self): + for t in NATIVE_MAP: + class SomeService(Service): + @rpc(t) + def some_call(ctx, arg): + pass + + nt, = SomeService.public_methods['some_call'].in_message \ + ._type_info.values() + + assert issubclass(nt, NATIVE_MAP[t]) + + def test_native_types_in_arrays(self): + for t in NATIVE_MAP: + class SomeService(Service): + @rpc(Array(t)) + def some_call(ctx, arg): + pass + + nt, = SomeService.public_methods['some_call'].in_message \ + ._type_info.values() + nt, = nt._type_info.values() + assert issubclass(nt, NATIVE_MAP[t]) + + +class TestBodyStyle(unittest.TestCase): + + def test_soap_bare_empty_output(self): + class SomeService(Service): + @rpc(String, _body_style='bare') + def some_call(ctx, s): + assert s == 'abc' + + app = Application([SomeService], 'tns', in_protocol=Soap11(), + out_protocol=Soap11(cleanup_namespaces=True)) + + req = b""" + + + abc + + + """ + + server = WsgiApplication(app) + resp = etree.fromstring(b''.join(server({ + 'QUERY_STRING': '', + 'PATH_INFO': '/call', + 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'text/xml; charset=utf8', + 'SERVER_NAME': 'localhost', + 'wsgi.input': BytesIO(req), + }, start_response, "http://null"))) + + print(etree.tostring(resp, pretty_print=True)) + + assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' + assert len(resp[0]) == 1 + assert resp[0][0].tag == '{tns}some_call' + RESPONSE_SUFFIX + assert len(resp[0][0]) == 0 + + def test_soap_bare_empty_input(self): + class SomeService(Service): + + @rpc(_body_style='bare', _returns=String) + def some_call(ctx): + return 'abc' + + app = Application([SomeService], 'tns', in_protocol=Soap11(), + out_protocol=Soap11(cleanup_namespaces=True)) + + req = b""" + + + + + + """ + + server = WsgiApplication(app) + resp = etree.fromstring(b''.join(server({ + 'QUERY_STRING': '', + 'PATH_INFO': '/call', + 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'text/xml; charset=utf8', + 'SERVER_NAME': 'localhost', + 'wsgi.input': BytesIO(req) + }, start_response, "http://null"))) + + print(etree.tostring(resp, pretty_print=True)) + + assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' + assert resp[0][0].tag == '{tns}some_call' + RESPONSE_SUFFIX + assert resp[0][0].text == 'abc' + + def test_soap_bare_empty_model_input_method_name(self): + class EmptyRequest(ComplexModel): + pass + + try: + class SomeService(Service): + @rpc(EmptyRequest, _body_style='bare', _returns=String) + def some_call(ctx, request): + return 'abc' + except Exception: + pass + else: + raise Exception("Must fail with exception: body_style='bare' does " + "not allow empty model as param") + + def test_implicit_class_conflict(self): + class someCallResponse(ComplexModel): + __namespace__ = 'tns' + s = String + + class SomeService(Service): + @rpc(someCallResponse, _returns=String) + def someCall(ctx, x): + return ['abc', 'def'] + + try: + Application([SomeService], 'tns', in_protocol=Soap11(), + out_protocol=Soap11(cleanup_namespaces=True)) + except ValueError as e: + print(e) + else: + raise Exception("must fail.") + + def test_soap_bare_wrapped_array_output(self): + class SomeService(Service): + @rpc(_body_style='bare', _returns=Array(String)) + def some_call(ctx): + return ['abc', 'def'] + + app = Application([SomeService], 'tns', in_protocol=Soap11(), + out_protocol=Soap11(cleanup_namespaces=True)) + + req = b""" + + + + + + """ + + server = WsgiApplication(app) + resp = etree.fromstring(b''.join(server({ + 'QUERY_STRING': '', + 'PATH_INFO': '/call', + 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'text/xml; charset=utf8', + 'wsgi.input': BytesIO(req) + }, start_response, "http://null"))) + + print(etree.tostring(resp, pretty_print=True)) + + assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' + assert resp[0][0].tag == '{tns}some_call' + RESPONSE_SUFFIX + assert resp[0][0][0].text == 'abc' + assert resp[0][0][1].text == 'def' + + def test_array_iterable(self): + class SomeService(Service): + @rpc(Array(Unicode), Iterable(Unicode)) + def some_call(ctx, a, b): + pass + + app = Application([SomeService], 'tns', in_protocol=Soap11(), + out_protocol=Soap11(cleanup_namespaces=True)) + + server = WsgiApplication(app) + + def test_invalid_self_reference(self): + try: + class SomeService(Service): + @rpc(_returns=SelfReference) + def method(ctx): + pass + except LogicError: + pass + else: + raise Exception("Must fail with: " + "'SelfReference can't be used inside @rpc and its ilk'") + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/test_service.pyc b/pym/calculate/contrib/spyne/test/test_service.pyc new file mode 100644 index 0000000..0381ad6 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/test_service.pyc differ diff --git a/pym/calculate/contrib/spyne/test/test_soft_validation.py b/pym/calculate/contrib/spyne/test/test_soft_validation.py new file mode 100644 index 0000000..2177bd7 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/test_soft_validation.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +# +# Most of the service tests are performed through the interop tests. +# + +import unittest + +from spyne.application import Application +from spyne.decorator import srpc +from spyne.error import ValidationError +from spyne.service import Service +from spyne.protocol.http import HttpRpc +from spyne.protocol.soap import Soap11 +from spyne.model.primitive import Integer +from spyne.model.primitive import String +from spyne.server import ServerBase +from spyne.server.wsgi import WsgiApplication + +from spyne import MethodContext +from spyne.server.wsgi import WsgiMethodContext + +Application.transport = 'test' + + +class TestValidationString(unittest.TestCase): + def test_min_len(self): + StrictType = String(min_len=3) + + self.assertEqual(StrictType.validate_string(StrictType, 'aaa'), True) + self.assertEqual(StrictType.validate_string(StrictType, 'a'), False) + + def test_max_len(self): + StrictType = String(max_len=3) + + self.assertEqual(StrictType.validate_string(StrictType, 'a'), True) + self.assertEqual(StrictType.validate_string(StrictType, 'aaa'), True) + self.assertEqual(StrictType.validate_string(StrictType, 'aaaa'), False) + + def test_pattern(self): + # Pattern match needs to be checked after the string is decoded, that's + # why we need to use validate_native here. + StrictType = String(pattern='[a-z]') + + self.assertEqual(StrictType.validate_native(StrictType, 'a'), True) + self.assertEqual(StrictType.validate_native(StrictType, 'a1'), False) + self.assertEqual(StrictType.validate_native(StrictType, '1'), False) + + +class TestValidationInteger(unittest.TestCase): + def test_lt(self): + StrictType = Integer(lt=3) + + self.assertEqual(StrictType.validate_native(StrictType, 2), True) + self.assertEqual(StrictType.validate_native(StrictType, 3), False) + + def test_le(self): + StrictType = Integer(le=3) + + self.assertEqual(StrictType.validate_native(StrictType, 2), True) + self.assertEqual(StrictType.validate_native(StrictType, 3), True) + self.assertEqual(StrictType.validate_native(StrictType, 4), False) + + def test_gt(self): + StrictType = Integer(gt=3) + + self.assertEqual(StrictType.validate_native(StrictType, 4), True) + self.assertEqual(StrictType.validate_native(StrictType, 3), False) + + def test_ge(self): + StrictType = Integer(ge=3) + + self.assertEqual(StrictType.validate_native(StrictType, 3), True) + self.assertEqual(StrictType.validate_native(StrictType, 2), False) + +class TestHttpRpcSoftValidation(unittest.TestCase): + def setUp(self): + class SomeService(Service): + @srpc(String(pattern='a')) + def some_method(s): + pass + @srpc(String(pattern='a', max_occurs=2)) + def some_other_method(s): + pass + + self.application = Application([SomeService], + in_protocol=HttpRpc(validator='soft'), + out_protocol=Soap11(), + name='Service', tns='tns', + ) + + + def __get_ctx(self, mn, qs): + server = WsgiApplication(self.application) + ctx = WsgiMethodContext(server, { + 'QUERY_STRING': qs, + 'PATH_INFO': '/%s' % mn, + 'REQUEST_METHOD': "GET", + 'SERVER_NAME': 'localhost', + }, 'some-content-type') + + ctx, = server.generate_contexts(ctx) + server.get_in_object(ctx) + + return ctx + + def test_http_rpc(self): + ctx = self.__get_ctx('some_method', 's=1') + self.assertEqual(ctx.in_error.faultcode, 'Client.ValidationError') + + ctx = self.__get_ctx('some_method', 's=a') + self.assertEqual(ctx.in_error, None) + + ctx = self.__get_ctx('some_other_method', 's=1') + self.assertEqual(ctx.in_error.faultcode, 'Client.ValidationError') + ctx = self.__get_ctx('some_other_method', 's=1&s=2') + self.assertEqual(ctx.in_error.faultcode, 'Client.ValidationError') + ctx = self.__get_ctx('some_other_method', 's=1&s=2&s=3') + self.assertEqual(ctx.in_error.faultcode, 'Client.ValidationError') + ctx = self.__get_ctx('some_other_method', 's=a&s=a&s=a') + self.assertEqual(ctx.in_error.faultcode, 'Client.ValidationError') + + ctx = self.__get_ctx('some_other_method', 's=a&s=a') + self.assertEqual(ctx.in_error, None) + ctx = self.__get_ctx('some_other_method', 's=a') + self.assertEqual(ctx.in_error, None) + ctx = self.__get_ctx('some_other_method', '') + self.assertEqual(ctx.in_error, None) + +class TestSoap11SoftValidation(unittest.TestCase): + def test_basic(self): + class SomeService(Service): + @srpc(String(pattern='a')) + def some_method(s): + pass + + application = Application([SomeService], + in_protocol=Soap11(validator='soft'), + out_protocol=Soap11(), + name='Service', tns='tns', + ) + server = ServerBase(application) + + ctx = MethodContext(server, MethodContext.SERVER) + ctx.in_string = [u""" + + + + OK + + + + """] + + ctx, = server.generate_contexts(ctx) + server.get_in_object(ctx) + + self.assertEqual(isinstance(ctx.in_error, ValidationError), True) + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/test_soft_validation.pyc b/pym/calculate/contrib/spyne/test/test_soft_validation.pyc new file mode 100644 index 0000000..837fbae Binary files /dev/null and b/pym/calculate/contrib/spyne/test/test_soft_validation.pyc differ diff --git a/pym/calculate/contrib/spyne/test/test_sqlalchemy.py b/pym/calculate/contrib/spyne/test/test_sqlalchemy.py new file mode 100644 index 0000000..9d7af0d --- /dev/null +++ b/pym/calculate/contrib/spyne/test/test_sqlalchemy.py @@ -0,0 +1,1302 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logging.basicConfig(level=logging.DEBUG) + +import inspect +import unittest +import sqlalchemy + +from pprint import pprint + +from sqlalchemy import create_engine +from sqlalchemy import MetaData +from sqlalchemy import Column +from sqlalchemy import Table +from sqlalchemy.exc import IntegrityError + +from sqlalchemy.orm import mapper +from sqlalchemy.orm import sessionmaker + +from spyne import M, Any, Double + +from spyne.model import XmlAttribute, File, XmlData, ComplexModel, Array, \ + Integer32, Unicode, Integer, Enum, TTableModel, DateTime, Boolean + +from spyne.model.binary import HybridFileStore +from spyne.model.complex import xml +from spyne.model.complex import table + +from spyne.store.relational import get_pk_columns +from spyne.store.relational.document import PGJsonB, PGJson, PGFileJson, \ + PGObjectJson + +TableModel = TTableModel() + + +class TestSqlAlchemyTypeMappings(unittest.TestCase): + def test_init(self): + fn = inspect.stack()[0][3] + from sqlalchemy.inspection import inspect as sqla_inspect + + class SomeClass1(TableModel): + __tablename__ = "%s_%d" % (fn, 1) + i = Integer32(pk=True) + e = Unicode(32) + + from spyne.util.dictdoc import get_dict_as_object + inst = get_dict_as_object(dict(i=4), SomeClass1) + assert not sqla_inspect(inst).attrs.e.history.has_changes() + + def test_bool(self): + fn = inspect.stack()[0][3] + + class SomeClass1(TableModel): + __tablename__ = "%s_%d" % (fn, 1) + i = Integer32(pk=True) + b = Boolean + + assert isinstance(SomeClass1.Attributes.sqla_table.c.b.type, + sqlalchemy.Boolean) + + class SomeClass2(TableModel): + __tablename__ = "%s_%d" % (fn, 2) + i = Integer32(pk=True) + b = Boolean(store_as=int) + + assert isinstance(SomeClass2.Attributes.sqla_table.c.b.type, + sqlalchemy.SmallInteger) + + def test_jsonb(self): + fn = inspect.stack()[0][3] + + class SomeClass1(TableModel): + __tablename__ = "%s_%d" % (fn, 1) + i = Integer32(pk=True) + a = Any(store_as='json') + + assert isinstance(SomeClass1.Attributes.sqla_table.c.a.type, PGJson) + + class SomeClass2(TableModel): + __tablename__ = "%s_%d" % (fn, 2) + i = Integer32(pk=True) + a = Any(store_as='jsonb') + + assert isinstance(SomeClass2.Attributes.sqla_table.c.a.type, PGJsonB) + + class SomeClass3(TableModel): + __tablename__ = "%s_%d" % (fn, 3) + i = Integer32(pk=True) + a = File(store_as=HybridFileStore("path", db_format='jsonb')) + + assert isinstance(SomeClass3.Attributes.sqla_table.c.a.type, PGFileJson) + assert SomeClass3.Attributes.sqla_table.c.a.type.dbt == 'jsonb' + + def test_obj_json(self): + fn = inspect.stack()[0][3] + + class SomeClass(ComplexModel): + s = Unicode + d = Double + + class SomeClass1(TableModel): + __tablename__ = "%s_%d" % (fn, 1) + _type_info = [ + ('i', Integer32(pk=True)), + ('a', Array(SomeClass, store_as='json')), + ] + + assert isinstance(SomeClass1.Attributes.sqla_table.c.a.type, + PGObjectJson) + + class SomeClass2(TableModel): + __tablename__ = "%s_%d" % (fn, 2) + i = Integer32(pk=True) + a = SomeClass.customize(store_as='json') + + assert isinstance(SomeClass2.Attributes.sqla_table.c.a.type, + PGObjectJson) + + +class TestSqlAlchemySchema(unittest.TestCase): + def setUp(self): + logging.getLogger('sqlalchemy').setLevel(logging.DEBUG) + + self.engine = create_engine('sqlite:///:memory:') + self.session = sessionmaker(bind=self.engine)() + self.metadata = TableModel.Attributes.sqla_metadata = MetaData() + self.metadata.bind = self.engine + logging.info('Testing against sqlalchemy-%s', sqlalchemy.__version__) + + def test_obj_json_dirty(self): + fn = inspect.stack()[0][3] + + class SomeClass(ComplexModel): + s = Unicode + d = Double + + class SomeClass1(TableModel): + __tablename__ = "%s_%d" % (fn, 1) + _type_info = [ + ('i', Integer32(pk=True)), + ('a', SomeClass.store_as('jsonb')), + ] + + self.metadata.create_all() + + sc1 = SomeClass1(i=5, a=SomeClass(s="s", d=42.0)) + self.session.add(sc1) + self.session.commit() + + from sqlalchemy.orm.attributes import flag_modified + + # TODO: maybe do the flag_modified() on setitem? + sc1.a.s = "ss" + flag_modified(sc1, 'a') + + assert sc1 in self.session.dirty + + self.session.commit() + assert sc1.a.s == "ss" + + # not implemented + #sc1.a[0].s = "sss" + #flag_modified(sc1.a[0], 's') + #assert sc1.a[0] in self.session.dirty + + def test_schema(self): + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True, autoincrement=False) + s = Unicode(64, unique=True) + i = Integer32(64, index=True) + + t = SomeClass.__table__ + self.metadata.create_all() # not needed, just nice to see. + + assert t.c.id.primary_key == True + assert t.c.id.autoincrement == False + indexes = list(t.indexes) + indexes.sort(key=lambda idx: idx.name) + for idx in indexes: + assert 'i' in idx.columns or 's' in idx.columns + if 's' in idx.columns: + assert idx.unique + + def test_colname_simple(self): + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True, autoincrement=False) + s = Unicode(64, sqla_column_args=dict(name='ss')) + + t = SomeClass.__table__ + self.metadata.create_all() # not needed, just nice to see. + + assert 'ss' in t.c + + def test_colname_complex_table(self): + class SomeOtherClass(TableModel): + __tablename__ = 'some_other_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + s = Unicode(64) + + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = ( + {"sqlite_autoincrement": True}, + ) + + id = Integer32(primary_key=True) + o = SomeOtherClass.customize(store_as='table', + sqla_column_args=dict(name='oo')) + + t = SomeClass.__table__ + self.metadata.create_all() # not needed, just nice to see. + + assert 'oo_id' in t.c + + def test_colname_complex_json(self): + class SomeOtherClass(TableModel): + __tablename__ = 'some_other_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + s = Unicode(64) + + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = ( + {"sqlite_autoincrement": True}, + ) + + id = Integer32(primary_key=True) + o = SomeOtherClass.customize(store_as='json', + sqla_column_args=dict(name='oo')) + + t = SomeClass.__table__ + self.metadata.create_all() # not needed, just nice to see. + + assert 'oo' in t.c + + def test_nested_sql(self): + class SomeOtherClass(TableModel): + __tablename__ = 'some_other_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + s = Unicode(64) + + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = ( + {"sqlite_autoincrement": True}, + ) + + id = Integer32(primary_key=True) + o = SomeOtherClass.customize(store_as='table') + + self.metadata.create_all() + + soc = SomeOtherClass(s='ehe') + sc = SomeClass(o=soc) + + self.session.add(sc) + self.session.commit() + self.session.close() + + sc_db = self.session.query(SomeClass).get(1) + print(sc_db) + assert sc_db.o.s == 'ehe' + assert sc_db.o_id == 1 + + sc_db.o = None + self.session.commit() + self.session.close() + + sc_db = self.session.query(SomeClass).get(1) + assert sc_db.o == None + assert sc_db.o_id == None + + def test_nested_sql_array_as_table(self): + class SomeOtherClass(TableModel): + __tablename__ = 'some_other_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + s = Unicode(64) + + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + others = Array(SomeOtherClass, store_as='table') + + self.metadata.create_all() + + soc1 = SomeOtherClass(s='ehe1') + soc2 = SomeOtherClass(s='ehe2') + sc = SomeClass(others=[soc1, soc2]) + + self.session.add(sc) + self.session.commit() + self.session.close() + + sc_db = self.session.query(SomeClass).get(1) + + assert sc_db.others[0].s == 'ehe1' + assert sc_db.others[1].s == 'ehe2' + + self.session.close() + + def test_nested_sql_array_as_multi_table(self): + class SomeOtherClass(TableModel): + __tablename__ = 'some_other_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + s = Unicode(64) + + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + others = Array(SomeOtherClass, store_as=table(multi=True)) + + self.metadata.create_all() + + soc1 = SomeOtherClass(s='ehe1') + soc2 = SomeOtherClass(s='ehe2') + sc = SomeClass(others=[soc1, soc2]) + + self.session.add(sc) + self.session.commit() + self.session.close() + + sc_db = self.session.query(SomeClass).get(1) + + assert sc_db.others[0].s == 'ehe1' + assert sc_db.others[1].s == 'ehe2' + + self.session.close() + + def test_nested_sql_array_as_multi_table_with_backref(self): + class SomeOtherClass(TableModel): + __tablename__ = 'some_other_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + s = Unicode(64) + + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + others = Array(SomeOtherClass, + store_as=table(multi=True, backref='some_classes')) + + self.metadata.create_all() + + soc1 = SomeOtherClass(s='ehe1') + soc2 = SomeOtherClass(s='ehe2') + sc = SomeClass(others=[soc1, soc2]) + + self.session.add(sc) + self.session.commit() + self.session.close() + + soc_db = self.session.query(SomeOtherClass).all() + + assert soc_db[0].some_classes[0].id == 1 + assert soc_db[1].some_classes[0].id == 1 + + self.session.close() + + def test_nested_sql_array_as_xml(self): + class SomeOtherClass(ComplexModel): + id = Integer32 + s = Unicode(64) + + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + others = Array(SomeOtherClass, store_as='xml') + + self.metadata.create_all() + + soc1 = SomeOtherClass(s='ehe1') + soc2 = SomeOtherClass(s='ehe2') + sc = SomeClass(others=[soc1, soc2]) + + self.session.add(sc) + self.session.commit() + self.session.close() + + sc_db = self.session.query(SomeClass).get(1) + + assert sc_db.others[0].s == 'ehe1' + assert sc_db.others[1].s == 'ehe2' + + self.session.close() + + def test_nested_sql_array_as_xml_no_ns(self): + class SomeOtherClass(ComplexModel): + id = Integer32 + s = Unicode(64) + + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + others = Array(SomeOtherClass, store_as=xml(no_ns=True)) + + self.metadata.create_all() + + soc1 = SomeOtherClass(s='ehe1') + soc2 = SomeOtherClass(s='ehe2') + sc = SomeClass(others=[soc1, soc2]) + + self.session.add(sc) + self.session.commit() + self.session.close() + + sc_xml = self.session.connection() \ + .execute("select others from some_class") .fetchall()[0][0] + + from lxml import etree + assert etree.fromstring(sc_xml).tag == 'SomeOtherClassArray' + + self.session.close() + + def test_inheritance(self): + class SomeOtherClass(TableModel): + __tablename__ = 'some_other_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + s = Unicode(64) + + class SomeClass(SomeOtherClass): + numbers = Array(Integer32).store_as(xml(no_ns=True, root_tag='a')) + + self.metadata.create_all() + + sc = SomeClass(id=5, s='s', numbers=[1, 2, 3, 4]) + + self.session.add(sc) + self.session.commit() + self.session.close() + + sc_db = self.session.query(SomeClass).get(5) + assert sc_db.numbers == [1, 2, 3, 4] + self.session.close() + + sc_db = self.session.query(SomeOtherClass).get(5) + assert sc_db.id == 5 + try: + sc_db.numbers + except AttributeError: + pass + else: + raise Exception("must fail") + + self.session.close() + + def test_inheritance_with_complex_fields(self): + class Foo(TableModel): + __tablename__ = 'foo' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + s = Unicode(64) + + class Bar(TableModel): + __tablename__ = 'bar' + __table_args__ = {"sqlite_autoincrement": True} + __mapper_args__ = { + 'polymorphic_on': 'type', + 'polymorphic_identity': 'bar', + 'with_polymorphic': '*', + } + + id = Integer32(primary_key=True) + s = Unicode(64) + type = Unicode(6) + foos = Array(Foo).store_as('table') + + class SubBar(Bar): + __mapper_args__ = { + 'polymorphic_identity': 'subbar', + } + i = Integer32 + + sqlalchemy.orm.configure_mappers() + + mapper_subbar = SubBar.Attributes.sqla_mapper + mapper_bar = Bar.Attributes.sqla_mapper + assert not mapper_subbar.concrete + + for inheriting in mapper_subbar.iterate_to_root(): + if inheriting is not mapper_subbar \ + and not (mapper_bar.relationships['foos'] is + mapper_subbar.relationships['foos']): + raise Exception("Thou shalt stop children relationships " + "from overriding the ones in parent") + + def test_mixins_with_complex_fields(self): + class Foo(TableModel): + __tablename__ = 'foo' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + s = Unicode(64) + + class Bar(TableModel): + __tablename__ = 'bar' + __table_args__ = {"sqlite_autoincrement": True} + __mixin__ = True + __mapper_args__ = { + 'polymorphic_on': 'type', + 'polymorphic_identity': 'bar', + 'with_polymorphic': '*', + } + + id = Integer32(primary_key=True) + s = Unicode(64) + type = Unicode(6) + foos = Array(Foo).store_as('table') + + class SubBar(Bar): + __mapper_args__ = { + 'polymorphic_identity': 'subbar', + } + i = Integer32 + + sqlalchemy.orm.configure_mappers() + + mapper_subbar = SubBar.Attributes.sqla_mapper + mapper_bar = Bar.Attributes.sqla_mapper + assert not mapper_subbar.concrete + + for inheriting in mapper_subbar.iterate_to_root(): + if inheriting is not mapper_subbar \ + and not (mapper_bar.relationships['foos'] is + mapper_subbar.relationships['foos']): + raise Exception("Thou shalt stop children relationships " + "from overriding the ones in parent") + + def test_sqlalchemy_inheritance(self): + # no spyne code is involved here. + # this is just to test test the sqlalchemy behavior that we rely on. + + class Employee(object): + def __init__(self, name): + self.name = name + + def __repr__(self): + return self.__class__.__name__ + " " + self.name + + class Manager(Employee): + def __init__(self, name, manager_data): + self.name = name + self.manager_data = manager_data + + def __repr__(self): + return ( + self.__class__.__name__ + " " + + self.name + " " + self.manager_data + ) + + class Engineer(Employee): + def __init__(self, name, engineer_info): + self.name = name + self.engineer_info = engineer_info + + def __repr__(self): + return ( + self.__class__.__name__ + " " + + self.name + " " + self.engineer_info + ) + + employees_table = Table('employees', self.metadata, + Column('employee_id', sqlalchemy.Integer, primary_key=True), + Column('name', sqlalchemy.String(50)), + Column('manager_data', sqlalchemy.String(50)), + Column('engineer_info', sqlalchemy.String(50)), + Column('type', sqlalchemy.String(20), nullable=False), + ) + + employee_mapper = mapper(Employee, employees_table, + polymorphic_on=employees_table.c.type, + polymorphic_identity='employee') + + manager_mapper = mapper(Manager, inherits=employee_mapper, + polymorphic_identity='manager') + + engineer_mapper = mapper(Engineer, inherits=employee_mapper, + polymorphic_identity='engineer') + + self.metadata.create_all() + + manager = Manager('name', 'data') + self.session.add(manager) + self.session.commit() + self.session.close() + + assert self.session.query(Employee).with_polymorphic('*') \ + .filter_by(employee_id=1) \ + .one().type == 'manager' + + def test_inheritance_polymorphic_with_non_nullables_in_subclasses(self): + class SomeOtherClass(TableModel): + __tablename__ = 'some_other_class' + __table_args__ = {"sqlite_autoincrement": True} + __mapper_args__ = {'polymorphic_on': 't', 'polymorphic_identity': 1} + + id = Integer32(primary_key=True) + t = Integer32(nillable=False) + s = Unicode(64, nillable=False) + + class SomeClass(SomeOtherClass): + __mapper_args__ = ( + (), + {'polymorphic_identity': 2}, + ) + + i = Integer(nillable=False) + + self.metadata.create_all() + + assert SomeOtherClass.__table__.c.s.nullable == False + + # this should be nullable to let other classes be added. + # spyne still checks this constraint when doing input validation. + # spyne should generate a constraint to check this at database level as + # well. + assert SomeOtherClass.__table__.c.i.nullable == True + + soc = SomeOtherClass(s='s') + self.session.add(soc) + self.session.commit() + soc_id = soc.id + + try: + sc = SomeClass(i=5) + self.session.add(sc) + self.session.commit() + except IntegrityError: + self.session.rollback() + else: + raise Exception("Must fail with IntegrityError.") + + sc2 = SomeClass(s='s') # this won't fail. should it? + self.session.add(sc2) + self.session.commit() + + self.session.expunge_all() + + assert self.session.query(SomeOtherClass).with_polymorphic('*') \ + .filter_by(id=soc_id).one().t == 1 + + self.session.close() + + def test_inheritance_polymorphic(self): + class SomeOtherClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + __mapper_args__ = {'polymorphic_on': 't', 'polymorphic_identity': 1} + + id = Integer32(primary_key=True) + s = Unicode(64) + t = Integer32(nillable=False) + + class SomeClass(SomeOtherClass): + __mapper_args__ = {'polymorphic_identity': 2} + numbers = Array(Integer32).store_as(xml(no_ns=True, root_tag='a')) + + self.metadata.create_all() + + sc = SomeClass(id=5, s='s', numbers=[1, 2, 3, 4]) + + self.session.add(sc) + self.session.commit() + self.session.close() + + assert self.session.query(SomeOtherClass).with_polymorphic('*') \ + .filter_by(id=5).one().t == 2 + self.session.close() + + def test_nested_sql_array_as_json(self): + class SomeOtherClass(ComplexModel): + id = Integer32 + s = Unicode(64) + + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + others = Array(SomeOtherClass, store_as='json') + + self.metadata.create_all() + + soc1 = SomeOtherClass(s='ehe1') + soc2 = SomeOtherClass(s='ehe2') + sc = SomeClass(others=[soc1, soc2]) + + self.session.add(sc) + self.session.commit() + self.session.close() + + sc_db = self.session.query(SomeClass).get(1) + + assert sc_db.others[0].s == 'ehe1' + assert sc_db.others[1].s == 'ehe2' + + self.session.close() + + def test_modifiers(self): + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + + i = XmlAttribute(Integer32(pk=True)) + s = XmlData(Unicode(64)) + + self.metadata.create_all() + self.session.add(SomeClass(s='s')) + self.session.commit() + self.session.expunge_all() + + ret = self.session.query(SomeClass).get(1) + assert ret.i == 1 # redundant + assert ret.s == 's' + + def test_default_ctor(self): + class SomeOtherClass(ComplexModel): + id = Integer32 + s = Unicode(64) + + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + others = Array(SomeOtherClass, store_as='json') + f = Unicode(32, default='uuu') + + self.metadata.create_all() + self.session.add(SomeClass()) + self.session.commit() + self.session.expunge_all() + + assert self.session.query(SomeClass).get(1).f == 'uuu' + + def test_default_value(self): + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + f = Unicode(32, db_default=u'uuu') + + self.metadata.create_all() + val = SomeClass() + assert val.f is None + + self.session.add(val) + self.session.commit() + + self.session.expunge_all() + + assert self.session.query(SomeClass).get(1).f == u'uuu' + + def test_default_ctor_with_sql_relationship(self): + class SomeOtherClass(TableModel): + __tablename__ = 'some_other_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + s = Unicode(64) + + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + o = SomeOtherClass.customize(store_as='table') + + self.metadata.create_all() + self.session.add(SomeClass()) + self.session.commit() + + def test_store_as_index(self): + class SomeOtherClass(TableModel): + __tablename__ = 'some_other_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + s = Unicode(64) + + class SomeClass(TableModel): + __tablename__ = 'some_class' + __table_args__ = {"sqlite_autoincrement": True} + + id = Integer32(primary_key=True) + o = SomeOtherClass.customize(store_as='table', index='btree') + + self.metadata.create_all() + idx, = SomeClass.__table__.indexes + assert 'o_id' in idx.columns + + def test_scalar_collection(self): + class SomeClass(TableModel): + __tablename__ = 'some_class' + + id = Integer32(primary_key=True) + values = Array(Unicode).store_as('table') + + self.metadata.create_all() + + self.session.add(SomeClass(id=1, values=['a', 'b', 'c'])) + self.session.commit() + sc = self.session.query(SomeClass).get(1) + assert sc.values == ['a', 'b', 'c'] + del sc + + sc = self.session.query(SomeClass).get(1) + sc.values.append('d') + self.session.commit() + del sc + sc = self.session.query(SomeClass).get(1) + assert sc.values == ['a', 'b', 'c', 'd'] + + sc = self.session.query(SomeClass).get(1) + sc.values = sc.values[1:] + self.session.commit() + del sc + sc = self.session.query(SomeClass).get(1) + assert sc.values == ['b', 'c', 'd'] + + def test_multiple_fk(self): + class SomeChildClass(TableModel): + __tablename__ = 'some_child_class' + + id = Integer32(primary_key=True) + s = Unicode(64) + i = Integer32 + + class SomeClass(TableModel): + __tablename__ = 'some_class' + + id = Integer32(primary_key=True) + children = Array(SomeChildClass).store_as('table') + mirror = SomeChildClass.store_as('table') + + self.metadata.create_all() + + children = [ + SomeChildClass(s='p', i=600), + SomeChildClass(s='|', i=10), + SomeChildClass(s='q', i=9), + ] + + sc = SomeClass(children=children) + self.session.add(sc) + self.session.flush() + sc.mirror = children[1] + self.session.commit() + del sc + + sc = self.session.query(SomeClass).get(1) + assert ''.join([scc.s for scc in sc.children]) == 'p|q' + assert sum([scc.i for scc in sc.children]) == 619 + + def test_simple_fk(self): + class SomeChildClass(TableModel): + __tablename__ = 'some_child_class' + + id = Integer32(primary_key=True) + s = Unicode(64) + i = Integer32 + + class SomeClass(TableModel): + __tablename__ = 'some_class' + + id = Integer32(primary_key=True) + child_id = Integer32(fk='some_child_class.id') + + foreign_keys = SomeClass.__table__.c['child_id'].foreign_keys + assert len(foreign_keys) == 1 + fk, = foreign_keys + assert fk._colspec == 'some_child_class.id' + + def test_multirel_single_table(self): + class SomeChildClass(TableModel): + __tablename__ = 'some_child_class' + + id = Integer32(primary_key=True) + s = Unicode(64) + + class SomeOtherChildClass(TableModel): + __tablename__ = 'some_other_child_class' + + id = Integer32(primary_key=True) + i = Integer32 + + class SomeClass(TableModel): + __tablename__ = 'some_class' + + id = Integer32(primary_key=True) + + children = Array(SomeChildClass, + store_as=table( + multi='children', lazy='joined', + left='parent_id', right='child_id', + fk_left_ondelete='cascade', + fk_right_ondelete='cascade', + ), + ) + + other_children = Array(SomeOtherChildClass, + store_as=table( + multi='children', lazy='joined', + left='parent_id', right='other_child_id', + fk_left_ondelete='cascade', + fk_right_ondelete='cascade', + ), + ) + + t = SomeClass.Attributes.sqla_metadata.tables['children'] + + fkp, = t.c.parent_id.foreign_keys + assert fkp._colspec == 'some_class.id' + + fkc, = t.c.child_id.foreign_keys + assert fkc._colspec == 'some_child_class.id' + + fkoc, = t.c.other_child_id.foreign_keys + assert fkoc._colspec == 'some_other_child_class.id' + + def test_reflection(self): + class SomeClass(TableModel): + __tablename__ = 'some_class' + + id = Integer32(primary_key=True) + s = Unicode(32) + + TableModel.Attributes.sqla_metadata.create_all() + + # create a new table model with empty metadata + TM2 = TTableModel() + TM2.Attributes.sqla_metadata.bind = self.engine + + # fill it with information from the db + TM2.Attributes.sqla_metadata.reflect() + + # convert sqla info to spyne info + class Reflected(TM2): + __table__ = TM2.Attributes.sqla_metadata.tables['some_class'] + + pprint(dict(Reflected._type_info).items()) + assert issubclass(Reflected._type_info['id'], Integer) + + # this looks at spyne attrs + assert [k for k, v in get_pk_columns(Reflected)] == ['id'] + + # this looks at sqla attrs + assert [k for k, v in Reflected.get_primary_keys()] == ['id'] + + assert issubclass(Reflected._type_info['s'], Unicode) + assert Reflected._type_info['s'].Attributes.max_len == 32 + + def _test_sqlalchemy_remapping(self): + class SomeTable(TableModel): + __tablename__ = 'some_table' + id = Integer32(pk=True) + i = Integer32 + s = Unicode(32) + + class SomeTableSubset(TableModel): + __table__ = SomeTable.__table__ + + id = Integer32(pk=True) # sqla session doesn't work without pk + i = Integer32 + + class SomeTableOtherSubset(TableModel): + __table__ = SomeTable.__table__ + _type_info = [(k, v) for k, v in SomeTable._type_info.items() + if k in ('id', 's')] + + self.session.add(SomeTable(id=1, i=2, s='s')) + self.session.commit() + + st = self.session.query(SomeTable).get(1) + sts = self.session.query(SomeTableSubset).get(1) + stos = self.session.query(SomeTableOtherSubset).get(1) + + sts.i = 3 + sts.s = 'ss' # will not be flushed to db + self.session.commit() + + assert st.s == 's' + assert stos.i == 3 + + def test_file_storage(self): + class C(TableModel): + __tablename__ = "c" + + id = Integer32(pk=True) + f = File(store_as=HybridFileStore('test_file_storage', 'json')) + + self.metadata.create_all() + c = C(f=File.Value(name=u"name", type=u"type", data=[b"data"])) + self.session.add(c) + self.session.flush() + self.session.commit() + + c = self.session.query(C).get(1) + print(c) + assert c.f.name == "name" + assert c.f.type == "type" + assert c.f.data[0][:] == b"data" + + def test_append_field_complex_existing_column(self): + class C(TableModel): + __tablename__ = "c" + u = Unicode(pk=True) + + class D(TableModel): + __tablename__ = "d" + d = Integer32(pk=True) + c = C.store_as('table') + + C.append_field('d', D.store_as('table')) + assert C.Attributes.sqla_mapper.get_property('d').argument is D + + def test_append_field_complex_delayed(self): + class C(TableModel): + __tablename__ = "c" + u = Unicode(pk=True) + + class D(C): + i = Integer32 + + C.append_field('d', DateTime) + + assert D.Attributes.sqla_mapper.has_property('d') + + def _test_append_field_complex_explicit_existing_column(self): + # FIXME: Test something! + + class C(TableModel): + __tablename__ = "c" + id = Integer32(pk=True) + + # c already also produces c_id. this is undefined behaviour, one of them + # gets ignored, whichever comes first. + class D(TableModel): + __tablename__ = "d" + id = Integer32(pk=True) + c = C.store_as('table') + c_id = Integer32(15) + + def test_append_field_complex_circular_array(self): + class C(TableModel): + __tablename__ = "cc" + id = Integer32(pk=True) + + class D(TableModel): + __tablename__ = "dd" + id = Integer32(pk=True) + c = Array(C).customize(store_as=table(right='dd_id')) + + C.append_field('d', D.customize(store_as=table(left='dd_id'))) + self.metadata.create_all() + + c1, c2 = C(id=1), C(id=2) + d = D(id=1, c=[c1, c2]) + self.session.add(d) + self.session.commit() + assert c1.d.id == 1 + + def test_append_field_complex_new_column(self): + class C(TableModel): + __tablename__ = "c" + u = Unicode(pk=True) + + class D(TableModel): + __tablename__ = "d" + id = Integer32(pk=True) + + C.append_field('d', D.store_as('table')) + assert C.Attributes.sqla_mapper.get_property('d').argument is D + assert isinstance(C.Attributes.sqla_table.c['d_id'].type, + sqlalchemy.Integer) + + def test_append_field_array(self): + class C(TableModel): + __tablename__ = "c" + id = Integer32(pk=True) + + class D(TableModel): + __tablename__ = "d" + id = Integer32(pk=True) + + C.append_field('d', Array(D).store_as('table')) + assert C.Attributes.sqla_mapper.get_property('d').argument is D + print(repr(D.Attributes.sqla_table)) + assert isinstance(D.Attributes.sqla_table.c['c_id'].type, + sqlalchemy.Integer) + + def test_append_field_array_many(self): + class C(TableModel): + __tablename__ = "c" + id = Integer32(pk=True) + + class D(TableModel): + __tablename__ = "d" + id = Integer32(pk=True) + + C.append_field('d', Array(D).store_as(table(multi='c_d'))) + assert C.Attributes.sqla_mapper.get_property('d').argument is D + rel_table = C.Attributes.sqla_metadata.tables['c_d'] + assert 'c_id' in rel_table.c + assert 'd_id' in rel_table.c + + def test_append_field_complex_cust(self): + class C(TableModel): + __tablename__ = "c" + id = Integer32(pk=True) + + class D(TableModel): + __tablename__ = "d" + id = Integer32(pk=True) + c = Array(C).store_as('table') + + C.append_field('d', D.customize( + nullable=False, + store_as=table(left='d_id'), + )) + assert C.__table__.c['d_id'].nullable == False + + def _test_append_field_cust(self): + class C(TableModel): + __tablename__ = "c" + id = Integer32(pk=True) + + C2 = C.customize() + + C.append_field("s", Unicode) + + C() + + self.metadata.create_all() + + assert "s" in C2._type_info + assert "s" in C2.Attributes.sqla_mapper.columns + + self.session.add(C2(s='foo')) + self.session.commit() + assert self.session.query(C).first().s == 'foo' + + def test_polymorphic_cust(self): + class C(TableModel): + __tablename__ = "c" + __mapper_args__ = { + 'polymorphic_on': 't', + 'polymorphic_identity': 1, + } + + id = Integer32(pk=True) + t = M(Integer32) + + class D(C): + __mapper_args__ = { + 'polymorphic_identity': 2, + } + d = Unicode + + D2 = D.customize() + + assert C().t == 1 + assert D().t == 2 + + # That's the way SQLAlchemy works. Don't use customized classes in + # anywhere other than interface definitions + assert D2().t == None + + def test_base_append_simple(self): + class B(TableModel): + __tablename__ = 'b' + __mapper_args__ = { + 'polymorphic_on': 't', + 'polymorphic_identity': 1, + } + + id = Integer32(pk=True) + t = M(Integer32) + + class C(B): + __mapper_args__ = { + 'polymorphic_identity': 1, + } + s = Unicode + + B.append_field('i', Integer32) + + self.metadata.create_all() + + self.session.add(C(s="foo", i=42)) + self.session.commit() + + c = self.session.query(C).first() + + assert c.s == 'foo' + assert c.i == 42 + assert c.t == 1 + + def test_base_append_complex(self): + class B(TableModel): + __tablename__ = 'b' + __mapper_args__ = { + 'polymorphic_on': 't', + 'polymorphic_identity': 1, + } + + id = Integer32(pk=True) + t = M(Integer32) + + class C(B): + __mapper_args__ = { + 'polymorphic_identity': 1, + } + s = Unicode + + class D(TableModel): + __tablename__ = 'd' + id = Integer32(pk=True) + i = M(Integer32) + + B.append_field('d', D.store_as('table')) + + self.metadata.create_all() + + self.session.add(C(d=D(i=42))) + self.session.commit() + + c = self.session.query(C).first() + + assert c.d.i == 42 + + +class TestSqlAlchemySchemaWithPostgresql(unittest.TestCase): + def setUp(self): + self.metadata = TableModel.Attributes.sqla_metadata = MetaData() + + def test_enum(self): + table_name = "test_enum" + + enums = ('SUBSCRIBED', 'UNSUBSCRIBED', 'UNCONFIRMED') + + class SomeClass(TableModel): + __tablename__ = table_name + + id = Integer32(primary_key=True) + e = Enum(*enums, type_name='status_choices') + + t = self.metadata.tables[table_name] + assert 'e' in t.c + assert tuple(t.c.e.type.enums) == enums + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/test_sqlalchemy.pyc b/pym/calculate/contrib/spyne/test/test_sqlalchemy.pyc new file mode 100644 index 0000000..e0cc881 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/test_sqlalchemy.pyc differ diff --git a/pym/calculate/contrib/spyne/test/test_sqlalchemy_deprecated.py b/pym/calculate/contrib/spyne/test/test_sqlalchemy_deprecated.py new file mode 100644 index 0000000..8607a06 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/test_sqlalchemy_deprecated.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import logging +logging.basicConfig(level=logging.DEBUG) + +import unittest +import sqlalchemy + +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm.mapper import Mapper + +from sqlalchemy import create_engine +from sqlalchemy import MetaData +from sqlalchemy import Column +from sqlalchemy import Table +from sqlalchemy import ForeignKey + +from sqlalchemy.orm import mapper +from sqlalchemy.orm import relationship +from sqlalchemy.orm import sessionmaker + +from sqlalchemy.schema import UniqueConstraint + +from spyne.application import Application +from spyne.decorator import rpc +from spyne.model import ComplexModel +from spyne.model import Array +from spyne.model import Unicode +from spyne.model import Integer +from spyne.model.table import TableModel +from spyne.protocol.http import HttpRpc +from spyne.protocol.soap import Soap11 +from spyne.server.wsgi import WsgiApplication +from spyne.server.wsgi import WsgiMethodContext + +# +# Deprecated Table Model Tests +# + +class TestSqlAlchemy(unittest.TestCase): + def setUp(self): + self.metadata = MetaData() + self.DeclarativeBase = declarative_base(metadata=self.metadata) + self.engine = create_engine('sqlite:///:memory:', echo=True) + self.Session = sessionmaker(bind=self.engine) + + def tearDown(self): + del self.metadata + del self.DeclarativeBase + del self.engine + del self.Session + + def test_declarative(self): + from sqlalchemy import Integer + from sqlalchemy import String + + class DbObject(TableModel, self.DeclarativeBase): + __tablename__ = 'db_object' + + id = Column(Integer, primary_key=True) + s = Column(String) + + self.metadata.create_all(self.engine) + + def test_mapper(self): + import sqlalchemy + + class Address(self.DeclarativeBase): + __tablename__ = 'address' + + id = Column(sqlalchemy.Integer, primary_key=True) + email = Column(sqlalchemy.String(50)) + user_id = Column(sqlalchemy.Integer, ForeignKey('user.id')) + + class User(self.DeclarativeBase): + __tablename__ = 'user' + + id = Column(sqlalchemy.Integer, primary_key=True) + name = Column(sqlalchemy.String(50)) + addresses = relationship("Address", backref="user") + + self.metadata.create_all(self.engine) + + import spyne.model.primitive + + class AddressDetail(ComplexModel): + id = spyne.model.primitive.Integer + user_name = spyne.model.primitive.String + address = spyne.model.primitive.String + + @classmethod + def mapper(cls, meta): + user_t = meta.tables['user'] + address_t = meta.tables['address'] + + cls._main_t = user_t.join(address_t) + + cls._properties = { + 'id': address_t.c.id, + 'user_name': user_t.c.name, + 'address': address_t.c.email, + } + + cls._mapper = mapper(cls, cls._main_t, + include_properties=cls._properties.values(), + properties=cls._properties, + primary_key=[address_t.c.id] + ) + + AddressDetail.mapper(self.metadata) + + def test_custom_mapper(self): + class CustomMapper(Mapper): + def __init__(self, class_, local_table, *args, **kwargs): + super(CustomMapper, self).__init__(class_, local_table, *args, + **kwargs) + + # Do not configure primary keys to check that CustomerMapper is + # actually used + def _configure_pks(self): + pass + + def custom_mapper(class_, local_table=None, *args, **params): + return CustomMapper(class_, local_table, *args, **params) + + CustomDeclarativeBase = declarative_base(metadata=self.metadata, + mapper=custom_mapper) + + class User(CustomDeclarativeBase): + __tablename__ = 'user' + + # CustomMapper should not fail because of no primary key + name = Column(sqlalchemy.String(50)) + + self.metadata.create_all(self.engine) + + def test_rpc(self): + import sqlalchemy + from sqlalchemy import sql + + class KeyValuePair(TableModel, self.DeclarativeBase): + __tablename__ = 'key_value_store' + __namespace__ = 'punk' + + key = Column(sqlalchemy.String(100), nullable=False, primary_key=True) + value = Column(sqlalchemy.String, nullable=False) + + self.metadata.create_all(self.engine) + + import hashlib + + session = self.Session() + + for i in range(1, 10): + key = str(i).encode() + m = hashlib.md5() + m.update(key) + value = m.hexdigest() + + session.add(KeyValuePair(key=key, value=value)) + + session.commit() + + from spyne.service import Service + from spyne.model.complex import Array + from spyne.model.primitive import String + + class Service(Service): + @rpc(String(max_occurs='unbounded'), + _returns=Array(KeyValuePair), + _in_variable_names={ + 'keys': 'key' + } + ) + def get_values(ctx, keys): + session = self.Session() + + return session.query(KeyValuePair).filter(sql.and_( + KeyValuePair.key.in_(keys) + )).order_by(KeyValuePair.key) + + application = Application([Service], + in_protocol=HttpRpc(), + out_protocol=Soap11(), + name='Service', tns='tns' + ) + server = WsgiApplication(application) + + initial_ctx = WsgiMethodContext(server, { + 'REQUEST_METHOD': 'GET', + 'QUERY_STRING': 'key=1&key=2&key=3', + 'PATH_INFO': '/get_values', + 'SERVER_NAME': 'localhost', + }, 'some-content-type') + + ctx, = server.generate_contexts(initial_ctx) + server.get_in_object(ctx) + server.get_out_object(ctx) + server.get_out_string(ctx) + + i = 0 + for e in ctx.out_document[0][0][0]: + i+=1 + key = str(i) + m = hashlib.md5() + m.update(key) + value = m.hexdigest() + + _key = e.find('{%s}key' % KeyValuePair.get_namespace()) + _value = e.find('{%s}value' % KeyValuePair.get_namespace()) + + print((_key, _key.text)) + print((_value, _value.text)) + + self.assertEqual(_key.text, key) + self.assertEqual(_value.text, value) + + def test_late_mapping(self): + import sqlalchemy + + user_t = Table('user', self.metadata, + Column('id', sqlalchemy.Integer, primary_key=True), + Column('name', sqlalchemy.String), + ) + + class User(TableModel, self.DeclarativeBase): + __table__ = user_t + + self.assertEqual(User._type_info['id'].__type_name__, 'integer') + self.assertEqual(User._type_info['name'].__type_name__, 'string') + + + def test_default_ctor(self): + import sqlalchemy + + class User1Mixin(object): + id = Column(sqlalchemy.Integer, primary_key=True) + name = Column(sqlalchemy.String(256)) + + class User1(self.DeclarativeBase, TableModel, User1Mixin): + __tablename__ = 'spyne_user1' + + mail = Column(sqlalchemy.String(256)) + + u = User1(id=1, mail="a@b.com", name='dummy') + + assert u.id == 1 + assert u.mail == "a@b.com" + assert u.name == "dummy" + + class User2Mixin(object): + id = Column(sqlalchemy.Integer, primary_key=True) + name = Column(sqlalchemy.String(256)) + + class User2(TableModel, self.DeclarativeBase, User2Mixin): + __tablename__ = 'spyne_user2' + + mail = Column(sqlalchemy.String(256)) + + u = User2(id=1, mail="a@b.com", name='dummy') + + assert u.id == 1 + assert u.mail == "a@b.com" + assert u.name == "dummy" + + def test_mixin_inheritance(self): + import sqlalchemy + + class UserMixin(object): + id = Column(sqlalchemy.Integer, primary_key=True) + name = Column(sqlalchemy.String(256)) + + class User(self.DeclarativeBase, TableModel, UserMixin): + __tablename__ = 'spyne_user_mixin' + + mail = Column(sqlalchemy.String(256)) + + assert 'mail' in User._type_info + assert 'name' in User._type_info + assert 'id' in User._type_info + + def test_same_table_inheritance(self): + import sqlalchemy + + class User(self.DeclarativeBase, TableModel): + __tablename__ = 'spyne_user_sti' + + id = Column(sqlalchemy.Integer, primary_key=True) + name = Column(sqlalchemy.String(256)) + + class UserMail(User): + mail = Column(sqlalchemy.String(256)) + + assert 'mail' in UserMail._type_info + assert 'name' in UserMail._type_info + assert 'id' in UserMail._type_info + + def test_relationship_array(self): + import sqlalchemy + class Permission(TableModel, self.DeclarativeBase): + __tablename__ = 'spyne_user_permission' + + id = Column(sqlalchemy.Integer, primary_key=True) + user_id = Column(sqlalchemy.Integer, ForeignKey("spyne_user.id")) + + + class User(TableModel, self.DeclarativeBase): + __tablename__ = 'spyne_user' + + id = Column(sqlalchemy.Integer, primary_key=True) + permissions = relationship(Permission) + + class Address(self.DeclarativeBase, TableModel): + __tablename__ = 'spyne_address' + + id = Column(sqlalchemy.Integer, primary_key=True) + address = Column(sqlalchemy.String(256)) + user_id = Column(sqlalchemy.Integer, ForeignKey(User.id), nullable=False) + user = relationship(User) + + assert 'permissions' in User._type_info + assert issubclass(User._type_info['permissions'], Array) + assert issubclass(User._type_info['permissions']._type_info.values()[0], Permission) + + #Address().user = None + #User().permissions = None # This fails, and actually is supposed to fail. + + +class TestSpyne2Sqlalchemy(unittest.TestCase): + def test_table(self): + class SomeClass(ComplexModel): + __metadata__ = MetaData() + __tablename__ = 'some_class' + + i = Integer(primary_key=True) + + t = SomeClass.Attributes.sqla_table + assert t.c['i'].type.__class__ is sqlalchemy.DECIMAL + + def test_table_args(self): + class SomeClass(ComplexModel): + __metadata__ = MetaData() + __tablename__ = 'some_class' + __table_args__ = ( + UniqueConstraint('j'), + ) + + i = Integer(primary_key=True) + j = Unicode(64) + + t = SomeClass.Attributes.sqla_table + assert isinstance(t.c['j'].type, sqlalchemy.String) + + for c in t.constraints: + if isinstance(c, UniqueConstraint): + assert list(c.columns) == [t.c.j] + break + else: + raise Exception("UniqueConstraint is missing.") + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/test_sqlalchemy_deprecated.pyc b/pym/calculate/contrib/spyne/test/test_sqlalchemy_deprecated.pyc new file mode 100644 index 0000000..32522e5 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/test_sqlalchemy_deprecated.pyc differ diff --git a/pym/calculate/contrib/spyne/test/transport/__init__.py b/pym/calculate/contrib/spyne/test/transport/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pym/calculate/contrib/spyne/test/transport/__init__.pyc b/pym/calculate/contrib/spyne/test/transport/__init__.pyc new file mode 100644 index 0000000..8a753cd Binary files /dev/null and b/pym/calculate/contrib/spyne/test/transport/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/test/transport/test_msgpack.py b/pym/calculate/contrib/spyne/test/transport/test_msgpack.py new file mode 100644 index 0000000..c94174a --- /dev/null +++ b/pym/calculate/contrib/spyne/test/transport/test_msgpack.py @@ -0,0 +1,94 @@ + +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import msgpack + +from spyne import Application, Service, rpc +from spyne.model import Unicode +from spyne.protocol.msgpack import MessagePackDocument + +from twisted.trial import unittest + + +class TestMessagePackServer(unittest.TestCase): + def gen_prot(self, app): + from spyne.server.twisted.msgpack import TwistedMessagePackProtocol + from twisted.test.proto_helpers import StringTransportWithDisconnection + from spyne.server.msgpack import MessagePackServerBase + + prot = TwistedMessagePackProtocol(MessagePackServerBase(app)) + transport = StringTransportWithDisconnection() + prot.makeConnection(transport) + transport.protocol = prot + + return prot + + def test_roundtrip(self): + v = "yaaay!" + class SomeService(Service): + @rpc(Unicode, _returns=Unicode) + def yay(ctx, u): + return u + + app = Application([SomeService], 'tns', + in_protocol=MessagePackDocument(), + out_protocol=MessagePackDocument()) + + prot = self.gen_prot(app) + request = msgpack.packb({'yay': [v]}) + prot.dataReceived(msgpack.packb([1, request])) + val = prot.transport.value() + print(repr(val)) + val = msgpack.unpackb(val) + print(repr(val)) + + self.assertEqual(val, [0, msgpack.packb(v)]) + + def test_roundtrip_deferred(self): + from twisted.internet import reactor + from twisted.internet.task import deferLater + + v = "yaaay!" + p_ctx = [] + class SomeService(Service): + @rpc(Unicode, _returns=Unicode) + def yay(ctx, u): + def _cb(): + return u + p_ctx.append(ctx) + return deferLater(reactor, 0.1, _cb) + + app = Application([SomeService], 'tns', + in_protocol=MessagePackDocument(), + out_protocol=MessagePackDocument()) + + prot = self.gen_prot(app) + request = msgpack.packb({'yay': [v]}) + def _ccb(_): + val = prot.transport.value() + print(repr(val)) + val = msgpack.unpackb(val) + print(repr(val)) + + self.assertEqual(val, [0, msgpack.packb(v)]) + + prot.dataReceived(msgpack.packb([1, request])) + + return p_ctx[0].out_object[0].addCallback(_ccb) + diff --git a/pym/calculate/contrib/spyne/test/transport/test_msgpack.pyc b/pym/calculate/contrib/spyne/test/transport/test_msgpack.pyc new file mode 100644 index 0000000..7463f8d Binary files /dev/null and b/pym/calculate/contrib/spyne/test/transport/test_msgpack.pyc differ diff --git a/pym/calculate/contrib/spyne/test/util/__init__.py b/pym/calculate/contrib/spyne/test/util/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pym/calculate/contrib/spyne/test/util/__init__.pyc b/pym/calculate/contrib/spyne/test/util/__init__.pyc new file mode 100644 index 0000000..f04c8a8 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/util/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/test/util/test_address.py b/pym/calculate/contrib/spyne/test/util/test_address.py new file mode 100644 index 0000000..04f8918 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/util/test_address.py @@ -0,0 +1,639 @@ +#!/usr/bin/env python +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +# The MIT License +# +# Copyright (c) Val Neekman @ Neekware Inc. http://neekware.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 unittest import TestCase + + +from spyne.util.address import set_address_parser_settings + +set_address_parser_settings(trusted_proxies=['177.139.233.100']) + +from spyne.util.address import address_parser + + +class IPv4TestCase(TestCase): + """IP address Test""" + + def test_meta_none(self): + request = { + } + ip = address_parser.get_real_ip(request) + self.assertIsNone(ip) + + def test_http_x_forwarded_for_multiple(self): + request = { + 'HTTP_X_FORWARDED_FOR': '192.168.255.182, 10.0.0.0, 127.0.0.1, 198.84.193.157, 177.139.233.139', + 'HTTP_X_REAL_IP': '177.139.233.132', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "198.84.193.157") + + def test_http_x_forwarded_for_multiple_left_most_ip(self): + request = { + 'HTTP_X_FORWARDED_FOR': '192.168.255.182, 198.84.193.157, 10.0.0.0, 127.0.0.1, 177.139.233.139', + 'HTTP_X_REAL_IP': '177.139.233.132', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "198.84.193.157") + + def test_http_x_forwarded_for_multiple_right_most_ip(self): + request = { + 'HTTP_X_FORWARDED_FOR': '192.168.255.182, 198.84.193.157, 10.0.0.0, 127.0.0.1, 177.139.233.139', + 'HTTP_X_REAL_IP': '177.139.233.132', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request, right_most_proxy=True) + self.assertEqual(ip, "177.139.233.139") + + def test_http_x_forwarded_for_multiple_right_most_ip_private(self): + request = { + 'HTTP_X_FORWARDED_FOR': '192.168.255.182, 198.84.193.157, 10.0.0.0, 127.0.0.1, 177.139.233.139', + 'HTTP_X_REAL_IP': '177.139.233.132', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request, right_most_proxy=True) + self.assertEqual(ip, "177.139.233.139") + + def test_http_x_forwarded_for_multiple_bad_address(self): + request = { + 'HTTP_X_FORWARDED_FOR': 'unknown, 192.168.255.182, 10.0.0.0, 127.0.0.1, 198.84.193.157, 177.139.233.139', + 'HTTP_X_REAL_IP': '177.139.233.132', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "198.84.193.157") + + def test_http_x_forwarded_for_singleton(self): + request = { + 'HTTP_X_FORWARDED_FOR': '177.139.233.139', + 'HTTP_X_REAL_IP': '177.139.233.132', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.139") + + def test_http_x_forwarded_for_singleton_private_address(self): + request = { + 'HTTP_X_FORWARDED_FOR': '192.168.255.182', + 'HTTP_X_REAL_IP': '177.139.233.132', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.132") + + def test_bad_http_x_forwarded_for_fallback_on_x_real_ip(self): + request = { + 'HTTP_X_FORWARDED_FOR': 'unknown 177.139.233.139', + 'HTTP_X_REAL_IP': '177.139.233.132', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.132") + + def test_empty_http_x_forwarded_for_fallback_on_x_real_ip(self): + request = { + 'HTTP_X_FORWARDED_FOR': '', + 'HTTP_X_REAL_IP': '177.139.233.132', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.132") + + def test_empty_http_x_forwarded_for_empty_x_real_ip_fallback_on_remote_addr(self): + request = { + 'HTTP_X_FORWARDED_FOR': '', + 'HTTP_X_REAL_IP': '', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.133") + + def test_empty_http_x_forwarded_for_private_x_real_ip_fallback_on_remote_addr(self): + request = { + 'HTTP_X_FORWARDED_FOR': '', + 'HTTP_X_REAL_IP': '192.168.255.182', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.133") + + def test_private_http_x_forward_for_ip_addr(self): + request = { + 'HTTP_X_FORWARDED_FOR': '127.0.0.1', + 'HTTP_X_REAL_IP': '', + 'REMOTE_ADDR': '', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, None) + + def test_private_remote_addr_for_ip_addr(self): + request = { + 'HTTP_X_FORWARDED_FOR': '', + 'REMOTE_ADDR': '127.0.0.1', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, None) + + def test_missing_x_forwarded(self): + request = { + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.133") + + def test_missing_x_forwarded_missing_real_ip(self): + request = { + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.133") + + def test_best_matched_real_ip(self): + request = { + 'HTTP_X_REAL_IP': '127.0.0.1', + 'REMOTE_ADDR': '172.31.233.133', + } + ip = address_parser.get_ip(request) + self.assertEqual(ip, "172.31.233.133") + + def test_best_matched_private_ip(self): + request = { + 'HTTP_X_REAL_IP': '127.0.0.1', + 'REMOTE_ADDR': '192.31.233.133', + } + ip = address_parser.get_ip(request) + self.assertEqual(ip, "192.31.233.133") + + def test_best_matched_private_ip_2(self): + request = { + 'HTTP_X_REAL_IP': '192.31.233.133', + 'REMOTE_ADDR': '127.0.0.1', + } + ip = address_parser.get_ip(request) + self.assertEqual(ip, "192.31.233.133") + + def test_x_forwarded_for_multiple(self): + request = { + 'X_FORWARDED_FOR': '192.168.255.182, 10.0.0.0, 127.0.0.1, 198.84.193.157, 177.139.233.139', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "198.84.193.157") + + def test_x_forwarded_for_multiple_left_most_ip(self): + request = { + 'X_FORWARDED_FOR': '192.168.255.182, 198.84.193.157, 10.0.0.0, 127.0.0.1, 177.139.233.139', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "198.84.193.157") + + def test_x_forwarded_for_multiple_right_most_ip(self): + request = { + 'X_FORWARDED_FOR': '192.168.255.182, 198.84.193.157, 10.0.0.0, 127.0.0.1, 177.139.233.139', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request, right_most_proxy=True) + self.assertEqual(ip, "177.139.233.139") + + def test_x_forwarded_for_multiple_right_most_ip_private(self): + request = { + 'X_FORWARDED_FOR': '192.168.255.182, 198.84.193.157, 10.0.0.0, 127.0.0.1, 177.139.233.139', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request, right_most_proxy=True) + self.assertEqual(ip, "177.139.233.139") + + def test_x_forwarded_for_multiple_bad_address(self): + request = { + 'X_FORWARDED_FOR': 'unknown, 192.168.255.182, 10.0.0.0, 127.0.0.1, 198.84.193.157, 177.139.233.139', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "198.84.193.157") + + def test_x_forwarded_for_singleton(self): + request = { + 'X_FORWARDED_FOR': '177.139.233.139', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.139") + + def test_x_forwarded_for_singleton_private_address(self): + request = { + 'X_FORWARDED_FOR': '192.168.255.182', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.133") + + def test_bad_x_forwarded_for_fallback_on_x_real_ip(self): + request = { + 'X_FORWARDED_FOR': 'unknown 177.139.233.139', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.133") + + def test_empty_x_forwarded_for_fallback_on_x_real_ip(self): + request = { + 'X_FORWARDED_FOR': '', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.133") + + def test_empty_x_forwarded_for_empty_x_real_ip_fallback_on_remote_addr(self): + request = { + 'X_FORWARDED_FOR': '', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.133") + + def test_empty_x_forwarded_for_private_x_real_ip_fallback_on_remote_addr(self): + request = { + 'X_FORWARDED_FOR': '', + 'REMOTE_ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.133") + + def test_private_x_forward_for_ip_addr(self): + request = { + 'X_FORWARDED_FOR': '127.0.0.1', + 'REMOTE_ADDR': '', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, None) + + def test_x_forwarded_for_singleton_hyphen_as_delimiter(self): + request = { + 'X-FORWARDED-FOR': '177.139.233.139', + 'REMOTE-ADDR': '177.139.233.133', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "177.139.233.139") + + +class IPv4TrustedProxiesTestCase(TestCase): + """Trusted Proxies - IP address Test""" + + def test_meta_none(self): + request = { + } + ip = address_parser.get_trusted_ip(request) + self.assertIsNone(ip) + + def test_http_x_forwarded_for_conf_settings(self): + request = { + 'HTTP_X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.100', + } + + ip = address_parser.get_trusted_ip(request) + self.assertEqual(ip, "198.84.193.157") + + def test_http_x_forwarded_for_no_proxy(self): + request = { + 'HTTP_X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', + } + ip = address_parser.get_trusted_ip(request, trusted_proxies=[]) + self.assertIsNone(ip) + + def test_http_x_forwarded_for_single_proxy(self): + request = { + 'HTTP_X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', + } + ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139.233.139']) + self.assertEqual(ip, "198.84.193.157") + + def test_http_x_forwarded_for_single_proxy_with_right_most(self): + request = { + 'HTTP_X_FORWARDED_FOR': '177.139.233.139, 177.139.200.139, 198.84.193.157', + } + ip = address_parser.get_trusted_ip(request, right_most_proxy=True, trusted_proxies=['177.139.233.139']) + self.assertEqual(ip, "198.84.193.157") + + def test_http_x_forwarded_for_multi_proxy(self): + request = { + 'HTTP_X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', + } + ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139.233.138', '177.139.233.139']) + self.assertEqual(ip, "198.84.193.157") + + def test_http_x_forwarded_for_all_proxies_in_subnet(self): + request = { + 'HTTP_X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', + } + ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139.233']) + self.assertEqual(ip, "198.84.193.157") + + def test_http_x_forwarded_for_all_proxies_in_subnet_2(self): + request = { + 'HTTP_X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', + } + ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139']) + self.assertEqual(ip, "198.84.193.157") + + def test_x_forwarded_for_single_proxy(self): + request = { + 'X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', + } + ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139.233.139']) + self.assertEqual(ip, "198.84.193.157") + + def test_x_forwarded_for_single_proxy_hyphens(self): + request = { + 'X-FORWARDED-FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', + } + ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139.233.139']) + self.assertEqual(ip, "198.84.193.157") + + def test_http_x_forwarded_for_and_x_forward_for_single_proxy(self): + request = { + 'HTTP_X_FORWARDED_FOR': '198.84.193.156, 177.139.200.139, 177.139.233.139', + 'X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', + } + ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139.233.139']) + self.assertEqual(ip, "198.84.193.156") + + +class IPv6TestCase(TestCase): + """IP address Test""" + + def test_http_x_forwarded_for_multiple(self): + request = { + 'HTTP_X_FORWARDED_FOR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf, 74dc::02ba', + 'HTTP_X_REAL_IP': '74dc::02ba', + 'REMOTE_ADDR': '74dc::02ba', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "3ffe:1900:4545:3:200:f8ff:fe21:67cf") + + def test_http_x_forwarded_for_multiple_bad_address(self): + request = { + 'HTTP_X_FORWARDED_FOR': 'unknown, ::1/128, 74dc::02ba', + 'HTTP_X_REAL_IP': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_http_x_forwarded_for_singleton(self): + request = { + 'HTTP_X_FORWARDED_FOR': '74dc::02ba', + 'HTTP_X_REAL_IP': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_http_x_forwarded_for_singleton_private_address(self): + request = { + 'HTTP_X_FORWARDED_FOR': '::1/128', + 'HTTP_X_REAL_IP': '74dc::02ba', + 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_bad_http_x_forwarded_for_fallback_on_x_real_ip(self): + request = { + 'HTTP_X_FORWARDED_FOR': 'unknown ::1/128', + 'HTTP_X_REAL_IP': '74dc::02ba', + 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_empty_http_x_forwarded_for_fallback_on_x_real_ip(self): + request = { + 'HTTP_X_FORWARDED_FOR': '', + 'HTTP_X_REAL_IP': '74dc::02ba', + 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_empty_http_x_forwarded_for_empty_x_real_ip_fallback_on_remote_addr(self): + request = { + 'HTTP_X_FORWARDED_FOR': '', + 'HTTP_X_REAL_IP': '', + 'REMOTE_ADDR': '74dc::02ba', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_empty_http_x_forwarded_for_private_x_real_ip_fallback_on_remote_addr(self): + request = { + 'HTTP_X_FORWARDED_FOR': '', + 'HTTP_X_REAL_IP': '::1/128', + 'REMOTE_ADDR': '74dc::02ba', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_private_http_x_forward_for_ip_addr(self): + request = { + 'HTTP_X_FORWARDED_FOR': '::1/128', + 'HTTP_X_REAL_IP': '', + 'REMOTE_ADDR': '', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, None) + + def test_private_real_ip_for_ip_addr(self): + request = { + 'HTTP_X_FORWARDED_FOR': '', + 'HTTP_X_REAL_IP': '::1/128', + 'REMOTE_ADDR': '', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, None) + + def test_private_remote_addr_for_ip_addr(self): + request = { + 'HTTP_X_FORWARDED_FOR': '', + 'HTTP_X_REAL_IP': '', + 'REMOTE_ADDR': '::1/128', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, None) + + def test_missing_x_forwarded(self): + request = { + 'HTTP_X_REAL_IP': '74dc::02ba', + 'REMOTE_ADDR': '74dc::02ba', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_missing_x_forwarded_missing_real_ip(self): + request = { + 'REMOTE_ADDR': '74dc::02ba', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_missing_x_forwarded_missing_real_ip_mix_case(self): + request = { + 'REMOTE_ADDR': '74DC::02BA', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_private_remote_address(self): + request = { + 'REMOTE_ADDR': 'fe80::02ba', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, None) + + def test_best_matched_real_ip(self): + request = { + 'HTTP_X_REAL_IP': '::1', + 'REMOTE_ADDR': 'fe80::02ba', + } + ip = address_parser.get_ip(request) + self.assertEqual(ip, "fe80::02ba") + + def test_x_forwarded_for_multiple(self): + request = { + 'X_FORWARDED_FOR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf, 74dc::02ba', + 'REMOTE_ADDR': '74dc::02ba', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "3ffe:1900:4545:3:200:f8ff:fe21:67cf") + + def test_x_forwarded_for_multiple_bad_address(self): + request = { + 'X_FORWARDED_FOR': 'unknown, ::1/128, 74dc::02ba', + 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_x_forwarded_for_singleton(self): + request = { + 'X_FORWARDED_FOR': '74dc::02ba', + 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_x_forwarded_for_singleton_private_address(self): + request = { + 'X_FORWARDED_FOR': '::1/128', + 'HTTP_X_REAL_IP': '74dc::02ba', + 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_bad_x_forwarded_for_fallback_on_x_real_ip(self): + request = { + 'X_FORWARDED_FOR': 'unknown ::1/128', + 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "3ffe:1900:4545:3:200:f8ff:fe21:67cf") + + def test_empty_x_forwarded_for_fallback_on_x_real_ip(self): + request = { + 'X_FORWARDED_FOR': '', + 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "3ffe:1900:4545:3:200:f8ff:fe21:67cf") + + def test_empty_x_forwarded_for_empty_x_real_ip_fallback_on_remote_addr(self): + request = { + 'X_FORWARDED_FOR': '', + 'REMOTE_ADDR': '74dc::02ba', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_empty_x_forwarded_for_private_x_real_ip_fallback_on_remote_addr(self): + request = { + 'X_FORWARDED_FOR': '', + 'REMOTE_ADDR': '74dc::02ba', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + def test_private_x_forward_for_ip_addr(self): + request = { + 'X_FORWARDED_FOR': '::1/128', + 'REMOTE_ADDR': '', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, None) + + def test_x_forwarded_for_singleton_hyphen_as_delimiter(self): + request = { + 'X-FORWARDED-FOR': '74dc::02ba', + 'REMOTE-ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', + } + ip = address_parser.get_real_ip(request) + self.assertEqual(ip, "74dc::02ba") + + +class IPv6TrustedProxiesTestCase(TestCase): + """Trusted Proxies - IP address Test""" + + def test_http_x_forwarded_for_no_proxy(self): + request = { + 'HTTP_X_FORWARDED_FOR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf, 74dc::02ba', + } + ip = address_parser.get_trusted_ip(request, trusted_proxies=[]) + self.assertIsNone(ip) + + def test_http_x_forwarded_for_single_proxy(self): + request = { + 'HTTP_X_FORWARDED_FOR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf, 74dc::02ba', + } + ip = address_parser.get_trusted_ip(request, trusted_proxies=['74dc::02ba']) + self.assertEqual(ip, "3ffe:1900:4545:3:200:f8ff:fe21:67cf") diff --git a/pym/calculate/contrib/spyne/test/util/test_address.pyc b/pym/calculate/contrib/spyne/test/util/test_address.pyc new file mode 100644 index 0000000..0c868f2 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/util/test_address.pyc differ diff --git a/pym/calculate/contrib/spyne/test/util/test_util.py b/pym/calculate/contrib/spyne/test/util/test_util.py new file mode 100644 index 0000000..3b8b5e7 --- /dev/null +++ b/pym/calculate/contrib/spyne/test/util/test_util.py @@ -0,0 +1,601 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import json +import decimal +import unittest + +import pytz +import sqlalchemy + +from pprint import pprint +from decimal import Decimal as D +from datetime import datetime + +from lxml import etree + +from spyne.const import MAX_STRING_FIELD_LENGTH + +from spyne.decorator import srpc +from spyne.application import Application + +from spyne.model.complex import XmlAttribute, TypeInfo +from spyne.model.complex import ComplexModel +from spyne.model.complex import Iterable +from spyne.model.complex import Array +from spyne.model.primitive import Decimal +from spyne.model.primitive import DateTime +from spyne.model.primitive import Integer +from spyne.model.primitive import Unicode + +from spyne.service import Service + +from spyne.util import AttrDict, AttrDictColl, get_version +from spyne.util import memoize, memoize_ignore_none, memoize_ignore, memoize_id + +from spyne.util.protocol import deserialize_request_string + +from spyne.util.dictdoc import get_dict_as_object, get_object_as_yaml, \ + get_object_as_json +from spyne.util.dictdoc import get_object_as_dict +from spyne.util.tdict import tdict +from spyne.util.tlist import tlist + +from spyne.util.xml import get_object_as_xml +from spyne.util.xml import get_xml_as_object +from spyne.util.xml import get_schema_documents +from spyne.util.xml import get_validation_schema + + +class TestUtil(unittest.TestCase): + def test_version(self): + assert get_version('sqlalchemy') == get_version(sqlalchemy) + assert '.'.join([str(i) for i in get_version('sqlalchemy')]) == \ + sqlalchemy.__version__ + + +class TestTypeInfo(unittest.TestCase): + def test_insert(self): + d = TypeInfo() + + d['a'] = 1 + assert d[0] == d['a'] == 1 + + d.insert(0, ('b', 2)) + + assert d[1] == d['a'] == 1 + assert d[0] == d['b'] == 2 + + def test_insert_existing(self): + d = TypeInfo() + + d["a"] = 1 + d["b"] = 2 + assert d[1] == d['b'] == 2 + + d.insert(0, ('b', 3)) + assert d[1] == d['a'] == 1 + assert d[0] == d['b'] == 3 + + def test_update(self): + d = TypeInfo() + d["a"] = 1 + d.update([('b', 2)]) + assert d[0] == d['a'] == 1 + assert d[1] == d['b'] == 2 + + +class TestXml(unittest.TestCase): + def test_serialize(self): + + class C(ComplexModel): + __namespace__ = "tns" + i = Integer + s = Unicode + + c = C(i=5, s="x") + + ret = get_object_as_xml(c, C) + print(etree.tostring(ret)) + assert ret.tag == "{tns}C" + + ret = get_object_as_xml(c, C, "X") + print(etree.tostring(ret)) + assert ret.tag == "{tns}X" + + ret = get_object_as_xml(c, C, "X", no_namespace=True) + print(etree.tostring(ret)) + assert ret.tag == "X" + + ret = get_object_as_xml(c, C, no_namespace=True) + print(etree.tostring(ret)) + assert ret.tag == "C" + + def test_deserialize(self): + class Punk(ComplexModel): + __namespace__ = 'some_namespace' + + a = Unicode + b = Integer + c = Decimal + d = DateTime + + class Foo(ComplexModel): + __namespace__ = 'some_other_namespace' + + a = Unicode + b = Integer + c = Decimal + d = DateTime + e = XmlAttribute(Integer) + + def __eq__(self, other): + # remember that this is a test object + assert ( + self.a == other.a and + self.b == other.b and + self.c == other.c and + self.d == other.d and + self.e == other.e + ) + + return True + + docs = get_schema_documents([Punk, Foo]) + pprint(docs) + assert docs['s0'].tag == '{http://www.w3.org/2001/XMLSchema}schema' + assert docs['tns'].tag == '{http://www.w3.org/2001/XMLSchema}schema' + print() + + print("the other namespace %r:" % docs['tns'].attrib['targetNamespace']) + assert docs['tns'].attrib['targetNamespace'] == 'some_namespace' + print(etree.tostring(docs['tns'], pretty_print=True)) + print() + + print("the other namespace %r:" % docs['s0'].attrib['targetNamespace']) + assert docs['s0'].attrib['targetNamespace'] == 'some_other_namespace' + print(etree.tostring(docs['s0'], pretty_print=True)) + print() + + foo = Foo(a=u'a', b=1, c=decimal.Decimal('3.4'), + d=datetime(2011,2,20,tzinfo=pytz.utc), e=5) + doc = get_object_as_xml(foo, Foo) + print(etree.tostring(doc, pretty_print=True)) + foo_back = get_xml_as_object(doc, Foo) + + assert foo_back == foo + + # as long as it doesn't fail, it's ok. + get_validation_schema([Punk, Foo]) + + +class TestCDict(unittest.TestCase): + def test_cdict(self): + from spyne.util.cdict import cdict + + class A(object): + pass + + class B(A): + pass + + class E(B): + pass + + class F(E): + pass + + class C(object): + pass + + d = cdict({A: "fun", F: 'zan'}) + + assert d[A] == 'fun' + assert d[B] == 'fun' + assert d[F] == 'zan' + try: + d[C] + except KeyError: + pass + else: + raise Exception("Must fail.") + + +class TestTDict(unittest.TestCase): + def test_tdict_notype(self): + d = tdict() + d[0] = 1 + assert d[0] == 1 + + d = tdict() + d.update({0:1}) + assert d[0] == 1 + + d = tdict.fromkeys([0], 1) + assert d[0] == 1 + + def test_tdict_k(self): + d = tdict(str) + try: + d[0] = 1 + except TypeError: + pass + else: + raise Exception("must fail") + + d = tdict(str) + d['s'] = 1 + assert d['s'] == 1 + + def test_tdict_v(self): + d = tdict(vt=str) + try: + d[0] = 1 + except TypeError: + pass + else: + raise Exception("must fail") + + d = tdict(vt=str) + d[0] = 's' + assert d[0] == 's' + + +class TestLogRepr(unittest.TestCase): + def test_log_repr_simple(self): + from spyne.model.complex import ComplexModel + from spyne.model.primitive import String + from spyne.util.web import log_repr + + class Z(ComplexModel): + z=String + + l = MAX_STRING_FIELD_LENGTH + 100 + print(log_repr(Z(z="a" * l))) + print("Z(z='%s'(...))" % ('a' * MAX_STRING_FIELD_LENGTH)) + + assert log_repr(Z(z="a" * l)) == "Z(z='%s'(...))" % \ + ('a' * MAX_STRING_FIELD_LENGTH) + assert log_repr(['a','b','c'], Array(String)) == "['a', 'b', (...)]" + + def test_log_repr_complex(self): + from spyne.model import ByteArray + from spyne.model import File + from spyne.model.complex import ComplexModel + from spyne.model.primitive import String + from spyne.util.web import log_repr + + class Z(ComplexModel): + _type_info = [ + ('f', File(logged=False)), + ('t', ByteArray(logged=False)), + ('z', Array(String)), + ] + l = MAX_STRING_FIELD_LENGTH + 100 + val = Z(z=["abc"] * l, t=['t'], f=File.Value(name='aaa', data=['t'])) + print(repr(val)) + + assert log_repr(val) == "Z(z=['abc', 'abc', (...)])" + + def test_log_repr_dict_vanilla(self): + from spyne.model import AnyDict + from spyne.util.web import log_repr + + t = AnyDict + + assert log_repr({1: 1}, t) == "{1: 1}" + assert log_repr({1: 1, 2: 2}, t) == "{1: 1, 2: 2}" + assert log_repr({1: 1, 2: 2, 3: 3}, t) == "{1: 1, 2: 2, (...)}" + + assert log_repr([1], t) == "[1]" + assert log_repr([1, 2], t) == "[1, 2]" + assert log_repr([1, 2, 3], t) == "[1, 2, (...)]" + + def test_log_repr_dict_keys(self): + from spyne.model import AnyDict + from spyne.util.web import log_repr + + t = AnyDict(logged='keys') + + assert log_repr({1: 1}, t) == "{1: (...)}" + + assert log_repr([1], t) == "[1]" + + def test_log_repr_dict_values(self): + from spyne.model import AnyDict + from spyne.util.web import log_repr + + t = AnyDict(logged='values') + + assert log_repr({1: 1}, t) == "{(...): 1}" + + assert log_repr([1], t) == "[1]" + + def test_log_repr_dict_full(self): + from spyne.model import AnyDict + from spyne.util.web import log_repr + + t = AnyDict(logged='full') + + assert log_repr({1: 1, 2: 2, 3: 3}, t) == "{1: 1, 2: 2, 3: 3}" + assert log_repr([1, 2, 3], t) == "[1, 2, 3]" + + def test_log_repr_dict_keys_full(self): + from spyne.model import AnyDict + from spyne.util.web import log_repr + + t = AnyDict(logged='keys-full') + + assert log_repr({1: 1, 2: 2, 3: 3}, t) == "{1: (...), 2: (...), 3: (...)}" + assert log_repr([1, 2, 3], t) == "[1, 2, 3]" + + def test_log_repr_dict_values_full(self): + from spyne.model import AnyDict + from spyne.util.web import log_repr + + t = AnyDict(logged='values-full') + + assert log_repr({1: 1, 2: 2, 3: 3}, t) == "{(...): 1, (...): 2, (...): 3}" + assert log_repr([1, 2, 3], t) == "[1, 2, 3]" + + +class TestDeserialize(unittest.TestCase): + def test_deserialize(self): + from spyne.protocol.soap import Soap11 + + class SomeService(Service): + @srpc(Integer, _returns=Iterable(Integer)) + def some_call(yo): + return range(yo) + + app = Application([SomeService], 'tns', in_protocol=Soap11(), + out_protocol=Soap11()) + + meat = 30 + + string = """ + + + + %s + + + + """ % meat + + obj = deserialize_request_string(string, app) + + assert obj.yo == meat + + +class TestEtreeDict(unittest.TestCase): + + longMessage = True + + def test_simple(self): + from lxml.etree import tostring + from spyne.util.etreeconv import root_dict_to_etree + assert tostring(root_dict_to_etree({'a':{'b':'c'}})) == b'c' + + def test_not_sized(self): + from lxml.etree import tostring + from spyne.util.etreeconv import root_dict_to_etree + + complex_value = root_dict_to_etree({'a':{'b':1}}) + self.assertEqual(tostring(complex_value), b'1', + "The integer should be properly rendered in the etree") + + complex_none = root_dict_to_etree({'a':{'b':None}}) + self.assertEqual(tostring(complex_none), b'', + "None should not be rendered in the etree") + + simple_value = root_dict_to_etree({'a': 1}) + self.assertEqual(tostring(simple_value), b'1', + "The integer should be properly rendered in the etree") + + none_value = root_dict_to_etree({'a': None}) + self.assertEqual(tostring(none_value), b'', + "None should not be rendered in the etree") + + string_value = root_dict_to_etree({'a': 'lol'}) + self.assertEqual(tostring(string_value), b'lol', + "A string should be rendered as a string") + + complex_string_value = root_dict_to_etree({'a': {'b': 'lol'}}) + self.assertEqual(tostring(complex_string_value), b'lol', + "A string should be rendered as a string") + + +class TestDictDoc(unittest.TestCase): + def test_the(self): + class C(ComplexModel): + __namespace__ = "tns" + i = Integer + s = Unicode + a = Array(DateTime) + + def __eq__(self, other): + print("Yaaay!") + return self.i == other.i and \ + self.s == other.s and \ + self.a == other.a + + c = C(i=5, s="x", a=[datetime(2011,12,22, tzinfo=pytz.utc)]) + + for iw, ca in ((False,dict), (True,dict), (False,list), (True, list)): + print() + print('complex_as:', ca) + d = get_object_as_dict(c, C, complex_as=ca) + print(d) + o = get_dict_as_object(d, C, complex_as=ca) + print(o) + print(c) + assert o == c + + +class TestAttrDict(unittest.TestCase): + def test_attr_dict(self): + assert AttrDict(a=1)['a'] == 1 + + def test_attr_dict_coll(self): + assert AttrDictColl('SomeDict').SomeDict.NAME == 'SomeDict' + assert AttrDictColl('SomeDict').SomeDict(a=1)['a'] == 1 + assert AttrDictColl('SomeDict').SomeDict(a=1).NAME == 'SomeDict' + + +class TestYaml(unittest.TestCase): + def test_deser(self): + class C(ComplexModel): + a = Unicode + b = Decimal + + ret = get_object_as_yaml(C(a='burak', b=D(30)), C) + assert ret == b"""C: + a: burak + b: '30' +""" + + +class TestJson(unittest.TestCase): + def test_deser(self): + class C(ComplexModel): + _type_info = [ + ('a', Unicode), + ('b', Decimal), + ] + + ret = get_object_as_json(C(a='burak', b=D(30)), C) + assert ret == b'["burak", "30"]' + ret = get_object_as_json(C(a='burak', b=D(30)), C, complex_as=dict) + assert json.loads(ret.decode('utf8')) == \ + json.loads(u'{"a": "burak", "b": "30"}') + + +class TestFifo(unittest.TestCase): + def test_msgpack_fifo(self): + import msgpack + + v1 = [1, 2, 3, 4] + v2 = [5, 6, 7, 8] + v3 = {b"a": 9, b"b": 10, b"c": 11} + + s1 = msgpack.packb(v1) + s2 = msgpack.packb(v2) + s3 = msgpack.packb(v3) + + unpacker = msgpack.Unpacker() + unpacker.feed(s1) + unpacker.feed(s2) + unpacker.feed(s3[:4]) + + assert next(iter(unpacker)) == v1 + assert next(iter(unpacker)) == v2 + try: + next(iter(unpacker)) + except StopIteration: + pass + else: + raise Exception("must fail") + + unpacker.feed(s3[4:]) + assert next(iter(unpacker)) == v3 + + +class TestTlist(unittest.TestCase): + def test_tlist(self): + tlist([], int) + + a = tlist([1, 2], int) + a.append(3) + a += [4] + a = [5] + [a] + a = a + [6] + a[0] = 1 + a[5:] = [5] + + try: + tlist([1, 2, 'a'], int) + a.append('a') + a += ['a'] + _ = ['a'] + a + _ = a + ['a'] + a[0] = 'a' + a[0:] = 'a' + + except TypeError: + pass + else: + raise Exception("Must fail") + + +class TestMemoization(unittest.TestCase): + def test_memoize(self): + counter = [0] + @memoize + def f(arg): + counter[0] += 1 + print(arg, counter) + + f(1) + f(1) + assert counter[0] == 1 + + f(2) + assert counter[0] == 2 + + def test_memoize_ignore_none(self): + counter = [0] + @memoize_ignore_none + def f(arg): + counter[0] += 1 + print(arg, counter) + return arg + + f(None) + f(None) + assert counter[0] == 2 + + f(1) + assert counter[0] == 3 + f(1) + assert counter[0] == 3 + + def test_memoize_ignore_values(self): + counter = [0] + @memoize_ignore((1,)) + def f(arg): + counter[0] += 1 + print(arg, counter) + return arg + + f(1) + f(1) + assert counter[0] == 2 + + f(2) + assert counter[0] == 3 + f(2) + assert counter[0] == 3 + + def test_memoize_id(self): + counter = [0] + @memoize_id + def f(arg): + counter[0] += 1 + print(arg, counter) + return arg + + d = {} + f(d) + f(d) + assert counter[0] == 1 + + f({}) + assert counter[0] == 2 + f({}) + assert counter[0] == 3 + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/calculate/contrib/spyne/test/util/test_util.pyc b/pym/calculate/contrib/spyne/test/util/test_util.pyc new file mode 100644 index 0000000..e7dc736 Binary files /dev/null and b/pym/calculate/contrib/spyne/test/util/test_util.pyc differ diff --git a/pym/calculate/contrib/spyne/util/__init__.py b/pym/calculate/contrib/spyne/util/__init__.py new file mode 100644 index 0000000..c644bb3 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/__init__.py @@ -0,0 +1,112 @@ + +# +# 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.util import six + +from spyne.util.coopmt import keepfirst +from spyne.util.coopmt import coroutine +from spyne.util.coopmt import Break + +from spyne.util.memo import memoize +from spyne.util.memo import memoize_first +from spyne.util.memo import memoize_ignore +from spyne.util.memo import memoize_ignore_none +from spyne.util.memo import memoize_id + +from spyne.util.attrdict import AttrDict +from spyne.util.attrdict import AttrDictColl +from spyne.util.attrdict import DefaultAttrDict + +from spyne.util._base import utctime +from spyne.util._base import get_version + + +try: + import thread + + from urllib import splittype, splithost, quote, urlencode + from urllib2 import urlopen, Request, HTTPError + +except ImportError: # Python 3 + import _thread as thread + + from urllib.parse import splittype, splithost, quote, urlencode + from urllib.request import urlopen, Request + from urllib.error import HTTPError + + +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 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 six.PY2: + def _bytes_join(val, joiner=''): + return joiner.join(val) +else: + def _bytes_join(val, joiner=b''): + return joiner.join(val) + + +def utf8(s): + if isinstance(s, bytes): + return s.decode('utf8') + + if isinstance(s, list): + return [utf8(ss) for ss in s] + + if isinstance(s, tuple): + return tuple([utf8(ss) for ss in s]) + + if isinstance(s, set): + return {utf8(ss) for ss in s} + + if isinstance(s, frozenset): + return frozenset([utf8(ss) for ss in s]) + + return s diff --git a/pym/calculate/contrib/spyne/util/__init__.pyc b/pym/calculate/contrib/spyne/util/__init__.pyc new file mode 100644 index 0000000..649f6da Binary files /dev/null and b/pym/calculate/contrib/spyne/util/__init__.pyc differ diff --git a/pym/calculate/contrib/spyne/util/_base.py b/pym/calculate/contrib/spyne/util/_base.py new file mode 100644 index 0000000..d23dea5 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/_base.py @@ -0,0 +1,44 @@ + +# +# 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 time import mktime +from datetime import datetime + +from spyne.util import memoize, six + + +def utctime(): + return mktime(datetime.utcnow().timetuple()) + + +@memoize +def get_version(package): + if isinstance(package, (six.text_type, six.binary_type)): + package = __import__(package) + + verstr = getattr(package, '__version__') + retval = [] + + for f in verstr.split("."): + try: + retval.append(int(f)) + except ValueError: + retval.append(f) + + return tuple(retval) diff --git a/pym/calculate/contrib/spyne/util/_base.pyc b/pym/calculate/contrib/spyne/util/_base.pyc new file mode 100644 index 0000000..1df1b01 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/_base.pyc differ diff --git a/pym/calculate/contrib/spyne/util/_twisted_ws.py b/pym/calculate/contrib/spyne/util/_twisted_ws.py new file mode 100644 index 0000000..df7468c --- /dev/null +++ b/pym/calculate/contrib/spyne/util/_twisted_ws.py @@ -0,0 +1,610 @@ +# -*- 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 diff --git a/pym/calculate/contrib/spyne/util/_twisted_ws.pyc b/pym/calculate/contrib/spyne/util/_twisted_ws.pyc new file mode 100644 index 0000000..248dabc Binary files /dev/null and b/pym/calculate/contrib/spyne/util/_twisted_ws.pyc differ diff --git a/pym/calculate/contrib/spyne/util/address.py b/pym/calculate/contrib/spyne/util/address.py new file mode 100644 index 0000000..55a0da0 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/address.py @@ -0,0 +1,276 @@ + +# +# 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 MIT License +# +# Copyright (c) Val Neekman @ Neekware Inc. http://neekware.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 __future__ import print_function + +# Direct plagiarization of https://github.com/un33k/django-ipware/ +# at 57897c03026913892e61a164bc8b022778802ab9 + +import socket + +# List of known proxy server(s) +TRUSTED_PROXIES = [] + +# Search for the real IP address in the following order +# Configurable via settings.py +PRECEDENCE = ( + 'HTTP_X_FORWARDED_FOR', 'X_FORWARDED_FOR', + # (client, proxy1, proxy2) OR (proxy2, proxy1, client) + 'HTTP_CLIENT_IP', + 'HTTP_X_REAL_IP', + 'HTTP_X_FORWARDED', + 'HTTP_X_CLUSTER_CLIENT_IP', + 'HTTP_FORWARDED_FOR', + 'HTTP_FORWARDED', + 'HTTP_VIA', + 'REMOTE_ADDR', +) + +# Private IP addresses +# http://en.wikipedia.org/wiki/List_of_assigned_/8_IPv4_address_blocks +# http://www.ietf.org/rfc/rfc3330.txt (IPv4) +# http://www.ietf.org/rfc/rfc5156.txt (IPv6) +# Regex would be ideal here, but this is keeping it simple +# as fields are configurable via settings.py +PRIVATE_IP_PREFIXES = ( + '0.', # externally non-routable + '10.', # class A private block + '169.254.', # link-local block + '172.16.', '172.17.', '172.18.', '172.19.', + '172.20.', '172.21.', '172.22.', '172.23.', + '172.24.', '172.25.', '172.26.', '172.27.', + '172.28.', '172.29.', '172.30.', '172.31.', + # class B private blocks + '192.0.2.', + # reserved for documentation and example code + '192.168.', # class C private block + '255.255.255.', # IPv4 broadcast address +) + ( + '2001:db8:', + # reserved for documentation and example code + 'fc00:', # IPv6 private block + 'fe80:', # link-local unicast + 'ff00:', # IPv6 multicast +) + +LOOPBACK_PREFIX = ( + '127.', # IPv4 loopback device + '::1', # IPv6 loopback device +) + +NON_PUBLIC_IP_PREFIXES = PRIVATE_IP_PREFIXES + LOOPBACK_PREFIX + + +def set_address_parser_settings(trusted_proxies, field_precedence=PRECEDENCE, + private_ip_prefixes=NON_PUBLIC_IP_PREFIXES): + """Changes global parameters for Spyne's residend ip address parser. + + :param trusted_proxies: Tuple of reverse proxies that are under YOUR control. + :param field_precedence: A tuple of field names that may contain address + information, in decreasing level of preference. + :param private_ip_prefixes: You might want to add your list of + public-but-otherwise-private ip prefixes or addresses here. + """ + + global address_parser + + address_parser = AddressParser(trusted_proxies=trusted_proxies, + field_precedence=field_precedence, + private_ip_prefixes=private_ip_prefixes) + + +class AddressParser(object): + def __init__(self, private_ip_prefixes=None, trusted_proxies=(), + field_precedence=PRECEDENCE): + if private_ip_prefixes is not None: + self.private_ip_prefixes = private_ip_prefixes + else: + self.private_ip_prefixes = \ + tuple([ip.lower() for ip in NON_PUBLIC_IP_PREFIXES]) + + if len(trusted_proxies) > 0: + self.trusted_proxies = trusted_proxies + + else: + self.trusted_proxies = \ + tuple([ip.lower() for ip in TRUSTED_PROXIES]) + + self.field_precedence = field_precedence + + + def get_port(self, wsgi_env): + return wsgi_env.get("REMOTE_PORT", 0) + + def get_ip(self, wsgi_env, real_ip_only=False, right_most_proxy=False): + """ + Returns client's best-matched ip-address, or None + """ + best_matched_ip = None + + for key in self.field_precedence: + value = wsgi_env.get(key, None) + if value is None: + value = wsgi_env.get(key.replace('_', '-'), None) + + if value is None or value == '': + continue + + ips = [ip.strip().lower() for ip in value.split(',')] + + if right_most_proxy and len(ips) > 1: + ips = reversed(ips) + + for ip_str in ips: + if ip_str is None or ip_str == '' or not \ + AddressParser.is_valid_ip(ip_str): + continue + + if not ip_str.startswith(self.private_ip_prefixes): + return ip_str + + if not real_ip_only: + loopback = LOOPBACK_PREFIX + + if best_matched_ip is None: + best_matched_ip = ip_str + + elif best_matched_ip.startswith(loopback) \ + and not ip_str.startswith(loopback): + best_matched_ip = ip_str + + return best_matched_ip + + def get_real_ip(self, wsgi_env, right_most_proxy=False): + """ + Returns client's best-matched `real` `externally-routable` ip-address, + or None + """ + return self.get_ip(wsgi_env, real_ip_only=True, + right_most_proxy=right_most_proxy) + + def get_trusted_ip(self, wsgi_env, right_most_proxy=False, + trusted_proxies=None): + """ + Returns client's ip-address from `trusted` proxy server(s) or None + """ + + if trusted_proxies is None: + trusted_proxies = self.trusted_proxies + + if trusted_proxies is None or len(trusted_proxies) == 0: + trusted_proxies = TRUSTED_PROXIES + + if trusted_proxies is None or len(trusted_proxies) == 0: + return + + meta_keys = ['HTTP_X_FORWARDED_FOR', 'X_FORWARDED_FOR'] + + for key in meta_keys: + value = wsgi_env.get(key, None) + if value is None: + value = wsgi_env.get(key.replace('_', '-'), None) + + if value is None or value == '': + continue + + ips = [ip.strip().lower() for ip in value.split(',')] + + if len(ips) > 1: + if right_most_proxy: + ips.reverse() + + for proxy in trusted_proxies: + if proxy in ips[-1]: + return ips[0] + + @staticmethod + def is_valid_ipv4(ip_str): + """ + Check the validity of an IPv4 address + """ + + if ip_str is None: + return False + + try: + socket.inet_pton(socket.AF_INET, ip_str) + + except AttributeError: # pragma: no cover + try: # Fall-back on legacy API or False + socket.inet_aton(ip_str) + except (AttributeError, socket.error): + return False + return ip_str.count('.') == 3 + + except socket.error: + return False + + return True + + @staticmethod + def is_valid_ipv6(ip_str): + """ + Check the validity of an IPv6 address + """ + + if ip_str is None: + return False + + try: + socket.inet_pton(socket.AF_INET6, ip_str) + + except socket.error: + return False + + return True + + @staticmethod + def is_valid_ip(ip_str): + """ + Check the validity of an IP address + """ + + return AddressParser.is_valid_ipv4(ip_str) or \ + AddressParser.is_valid_ipv6(ip_str) + + +address_parser = AddressParser() diff --git a/pym/calculate/contrib/spyne/util/address.pyc b/pym/calculate/contrib/spyne/util/address.pyc new file mode 100644 index 0000000..e89a2c5 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/address.pyc differ diff --git a/pym/calculate/contrib/spyne/util/appreg.py b/pym/calculate/contrib/spyne/util/appreg.py new file mode 100644 index 0000000..31c8637 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/appreg.py @@ -0,0 +1,88 @@ + +# +# 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 unregister_application(app): + key = (app.tns, app.name) + del applications[key] + + +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) diff --git a/pym/calculate/contrib/spyne/util/appreg.pyc b/pym/calculate/contrib/spyne/util/appreg.pyc new file mode 100644 index 0000000..c1bd484 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/appreg.pyc differ diff --git a/pym/calculate/contrib/spyne/util/attrdict.py b/pym/calculate/contrib/spyne/util/attrdict.py new file mode 100644 index 0000000..de996aa --- /dev/null +++ b/pym/calculate/contrib/spyne/util/attrdict.py @@ -0,0 +1,87 @@ + +# +# 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 +# + + +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)) diff --git a/pym/calculate/contrib/spyne/util/attrdict.pyc b/pym/calculate/contrib/spyne/util/attrdict.pyc new file mode 100644 index 0000000..52e5260 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/attrdict.pyc differ diff --git a/pym/calculate/contrib/spyne/util/autorel.py b/pym/calculate/contrib/spyne/util/autorel.py new file mode 100644 index 0000000..2fc685a --- /dev/null +++ b/pym/calculate/contrib/spyne/util/autorel.py @@ -0,0 +1,258 @@ +# 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) 2004-2016, CherryPy Team (team@cherrypy.org) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the CherryPy Team nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +import logging +logger = logging.getLogger(__name__) + +import os, re, sys + +from spyne.util.color import YEL + +# _module__file__base is used by Autoreload to make +# absolute any filenames retrieved from sys.modules which are not +# already absolute paths. This is to work around Python's quirk +# of importing the startup script and using a relative filename +# for it in sys.modules. +# +# Autoreload examines sys.modules afresh every time it runs. If an application +# changes the current directory by executing os.chdir(), then the next time +# Autoreload runs, it will not be able to find any filenames which are +# not absolute paths, because the current directory is not the same as when the +# module was first imported. Autoreload will then wrongly conclude the file +# has "changed", and initiate the shutdown/re-exec sequence. +# See cherrypy ticket #917. +# For this workaround to have a decent probability of success, this module +# needs to be imported as early as possible, before the app has much chance +# to change the working directory. +_module__file__base = os.getcwd() + +try: + import fcntl +except ImportError: + MAX_FILES = 0 +else: + try: + MAX_FILES = os.sysconf('SC_OPEN_MAX') + except AttributeError: + MAX_FILES = 1024 + + + +class AutoReloader(object): + """Monitor which re-executes the process when files change. + + This :ref:`plugin` restarts the process (via :func:`os.execv`) + if any of the files it monitors change (or is deleted). By default, the + autoreloader monitors all imported modules; you can add to the + set by adding to ``autoreload.files``:: + + spyne.util.autorel.AutoReloader.FILES.add(myFile) + + + spyne.util.autorel.AutoReloader.match = r'^(?!cherrypy).+' + + The autoreload plugin takes a ``frequency`` argument. The default is + 1 second; that is, the autoreloader will examine files once each second. + """ + + FILES = set() + """The set of files to poll for modifications.""" + + def __init__(self, frequency=1, match='.*'): + self.max_cloexec_files = MAX_FILES + + self.mtimes = {} + self.files = set(AutoReloader.FILES) + + self.match = match + """A regular expression by which to match filenames. + + If there are imported files you do *not* wish to monitor, you can + adjust the ``match`` attribute, a regular expression. For example, + to stop monitoring cherrypy itself, try ``match=r'^(?!cherrypy).+'``\\. + """ + + self.frequency = frequency + """The interval in seconds at which to poll for modified files.""" + + def start(self): + from twisted.internet.task import LoopingCall + + retval = LoopingCall(self.run) + retval.start(self.frequency) + return retval # oh no + + def sysfiles(self): + """Return a Set of sys.modules filenames to monitor.""" + files = set() + for k, m in list(sys.modules.items()): + if re.match(self.match, k): + if ( + hasattr(m, '__loader__') and + hasattr(m.__loader__, 'archive') + ): + f = m.__loader__.archive + else: + try: + f = getattr(m, '__file__', None) + except ImportError: + f = None + + if f is not None and not os.path.isabs(f): + # ensure absolute paths so a os.chdir() in the app + # doesn't break me + f = os.path.normpath( + os.path.join(_module__file__base, f)) + files.add(f) + return files + + def run(self): + """Reload the process if registered files have been modified.""" + for filename in self.sysfiles() | self.files: + if filename: + if filename.endswith('.pyc'): + filename = filename[:-1] + + oldtime = self.mtimes.get(filename, 0) + if oldtime is None: + # Module with no .py file. Skip it. + continue + + try: + mtime = os.stat(filename).st_mtime + except OSError: + # Either a module with no .py file, or it's been deleted. + mtime = None + + if filename not in self.mtimes: + # If a module has no .py file, this will be None. + self.mtimes[filename] = mtime + else: + if mtime is None or mtime > oldtime: + # The file has been deleted or modified. + logger.info("Restarting because '%s' has changed." % + filename) + + from twisted.internet import reactor + reactor.stop() + self._do_execv() + return + + @staticmethod + def _extend_pythonpath(env): + """ + If sys.path[0] is an empty string, the interpreter was likely + invoked with -m and the effective path is about to change on + re-exec. Add the current directory to $PYTHONPATH to ensure + that the new process sees the same path. + + This issue cannot be addressed in the general case because + Python cannot reliably reconstruct the + original command line (http://bugs.python.org/issue14208). + + (This idea filched from tornado.autoreload) + """ + + path_prefix = '.' + os.pathsep + existing_path = env.get('PYTHONPATH', '') + needs_patch = ( + sys.path[0] == '' and + not existing_path.startswith(path_prefix) + ) + + if needs_patch: + env["PYTHONPATH"] = path_prefix + existing_path + + def _set_cloexec(self): + """Set the CLOEXEC flag on all open files (except stdin/out/err). + + If self.max_cloexec_files is an integer (the default), then on + platforms which support it, it represents the max open files setting + for the operating system. This function will be called just before + the process is restarted via os.execv() to prevent open files + from persisting into the new process. + + Set self.max_cloexec_files to 0 to disable this behavior. + """ + for fd in range(3, self.max_cloexec_files): # skip stdin/out/err + try: + flags = fcntl.fcntl(fd, fcntl.F_GETFD) + except IOError: + continue + fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) + + def _do_execv(self): + """Re-execute the current process. + + This must be called from the main thread, because certain platforms + (OS X) don't allow execv to be called in a child thread very well. + """ + args = sys.argv[:] + + self._extend_pythonpath(os.environ) + + logger.info('Re-spawning %s' % ' '.join(args)) + logger.info("") + logger.info("%s Bye! %s", YEL("-" * 35), YEL("-" * 35)) + logger.info("") + + if sys.platform[:4] == 'java': + from _systemrestart import SystemRestart + raise SystemRestart + + args.insert(0, sys.executable) + if sys.platform == 'win32': + args = ['"%s"' % arg for arg in args] + + os.chdir(_module__file__base) + logger.debug("Change working directory to: %s", _module__file__base) + + if self.max_cloexec_files: + self._set_cloexec() + + os.execv(sys.executable, args) diff --git a/pym/calculate/contrib/spyne/util/autorel.pyc b/pym/calculate/contrib/spyne/util/autorel.pyc new file mode 100644 index 0000000..42ee445 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/autorel.pyc differ diff --git a/pym/calculate/contrib/spyne/util/cdict.py b/pym/calculate/contrib/spyne/util/cdict.py new file mode 100644 index 0000000..8e4d170 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/cdict.py @@ -0,0 +1,85 @@ + +# +# 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. It also assumes cdict never changes after +the first lookup. + +>>> 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] +*** 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 reversed(cls.__bases__): + try: + retval = self[b] + # this is why a cdict instance must never be modified after + # the first lookup + self[cls] = retval + return retval + except KeyError: + pass + raise e + + def get(self, k, d=None): + try: + return self[k] + + except KeyError: + return d diff --git a/pym/calculate/contrib/spyne/util/cdict.pyc b/pym/calculate/contrib/spyne/util/cdict.pyc new file mode 100644 index 0000000..bc978a9 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/cdict.pyc differ diff --git a/pym/calculate/contrib/spyne/util/cherry.py b/pym/calculate/contrib/spyne/util/cherry.py new file mode 100644 index 0000000..b604fc7 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/cherry.py @@ -0,0 +1,41 @@ +# 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 calculate.contrib.cherrypy as cherrypy + + +def cherry_graft_and_start(wsgi_application, host="0.0.0.0", port=8000, + 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() diff --git a/pym/calculate/contrib/spyne/util/cherry.pyc b/pym/calculate/contrib/spyne/util/cherry.pyc new file mode 100644 index 0000000..241d87e Binary files /dev/null and b/pym/calculate/contrib/spyne/util/cherry.pyc differ diff --git a/pym/calculate/contrib/spyne/util/color.py b/pym/calculate/contrib/spyne/util/color.py new file mode 100644 index 0000000..f12490a --- /dev/null +++ b/pym/calculate/contrib/spyne/util/color.py @@ -0,0 +1,74 @@ + +# +# 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)) + + DARK_R = lambda s: ''.join((colorama.Fore.RED, s, colorama.Style.RESET_ALL)) + DARK_G = lambda s: ''.join((colorama.Fore.GREEN, s, colorama.Style.RESET_ALL)) + DARK_B = lambda s: ''.join((colorama.Fore.BLUE, 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)) + + DARK_YEL = lambda s: ''.join((colorama.Fore.YELLOW, s, + colorama.Style.RESET_ALL)) + DARK_MAG = lambda s: ''.join((colorama.Fore.MAGENTA, s, + colorama.Style.RESET_ALL)) + DARK_CYA = lambda s: ''.join((colorama.Fore.CYAN, s, + colorama.Style.RESET_ALL)) + +except ImportError: + R = lambda s: s + G = lambda s: s + B = lambda s: s + DARK_R = lambda s: s + DARK_G = lambda s: s + DARK_B = lambda s: s + YEL = lambda s: s + MAG = lambda s: s + CYA = lambda s: s + DARK_YEL = lambda s: s + DARK_MAG = lambda s: s + DARK_CYA = lambda s: s + + +if __name__ == '__main__': + print(R("RED")) + print(G("GREEN")) + print(B("BLUE")) + print(DARK_R("DARK_RED")) + print(DARK_G("DARK_GREEN")) + print(DARK_B("DARK_BLUE")) + print(YEL("YELLOW")) + print(MAG("MAGENTA")) + print(CYA("CYAN")) diff --git a/pym/calculate/contrib/spyne/util/color.pyc b/pym/calculate/contrib/spyne/util/color.pyc new file mode 100644 index 0000000..9ea7489 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/color.pyc differ diff --git a/pym/calculate/contrib/spyne/util/coopmt.py b/pym/calculate/contrib/spyne/util/coopmt.py new file mode 100644 index 0000000..62fb142 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/coopmt.py @@ -0,0 +1,102 @@ + +# +# 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 cooperative multitasking module. It includes the coroutine stuff. + +This could have been named just coroutine.py if it wasn't for the coroutine +decorator. +""" + + +import logging +logger = logging.getLogger(__name__) + +from itertools import chain +from inspect import isgeneratorfunction + + +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: + return None + + except Exception as e: + if not hasattr(e, 'logged'): + logger.error("Exception in coroutine") + logger.exception(e) + try: + e.logged = True + except: + pass + + raise + + return ret + + return start + + +def keepfirst(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: + first = next(ret) + + except StopIteration: + return None + + except Exception as e: + if not hasattr(e, 'logged'): + logger.error("Exception in coroutine") + logger.exception(e) + try: + e.logged = True + except: + pass + + raise + + return chain((first,), ret) + + return start diff --git a/pym/calculate/contrib/spyne/util/coopmt.pyc b/pym/calculate/contrib/spyne/util/coopmt.pyc new file mode 100644 index 0000000..2a4f01f Binary files /dev/null and b/pym/calculate/contrib/spyne/util/coopmt.pyc differ diff --git a/pym/calculate/contrib/spyne/util/dictdoc.py b/pym/calculate/contrib/spyne/util/dictdoc.py new file mode 100644 index 0000000..77571cd --- /dev/null +++ b/pym/calculate/contrib/spyne/util/dictdoc.py @@ -0,0 +1,214 @@ + +# +# 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.context import FakeContext + +from spyne.protocol.dictdoc import HierDictDocument +from spyne.protocol.dictdoc import SimpleDictDocument + +try: + from spyne.protocol.json import JsonDocument +except ImportError as _import_error: + _local_import_error = _import_error + def JsonDocument(*args, **kwargs): + raise _local_import_error + + +try: + from spyne.protocol.yaml import YamlDocument +except ImportError as _import_error: + _local_import_error = _import_error + def YamlDocument(*args, **kwargs): + raise _local_import_error + + +try: + from spyne.protocol.msgpack import MessagePackDocument +except ImportError as _import_error: + _local_import_error = _import_error + def MessagePackDocument(*args, **kwargs): + raise _local_import_error + + +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_doc_as_object(d, cls, ignore_wrappers=True, complex_as=list, + protocol=_UtilProtocol, protocol_inst=None): + if protocol_inst is None: + protocol_inst = protocol(ignore_wrappers=ignore_wrappers, + complex_as=complex_as) + + return protocol_inst._doc_to_object(None, cls, d) + + +get_dict_as_object = get_doc_as_object +"""DEPRECATED: Use ``get_doc_as_object`` instead""" + + +def get_object_as_doc(o, cls=None, ignore_wrappers=True, complex_as=dict, + protocol=_UtilProtocol, protocol_inst=None): + if cls is None: + cls = o.__class__ + + if protocol_inst is None: + protocol_inst = protocol(ignore_wrappers=ignore_wrappers, + complex_as=complex_as) + + retval = protocol_inst._object_to_doc(cls, o) + + if not ignore_wrappers: + return {cls.get_type_name(): retval} + + return retval + + +get_object_as_dict = get_object_as_doc +"""DEPRECATED: Use ``get_object_as_doc`` instead.""" + +def get_object_as_simple_dict(o, cls=None, hier_delim='.', prefix=None): + if cls is None: + cls = o.__class__ + + return SimpleDictDocument(hier_delim=hier_delim) \ + .object_to_simple_dict(cls, o, prefix=prefix) + + +def get_object_as_json(o, cls=None, ignore_wrappers=True, complex_as=list, + encoding='utf8', polymorphic=False, indent=None, **kwargs): + if cls is None: + cls = o.__class__ + + prot = JsonDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as, + polymorphic=polymorphic, indent=indent, **kwargs) + ctx = FakeContext(out_document=[prot._object_to_doc(cls, o)]) + prot.create_out_string(ctx, encoding) + return b''.join(ctx.out_string) + + +def get_object_as_json_doc(o, cls=None, ignore_wrappers=True, complex_as=list, + polymorphic=False, indent=None, **kwargs): + if cls is None: + cls = o.__class__ + + prot = JsonDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as, + polymorphic=polymorphic, indent=indent, **kwargs) + + return prot._object_to_doc(cls, o) + + +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 b''.join(ctx.out_string) + + +def get_object_as_yaml_doc(o, cls=None, ignore_wrappers=False, complex_as=dict, + polymorphic=False): + if cls is None: + cls = o.__class__ + + prot = YamlDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as, + polymorphic=polymorphic) + return prot._object_to_doc(cls, o) + + +def get_object_as_msgpack(o, cls=None, ignore_wrappers=False, complex_as=dict, + 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) + return b''.join(ctx.out_string) + + +def get_object_as_msgpack_doc(o, cls=None, ignore_wrappers=False, + complex_as=dict, polymorphic=False): + if cls is None: + cls = o.__class__ + + prot = MessagePackDocument(ignore_wrappers=ignore_wrappers, + complex_as=complex_as, polymorphic=polymorphic) + + return prot._object_to_doc(cls, o) + + +def json_loads(s, cls, protocol=JsonDocument, **kwargs): + if s is None: + return None + if s == '': + return None + prot = protocol(**kwargs) + ctx = FakeContext(in_string=[s]) + prot.create_in_document(ctx) + return prot._doc_to_object(None, cls, ctx.in_document, + validator=prot.validator) + + +get_json_as_object = json_loads + + +def yaml_loads(s, cls, protocol=YamlDocument, ignore_wrappers=False, **kwargs): + if s is None: + return None + if s == '' or s == b'': + return None + prot = protocol(ignore_wrappers=ignore_wrappers, **kwargs) + ctx = FakeContext(in_string=[s]) + prot.create_in_document(ctx) + retval = prot._doc_to_object(None, cls, ctx.in_document, + validator=prot.validator) + return retval + + +get_yaml_as_object = yaml_loads diff --git a/pym/calculate/contrib/spyne/util/dictdoc.pyc b/pym/calculate/contrib/spyne/util/dictdoc.pyc new file mode 100644 index 0000000..f0c2310 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/dictdoc.pyc differ diff --git a/pym/calculate/contrib/spyne/util/django.py b/pym/calculate/contrib/spyne/util/django.py new file mode 100644 index 0000000..c51e622 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/django.py @@ -0,0 +1,538 @@ +# 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 + +""" + +from __future__ import absolute_import + +import logging +logger = logging.getLogger(__name__) + +import re + +from itertools import chain + +from django.core.exceptions import (ImproperlyConfigured, ObjectDoesNotExist, + ValidationError as DjValidationError) +from django.core.validators import (slug_re, + MinLengthValidator, MaxLengthValidator) +try: + from django.core.validators import comma_separated_int_list_re +except ImportError: + comma_separated_int_list_re = re.compile(r'^[\d,]+$') + +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 Service +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() if hasattr(field, 'rel') else field.remote_field.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_all_field_names(meta): + if hasattr(meta, 'get_all_field_names'): + return meta.get_all_field_names() + + return list(set(chain.from_iterable( + (field.name, field.attname) if hasattr(field, 'attname') else ( + field.name,) + for field in meta.get_fields() + # For complete backwards compatibility, you may want to exclude + # GenericForeignKey from the results. + if not (field.many_to_one and field.related_model is None) + ))) + + @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(DjangoModelMapper.get_all_field_names(meta)) + + 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 DjangoService(Service): + + """Service with common Django exception handling.""" + + @classmethod + def call_wrapper(cls, ctx): + """Handle common Django exceptions.""" + try: + out_object = super(DjangoService, cls).call_wrapper(ctx) + except ObjectDoesNotExist as e: + raise ObjectNotFoundError(e) + except DjValidationError as e: + raise ValidationError(e) + return out_object + + +# FIXME: To be removed in Spyne 3 +DjangoServiceBase = DjangoService diff --git a/pym/calculate/contrib/spyne/util/django.pyc b/pym/calculate/contrib/spyne/util/django.pyc new file mode 100644 index 0000000..f7fb8c0 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/django.pyc differ diff --git a/pym/calculate/contrib/spyne/util/dyninit.py b/pym/calculate/contrib/spyne/util/dyninit.py new file mode 100644 index 0000000..21eb775 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/dyninit.py @@ -0,0 +1,147 @@ +# encoding: utf-8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +from datetime import date, datetime + +from spyne import D, Integer, ModelBase, Date, DateTime, IpAddress, Decimal, \ + Boolean +from spyne.protocol import ProtocolBase +from spyne.util import six +from spyne.util.cdict import cdict + + +BOOL_VALUES_BYTES_TRUE = (b't', b'1', b'on', b'yes', b'true') +BOOL_VALUES_STR_TRUE = (u't', u'1', u'on', u'yes', u'true') + +BOOL_VALUES_BYTES_FALSE = (b'f', b'0', b'off', b'no', b'false') +BOOL_VALUES_STR_FALSE = (u'f', u'0', u'off', u'no', u'false') + +BOOL_VALUES_NONE = (None, '') + + +if six.PY2: + bytes = str +else: + unicode = str + + +_prot = ProtocolBase() + +def _bool_from_int(i): + if i in (0, 1): + return i == 1 + raise ValueError(i) + + +def _bool_from_bytes(s): + if s in BOOL_VALUES_NONE: + return None + + s = s.strip() + if s in BOOL_VALUES_NONE: + return None + s = s.lower() + if s in BOOL_VALUES_BYTES_TRUE: + return True + if s in BOOL_VALUES_BYTES_FALSE: + return False + raise ValueError(s) + + +def _bool_from_str(s): + if s in BOOL_VALUES_NONE: + return None + + s = s.strip() + if s in BOOL_VALUES_NONE: + return None + if s in BOOL_VALUES_STR_TRUE: + return True + if s in BOOL_VALUES_STR_FALSE: + return False + raise ValueError(s) + + +MAP = cdict({ + ModelBase: cdict({ + object: lambda _: _, + bytes: lambda _: _.strip(), + unicode: lambda _: _.strip(), + }), + + Decimal: cdict({ + D: lambda d: d, + int: lambda i: D(i), + bytes: lambda s: None if s.strip() == '' else D(s.strip()), + unicode: lambda s: None if s.strip() == u'' else D(s.strip()), + }), + + Boolean: cdict({ + D: lambda d: _bool_from_int(int(d)), + int: _bool_from_int, + bytes: _bool_from_bytes, + unicode: _bool_from_str, + }), + + Integer: cdict({ + D: lambda _: _, + int: lambda _: _, + bytes: lambda s: None if s.strip() == '' else int(s.strip()), + unicode: lambda s: None if s.strip() == u'' else int(s.strip()), + }), + + Date: cdict({ + date: lambda _: _, + datetime: lambda _: _.date(), + object: lambda _:_, + bytes: lambda s: None if s.strip() in ('', '0000-00-00') + else _prot.date_from_unicode(Date, s.strip()), + unicode: lambda s: None if s.strip() in (u'', u'0000-00-00') + else _prot.date_from_unicode(Date, s.strip()), + }), + + DateTime: cdict({ + date: lambda _: datetime(date.year, date.month, date.day), + datetime: lambda _: _, + object: lambda _:_, + bytes: lambda s: None if s.strip() in ('', '0000-00-00 00:00:00') + else _prot.datetime_from_unicode(DateTime, s.strip()), + unicode: lambda s: None if s.strip() in (u'', u'0000-00-00 00:00:00') + else _prot.datetime_from_unicode(DateTime, s.strip()), + }), + + IpAddress: cdict({ + object: lambda _: _, + bytes: lambda s: None if s.strip() == '' else s.strip(), + unicode: lambda s: None if s.strip() == u'' else s.strip(), + }) +}) + + +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 diff --git a/pym/calculate/contrib/spyne/util/dyninit.pyc b/pym/calculate/contrib/spyne/util/dyninit.pyc new file mode 100644 index 0000000..48a318b Binary files /dev/null and b/pym/calculate/contrib/spyne/util/dyninit.pyc differ diff --git a/pym/calculate/contrib/spyne/util/email.py b/pym/calculate/contrib/spyne/util/email.py new file mode 100644 index 0000000..456f7f5 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/email.py @@ -0,0 +1,129 @@ + +# +# 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 subprocess import Popen, PIPE + +from email.utils import COMMASPACE, formatdate +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from email.mime.application import MIMEApplication + +from spyne.util import six + + +def email_exception(exception_address, message="", bcc=None): + # 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' + recipients = [exception_address] + if bcc is not None: + recipients.extend(bcc) + + 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, recipients, 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_smtp(addresses, sender=None, subject='', message="", + host='localhost', port=25): + 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(host, port) + if six.PY2: + smtp_object.sendmail(sender, addresses, msg.as_string()) + else: + smtp_object.sendmail(sender, addresses, msg.as_bytes()) + logger.info("Text email sent to: %r.", addresses) + + +def email_text(addresses, sender=None, subject='', message="", bcc=None, + att=None): + if att is None: + att = {} + + if sender is None: + sender = 'Spyne ' + + exc = traceback.format_exc() + if exc is not None and exc != 'None\n': + message = (u"%s\n\n%s" % (message, exc)) + msg = MIMEText(message.encode('utf8'), 'plain', 'utf8') + if len(att) > 0: + newmsg = MIMEMultipart() + newmsg.attach(msg) + for k, v in att.items(): + part = MIMEApplication(v) + part.add_header('Content-Disposition', 'attachment', filename=k) + newmsg.attach(part) + + msg = newmsg + + msg['To'] = COMMASPACE.join(addresses) + msg['From'] = sender + msg['Date'] = formatdate() + msg['Subject'] = subject + + cmd = ["/usr/sbin/sendmail", "-oi", '--'] + cmd.extend(addresses) + if bcc is not None: + cmd.extend(bcc) + + p = Popen(cmd, stdin=PIPE) + if six.PY2: + p.communicate(msg.as_string()) + else: + p.communicate(msg.as_bytes()) + + logger.info("Text email sent to: %r.", addresses) diff --git a/pym/calculate/contrib/spyne/util/email.pyc b/pym/calculate/contrib/spyne/util/email.pyc new file mode 100644 index 0000000..61685fe Binary files /dev/null and b/pym/calculate/contrib/spyne/util/email.pyc differ diff --git a/pym/calculate/contrib/spyne/util/etreeconv.py b/pym/calculate/contrib/spyne/util/etreeconv.py new file mode 100644 index 0000000..cbd440c --- /dev/null +++ b/pym/calculate/contrib/spyne/util/etreeconv.py @@ -0,0 +1,131 @@ + +# +# 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, "Incoming dict len must be exactly 1. Data: %r" % d + + 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 diff --git a/pym/calculate/contrib/spyne/util/etreeconv.pyc b/pym/calculate/contrib/spyne/util/etreeconv.pyc new file mode 100644 index 0000000..c54b43e Binary files /dev/null and b/pym/calculate/contrib/spyne/util/etreeconv.pyc differ diff --git a/pym/calculate/contrib/spyne/util/fileproxy.py b/pym/calculate/contrib/spyne/util/fileproxy.py new file mode 100644 index 0000000..878c3a2 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/fileproxy.py @@ -0,0 +1,190 @@ + +# +# 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 os + +from spyne.util.six.moves.collections_abc import Iterator + +__all__ = 'FileProxy', 'ReusableFileProxy', 'SeekableFileProxy' + + +class FileProxy(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) diff --git a/pym/calculate/contrib/spyne/util/fileproxy.pyc b/pym/calculate/contrib/spyne/util/fileproxy.pyc new file mode 100644 index 0000000..9f103cc Binary files /dev/null and b/pym/calculate/contrib/spyne/util/fileproxy.pyc differ diff --git a/pym/calculate/contrib/spyne/util/gencpp.py b/pym/calculate/contrib/spyne/util/gencpp.py new file mode 100644 index 0000000..01ad206 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/gencpp.py @@ -0,0 +1,254 @@ + +# +# 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 PoC that implements like 2% of the job of converting Spyne objects to +standard C++ classes.""" + +import sys + +INDENT = ' ' + +class Object(object): + def __init__(self): + self.parent = None + self.comment_before = None + self.comment_after = None + + def _comment_before_to_stream(self, ostr, indent): + if self.comment_before is None: + return + + ostr.write("\n") + ostr.write(INDENT * indent) + ostr.write("/**\n") + ostr.write(INDENT * indent) + ostr.write(" *") + for line in self.comment_before.split('\n'): + ostr.write(" ") + ostr.write(line) + ostr.write('\n') + ostr.write(INDENT * indent) + ostr.write(" */") + ostr.write("\n") + + def _comment_after_to_stream(self, ostr, indent): + if self.comment_after is None: + return + + lines = self.comment_after.split('\n') + + if len(lines) < 2: + ostr.write(" // ") + ostr.write(self.comment_after) + + else: + ostr.write(INDENT * indent) + ostr.write("/**\n") + ostr.write(INDENT * indent) + ostr.write(" *") + for line in lines: + ostr.write(" ") + ostr.write(line) + ostr.write('\n') + ostr.write(INDENT * indent) + ostr.write(" */") + ostr.write("\n") + + +class Entry(Object): + def __init__(self, modifier=None): + super(Entry, self).__init__() + self.modifier = modifier + + def to_decl_stream(self, ostr, indent): + raise NotImplemented() + + def to_defn_stream(self, ostr, indent): + raise NotImplemented() + + +class Literal(Object): + def __init__(self, value): + super(Literal, self).__init__() + self.value = value + + +class StringLiteral(Literal): + def to_stream(self, ostr, indent): + self._comment_before_to_stream(ostr, indent) + + ostr.write('"') + ostr.write(self.value) # TODO: escaping + ostr.write('"') + + self._comment_after_to_stream(ostr, indent) + + +class DataMember(Entry): + def __init__(self, modifier, type, name, initializer=None): + super(DataMember, self).__init__(modifier) + self.type = type + self.name = name + self.initializer = initializer + + def to_decl_stream(self, ostr, indent): + ostr.write(INDENT * indent) + if self.modifier is not None: + ostr.write(self.modifier) + ostr.write(" ") + ostr.write(self.type) + ostr.write(" ") + ostr.write(self.name) + + if self.modifier != 'static' and self.initializer is not None: + ostr.write(" = ") + self.initializer.to_stream(ostr, indent) + + ostr.write(";") + ostr.write("\n") + + def to_defn_stream(self, ostr, indent): + if self.modifier != 'static': + return + + self._comment_before_to_stream(ostr, indent) + + ostr.write(INDENT * indent) + + ostr.write(self.type) + ostr.write(" ") + + parents = [] + parent = self.parent + while parent is not None: + parents.insert(0, parent) + parent = parent.parent + + for parent in parents: + ostr.write(parent.name) + ostr.write("::") + + ostr.write(self.name) + + if self.initializer is not None: + ostr.write(" = ") + self.initializer.to_stream(ostr, indent) + + ostr.write(";") + ostr.write("\n") + + self._comment_after_to_stream(ostr, indent) + + +class Class(Entry): + def __init__(self): + super(Class, self).__init__() + + self.name = None + self.namespace = None + self.type = 'class' + self.public_entries = [] + self.protected_entries = [] + self.private_entries = [] + + def to_decl_stream(self, ostr, indent=0): + if self.namespace is not None: + ostr.write("namespace ") + ostr.write(self.namespace) + ostr.write(" {\n") + + ostr.write(INDENT * indent) + ostr.write("%s %s {\n" % (self.type, self.name,)) + + if len(self.public_entries) > 0: + ostr.write(INDENT * indent) + ostr.write("public:\n") + for e in self.public_entries: + e.to_decl_stream(ostr, indent + 1) + ostr.write("\n") + + if len(self.protected_entries) > 0: + ostr.write(INDENT * indent) + ostr.write("protected:\n") + for e in self.protected_entries: + e.to_decl_stream(ostr, indent + 1) + ostr.write("\n") + + if len(self.private_entries) > 0: + ostr.write(INDENT * indent) + ostr.write("private:\n") + for e in self.private_entries: + e.to_decl_stream(ostr, indent + 1) + ostr.write("\n") + + ostr.write(INDENT * indent) + ostr.write("};\n") + + if self.namespace is not None: + ostr.write("}\n") + + def to_defn_stream(self, ostr, indent=0): + if self.namespace is not None: + ostr.write("namespace ") + ostr.write(self.namespace) + ostr.write(" {\n") + + if len(self.public_entries) > 0: + for e in self.public_entries: + e.to_defn_stream(ostr, indent) + + if len(self.protected_entries) > 0: + for e in self.protected_entries: + e.to_defn_stream(ostr, indent) + + if len(self.private_entries) > 0: + for e in self.private_entries: + e.to_defn_stream(ostr, indent) + + if self.namespace is not None: + ostr.write("}\n") + +def gen_cpp_class(cls, namespace=None, type_map=None): + if type_map is None: + type_map = dict() + + ocls = Class() + ocls.name = cls.get_type_name() + ocls.namespace = namespace + + keys = Class() + keys.name = "Key" + keys.parent = ocls + keys.type = "struct" + ocls.public_entries.append(keys) + + for k, v in cls.get_flat_type_info(cls).items(): + member = DataMember( + "static", "const std::string", + k, StringLiteral(v.Attributes.sub_name or k) + ) + + member.comment_before = v.Annotations.doc + member.parent = keys + + keys.public_entries.append(member) + + ocls.to_decl_stream(sys.stdout) + sys.stdout.write("\n\n\n\n") + ocls.to_defn_stream(sys.stdout) diff --git a/pym/calculate/contrib/spyne/util/gencpp.pyc b/pym/calculate/contrib/spyne/util/gencpp.pyc new file mode 100644 index 0000000..90d0fef Binary files /dev/null and b/pym/calculate/contrib/spyne/util/gencpp.pyc differ diff --git a/pym/calculate/contrib/spyne/util/http.py b/pym/calculate/contrib/spyne/util/http.py new file mode 100644 index 0000000..3e21a8a --- /dev/null +++ b/pym/calculate/contrib/spyne/util/http.py @@ -0,0 +1,66 @@ +# 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) diff --git a/pym/calculate/contrib/spyne/util/http.pyc b/pym/calculate/contrib/spyne/util/http.pyc new file mode 100644 index 0000000..27e46fe Binary files /dev/null and b/pym/calculate/contrib/spyne/util/http.pyc differ diff --git a/pym/calculate/contrib/spyne/util/invregexp.py b/pym/calculate/contrib/spyne/util/invregexp.py new file mode 100644 index 0000000..1e7f1e5 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/invregexp.py @@ -0,0 +1,322 @@ + +# +# invRegex.py +# +# Copyright 2008, Paul McGuire +# +# The pyparsing license follows: +# +######### +# +# 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. +# +######### +# +# 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() diff --git a/pym/calculate/contrib/spyne/util/invregexp.pyc b/pym/calculate/contrib/spyne/util/invregexp.pyc new file mode 100644 index 0000000..bb3e2ca Binary files /dev/null and b/pym/calculate/contrib/spyne/util/invregexp.pyc differ diff --git a/pym/calculate/contrib/spyne/util/memo.py b/pym/calculate/contrib/spyne/util/memo.py new file mode 100644 index 0000000..d64efb5 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/memo.py @@ -0,0 +1,180 @@ + +# +# 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 module for memoization stuff. + +When you have memory leaks in your daemon, the reason could very well be +reckless usage of the tools here. + +These are NOT thread-safe. If you are relying on exactly-one-execution-per-key +behavior in a multithreaded environment, roll your own stuff. +""" + + +import logging +logger = logging.getLogger(__name__) + +import threading + + +MEMOIZATION_STATS_LOG_INTERVAL = 60.0 + + +def _log_all(): + logger.info("%d memoizers", len(memoize.registry)) + for memo in memoize.registry: + logger.info("%r: %d entries.", memo.func, len(memo.memo)) + + +def _log_func(func): + for memo in memoize.registry: + if memo.func is func.func.im_self.func: + break + else: + logger.error("%r not found in memoization regisry", func) + return + + logger.info("%r: %d entries.", memo.func, len(memo.memo)) + for k, v in memo.memo.items(): + logger.info("\t%r: %r", k, v) + + +def start_memoization_stats_logger(func=None): + logger.info("Enabling @memoize statistics every %d second(s).", + MEMOIZATION_STATS_LOG_INTERVAL) + + if func is None: + _log_all() + else: + _log_func(func) + + t = threading.Timer(MEMOIZATION_STATS_LOG_INTERVAL, + start_memoization_stats_logger, (func,)) + + t.daemon = True + t.start() + + +class memoize(object): + """A memoization decorator that keeps caching until reset.""" + + registry = [] + + def __init__(self, func): + self.func = func + self.memo = {} + self.lock = threading.RLock() + memoize.registry.append(self) + + def __call__(self, *args, **kwargs): + key = self.get_key(args, kwargs) + # we hope that gil makes this comparison is race-free + if not key in self.memo: + with self.lock: + # make sure the situation hasn't changed after lock acq + if not key in self.memo: + value = self.func(*args, **kwargs) + self.memo[key] = value + return value + return self.memo.get(key) + + def get_key(self, args, kwargs): + return tuple(args), tuple(kwargs.items()) + + def reset(self): + self.memo = {} + + +class memoize_first(object): + """A memoization decorator that keeps the first call without condition, aka + a singleton accessor.""" + + registry = [] + + def __init__(self, func): + self.func = func + self.lock = threading.RLock() + memoize.registry.append(self) + + def __call__(self, *args, **kwargs): + if not hasattr(self, 'memo'): + value = self.func(*args, **kwargs) + self.memo = value + return value + return self.memo + + def reset(self): + del self.memo + + +def memoize_ignore(values): + """A memoization decorator that does memoization unless the returned + value is in the 'values' iterable. eg let `values = (2,)` and + `add = lambda x, y: x + y`, the result of `add(1, 1)` (=2) is not memoized + but the result of `add(5, 5)` (=10) is. + """ + + assert iter(values), \ + "memoize_ignore requires an iterable of values to ignore" + + class _memoize_ignored(memoize): + def __call__(self, *args, **kwargs): + key = self.get_key(args, kwargs) + # we hope that gil makes this comparison is race-free + if not key in self.memo: + with self.lock: + # make sure the situation hasn't changed after lock acq + if not key in self.memo: + value = self.func(*args, **kwargs) + if not value in values: + self.memo[key] = value + + return value + return self.memo.get(key) + + return _memoize_ignored + + +class memoize_ignore_none(memoize): + """A memoization decorator that ignores `None` values. ie when the decorated + function returns `None`, the value is returned but not memoized. + """ + + def __call__(self, *args, **kwargs): + key = self.get_key(args, kwargs) + # we hope that gil makes this comparison is race-free + if not key in self.memo: + with self.lock: + # make sure the situation hasn't changed after lock acq + if not key in self.memo: + value = self.func(*args, **kwargs) + if not (value is None): + self.memo[key] = value + + return value + return self.memo.get(key) + + +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()]) diff --git a/pym/calculate/contrib/spyne/util/memo.pyc b/pym/calculate/contrib/spyne/util/memo.pyc new file mode 100644 index 0000000..d5c1604 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/memo.pyc differ diff --git a/pym/calculate/contrib/spyne/util/meta.py b/pym/calculate/contrib/spyne/util/meta.py new file mode 100644 index 0000000..81f8da0 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/meta.py @@ -0,0 +1,139 @@ +# 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 +# + +"""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 running under Nuitka") + + return type.__new__(cls, name, bases, attributes) diff --git a/pym/calculate/contrib/spyne/util/meta.pyc b/pym/calculate/contrib/spyne/util/meta.pyc new file mode 100644 index 0000000..c5069ab Binary files /dev/null and b/pym/calculate/contrib/spyne/util/meta.pyc differ diff --git a/pym/calculate/contrib/spyne/util/odict.py b/pym/calculate/contrib/spyne/util/odict.py new file mode 100644 index 0000000..d8b996d --- /dev/null +++ b/pym/calculate/contrib/spyne/util/odict.py @@ -0,0 +1,134 @@ + +# +# 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): + super(odict, self).__delitem__(key) + key = self.__list.index(key) # ouch. + else: + 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, **kwargs): + if isinstance(data, (dict, odict)): + data = data.items() + + for k, v in data: + self[k] = v + + for k, v in kwargs.items(): + 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) diff --git a/pym/calculate/contrib/spyne/util/odict.pyc b/pym/calculate/contrib/spyne/util/odict.pyc new file mode 100644 index 0000000..24014ae Binary files /dev/null and b/pym/calculate/contrib/spyne/util/odict.pyc differ diff --git a/pym/calculate/contrib/spyne/util/oset.py b/pym/calculate/contrib/spyne/util/oset.py new file mode 100644 index 0000000..03e7c59 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/oset.py @@ -0,0 +1,96 @@ +# http://code.activestate.com/recipes/576694/ + +from spyne.util.six.moves.collections_abc import MutableSet + +KEY, PREV, NEXT = list(range(3)) + +"""This module contains an ordered set implementation from +http://code.activestate.com/recipes/576694/ """ + +class oset(MutableSet): + """An ordered set implementation.""" + + def __init__(self, iterable=None): + self.end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.map = {} # key --> [key, prev, next] + if iterable is not None: + self |= iterable + + def __len__(self): + return len(self.map) + + def __contains__(self, key): + return key in self.map + + def add(self, key): + if key not in self.map: + end = self.end + curr = end[PREV] + curr[NEXT] = end[PREV] = self.map[key] = [key, curr, end] + + def 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) diff --git a/pym/calculate/contrib/spyne/util/oset.pyc b/pym/calculate/contrib/spyne/util/oset.pyc new file mode 100644 index 0000000..305f673 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/oset.pyc differ diff --git a/pym/calculate/contrib/spyne/util/protocol.py b/pym/calculate/contrib/spyne/util/protocol.py new file mode 100644 index 0000000..4ae305c --- /dev/null +++ b/pym/calculate/contrib/spyne/util/protocol.py @@ -0,0 +1,38 @@ + +# +# 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 diff --git a/pym/calculate/contrib/spyne/util/protocol.pyc b/pym/calculate/contrib/spyne/util/protocol.pyc new file mode 100644 index 0000000..3879acc Binary files /dev/null and b/pym/calculate/contrib/spyne/util/protocol.pyc differ diff --git a/pym/calculate/contrib/spyne/util/resource.py b/pym/calculate/contrib/spyne/util/resource.py new file mode 100644 index 0000000..bde03e8 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/resource.py @@ -0,0 +1,72 @@ +# encoding: utf8 +# +# spyne - Copyright (C) Spyne contributors. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# + +import os.path + +import spyne.util.autorel + + +def get_resource_path(ns, fn): + try: + from spyne._deploymentinfo import resource_filename + except ImportError: + from pkg_resources import resource_filename + + resfn = resource_filename(ns, fn) + spyne.util.autorel.AutoReloader.FILES.add(resfn) + path = os.path.abspath(resfn) + return path + + +def get_resource_file(ns, fn): + return open(get_resource_path(ns, fn), 'rb') + + +def get_resource_file_contents(ns, fn, enc=None): + resfn = get_resource_path(ns, fn) + + if enc is None: + return open(resfn, 'rb').read() + else: + return open(resfn, 'rb').read().decode(enc) + + +def parse_xml_resource(ns, fn): + from lxml import etree + + retval = etree.parse(get_resource_file(ns, fn)) + + return retval.getroot() + + +def parse_html_resource(ns, fn): + from lxml import html + + retval = html.parse(get_resource_file(ns, fn)) + + return retval.getroot() + + +def parse_cloth_resource(ns, fn): + from lxml import html + + retval = html.fragment_fromstring(get_resource_file_contents(ns, fn), + create_parent='spyne-root') + retval.attrib['spyne-tagbag'] = '' + return retval diff --git a/pym/calculate/contrib/spyne/util/resource.pyc b/pym/calculate/contrib/spyne/util/resource.pyc new file mode 100644 index 0000000..ad3103b Binary files /dev/null and b/pym/calculate/contrib/spyne/util/resource.pyc differ diff --git a/pym/calculate/contrib/spyne/util/simple.py b/pym/calculate/contrib/spyne/util/simple.py new file mode 100644 index 0000000..164be68 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/simple.py @@ -0,0 +1,57 @@ + +# +# 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) diff --git a/pym/calculate/contrib/spyne/util/simple.pyc b/pym/calculate/contrib/spyne/util/simple.pyc new file mode 100644 index 0000000..c09ea7c Binary files /dev/null and b/pym/calculate/contrib/spyne/util/simple.pyc differ diff --git a/pym/calculate/contrib/spyne/util/six.py b/pym/calculate/contrib/spyne/util/six.py new file mode 100644 index 0000000..d13ddbe --- /dev/null +++ b/pym/calculate/contrib/spyne/util/six.py @@ -0,0 +1,985 @@ +# Copyright (c) 2010-2020 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. + +"""Utilities for writing code that runs on Python 2 and 3""" + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.14.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +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("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("getoutput", "commands", "subprocess"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "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("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_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_base", "email.MIMEBase", "email.mime.base"), + MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), + 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("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"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + 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("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("splitvalue", "urllib", "urllib.parse"), + MovedAttribute("splittype", "urllib", "urllib.parse"), + MovedAttribute("splithost", "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"), + MovedAttribute("parse_http_list", "urllib2", "urllib.request"), + MovedAttribute("parse_keqv_list", "urllib2", "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 + + def create_unbound_method(func, cls): + return func + + 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__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + 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 d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return 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 + import struct + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + del io + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" + _assertNotRegex = "assertNotRegex" +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" + _assertNotRegex = "assertNotRegexpMatches" +_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) + + +def assertNotRegex(self, *args, **kwargs): + return getattr(self, _assertNotRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + +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): + try: + raise tp, value, tb + finally: + tb = None +""") + + +if sys.version_info[:2] > (3,): + exec_("""def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""") +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): + # This does exactly the same what the :func:`py3:functools.update_wrapper` + # function does on Python versions after 3.2. It sets the ``__wrapped__`` + # attribute on ``wrapper`` object and it doesn't raise an error if any of + # the attributes mentioned in ``assigned`` and ``updated`` are missing on + # ``wrapped`` object. + def _update_wrapper(wrapper, wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + continue + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + wrapper.__wrapped__ = wrapped + return wrapper + _update_wrapper.__doc__ = functools.update_wrapper.__doc__ + + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + return functools.partial(_update_wrapper, wrapped=wrapped, + assigned=assigned, updated=updated) + wraps.__doc__ = functools.wraps.__doc__ + +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(type): + + def __new__(cls, name, this_bases, d): + if sys.version_info[:2] >= (3, 7): + # This version introduced PEP 560 that requires a bit + # of extra care (we mimic what is done by __build_class__). + resolved_bases = types.resolve_bases(bases) + if resolved_bases is not bases: + d['__orig_bases__'] = bases + else: + resolved_bases = bases + return meta(name, resolved_bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + 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) + if hasattr(cls, '__qualname__'): + orig_vars['__qualname__'] = cls.__qualname__ + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def ensure_binary(s, encoding='utf-8', errors='strict'): + """Coerce **s** to six.binary_type. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> encoded to `bytes` + - `bytes` -> `bytes` + """ + if isinstance(s, text_type): + return s.encode(encoding, errors) + elif isinstance(s, binary_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + +def ensure_str(s, encoding='utf-8', errors='strict'): + """Coerce *s* to `str`. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) + if PY2 and isinstance(s, text_type): + s = s.encode(encoding, errors) + elif PY3 and isinstance(s, binary_type): + s = s.decode(encoding, errors) + return s + + +def ensure_text(s, encoding='utf-8', errors='strict'): + """Coerce *s* to six.text_type. + + For Python 2: + - `unicode` -> `unicode` + - `str` -> `unicode` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if isinstance(s, binary_type): + return s.decode(encoding, errors) + elif isinstance(s, text_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + +def python_2_unicode_compatible(klass): + """ + A class 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) \ No newline at end of file diff --git a/pym/calculate/contrib/spyne/util/six.pyc b/pym/calculate/contrib/spyne/util/six.pyc new file mode 100644 index 0000000..af02b04 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/six.pyc differ diff --git a/pym/calculate/contrib/spyne/util/tdict.py b/pym/calculate/contrib/spyne/util/tdict.py new file mode 100644 index 0000000..82875e2 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/tdict.py @@ -0,0 +1,101 @@ + +# +# 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__()) diff --git a/pym/calculate/contrib/spyne/util/tdict.pyc b/pym/calculate/contrib/spyne/util/tdict.pyc new file mode 100644 index 0000000..7782a65 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/tdict.pyc differ diff --git a/pym/calculate/contrib/spyne/util/test.py b/pym/calculate/contrib/spyne/util/test.py new file mode 100644 index 0000000..850f20d --- /dev/null +++ b/pym/calculate/contrib/spyne/util/test.py @@ -0,0 +1,97 @@ +# 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 + +from spyne.util 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 = { + u'QUERY_STRING': urlencode(body_pairs), + u'PATH_INFO': '/%s' % mn, + u'REQUEST_METHOD': u'GET', + u'SERVER_NAME': u'spyne.test', + u'SERVER_PORT': u'0', + u'wsgi.url_scheme': u'http', + } + + print(headers) + request.update(headers) + + out_string = [] + t = None + for s in app(request, _start_response): + t = type(s) + out_string.append(s) + + if t == bytes: + out_string = b''.join(out_string) + else: + out_string = ''.join(out_string) + + 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), 'wb') + else: + f = open(fn, 'wb') + + f.write(html.tostring(elt, pretty_print=True, doctype="")) diff --git a/pym/calculate/contrib/spyne/util/test.pyc b/pym/calculate/contrib/spyne/util/test.pyc new file mode 100644 index 0000000..4b5eb66 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/test.pyc differ diff --git a/pym/calculate/contrib/spyne/util/tlist.py b/pym/calculate/contrib/spyne/util/tlist.py new file mode 100644 index 0000000..ee83666 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/tlist.py @@ -0,0 +1,103 @@ + +# +# 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 +# + +# adapted from: https://gist.github.com/KrzysztofCiba/4579691 + +## +# @file TypedList.py +# @author Krzysztof.Ciba@NOSPAMgmail.com +# @date 2012/07/19 08:21:22 +# @brief Definition of TypedList class. + + +class tlist(list): + """ + .. class:: tlist + + A list-like class holding only objects of specified type(s). + """ + + def __init__(self, iterable=None, types=None): + iterable = list() if not iterable else iterable + + # make sure it is iterable + iter(iterable) + + types = types if isinstance(types, tuple) else (types,) + for item in types: + if not isinstance(item, type): + raise TypeError("%s is not a type" % repr(item)) + + self._types = types + for i in iterable: + self._type_check(i) + list.__init__(self, iterable) + + def types(self): + return self._types + + def _type_check(self, val): + if not self._types: + return + + if not isinstance(val, self._types): + raise TypeError( + "Wrong type %s, this list can hold only instances of %s" + % (type(val), str(self._types))) + + def __iadd__(self, other): + map(self._type_check, other) + list.__iadd__(self, other) + return self + + def __add__(self, other): + iterable = [item for item in self] + [item for item in other] + return tlist(iterable, self._types) + + def __radd__(self, other): + iterable = [item for item in other] + [item for item in self] + if isinstance(other, tlist): + return self.__class__(iterable, other.types()) + return tlist(iterable, self._types) + + def __setitem__(self, key, value): + itervalue = (value,) + if isinstance(key, slice): + iter(value) + itervalue = value + map(self._type_check, itervalue) + list.__setitem__(self, key, value) + + def __setslice__(self, i, j, iterable): + iter(iterable) + map(self._type_check, iterable) + list.__setslice__(self, i, j, iterable) + + def append(self, val): + self._type_check(val) + list.append(self, val) + + def extend(self, iterable): + iter(iterable) + map(self._type_check, iterable) + list.extend(self, iterable) + + def insert(self, i, val): + self._type_check(val) + list.insert(self, i, val) diff --git a/pym/calculate/contrib/spyne/util/tlist.pyc b/pym/calculate/contrib/spyne/util/tlist.pyc new file mode 100644 index 0000000..5e7d6c5 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/tlist.pyc differ diff --git a/pym/calculate/contrib/spyne/util/toposort.py b/pym/calculate/contrib/spyne/util/toposort.py new file mode 100644 index 0000000..6fba0ce --- /dev/null +++ b/pym/calculate/contrib/spyne/util/toposort.py @@ -0,0 +1,54 @@ +# 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: + # Python 3 + from functools import reduce +except: + pass + + +def toposort2(data): + if len(data) == 0: + return + + 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) diff --git a/pym/calculate/contrib/spyne/util/toposort.pyc b/pym/calculate/contrib/spyne/util/toposort.pyc new file mode 100644 index 0000000..e4e2360 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/toposort.pyc differ diff --git a/pym/calculate/contrib/spyne/util/web.py b/pym/calculate/contrib/spyne/util/web.py new file mode 100644 index 0000000..1dd3847 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/web.py @@ -0,0 +1,354 @@ + +# +# 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 + +import logging +logger = logging.getLogger(__name__) + +from inspect import isclass + +from spyne import rpc, Any, AnyDict, NATIVE_MAP, M, Array, ComplexModelBase, \ + UnsignedInteger32, PushBase, Iterable, ModelBase, File, Service, \ + ResourceNotFoundError, Unicode + +from spyne.const import MAX_ARRAY_ELEMENT_NUM, MAX_DICT_ELEMENT_NUM, \ + MAX_STRING_FIELD_LENGTH, MAX_FIELD_NUM + +try: + from spyne.store.relational.document import FileData + from sqlalchemy.orm.exc import DetachedInstanceError +except ImportError: + # these are used just for isinstance checks. so we just set it to an + # anonymous value + FileData = type('__hidden', (object, ), {}) + DetachedInstanceError = type('__hidden', (Exception, ), {}) + +from spyne.util import memoize, six + +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 ReaderService(Service): + pass + + +class WriterService(Service): + pass + + +def log_repr(obj, cls=None, given_len=None, parent=None, from_array=False, + tags=None, prot=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' + + objcls = None + if hasattr(obj, '__class__'): + objcls = obj.__class__ + + if objcls in (list, tuple): + objcls = Array(Any) + + elif objcls is dict: + objcls = AnyDict + + elif objcls in NATIVE_MAP: + objcls = NATIVE_MAP[objcls] + + if objcls is not None and (cls is None or issubclass(objcls, cls)): + cls = objcls + + cls_attrs = None + logged = None + + if hasattr(cls, 'Attributes'): + if prot is None: + cls_attrs = cls.Attributes + else: + cls_attrs = prot.get_cls_attrs(cls) + + logged = cls_attrs.logged + if not logged: + return "%s(...)" % cls.get_type_name() + + if logged == '...': + return "(...)" + + if issubclass(cls, File) and isinstance(obj, File.Value): + cls = obj.__class__ + + if cls_attrs.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, ValueError): + if given_len is not None: + l = str(given_len) + + return "" % l + + if callable(cls_attrs.logged): + try: + return cls_attrs.logged(obj) + except Exception as e: + logger.error("Exception %r in log_repr transformer ignored", e) + logger.exception(e) + pass + + if issubclass(cls, AnyDict): + retval = [] + + if isinstance(obj, dict): + if logged == 'full': + for i, (k, v) in enumerate(obj.items()): + retval.append('%r: %r' % (k, v)) + + elif logged == 'keys': + for i, k in enumerate(obj.keys()): + if i >= MAX_DICT_ELEMENT_NUM: + retval.append("(...)") + break + + retval.append('%r: (...)' % (k,)) + + elif logged == 'values': + for i, v in enumerate(obj.values()): + if i >= MAX_DICT_ELEMENT_NUM: + retval.append("(...)") + break + + retval.append('(...): %s' % (log_repr(v, tags=tags),)) + + elif logged == 'keys-full': + for k in obj.keys(): + retval.append('%r: (...)' % (k,)) + + elif logged == 'values-full': + for v in obj.values(): + retval.append('(...): %r' % (v,)) + + elif logged is True: # default behaviour + for i, (k, v) in enumerate(obj.items()): + if i >= MAX_DICT_ELEMENT_NUM: + retval.append("(...)") + break + + retval.append('%r: %s' % (k, + log_repr(v, parent=k, tags=tags))) + else: + raise ValueError("Invalid value logged=%r", logged) + + return "{%s}" % ', '.join(retval) + + else: + if logged in ('full', 'keys-full', 'values-full'): + retval = [repr(s) for s in obj] + + else: + for i, v in enumerate(obj): + if i >= MAX_DICT_ELEMENT_NUM: + retval.append("(...)") + break + + retval.append(log_repr(v, tags=tags)) + + return "[%s]" % ', '.join(retval) + + if (issubclass(cls, Array) or (cls_attrs.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): + return '[]' + + if logged is None: + logged = cls_attrs.logged + + for i, o in enumerate(obj): + if logged != 'full' and i >= MAX_ARRAY_ELEMENT_NUM: + retval.append("(...)") + break + + retval.append(log_repr(o, subcls, from_array=True, tags=tags)) + + return "[%s]" % (', '.join(retval)) + + if 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 + + if logged == '...': + retval.append("%s=(...)" % k) + continue + + try: + v = getattr(obj, k, None) + except (AttributeError, KeyError, DetachedInstanceError): + 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)) + + if issubclass(cls, Unicode) and isinstance(obj, six.string_types): + if len(obj) > MAX_STRING_FIELD_LENGTH: + return '%r(...)' % obj[:MAX_STRING_FIELD_LENGTH] + + return repr(obj) + + if issubclass(cls, File) and isinstance(obj, FileData): + return log_repr(obj, FileData, tags=tags) + + retval = repr(obj) + + if len(retval) > MAX_STRING_FIELD_LENGTH: + retval = retval[:MAX_STRING_FIELD_LENGTH] + "(...)" + + return retval + + +def TReaderService(T, T_name): + class ReaderService(ReaderService): + @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 + + +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(WriterService): + @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 diff --git a/pym/calculate/contrib/spyne/util/web.pyc b/pym/calculate/contrib/spyne/util/web.pyc new file mode 100644 index 0000000..25817eb Binary files /dev/null and b/pym/calculate/contrib/spyne/util/web.pyc differ diff --git a/pym/calculate/contrib/spyne/util/wsgi_wrapper.py b/pym/calculate/contrib/spyne/util/wsgi_wrapper.py new file mode 100644 index 0000000..b150849 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/wsgi_wrapper.py @@ -0,0 +1,126 @@ + +# +# 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() diff --git a/pym/calculate/contrib/spyne/util/wsgi_wrapper.pyc b/pym/calculate/contrib/spyne/util/wsgi_wrapper.pyc new file mode 100644 index 0000000..efc9cd3 Binary files /dev/null and b/pym/calculate/contrib/spyne/util/wsgi_wrapper.pyc differ diff --git a/pym/calculate/contrib/spyne/util/xml.py b/pym/calculate/contrib/spyne/util/xml.py new file mode 100644 index 0000000..d52ada5 --- /dev/null +++ b/pym/calculate/contrib/spyne/util/xml.py @@ -0,0 +1,309 @@ + +# +# 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 inspect import isgenerator + +from lxml import etree + +from os.path import dirname +from os.path import abspath + +from spyne import ServiceBase, Application, srpc +from spyne.context import FakeContext +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 import ProtocolMixin +from spyne.protocol.cloth import XmlCloth + +from spyne.protocol.xml import XmlDocument +from spyne.util.appreg import unregister_application +from spyne.util.six import BytesIO +from spyne.util.tlist import tlist + + +class FakeApplication(object): + def __init__(self, default_namespace): + self.tns = default_namespace + self.services = () + self.classes = () + + +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(default_namespace) + + 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(default_namespace) + + 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__ + + 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_object_as_xml_polymorphic(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 no_namespace: + app = Application([ServiceBase], tns="", + out_protocol=XmlDocument(polymorphic=True)) + else: + tns = cls.get_namespace() + if tns is None: + raise ValueError( + "Either set a namespace for %r or pass no_namespace=True" + % (cls, )) + + class _DummyService(ServiceBase): + @srpc(cls) + def f(_): + pass + + app = Application([_DummyService], tns=tns, + out_protocol=XmlDocument(polymorphic=True)) + + unregister_application(app) + + parent = etree.Element("parent", nsmap=app.interface.nsmap) + + app.out_protocol.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_polymorphic(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. + """ + + tns = cls.get_namespace() + if tns is None: + raise ValueError("Please set a namespace for %r" % (cls, )) + + class _DummyService(ServiceBase): + @srpc(cls) + def f(_): + pass + + app = Application([_DummyService], tns=tns, + in_protocol=XmlDocument(polymorphic=True)) + + unregister_application(app) + + return app.in_protocol.from_element(FakeContext(app=app), cls, elt) + + +def get_object_as_xml_cloth(inst, cls=None, no_namespace=False, encoding='utf8'): + """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 + + ostr = BytesIO() + xml_cloth = XmlCloth(use_ns=(not no_namespace)) + ctx = FakeContext() + with etree.xmlfile(ostr, encoding=encoding) as xf: + ctx.outprot_ctx.doctype_written = False + ctx.protocol.prot_stack = tlist([], ProtocolMixin) + tn = cls.get_type_name() + ret = xml_cloth.subserialize(ctx, cls, inst, xf, tn) + + assert not isgenerator(ret) + + return ostr.getvalue() + + +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=None, 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. + """ + + if files is None: + files = dict() + + 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) diff --git a/pym/calculate/contrib/spyne/util/xml.pyc b/pym/calculate/contrib/spyne/util/xml.pyc new file mode 100644 index 0000000..c03696d Binary files /dev/null and b/pym/calculate/contrib/spyne/util/xml.pyc differ diff --git a/pym/calculate/contrib/suds/__init__.py b/pym/calculate/contrib/suds/__init__.py new file mode 100644 index 0000000..c45ca33 --- /dev/null +++ b/pym/calculate/contrib/suds/__init__.py @@ -0,0 +1,164 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Lightweight SOAP Python client providing a Web Service proxy. + +""" + +import sys + + +# +# Project properties +# + +from version import __build__, __version__ + + +# +# Exceptions +# + +class MethodNotFound(Exception): + def __init__(self, name): + Exception.__init__(self, u"Method not found: '%s'" % (name,)) + +class PortNotFound(Exception): + def __init__(self, name): + Exception.__init__(self, u"Port not found: '%s'" % (name,)) + +class ServiceNotFound(Exception): + def __init__(self, name): + Exception.__init__(self, u"Service not found: '%s'" % (name,)) + +class TypeNotFound(Exception): + def __init__(self, name): + Exception.__init__(self, u"Type not found: '%s'" % (tostr(name),)) + +class BuildError(Exception): + def __init__(self, name, exception): + Exception.__init__(self, u"An error occurred while building an " + "instance of (%s). As a result the object you requested could not " + "be constructed. It is recommended that you construct the type " + "manually using a Suds object. Please open a ticket with a " + "description of this error. Reason: %s" % (name, exception)) + +class WebFault(Exception): + def __init__(self, fault, document): + if hasattr(fault, "faultstring"): + Exception.__init__(self, u"Server raised fault: '%s'" % + (fault.faultstring,)) + self.fault = fault + self.document = document + + +# +# Logging +# + +class Repr: + def __init__(self, x): + self.x = x + def __str__(self): + return repr(self.x) + + +# +# Utility +# + +class null: + """I{null} object used to pass NULL for optional XML nodes.""" + pass + +def objid(obj): + return obj.__class__.__name__ + ":" + hex(id(obj)) + +def tostr(object, encoding=None): + """Get a unicode safe string representation of an object.""" + if isinstance(object, basestring): + if encoding is None: + return object + return object.encode(encoding) + if isinstance(object, tuple): + s = ["("] + for item in object: + s.append(tostr(item)) + s.append(", ") + s.append(")") + return "".join(s) + if isinstance(object, list): + s = ["["] + for item in object: + s.append(tostr(item)) + s.append(", ") + s.append("]") + return "".join(s) + if isinstance(object, dict): + s = ["{"] + for item in object.items(): + s.append(tostr(item[0])) + s.append(" = ") + s.append(tostr(item[1])) + s.append(", ") + s.append("}") + return "".join(s) + try: + return unicode(object) + except Exception: + return str(object) + + +# +# Python 3 compatibility +# + +if sys.version_info < (3, 0): + from cStringIO import StringIO as BytesIO +else: + from io import BytesIO + +# Idea from 'http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python'. +class UnicodeMixin(object): + if sys.version_info >= (3, 0): + # For Python 3, __str__() and __unicode__() should be identical. + __str__ = lambda x: x.__unicode__() + else: + __str__ = lambda x: unicode(x).encode("utf-8") + +# Used instead of byte literals as they are not supported on Python versions +# prior to 2.6. +def byte_str(s="", encoding="utf-8", input_encoding="utf-8", errors="strict"): + """ + Returns a byte string version of 's', encoded as specified in 'encoding'. + + Accepts str & unicode objects, interpreting non-unicode strings as byte + strings encoded using the given input encoding. + + """ + assert isinstance(s, basestring) + if isinstance(s, unicode): + return s.encode(encoding, errors) + if s and encoding != input_encoding: + return s.decode(input_encoding, errors).encode(encoding, errors) + return s + +# Class used to represent a byte string. Useful for asserting that correct +# string types are being passed around where needed. +if sys.version_info >= (3, 0): + byte_str_class = bytes +else: + byte_str_class = str diff --git a/pym/calculate/contrib/suds/__init__.pyc b/pym/calculate/contrib/suds/__init__.pyc new file mode 100644 index 0000000..abc072b Binary files /dev/null and b/pym/calculate/contrib/suds/__init__.pyc differ diff --git a/pym/calculate/contrib/suds/argparser.py b/pym/calculate/contrib/suds/argparser.py new file mode 100644 index 0000000..4c06b97 --- /dev/null +++ b/pym/calculate/contrib/suds/argparser.py @@ -0,0 +1,419 @@ +# -*- coding: utf-8 -*- + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) + +""" +Suds web service operation invocation function argument parser. + +See the parse_args() function description for more detailed information. + +""" + +__all__ = ["parse_args"] + + +def parse_args(method_name, param_defs, args, kwargs, external_param_processor, + extra_parameter_errors): + """ + Parse arguments for suds web service operation invocation functions. + + Suds prepares Python function objects for invoking web service operations. + This function implements generic binding agnostic part of processing the + arguments passed when calling those function objects. + + Argument parsing rules: + * Each input parameter element should be represented by single regular + Python function argument. + * At most one input parameter belonging to a single choice parameter + structure may have its value specified as something other than None. + * Positional arguments are mapped to choice group input parameters the + same as is done for a simple all/sequence group - each in turn. + + Expects to be passed the web service operation's parameter definitions + (parameter name, type & optional ancestry information) in order and, based + on that, extracts the values for those parameter from the arguments + provided in the web service operation invocation call. + + Ancestry information describes parameters constructed based on suds + library's automatic input parameter structure unwrapping. It is expected to + include the parameter's XSD schema 'ancestry' context, i.e. a list of all + the parent XSD schema tags containing the parameter's tag. Such + ancestry context provides detailed information about how the parameter's + value is expected to be used, especially in relation to other input + parameters, e.g. at most one parameter value may be specified for + parameters directly belonging to the same choice input group. + + Rules on acceptable ancestry items: + * Ancestry item's choice() method must return whether the item + represents a XSD schema tag. + * Passed ancestry items are used 'by address' internally and the same XSD + schema tag is expected to be identified by the exact same ancestry item + object during the whole argument processing. + + During processing, each parameter's definition and value, together with any + additional pertinent information collected from the encountered parameter + definition structure, is passed on to the provided external parameter + processor function. There that information is expected to be used to + construct the actual binding specific web service operation invocation + request. + + Raises a TypeError exception in case any argument related errors are + detected. The exceptions raised have been constructed to make them as + similar as possible to their respective exceptions raised during regular + Python function argument checking. + + Does not support multiple same-named input parameters. + + """ + arg_parser = _ArgParser(method_name, param_defs, external_param_processor) + return arg_parser(args, kwargs, extra_parameter_errors) + + +class _ArgParser: + """Internal argument parser implementation function object.""" + + def __init__(self, method_name, param_defs, external_param_processor): + self.__method_name = method_name + self.__param_defs = param_defs + self.__external_param_processor = external_param_processor + self.__stack = [] + + def __call__(self, args, kwargs, extra_parameter_errors): + """ + Runs the main argument parsing operation. + + Passed args & kwargs objects are not modified during parsing. + + Returns an informative 2-tuple containing the number of required & + allowed arguments. + + """ + assert not self.active(), "recursive argument parsing not allowed" + self.__init_run(args, kwargs, extra_parameter_errors) + try: + self.__process_parameters() + return self.__all_parameters_processed() + finally: + self.__cleanup_run() + assert not self.active() + + def active(self): + """ + Return whether this object is currently running argument processing. + + Used to avoid recursively entering argument processing from within an + external parameter processor. + + """ + return bool(self.__stack) + + def __all_parameters_processed(self): + """ + Finish the argument processing. + + Should be called after all the web service operation's parameters have + been successfully processed and, afterwards, no further parameter + processing is allowed. + + Returns a 2-tuple containing the number of required & allowed + arguments. + + See the _ArgParser class description for more detailed information. + + """ + assert self.active() + sentinel_frame = self.__stack[0] + self.__pop_frames_above(sentinel_frame) + assert len(self.__stack) == 1 + self.__pop_top_frame() + assert not self.active() + args_required = sentinel_frame.args_required() + args_allowed = sentinel_frame.args_allowed() + self.__check_for_extra_arguments(args_required, args_allowed) + return args_required, args_allowed + + def __check_for_extra_arguments(self, args_required, args_allowed): + """ + Report an error in case any extra arguments are detected. + + Does nothing if reporting extra arguments as exceptions has not been + enabled. + + May only be called after the argument processing has been completed. + + """ + assert not self.active() + if not self.__extra_parameter_errors: + return + + if self.__kwargs: + param_name = self.__kwargs.keys()[0] + if param_name in self.__params_with_arguments: + msg = "got multiple values for parameter '%s'" + else: + msg = "got an unexpected keyword argument '%s'" + self.__error(msg % (param_name,)) + + if self.__args: + def plural_suffix(count): + if count == 1: + return "" + return "s" + def plural_was_were(count): + if count == 1: + return "was" + return "were" + expected = args_required + if args_required != args_allowed: + expected = "%d to %d" % (args_required, args_allowed) + given = self.__args_count + msg_parts = ["takes %s positional argument" % (expected,), + plural_suffix(expected), " but %d " % (given,), + plural_was_were(given), " given"] + self.__error("".join(msg_parts)) + + def __cleanup_run(self): + """Cleans up after a completed argument parsing run.""" + self.__stack = [] + assert not self.active() + + def __error(self, message): + """Report an argument processing error.""" + raise TypeError("%s() %s" % (self.__method_name, message)) + + def __frame_factory(self, ancestry_item): + """Construct a new frame representing the given ancestry item.""" + frame_class = Frame + if ancestry_item is not None and ancestry_item.choice(): + frame_class = ChoiceFrame + return frame_class(ancestry_item, self.__error, + self.__extra_parameter_errors) + + def __get_param_value(self, name): + """ + Extract a parameter value from the remaining given arguments. + + Returns a 2-tuple consisting of the following: + * Boolean indicating whether an argument has been specified for the + requested input parameter. + * Parameter value. + + """ + if self.__args: + return True, self.__args.pop(0) + try: + value = self.__kwargs.pop(name) + except KeyError: + return False, None + return True, value + + def __in_choice_context(self): + """ + Whether we are currently processing a choice parameter group. + + This includes processing a parameter defined directly or indirectly + within such a group. + + May only be called during parameter processing or the result will be + calculated based on the context left behind by the previous parameter + processing if any. + + """ + for x in self.__stack: + if x.__class__ is ChoiceFrame: + return True + return False + + def __init_run(self, args, kwargs, extra_parameter_errors): + """Initializes data for a new argument parsing run.""" + assert not self.active() + self.__args = list(args) + self.__kwargs = dict(kwargs) + self.__extra_parameter_errors = extra_parameter_errors + self.__args_count = len(args) + len(kwargs) + self.__params_with_arguments = set() + self.__stack = [] + self.__push_frame(None) + + def __match_ancestry(self, ancestry): + """ + Find frames matching the given ancestry. + + Returns a tuple containing the following: + * Topmost frame matching the given ancestry or the bottom-most sentry + frame if no frame matches. + * Unmatched ancestry part. + + """ + stack = self.__stack + if len(stack) == 1: + return stack[0], ancestry + previous = stack[0] + for frame, n in zip(stack[1:], xrange(len(ancestry))): + if frame.id() is not ancestry[n]: + return previous, ancestry[n:] + previous = frame + return frame, ancestry[n + 1:] + + def __pop_frames_above(self, frame): + """Pops all the frames above, but not including the given frame.""" + while self.__stack[-1] is not frame: + self.__pop_top_frame() + assert self.__stack + + def __pop_top_frame(self): + """Pops the top frame off the frame stack.""" + popped = self.__stack.pop() + if self.__stack: + self.__stack[-1].process_subframe(popped) + + def __process_parameter(self, param_name, param_type, ancestry=None): + """Collect values for a given web service operation input parameter.""" + assert self.active() + param_optional = param_type.optional() + has_argument, value = self.__get_param_value(param_name) + if has_argument: + self.__params_with_arguments.add(param_name) + self.__update_context(ancestry) + self.__stack[-1].process_parameter(param_optional, value is not None) + self.__external_param_processor(param_name, param_type, + self.__in_choice_context(), value) + + def __process_parameters(self): + """Collect values for given web service operation input parameters.""" + for pdef in self.__param_defs: + self.__process_parameter(*pdef) + + def __push_frame(self, ancestry_item): + """Push a new frame on top of the frame stack.""" + frame = self.__frame_factory(ancestry_item) + self.__stack.append(frame) + + def __push_frames(self, ancestry): + """ + Push new frames representing given ancestry items. + + May only be given ancestry items other than None. Ancestry item None + represents the internal sentinel item and should never appear in a + given parameter's ancestry information. + + """ + for x in ancestry: + assert x is not None + self.__push_frame(x) + + def __update_context(self, ancestry): + if not ancestry: + return + match_result = self.__match_ancestry(ancestry) + last_matching_frame, unmatched_ancestry = match_result + self.__pop_frames_above(last_matching_frame) + self.__push_frames(unmatched_ancestry) + + +class Frame: + """ + Base _ArgParser context frame. + + When used directly, as opposed to using a derived class, may represent any + input parameter context/ancestry item except a choice order indicator. + + """ + + def __init__(self, id, error, extra_parameter_errors): + """ + Construct a new Frame instance. + + Passed error function is used to report any argument checking errors. + + """ + assert self.__class__ != Frame or not id or not id.choice() + self.__id = id + self._error = error + self._extra_parameter_errors = extra_parameter_errors + self._args_allowed = 0 + self._args_required = 0 + self._has_value = False + + def args_allowed(self): + return self._args_allowed + + def args_required(self): + return self._args_required + + def has_value(self): + return self._has_value + + def id(self): + return self.__id + + def process_parameter(self, optional, has_value): + args_required = 1 + if optional: + args_required = 0 + self._process_item(has_value, 1, args_required) + + def process_subframe(self, subframe): + self._process_item( + subframe.has_value(), + subframe.args_allowed(), + subframe.args_required()) + + def _process_item(self, has_value, args_allowed, args_required): + self._args_allowed += args_allowed + self._args_required += args_required + if has_value: + self._has_value = True + + +class ChoiceFrame(Frame): + """ + _ArgParser context frame representing a choice order indicator. + + A choice requires as many input arguments as are needed to satisfy the + least requiring of its items. For example, if we use I(n) to identify an + item requiring n parameter, then a choice containing I(2), I(3) & I(7) + requires 2 arguments while a choice containing I(5) & I(4) requires 4. + + Accepts an argument for each of its contained elements but allows at most + one of its directly contained items to have a defined value. + + """ + + def __init__(self, id, error, extra_parameter_errors): + assert id.choice() + Frame.__init__(self, id, error, extra_parameter_errors) + self.__has_item = False + + def _process_item(self, has_value, args_allowed, args_required): + self._args_allowed += args_allowed + self.__update_args_required_for_item(args_required) + self.__update_has_value_for_item(has_value) + + def __update_args_required_for_item(self, item_args_required): + if not self.__has_item: + self.__has_item = True + self._args_required = item_args_required + return + self._args_required = min(self.args_required(), item_args_required) + + def __update_has_value_for_item(self, item_has_value): + if item_has_value: + if self.has_value() and self._extra_parameter_errors: + self._error("got multiple values for a single choice " + "parameter") + self._has_value = True diff --git a/pym/calculate/contrib/suds/argparser.pyc b/pym/calculate/contrib/suds/argparser.pyc new file mode 100644 index 0000000..e5d658d Binary files /dev/null and b/pym/calculate/contrib/suds/argparser.pyc differ diff --git a/pym/calculate/contrib/suds/bindings/__init__.py b/pym/calculate/contrib/suds/bindings/__init__.py new file mode 100644 index 0000000..1704fa9 --- /dev/null +++ b/pym/calculate/contrib/suds/bindings/__init__.py @@ -0,0 +1,18 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides modules containing classes to support Web Services (SOAP) bindings. +""" diff --git a/pym/calculate/contrib/suds/bindings/__init__.pyc b/pym/calculate/contrib/suds/bindings/__init__.pyc new file mode 100644 index 0000000..e557408 Binary files /dev/null and b/pym/calculate/contrib/suds/bindings/__init__.pyc differ diff --git a/pym/calculate/contrib/suds/bindings/binding.py b/pym/calculate/contrib/suds/bindings/binding.py new file mode 100644 index 0000000..4af184c --- /dev/null +++ b/pym/calculate/contrib/suds/bindings/binding.py @@ -0,0 +1,510 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +(WS) SOAP binding classes. + +""" + +from suds import * +from suds.sax import Namespace +from suds.sax.document import Document +from suds.sax.element import Element +from suds.sudsobject import Factory +from suds.mx import Content +from suds.mx.literal import Literal as MxLiteral +from suds.umx.typed import Typed as UmxTyped +from suds.bindings.multiref import MultiRef +from suds.xsd.query import TypeQuery, ElementQuery +from suds.xsd.sxbasic import Element as SchemaElement +from suds.options import Options +from suds.plugin import PluginContainer + +from copy import deepcopy + + +envns = ("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/") + + +class Binding(object): + """ + The SOAP binding class used to process outgoing and incoming SOAP messages + per the WSDL port binding. + + @ivar wsdl: The WSDL. + @type wsdl: L{suds.wsdl.Definitions} + @ivar schema: The collective schema contained within the WSDL. + @type schema: L{xsd.schema.Schema} + @ivar options: A dictionary options. + @type options: L{Options} + + """ + + def __init__(self, wsdl): + """ + @param wsdl: A WSDL. + @type wsdl: L{wsdl.Definitions} + + """ + self.wsdl = wsdl + self.multiref = MultiRef() + + def schema(self): + return self.wsdl.schema + + def options(self): + return self.wsdl.options + + def unmarshaller(self): + """ + Get the appropriate schema based XML decoder. + + @return: Typed unmarshaller. + @rtype: L{UmxTyped} + + """ + return UmxTyped(self.schema()) + + def marshaller(self): + """ + Get the appropriate XML encoder. + + @return: An L{MxLiteral} marshaller. + @rtype: L{MxLiteral} + + """ + return MxLiteral(self.schema(), self.options().xstq) + + def param_defs(self, method): + """ + Get parameter definitions. + + Each I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple. + + @param method: A service method. + @type method: I{service.Method} + @return: A collection of parameter definitions + @rtype: [I{pdef},...] + + """ + raise Exception("not implemented") + + def get_message(self, method, args, kwargs): + """ + Get a SOAP message for the specified method, args and SOAP headers. + + This is the entry point for creating an outbound SOAP message. + + @param method: The method being invoked. + @type method: I{service.Method} + @param args: A list of args for the method invoked. + @type args: list + @param kwargs: Named (keyword) args for the method invoked. + @type kwargs: dict + @return: The SOAP envelope. + @rtype: L{Document} + + """ + content = self.headercontent(method) + header = self.header(content) + content = self.bodycontent(method, args, kwargs) + body = self.body(content) + env = self.envelope(header, body) + if self.options().prefixes: + body.normalizePrefixes() + env.promotePrefixes() + else: + env.refitPrefixes() + return Document(env) + + def get_reply(self, method, replyroot): + """ + Process the I{reply} for the specified I{method} by unmarshalling it + into into Python object(s). + + @param method: The name of the invoked method. + @type method: str + @param replyroot: The reply XML root node received after invoking the + specified method. + @type replyroot: L{Element} + @return: The unmarshalled reply. The returned value is an L{Object} or + a I{list} depending on whether the service returns a single object + or a collection. + @rtype: L{Object} or I{list} + + """ + soapenv = replyroot.getChild("Envelope", envns) + soapenv.promotePrefixes() + soapbody = soapenv.getChild("Body", envns) + soapbody = self.multiref.process(soapbody) + nodes = self.replycontent(method, soapbody) + rtypes = self.returned_types(method) + if len(rtypes) > 1: + return self.replycomposite(rtypes, nodes) + if len(rtypes) == 0: + return + if rtypes[0].multi_occurrence(): + return self.replylist(rtypes[0], nodes) + if len(nodes): + resolved = rtypes[0].resolve(nobuiltin=True) + return self.unmarshaller().process(nodes[0], resolved) + + def replylist(self, rt, nodes): + """ + Construct a I{list} reply. + + Called for replies with possible multiple occurrences. + + @param rt: The return I{type}. + @type rt: L{suds.xsd.sxbase.SchemaObject} + @param nodes: A collection of XML nodes. + @type nodes: [L{Element},...] + @return: A list of I{unmarshalled} objects. + @rtype: [L{Object},...] + + """ + resolved = rt.resolve(nobuiltin=True) + unmarshaller = self.unmarshaller() + return [unmarshaller.process(node, resolved) for node in nodes] + + def replycomposite(self, rtypes, nodes): + """ + Construct a I{composite} reply. + + Called for replies with multiple output nodes. + + @param rtypes: A list of known return I{types}. + @type rtypes: [L{suds.xsd.sxbase.SchemaObject},...] + @param nodes: A collection of XML nodes. + @type nodes: [L{Element},...] + @return: The I{unmarshalled} composite object. + @rtype: L{Object},... + + """ + dictionary = {} + for rt in rtypes: + dictionary[rt.name] = rt + unmarshaller = self.unmarshaller() + composite = Factory.object("reply") + for node in nodes: + tag = node.name + rt = dictionary.get(tag) + if rt is None: + if node.get("id") is None and not self.options().allowUnknownMessageParts: + message = "<%s/> not mapped to message part" % (tag,) + raise Exception(message) + continue + resolved = rt.resolve(nobuiltin=True) + sobject = unmarshaller.process(node, resolved) + value = getattr(composite, tag, None) + if value is None: + if rt.multi_occurrence(): + value = [] + setattr(composite, tag, value) + value.append(sobject) + else: + setattr(composite, tag, sobject) + else: + if not isinstance(value, list): + value = [value,] + setattr(composite, tag, value) + value.append(sobject) + return composite + + def mkparam(self, method, pdef, object): + """ + Builds a parameter for the specified I{method} using the parameter + definition (pdef) and the specified value (object). + + @param method: A method name. + @type method: str + @param pdef: A parameter definition. + @type pdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject}) + @param object: The parameter value. + @type object: any + @return: The parameter fragment. + @rtype: L{Element} + + """ + marshaller = self.marshaller() + content = Content(tag=pdef[0], value=object, type=pdef[1], + real=pdef[1].resolve()) + return marshaller.process(content) + + def mkheader(self, method, hdef, object): + """ + Builds a soapheader for the specified I{method} using the header + definition (hdef) and the specified value (object). + + @param method: A method name. + @type method: str + @param hdef: A header definition. + @type hdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject}) + @param object: The header value. + @type object: any + @return: The parameter fragment. + @rtype: L{Element} + + """ + marshaller = self.marshaller() + if isinstance(object, (list, tuple)): + return [self.mkheader(method, hdef, item) for item in object] + content = Content(tag=hdef[0], value=object, type=hdef[1]) + return marshaller.process(content) + + def envelope(self, header, body): + """ + Build the B{} for a SOAP outbound message. + + @param header: The SOAP message B{header}. + @type header: L{Element} + @param body: The SOAP message B{body}. + @type body: L{Element} + @return: The SOAP envelope containing the body and header. + @rtype: L{Element} + + """ + env = Element("Envelope", ns=envns) + env.addPrefix(Namespace.xsins[0], Namespace.xsins[1]) + env.append(header) + env.append(body) + return env + + def header(self, content): + """ + Build the B{} for a SOAP outbound message. + + @param content: The header content. + @type content: L{Element} + @return: The SOAP body fragment. + @rtype: L{Element} + + """ + header = Element("Header", ns=envns) + header.append(content) + return header + + def bodycontent(self, method, args, kwargs): + """ + Get the content for the SOAP I{body} node. + + @param method: A service method. + @type method: I{service.Method} + @param args: method parameter values. + @type args: list + @param kwargs: Named (keyword) args for the method invoked. + @type kwargs: dict + @return: The XML content for the . + @rtype: [L{Element},...] + + """ + raise Exception("not implemented") + + def headercontent(self, method): + """ + Get the content for the SOAP I{Header} node. + + @param method: A service method. + @type method: I{service.Method} + @return: The XML content for the . + @rtype: [L{Element},...] + + """ + content = [] + wsse = self.options().wsse + if wsse is not None: + content.append(wsse.xml()) + headers = self.options().soapheaders + if not isinstance(headers, (tuple, list, dict)): + headers = (headers,) + elif not headers: + return content + pts = self.headpart_types(method) + if isinstance(headers, (tuple, list)): + n = 0 + for header in headers: + if isinstance(header, Element): + content.append(deepcopy(header)) + continue + if len(pts) == n: + break + h = self.mkheader(method, pts[n], header) + ns = pts[n][1].namespace("ns0") + h.setPrefix(ns[0], ns[1]) + content.append(h) + n += 1 + else: + for pt in pts: + header = headers.get(pt[0]) + if header is None: + continue + h = self.mkheader(method, pt, header) + ns = pt[1].namespace("ns0") + h.setPrefix(ns[0], ns[1]) + content.append(h) + return content + + def replycontent(self, method, body): + """ + Get the reply body content. + + @param method: A service method. + @type method: I{service.Method} + @param body: The SOAP body. + @type body: L{Element} + @return: The body content. + @rtype: [L{Element},...] + + """ + raise Exception("not implemented") + + def body(self, content): + """ + Build the B{} for a SOAP outbound message. + + @param content: The body content. + @type content: L{Element} + @return: The SOAP body fragment. + @rtype: L{Element} + + """ + body = Element("Body", ns=envns) + body.append(content) + return body + + def bodypart_types(self, method, input=True): + """ + Get a list of I{parameter definitions} (pdefs) defined for the + specified method. + + An input I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple, + while an output I{pdef} is a L{xsd.sxbase.SchemaObject}. + + @param method: A service method. + @type method: I{service.Method} + @param input: Defines input/output message. + @type input: boolean + @return: A list of parameter definitions + @rtype: [I{pdef},...] + + """ + if input: + parts = method.soap.input.body.parts + else: + parts = method.soap.output.body.parts + return [self.__part_type(p, input) for p in parts] + + def headpart_types(self, method, input=True): + """ + Get a list of header I{parameter definitions} (pdefs) defined for the + specified method. + + An input I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple, + while an output I{pdef} is a L{xsd.sxbase.SchemaObject}. + + @param method: A service method. + @type method: I{service.Method} + @param input: Defines input/output message. + @type input: boolean + @return: A list of parameter definitions + @rtype: [I{pdef},...] + + """ + if input: + headers = method.soap.input.headers + else: + headers = method.soap.output.headers + return [self.__part_type(h.part, input) for h in headers] + + def returned_types(self, method): + """ + Get the I{method} return value type(s). + + @param method: A service method. + @type method: I{service.Method} + @return: Method return value type. + @rtype: [L{xsd.sxbase.SchemaObject},...] + + """ + return self.bodypart_types(method, input=False) + + def __part_type(self, part, input): + """ + Get a I{parameter definition} (pdef) defined for a given body or header + message part. + + An input I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple, + while an output I{pdef} is a L{xsd.sxbase.SchemaObject}. + + @param part: A service method input or output part. + @type part: I{suds.wsdl.Part} + @param input: Defines input/output message. + @type input: boolean + @return: A list of parameter definitions + @rtype: [I{pdef},...] + + """ + if part.element is None: + query = TypeQuery(part.type) + else: + query = ElementQuery(part.element) + part_type = query.execute(self.schema()) + if part_type is None: + raise TypeNotFound(query.ref) + if part.type is not None: + part_type = PartElement(part.name, part_type) + if not input: + return part_type + if part_type.name is None: + return part.name, part_type + return part_type.name, part_type + + +class PartElement(SchemaElement): + """ + Message part referencing an XSD type and thus acting like an XSD element. + + @ivar resolved: The part type. + @type resolved: L{suds.xsd.sxbase.SchemaObject} + + """ + + def __init__(self, name, resolved): + """ + @param name: The part name. + @type name: str + @param resolved: The part type. + @type resolved: L{suds.xsd.sxbase.SchemaObject} + + """ + root = Element("element", ns=Namespace.xsdns) + SchemaElement.__init__(self, resolved.schema, root) + self.__resolved = resolved + self.name = name + self.form_qualified = False + + def implany(self): + pass + + def optional(self): + return True + + def namespace(self, prefix=None): + return Namespace.default + + def resolve(self, nobuiltin=False): + if nobuiltin and self.__resolved.builtin(): + return self + return self.__resolved diff --git a/pym/calculate/contrib/suds/bindings/binding.pyc b/pym/calculate/contrib/suds/bindings/binding.pyc new file mode 100644 index 0000000..002fac8 Binary files /dev/null and b/pym/calculate/contrib/suds/bindings/binding.pyc differ diff --git a/pym/calculate/contrib/suds/bindings/document.py b/pym/calculate/contrib/suds/bindings/document.py new file mode 100644 index 0000000..825a677 --- /dev/null +++ b/pym/calculate/contrib/suds/bindings/document.py @@ -0,0 +1,143 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Classes for the (WS) SOAP I{document/literal} binding. + +""" + +from suds import * +from suds.argparser import parse_args +from suds.bindings.binding import Binding +from suds.sax.element import Element + + +class Document(Binding): + """ + The document/literal style. Literal is the only (@use) supported since + document/encoded is pretty much dead. + + Although the SOAP specification supports multiple documents within the SOAP + , it is very uncommon. As such, suds library supports presenting an + I{RPC} view of service methods defined with only a single document + parameter. To support the complete specification, service methods defined + with multiple documents (multiple message parts), are still presented using + a full I{document} view. + + More detailed description: + + An interface is considered I{wrapped} if: + - There is exactly one message part in that interface. + - The message part resolves to an element of a non-builtin type. + Otherwise it is considered I{bare}. + + I{Bare} interface is interpreted directly as specified in the WSDL schema, + with each message part represented by a single parameter in the suds + library web service operation proxy interface (input or output). + + I{Wrapped} interface is interpreted without the external wrapping document + structure, with each of its contained elements passed through suds + library's web service operation proxy interface (input or output) + individually instead of as a single I{document} object. + + """ + def bodycontent(self, method, args, kwargs): + wrapped = method.soap.input.body.wrapped + if wrapped: + pts = self.bodypart_types(method) + root = self.document(pts[0]) + else: + root = [] + + def add_param(param_name, param_type, in_choice_context, value): + """ + Construct request data for the given input parameter. + + Called by our argument parser for every input parameter, in order. + + A parameter's type is identified by its corresponding XSD schema + element. + + """ + # Do not construct request data for undefined input parameters + # defined inside a choice order indicator. An empty choice + # parameter can still be included in the constructed request by + # explicitly providing an empty string value for it. + #TODO: This functionality might be better placed inside the + # mkparam() function but to do that we would first need to better + # understand how different Binding subclasses in suds work and how + # they would be affected by this change. + if in_choice_context and value is None: + return + + # Construct request data for the current input parameter. + pdef = (param_name, param_type) + p = self.mkparam(method, pdef, value) + if p is None: + return + if not wrapped: + ns = param_type.namespace("ns0") + p.setPrefix(ns[0], ns[1]) + root.append(p) + + parse_args(method.name, self.param_defs(method), args, kwargs, + add_param, self.options().extraArgumentErrors) + + return root + + def replycontent(self, method, body): + if method.soap.output.body.wrapped: + return body[0].children + return body.children + + def document(self, wrapper): + """ + Get the document root. For I{document/literal}, this is the name of the + wrapper element qualified by the schema's target namespace. + + @param wrapper: The method name. + @type wrapper: L{xsd.sxbase.SchemaObject} + @return: A root element. + @rtype: L{Element} + + """ + tag = wrapper[1].name + ns = wrapper[1].namespace("ns0") + return Element(tag, ns=ns) + + def mkparam(self, method, pdef, object): + """ + Expand list parameters into individual parameters each with the type + information. This is because in document arrays are simply + multi-occurrence elements. + + """ + if isinstance(object, (list, tuple)): + return [self.mkparam(method, pdef, item) for item in object] + return super(Document, self).mkparam(method, pdef, object) + + def param_defs(self, method): + """Get parameter definitions for document literal.""" + pts = self.bodypart_types(method) + if not method.soap.input.body.wrapped: + return pts + pt = pts[0][1].resolve() + return [(c.name, c, a) for c, a in pt if not c.isattr()] + + def returned_types(self, method): + rts = super(Document, self).returned_types(method) + if not method.soap.output.body.wrapped: + return rts + return [child for child, ancestry in rts[0].resolve(nobuiltin=True)] diff --git a/pym/calculate/contrib/suds/bindings/document.pyc b/pym/calculate/contrib/suds/bindings/document.pyc new file mode 100644 index 0000000..df7717b Binary files /dev/null and b/pym/calculate/contrib/suds/bindings/document.pyc differ diff --git a/pym/calculate/contrib/suds/bindings/multiref.py b/pym/calculate/contrib/suds/bindings/multiref.py new file mode 100644 index 0000000..52fa47a --- /dev/null +++ b/pym/calculate/contrib/suds/bindings/multiref.py @@ -0,0 +1,124 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides classes for handling soap multirefs. +""" + +from suds import * +from suds.sax.element import Element + + +soapenc = (None, 'http://schemas.xmlsoap.org/soap/encoding/') + +class MultiRef: + """ + Resolves and replaces multirefs. + @ivar nodes: A list of non-multiref nodes. + @type nodes: list + @ivar catalog: A dictionary of multiref nodes by id. + @type catalog: dict + """ + + def __init__(self): + self.nodes = [] + self.catalog = {} + + def process(self, body): + """ + Process the specified soap envelope body and replace I{multiref} node + references with the contents of the referenced node. + @param body: A soap envelope body node. + @type body: L{Element} + @return: The processed I{body} + @rtype: L{Element} + """ + self.nodes = [] + self.catalog = {} + self.build_catalog(body) + self.update(body) + body.children = self.nodes + return body + + def update(self, node): + """ + Update the specified I{node} by replacing the I{multiref} references with + the contents of the referenced nodes and remove the I{href} attribute. + @param node: A node to update. + @type node: L{Element} + @return: The updated node + @rtype: L{Element} + """ + self.replace_references(node) + for c in node.children: + self.update(c) + return node + + def replace_references(self, node): + """ + Replacing the I{multiref} references with the contents of the + referenced nodes and remove the I{href} attribute. Warning: since + the I{ref} is not cloned, + @param node: A node to update. + @type node: L{Element} + """ + href = node.getAttribute('href') + if href is None: + return + id = href.getValue() + ref = self.catalog.get(id) + if ref is None: + import logging + log = logging.getLogger(__name__) + log.error('soap multiref: %s, not-resolved', id) + return + node.append(ref.children) + node.setText(ref.getText()) + for a in ref.attributes: + if a.name != 'id': + node.append(a) + node.remove(href) + + def build_catalog(self, body): + """ + Create the I{catalog} of multiref nodes by id and the list of + non-multiref nodes. + @param body: A soap envelope body node. + @type body: L{Element} + """ + for child in body.children: + if self.soaproot(child): + self.nodes.append(child) + id = child.get('id') + if id is None: continue + key = '#%s' % id + self.catalog[key] = child + + def soaproot(self, node): + """ + Get whether the specified I{node} is a soap encoded root. + This is determined by examining @soapenc:root='1'. + The node is considered to be a root when the attribute + is not specified. + @param node: A node to evaluate. + @type node: L{Element} + @return: True if a soap encoded root. + @rtype: bool + """ + root = node.getAttribute('root', ns=soapenc) + if root is None: + return True + else: + return ( root.value == '1' ) diff --git a/pym/calculate/contrib/suds/bindings/multiref.pyc b/pym/calculate/contrib/suds/bindings/multiref.pyc new file mode 100644 index 0000000..2d58775 Binary files /dev/null and b/pym/calculate/contrib/suds/bindings/multiref.pyc differ diff --git a/pym/calculate/contrib/suds/bindings/rpc.py b/pym/calculate/contrib/suds/bindings/rpc.py new file mode 100644 index 0000000..f0d5bd9 --- /dev/null +++ b/pym/calculate/contrib/suds/bindings/rpc.py @@ -0,0 +1,91 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Classes for the (WS) SOAP I{rpc/literal} and I{rpc/encoded} bindings. + +""" + +from suds import * +from suds.mx.encoded import Encoded as MxEncoded +from suds.umx.encoded import Encoded as UmxEncoded +from suds.bindings.binding import Binding, envns +from suds.sax.element import Element + + +encns = ("SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/") + + +class RPC(Binding): + """RPC/Literal binding style.""" + + def param_defs(self, method): + return self.bodypart_types(method) + + def envelope(self, header, body): + env = super(RPC, self).envelope(header, body) + env.addPrefix(encns[0], encns[1]) + env.set("%s:encodingStyle" % (envns[0],), encns[1]) + return env + + def bodycontent(self, method, args, kwargs): + n = 0 + root = self.method(method) + for pd in self.param_defs(method): + if n < len(args): + value = args[n] + else: + value = kwargs.get(pd[0]) + p = self.mkparam(method, pd, value) + if p is not None: + root.append(p) + n += 1 + return root + + def replycontent(self, method, body): + return body[0].children + + def method(self, method): + """ + Get the document root. For I{rpc/(literal|encoded)}, this is the name + of the method qualified by the schema tns. + + @param method: A service method. + @type method: I{service.Method} + @return: A root element. + @rtype: L{Element} + + """ + ns = method.soap.input.body.namespace + if ns[0] is None: + ns = ('ns0', ns[1]) + return Element(method.name, ns=ns) + + +class Encoded(RPC): + """RPC/Encoded (section 5) binding style.""" + + def marshaller(self): + return MxEncoded(self.schema()) + + def unmarshaller(self): + """ + Get the appropriate schema based XML decoder. + + @return: Typed unmarshaller. + @rtype: L{UmxTyped} + + """ + return UmxEncoded(self.schema()) diff --git a/pym/calculate/contrib/suds/bindings/rpc.pyc b/pym/calculate/contrib/suds/bindings/rpc.pyc new file mode 100644 index 0000000..3158ef7 Binary files /dev/null and b/pym/calculate/contrib/suds/bindings/rpc.pyc differ diff --git a/pym/calculate/contrib/suds/builder.py b/pym/calculate/contrib/suds/builder.py new file mode 100644 index 0000000..a844f0d --- /dev/null +++ b/pym/calculate/contrib/suds/builder.py @@ -0,0 +1,122 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{builder} module provides an wsdl/xsd defined types factory +""" + +from suds import * +from suds.sudsobject import Factory + + +class Builder: + """ Builder used to construct an object for types defined in the schema """ + + def __init__(self, resolver): + """ + @param resolver: A schema object name resolver. + @type resolver: L{resolver.Resolver} + """ + self.resolver = resolver + + def build(self, name): + """ build a an object for the specified typename as defined in the schema """ + if isinstance(name, basestring): + type = self.resolver.find(name) + if type is None: + raise TypeNotFound(name) + else: + type = name + cls = type.name + if type.mixed(): + data = Factory.property(cls) + else: + data = Factory.object(cls) + resolved = type.resolve() + md = data.__metadata__ + md.sxtype = resolved + md.ordering = self.ordering(resolved) + history = [] + self.add_attributes(data, resolved) + for child, ancestry in type.children(): + if self.skip_child(child, ancestry): + continue + + self.process(data, child, history[:]) + return data + + def process(self, data, type, history): + """ process the specified type then process its children """ + if type in history: + return + if type.enum(): + return + history.append(type) + resolved = type.resolve() + value = None + + + + if type.multi_occurrence(): + value = [] + else: + if len(resolved) > 0: + if resolved.mixed(): + value = Factory.property(resolved.name) + md = value.__metadata__ + md.sxtype = resolved + else: + value = Factory.object(resolved.name) + md = value.__metadata__ + md.sxtype = resolved + md.ordering = self.ordering(resolved) + + setattr(data, type.name, value if not type.optional() or type.multi_occurrence() else None) + if value is not None: + data = value + if not isinstance(data, list): + self.add_attributes(data, resolved) + for child, ancestry in resolved.children(): + if self.skip_child(child, ancestry): + continue + self.process(data, child, history[:]) + + def add_attributes(self, data, type): + """ add required attributes """ + for attr, ancestry in type.attributes(): + name = '_%s' % attr.name + value = attr.get_default() + setattr(data, name, value) + + def skip_child(self, child, ancestry): + """ get whether or not to skip the specified child """ + if child.any(): return True + for x in ancestry: + if x.choice(): + return True + return False + + def ordering(self, type): + """ get the ordering """ + result = [] + for child, ancestry in type.resolve(): + name = child.name + if child.name is None: + continue + if child.isattr(): + name = '_%s' % child.name + result.append(name) + return result diff --git a/pym/calculate/contrib/suds/builder.pyc b/pym/calculate/contrib/suds/builder.pyc new file mode 100644 index 0000000..cd2b4cb Binary files /dev/null and b/pym/calculate/contrib/suds/builder.pyc differ diff --git a/pym/calculate/contrib/suds/cache.py b/pym/calculate/contrib/suds/cache.py new file mode 100644 index 0000000..8e53229 --- /dev/null +++ b/pym/calculate/contrib/suds/cache.py @@ -0,0 +1,334 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Basic caching classes. + +""" + +import suds +import suds.sax.element +import suds.sax.parser + +import datetime +import os +try: + import cPickle as pickle +except Exception: + import pickle +import shutil +import tempfile + +from logging import getLogger +log = getLogger(__name__) + + +class Cache(object): + """An object cache.""" + + def get(self, id): + """ + Get an object from the cache by id. + + @param id: The object id. + @type id: str + @return: The object, else None. + @rtype: any + + """ + raise Exception("not-implemented") + + def put(self, id, object): + """ + Put an object into the cache. + + @param id: The object id. + @type id: str + @param object: The object to add. + @type object: any + + """ + raise Exception("not-implemented") + + def purge(self, id): + """ + Purge an object from the cache by id. + + @param id: A object id. + @type id: str + + """ + raise Exception("not-implemented") + + def clear(self): + """Clear all objects from the cache.""" + raise Exception("not-implemented") + + +class NoCache(Cache): + """The pass-through object cache.""" + + def get(self, id): + return + + def put(self, id, object): + pass + + +class FileCache(Cache): + """ + A file-based URL cache. + + @cvar fnprefix: The file name prefix. + @type fnprefix: str + @cvar remove_default_location_on_exit: Whether to remove the default cache + location on process exit (default=True). + @type remove_default_location_on_exit: bool + @ivar duration: The duration after which cached entries expire (0=never). + @type duration: datetime.timedelta + @ivar location: The cached file folder. + @type location: str + + """ + fnprefix = "suds" + __default_location = None + remove_default_location_on_exit = True + + def __init__(self, location=None, **duration): + """ + Initialized a new FileCache instance. + + If no cache location is specified, a temporary default location will be + used. Such default cache location will be shared by all FileCache + instances with no explicitly specified location within the same + process. The default cache location will be removed automatically on + process exit unless user sets the remove_default_location_on_exit + FileCache class attribute to False. + + @param location: The cached file folder. + @type location: str + @param duration: The duration after which cached entries expire + (default: 0=never). + @type duration: keyword arguments for datetime.timedelta constructor + + """ + if location is None: + location = self.__get_default_location() + self.location = location + self.duration = datetime.timedelta(**duration) + self.__check_version() + + def clear(self): + for filename in os.listdir(self.location): + path = os.path.join(self.location, filename) + if os.path.isdir(path): + continue + if filename.startswith(self.fnprefix): + os.remove(path) + log.debug("deleted: %s", path) + + def fnsuffix(self): + """ + Get the file name suffix. + + @return: The suffix. + @rtype: str + + """ + return "gcf" + + def get(self, id): + try: + f = self._getf(id) + try: + return f.read() + finally: + f.close() + except Exception: + pass + + def purge(self, id): + filename = self.__filename(id) + try: + os.remove(filename) + except Exception: + pass + + def put(self, id, data): + try: + filename = self.__filename(id) + f = self.__open(filename, "wb") + try: + f.write(data) + finally: + f.close() + return data + except Exception: + log.debug(id, exc_info=1) + return data + + def _getf(self, id): + """Open a cached file with the given id for reading.""" + try: + filename = self.__filename(id) + self.__remove_if_expired(filename) + return self.__open(filename, "rb") + except Exception: + pass + + def __check_version(self): + path = os.path.join(self.location, "version") + try: + f = self.__open(path) + try: + version = f.read() + finally: + f.close() + if version != suds.__version__: + raise Exception() + except Exception: + self.clear() + f = self.__open(path, "w") + try: + f.write(suds.__version__) + finally: + f.close() + + def __filename(self, id): + """Return the cache file name for an entry with a given id.""" + suffix = self.fnsuffix() + filename = "%s-%s.%s" % (self.fnprefix, id, suffix) + return os.path.join(self.location, filename) + + @staticmethod + def __get_default_location(): + """ + Returns the current process's default cache location folder. + + The folder is determined lazily on first call. + + """ + if not FileCache.__default_location: + tmp = tempfile.mkdtemp("suds-default-cache") + FileCache.__default_location = tmp + import atexit + atexit.register(FileCache.__remove_default_location) + return FileCache.__default_location + + def __mktmp(self): + """Create the I{location} folder if it does not already exist.""" + try: + if not os.path.isdir(self.location): + os.makedirs(self.location) + except Exception: + log.debug(self.location, exc_info=1) + return self + + def __open(self, filename, *args): + """Open cache file making sure the I{location} folder is created.""" + self.__mktmp() + return open(filename, *args) + + @staticmethod + def __remove_default_location(): + """ + Removes the default cache location folder. + + This removal may be disabled by setting the + remove_default_location_on_exit FileCache class attribute to False. + + """ + if FileCache.remove_default_location_on_exit: + # We must not load shutil here on-demand as under some + # circumstances this may cause the shutil.rmtree() operation to + # fail due to not having some internal module loaded. E.g. this + # happens if you run the project's test suite using the setup.py + # test command on Python 2.4.x. + shutil.rmtree(FileCache.__default_location, ignore_errors=True) + + def __remove_if_expired(self, filename): + """ + Remove a cached file entry if it expired. + + @param filename: The file name. + @type filename: str + + """ + if not self.duration: + return + created = datetime.datetime.fromtimestamp(os.path.getctime(filename)) + expired = created + self.duration + if expired < datetime.datetime.now(): + os.remove(filename) + log.debug("%s expired, deleted", filename) + + +class DocumentCache(FileCache): + """XML document file cache.""" + + def fnsuffix(self): + return "xml" + + def get(self, id): + fp = None + try: + fp = self._getf(id) + if fp is not None: + p = suds.sax.parser.Parser() + cached = p.parse(fp) + fp.close() + return cached + except Exception: + if fp is not None: + fp.close() + self.purge(id) + + def put(self, id, object): + if isinstance(object, + (suds.sax.document.Document, suds.sax.element.Element)): + super(DocumentCache, self).put(id, suds.byte_str(str(object))) + return object + + +class ObjectCache(FileCache): + """ + Pickled object file cache. + + @cvar protocol: The pickling protocol. + @type protocol: int + + """ + protocol = 2 + + def fnsuffix(self): + return "px" + + def get(self, id): + fp = None + try: + fp = self._getf(id) + if fp is not None: + cached = pickle.load(fp) + fp.close() + return cached + except Exception: + if fp is not None: + fp.close() + self.purge(id) + + def put(self, id, object): + data = pickle.dumps(object, self.protocol) + super(ObjectCache, self).put(id, data) + return object diff --git a/pym/calculate/contrib/suds/cache.pyc b/pym/calculate/contrib/suds/cache.pyc new file mode 100644 index 0000000..497c5b8 Binary files /dev/null and b/pym/calculate/contrib/suds/cache.pyc differ diff --git a/pym/calculate/contrib/suds/client.py b/pym/calculate/contrib/suds/client.py new file mode 100644 index 0000000..f6e2007 --- /dev/null +++ b/pym/calculate/contrib/suds/client.py @@ -0,0 +1,950 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Service proxy implementation providing access to web services. + +""" + +import suds +from suds import * +import suds.bindings.binding +from suds.builder import Builder +import suds.cache +import suds.metrics as metrics +from suds.options import Options +from suds.plugin import PluginContainer +from suds.properties import Unskin +from suds.reader import DefinitionsReader +from suds.resolver import PathResolver +from suds.sax.document import Document +import suds.sax.parser +from suds.servicedefinition import ServiceDefinition +import suds.transport +import suds.transport.https +from suds.umx.basic import Basic as UmxBasic +from suds.wsdl import Definitions +import sudsobject + +from cookielib import CookieJar +from copy import deepcopy +import httplib + +from logging import getLogger +log = getLogger(__name__) + + +class Client(UnicodeMixin): + """ + A lightweight web service client. + + @ivar wsdl: The WSDL object. + @type wsdl:L{Definitions} + @ivar service: The service proxy used to invoke operations. + @type service: L{Service} + @ivar factory: The factory used to create objects. + @type factory: L{Factory} + @ivar sd: The service definition + @type sd: L{ServiceDefinition} + + """ + + @classmethod + def items(cls, sobject): + """ + Extract I{items} from a suds object. + + Much like the items() method works on I{dict}. + + @param sobject: A suds object + @type sobject: L{Object} + @return: A list of items contained in I{sobject}. + @rtype: [(key, value),...] + + """ + return sudsobject.items(sobject) + + @classmethod + def dict(cls, sobject): + """ + Convert a sudsobject into a dictionary. + + @param sobject: A suds object + @type sobject: L{Object} + @return: A dictionary of items contained in I{sobject}. + @rtype: dict + + """ + return sudsobject.asdict(sobject) + + @classmethod + def metadata(cls, sobject): + """ + Extract the metadata from a suds object. + + @param sobject: A suds object + @type sobject: L{Object} + @return: The object's metadata + @rtype: L{sudsobject.Metadata} + + """ + return sobject.__metadata__ + + def __init__(self, url, **kwargs): + """ + @param url: The URL for the WSDL. + @type url: str + @param kwargs: keyword arguments. + @see: L{Options} + + """ + options = Options() + options.transport = suds.transport.https.HttpAuthenticated() + self.options = options + if "cache" not in kwargs: + kwargs["cache"] = suds.cache.ObjectCache(days=1) + self.set_options(**kwargs) + reader = DefinitionsReader(options, Definitions) + self.wsdl = reader.open(url) + plugins = PluginContainer(options.plugins) + plugins.init.initialized(wsdl=self.wsdl) + self.factory = Factory(self.wsdl) + self.service = ServiceSelector(self, self.wsdl.services) + self.sd = [] + for s in self.wsdl.services: + sd = ServiceDefinition(self.wsdl, s) + self.sd.append(sd) + + def set_options(self, **kwargs): + """ + Set options. + + @param kwargs: keyword arguments. + @see: L{Options} + + """ + p = Unskin(self.options) + p.update(kwargs) + + def add_prefix(self, prefix, uri): + """ + Add I{static} mapping of an XML namespace prefix to a namespace. + + Useful for cases when a WSDL and referenced XSD schemas make heavy use + of namespaces and those namespaces are subject to change. + + @param prefix: An XML namespace prefix. + @type prefix: str + @param uri: An XML namespace URI. + @type uri: str + @raise Exception: prefix already mapped. + + """ + root = self.wsdl.root + mapped = root.resolvePrefix(prefix, None) + if mapped is None: + root.addPrefix(prefix, uri) + return + if mapped[1] != uri: + raise Exception('"%s" already mapped as "%s"' % (prefix, mapped)) + + def clone(self): + """ + Get a shallow clone of this object. + + The clone only shares the WSDL. All other attributes are unique to the + cloned object including options. + + @return: A shallow clone. + @rtype: L{Client} + + """ + class Uninitialized(Client): + def __init__(self): + pass + clone = Uninitialized() + clone.options = Options() + cp = Unskin(clone.options) + mp = Unskin(self.options) + cp.update(deepcopy(mp)) + clone.wsdl = self.wsdl + clone.factory = self.factory + clone.service = ServiceSelector(clone, self.wsdl.services) + clone.sd = self.sd + return clone + + def __unicode__(self): + s = ["\n"] + s.append("Suds ( https://fedorahosted.org/suds/ )") + s.append(" version: %s" % (suds.__version__,)) + if suds.__build__: + s.append(" build: %s" % (suds.__build__,)) + for sd in self.sd: + s.append("\n\n%s" % (unicode(sd),)) + return "".join(s) + + +class Factory: + """ + A factory for instantiating types defined in the WSDL. + + @ivar resolver: A schema type resolver. + @type resolver: L{PathResolver} + @ivar builder: A schema object builder. + @type builder: L{Builder} + + """ + + def __init__(self, wsdl): + """ + @param wsdl: A schema object. + @type wsdl: L{wsdl.Definitions} + + """ + self.wsdl = wsdl + self.resolver = PathResolver(wsdl) + self.builder = Builder(self.resolver) + + def create(self, name): + """ + Create a WSDL type by name. + + @param name: The name of a type defined in the WSDL. + @type name: str + @return: The requested object. + @rtype: L{Object} + + """ + timer = metrics.Timer() + timer.start() + type = self.resolver.find(name) + if type is None: + raise TypeNotFound(name) + if type.enum(): + result = sudsobject.Factory.object(name) + for e, a in type.children(): + setattr(result, e.name, e.name) + else: + try: + result = self.builder.build(type) + except Exception, e: + log.error("create '%s' failed", name, exc_info=True) + raise BuildError(name, e) + timer.stop() + metrics.log.debug("%s created: %s", name, timer) + return result + + def separator(self, ps): + """ + Set the path separator. + + @param ps: The new path separator. + @type ps: char + + """ + self.resolver = PathResolver(self.wsdl, ps) + + +class ServiceSelector: + """ + The B{service} selector is used to select a web service. + + Most WSDLs only define a single service in which case access by subscript + is passed through to a L{PortSelector}. This is also the behavior when a + I{default} service has been specified. In cases where multiple services + have been defined and no default has been specified, the service is found + by name (or index) and a L{PortSelector} for the service is returned. In + all cases, attribute access is forwarded to the L{PortSelector} for either + the I{first} service or the I{default} service (when specified). + + @ivar __client: A suds client. + @type __client: L{Client} + @ivar __services: A list of I{WSDL} services. + @type __services: list + + """ + def __init__(self, client, services): + """ + @param client: A suds client. + @type client: L{Client} + @param services: A list of I{WSDL} services. + @type services: list + + """ + self.__client = client + self.__services = services + + def __getattr__(self, name): + """ + Attribute access is forwarded to the L{PortSelector}. + + Uses the I{default} service if specified or the I{first} service + otherwise. + + @param name: Method name. + @type name: str + @return: A L{PortSelector}. + @rtype: L{PortSelector}. + + """ + default = self.__ds() + if default is None: + port = self.__find(0) + else: + port = default + return getattr(port, name) + + def __getitem__(self, name): + """ + Provides I{service} selection by name (string) or index (integer). + + In cases where only a single service is defined or a I{default} has + been specified, the request is forwarded to the L{PortSelector}. + + @param name: The name (or index) of a service. + @type name: int|str + @return: A L{PortSelector} for the specified service. + @rtype: L{PortSelector}. + + """ + if len(self.__services) == 1: + port = self.__find(0) + return port[name] + default = self.__ds() + if default is not None: + port = default + return port[name] + return self.__find(name) + + def __find(self, name): + """ + Find a I{service} by name (string) or index (integer). + + @param name: The name (or index) of a service. + @type name: int|str + @return: A L{PortSelector} for the found service. + @rtype: L{PortSelector}. + + """ + service = None + if not self.__services: + raise Exception, "No services defined" + if isinstance(name, int): + try: + service = self.__services[name] + name = service.name + except IndexError: + raise ServiceNotFound, "at [%d]" % (name,) + else: + for s in self.__services: + if name == s.name: + service = s + break + if service is None: + raise ServiceNotFound, name + return PortSelector(self.__client, service.ports, name) + + def __ds(self): + """ + Get the I{default} service if defined in the I{options}. + + @return: A L{PortSelector} for the I{default} service. + @rtype: L{PortSelector}. + + """ + ds = self.__client.options.service + if ds is not None: + return self.__find(ds) + + +class PortSelector: + """ + The B{port} selector is used to select a I{web service} B{port}. + + In cases where multiple ports have been defined and no default has been + specified, the port is found by name (or index) and a L{MethodSelector} for + the port is returned. In all cases, attribute access is forwarded to the + L{MethodSelector} for either the I{first} port or the I{default} port (when + specified). + + @ivar __client: A suds client. + @type __client: L{Client} + @ivar __ports: A list of I{service} ports. + @type __ports: list + @ivar __qn: The I{qualified} name of the port (used for logging). + @type __qn: str + + """ + def __init__(self, client, ports, qn): + """ + @param client: A suds client. + @type client: L{Client} + @param ports: A list of I{service} ports. + @type ports: list + @param qn: The name of the service. + @type qn: str + + """ + self.__client = client + self.__ports = ports + self.__qn = qn + + def __getattr__(self, name): + """ + Attribute access is forwarded to the L{MethodSelector}. + + Uses the I{default} port when specified or the I{first} port otherwise. + + @param name: The name of a method. + @type name: str + @return: A L{MethodSelector}. + @rtype: L{MethodSelector}. + + """ + default = self.__dp() + if default is None: + m = self.__find(0) + else: + m = default + return getattr(m, name) + + def __getitem__(self, name): + """ + Provides I{port} selection by name (string) or index (integer). + + In cases where only a single port is defined or a I{default} has been + specified, the request is forwarded to the L{MethodSelector}. + + @param name: The name (or index) of a port. + @type name: int|str + @return: A L{MethodSelector} for the specified port. + @rtype: L{MethodSelector}. + + """ + default = self.__dp() + if default is None: + return self.__find(name) + return default + + def __find(self, name): + """ + Find a I{port} by name (string) or index (integer). + + @param name: The name (or index) of a port. + @type name: int|str + @return: A L{MethodSelector} for the found port. + @rtype: L{MethodSelector}. + + """ + port = None + if not self.__ports: + raise Exception, "No ports defined: %s" % (self.__qn,) + if isinstance(name, int): + qn = "%s[%d]" % (self.__qn, name) + try: + port = self.__ports[name] + except IndexError: + raise PortNotFound, qn + else: + qn = ".".join((self.__qn, name)) + for p in self.__ports: + if name == p.name: + port = p + break + if port is None: + raise PortNotFound, qn + qn = ".".join((self.__qn, port.name)) + return MethodSelector(self.__client, port.methods, qn) + + def __dp(self): + """ + Get the I{default} port if defined in the I{options}. + + @return: A L{MethodSelector} for the I{default} port. + @rtype: L{MethodSelector}. + + """ + dp = self.__client.options.port + if dp is not None: + return self.__find(dp) + + +class MethodSelector: + """ + The B{method} selector is used to select a B{method} by name. + + @ivar __client: A suds client. + @type __client: L{Client} + @ivar __methods: A dictionary of methods. + @type __methods: dict + @ivar __qn: The I{qualified} name of the method (used for logging). + @type __qn: str + + """ + def __init__(self, client, methods, qn): + """ + @param client: A suds client. + @type client: L{Client} + @param methods: A dictionary of methods. + @type methods: dict + @param qn: The I{qualified} name of the port. + @type qn: str + + """ + self.__client = client + self.__methods = methods + self.__qn = qn + + def __getattr__(self, name): + """ + Get a method by name and return it in an I{execution wrapper}. + + @param name: The name of a method. + @type name: str + @return: An I{execution wrapper} for the specified method name. + @rtype: L{Method} + + """ + return self[name] + + def __getitem__(self, name): + """ + Get a method by name and return it in an I{execution wrapper}. + + @param name: The name of a method. + @type name: str + @return: An I{execution wrapper} for the specified method name. + @rtype: L{Method} + + """ + m = self.__methods.get(name) + if m is None: + qn = ".".join((self.__qn, name)) + raise MethodNotFound, qn + return Method(self.__client, m) + + +class Method: + """ + The I{method} (namespace) object. + + @ivar client: A client object. + @type client: L{Client} + @ivar method: A I{WSDL} method. + @type I{raw} Method. + + """ + + def __init__(self, client, method): + """ + @param client: A client object. + @type client: L{Client} + @param method: A I{raw} method. + @type I{raw} Method. + + """ + self.client = client + self.method = method + + def __call__(self, *args, **kwargs): + """Invoke the method.""" + clientclass = self.clientclass(kwargs) + client = clientclass(self.client, self.method) + try: + return client.invoke(args, kwargs) + except WebFault, e: + if self.faults(): + raise + return httplib.INTERNAL_SERVER_ERROR, e + + def faults(self): + """Get faults option.""" + return self.client.options.faults + + def clientclass(self, kwargs): + """Get SOAP client class.""" + if _SimClient.simulation(kwargs): + return _SimClient + return _SoapClient + + +class RequestContext: + """ + A request context. + + Returned by a suds Client when invoking a web service operation with the + ``nosend`` enabled. Allows the caller to take care of sending the request + himself and return back the reply data for further processing. + + @ivar envelope: The SOAP request envelope. + @type envelope: I{bytes} + + """ + + def __init__(self, process_reply, envelope): + """ + @param process_reply: A callback for processing a user defined reply. + @type process_reply: I{callable} + @param envelope: The SOAP request envelope. + @type envelope: I{bytes} + + """ + self.__process_reply = process_reply + self.envelope = envelope + + def process_reply(self, reply, status=None, description=None): + """ + Re-entry for processing a successful reply. + + Depending on how the ``retxml`` option is set, may return the SOAP + reply XML or process it and return the Python object representing the + returned value. + + @param reply: The SOAP reply envelope. + @type reply: I{bytes} + @param status: The HTTP status code. + @type status: int + @param description: Additional status description. + @type description: I{bytes} + @return: The invoked web service operation return value. + @rtype: I{builtin}|I{subclass of} L{Object}|I{bytes}|I{None} + + """ + return self.__process_reply(reply, status, description) + + +class _SoapClient: + """ + An internal lightweight SOAP based web service operation client. + + Each instance is constructed for specific web service operation and knows + how to: + - Construct a SOAP request for it. + - Transport a SOAP request for it using a configured transport. + - Receive a SOAP reply using a configured transport. + - Process the received SOAP reply. + + Depending on the given suds options, may do all the tasks listed above or + may stop the process at an earlier point and return some intermediate + result, e.g. the constructed SOAP request or the raw received SOAP reply. + See the invoke() method for more detailed information. + + @ivar service: The target method. + @type service: L{Service} + @ivar method: A target method. + @type method: L{Method} + @ivar options: A dictonary of options. + @type options: dict + @ivar cookiejar: A cookie jar. + @type cookiejar: libcookie.CookieJar + + """ + + TIMEOUT_ARGUMENT = "__timeout" + + def __init__(self, client, method): + """ + @param client: A suds client. + @type client: L{Client} + @param method: A target method. + @type method: L{Method} + + """ + self.client = client + self.method = method + self.options = client.options + self.cookiejar = CookieJar() + + def invoke(self, args, kwargs): + """ + Invoke a specified web service method. + + Depending on how the ``nosend`` & ``retxml`` options are set, may do + one of the following: + * Return a constructed web service operation SOAP request without + sending it to the web service. + * Invoke the web service operation and return its SOAP reply XML. + * Invoke the web service operation, process its results and return + the Python object representing the returned value. + + When returning a SOAP request, the request is wrapped inside a + RequestContext object allowing the user to acquire a corresponding SOAP + reply himself and then pass it back to suds for further processing. + + Constructed request data is automatically processed using registered + plugins and serialized into a byte-string. Exact request XML formatting + may be affected by the ``prettyxml`` suds option. + + @param args: A list of args for the method invoked. + @type args: list|tuple + @param kwargs: Named (keyword) args for the method invoked. + @type kwargs: dict + @return: SOAP request, SOAP reply or a web service return value. + @rtype: L{RequestContext}|I{builtin}|I{subclass of} L{Object}|I{bytes}| + I{None} + + """ + timer = metrics.Timer() + timer.start() + binding = self.method.binding.input + timeout = kwargs.pop(_SoapClient.TIMEOUT_ARGUMENT, None) + soapenv = binding.get_message(self.method, args, kwargs) + timer.stop() + method_name = self.method.name + metrics.log.debug("message for '%s' created: %s", method_name, timer) + timer.start() + result = self.send(soapenv, timeout=timeout) + timer.stop() + metrics.log.debug("method '%s' invoked: %s", method_name, timer) + return result + + def send(self, soapenv, timeout=None): + """ + Send SOAP message. + + Depending on how the ``nosend`` & ``retxml`` options are set, may do + one of the following: + * Return a constructed web service operation request without sending + it to the web service. + * Invoke the web service operation and return its SOAP reply XML. + * Invoke the web service operation, process its results and return + the Python object representing the returned value. + + @param soapenv: A SOAP envelope to send. + @type soapenv: L{Document} + @return: SOAP request, SOAP reply or a web service return value. + @rtype: L{RequestContext}|I{builtin}|I{subclass of} L{Object}|I{bytes}| + I{None} + + """ + location = self.__location() + log.debug("sending to (%s)\nmessage:\n%s", location, soapenv) + plugins = PluginContainer(self.options.plugins) + plugins.message.marshalled(envelope=soapenv.root()) + if self.options.prettyxml: + soapenv = soapenv.str() + else: + soapenv = soapenv.plain() + soapenv = soapenv.encode("utf-8") + ctx = plugins.message.sending(envelope=soapenv) + soapenv = ctx.envelope + if self.options.nosend: + return RequestContext(self.process_reply, soapenv) + request = suds.transport.Request(location, soapenv, timeout) + request.headers = self.__headers() + try: + timer = metrics.Timer() + timer.start() + reply = self.options.transport.send(request) + timer.stop() + metrics.log.debug("waited %s on server reply", timer) + except suds.transport.TransportError, e: + content = e.fp and e.fp.read() or "" + return self.process_reply(content, e.httpcode, tostr(e)) + return self.process_reply(reply.message, None, None) + + def process_reply(self, reply, status, description): + """ + Process a web service operation SOAP reply. + + Depending on how the ``retxml`` option is set, may return the SOAP + reply XML or process it and return the Python object representing the + returned value. + + @param reply: The SOAP reply envelope. + @type reply: I{bytes} + @param status: The HTTP status code (None indicates httplib.OK). + @type status: int|I{None} + @param description: Additional status description. + @type description: str + @return: The invoked web service operation return value. + @rtype: I{builtin}|I{subclass of} L{Object}|I{bytes}|I{None} + + """ + if status is None: + status = httplib.OK + debug_message = "Reply HTTP status - %d" % (status,) + if status in (httplib.ACCEPTED, httplib.NO_CONTENT): + log.debug(debug_message) + return + #TODO: Consider whether and how to allow plugins to handle error, + # httplib.ACCEPTED & httplib.NO_CONTENT replies as well as successful + # ones. + if status == httplib.OK: + log.debug("%s\n%s", debug_message, reply) + else: + log.debug("%s - %s\n%s", debug_message, description, reply) + + plugins = PluginContainer(self.options.plugins) + ctx = plugins.message.received(reply=reply) + reply = ctx.reply + + # SOAP standard states that SOAP errors must be accompanied by HTTP + # status code 500 - internal server error: + # + # From SOAP 1.1 specification: + # In case of a SOAP error while processing the request, the SOAP HTTP + # server MUST issue an HTTP 500 "Internal Server Error" response and + # include a SOAP message in the response containing a SOAP Fault + # element (see section 4.4) indicating the SOAP processing error. + # + # From WS-I Basic profile: + # An INSTANCE MUST use a "500 Internal Server Error" HTTP status code + # if the response message is a SOAP Fault. + replyroot = None + if status in (httplib.OK, httplib.INTERNAL_SERVER_ERROR): + replyroot = _parse(reply) + plugins.message.parsed(reply=replyroot) + fault = self.__get_fault(replyroot) + if fault: + if status != httplib.INTERNAL_SERVER_ERROR: + log.warning("Web service reported a SOAP processing fault " + "using an unexpected HTTP status code %d. Reporting " + "as an internal server error.", status) + if self.options.faults: + raise WebFault(fault, replyroot) + return httplib.INTERNAL_SERVER_ERROR, fault + if status != httplib.OK: + if self.options.faults: + #TODO: Use a more specific exception class here. + raise Exception((status, description)) + return status, description + + if self.options.retxml: + return reply + + result = replyroot and self.method.binding.output.get_reply( + self.method, replyroot) + ctx = plugins.message.unmarshalled(reply=result) + result = ctx.reply + if self.options.faults: + return result + return httplib.OK, result + + def __get_fault(self, replyroot): + """ + Extract fault information from a SOAP reply. + + Returns an I{unmarshalled} fault L{Object} or None in case the given + XML document does not contain a SOAP element. + + @param replyroot: A SOAP reply message root XML element or None. + @type replyroot: L{Element}|I{None} + @return: A fault object. + @rtype: L{Object} + + """ + envns = suds.bindings.binding.envns + soapenv = replyroot and replyroot.getChild("Envelope", envns) + soapbody = soapenv and soapenv.getChild("Body", envns) + fault = soapbody and soapbody.getChild("Fault", envns) + return fault is not None and UmxBasic().process(fault) + + def __headers(self): + """ + Get HTTP headers for a HTTP/HTTPS SOAP request. + + @return: A dictionary of header/values. + @rtype: dict + + """ + action = self.method.soap.action + if isinstance(action, unicode): + action = action.encode("utf-8") + result = { + "Content-Type": "text/xml; charset=utf-8", + "SOAPAction": action} + result.update(**self.options.headers) + log.debug("headers = %s", result) + return result + + def __location(self): + """Returns the SOAP request's target location URL.""" + return Unskin(self.options).get("location", self.method.location) + + +class _SimClient(_SoapClient): + """ + Loopback _SoapClient used for SOAP request/reply simulation. + + Used when a web service operation is invoked with injected SOAP request or + reply data. + + """ + + __injkey = "__inject" + + @classmethod + def simulation(cls, kwargs): + """Get whether injected data has been specified in I{kwargs}.""" + return kwargs.has_key(_SimClient.__injkey) + + def invoke(self, args, kwargs): + """ + Invoke a specified web service method. + + Uses an injected SOAP request/response instead of a regularly + constructed/received one. + + Depending on how the ``nosend`` & ``retxml`` options are set, may do + one of the following: + * Return a constructed web service operation request without sending + it to the web service. + * Invoke the web service operation and return its SOAP reply XML. + * Invoke the web service operation, process its results and return + the Python object representing the returned value. + + @param args: Positional arguments for the method invoked. + @type args: list|tuple + @param kwargs: Keyword arguments for the method invoked. + @type kwargs: dict + @return: SOAP request, SOAP reply or a web service return value. + @rtype: L{RequestContext}|I{builtin}|I{subclass of} L{Object}|I{bytes}| + I{None} + + """ + simulation = kwargs.pop(self.__injkey) + msg = simulation.get("msg") + if msg is not None: + assert msg.__class__ is suds.byte_str_class + return self.send(_parse(msg)) + msg = self.method.binding.input.get_message(self.method, args, kwargs) + log.debug("inject (simulated) send message:\n%s", msg) + reply = simulation.get("reply") + if reply is not None: + assert reply.__class__ is suds.byte_str_class + status = simulation.get("status") + description = simulation.get("description") + if description is None: + description = "injected reply" + return self.process_reply(reply, status, description) + raise Exception("reply or msg injection parameter expected") + + +def _parse(string): + """ + Parses given XML document content. + + Returns the resulting root XML element node or None if the given XML + content is empty. + + @param string: XML document content to parse. + @type string: I{bytes} + @return: Resulting root XML element node or None. + @rtype: L{Element}|I{None} + + """ + if string: + return suds.sax.parser.Parser().parse(string=string) diff --git a/pym/calculate/contrib/suds/client.pyc b/pym/calculate/contrib/suds/client.pyc new file mode 100644 index 0000000..cfcbddb Binary files /dev/null and b/pym/calculate/contrib/suds/client.pyc differ diff --git a/pym/calculate/contrib/suds/metrics.py b/pym/calculate/contrib/suds/metrics.py new file mode 100644 index 0000000..9b15f18 --- /dev/null +++ b/pym/calculate/contrib/suds/metrics.py @@ -0,0 +1,63 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{metrics} module defines classes and other resources +designed for collecting and reporting performance metrics. +""" + +import time +from suds import * +from math import modf + +from logging import getLogger +log = getLogger(__name__) + + +class Timer: + + def __init__(self): + self.started = 0 + self.stopped = 0 + + def start(self): + self.started = time.time() + self.stopped = 0 + return self + + def stop(self): + if self.started > 0: + self.stopped = time.time() + return self + + def duration(self): + return ( self.stopped - self.started ) + + def __str__(self): + if self.started == 0: + return 'not-running' + if self.started > 0 and self.stopped == 0: + return 'started: %d (running)' % self.started + duration = self.duration() + jmod = ( lambda m : (m[1], m[0]*1000) ) + if duration < 1: + ms = (duration*1000) + return '%d (ms)' % ms + if duration < 60: + m = modf(duration) + return '%d.%.3d (seconds)' % jmod(m) + m = modf(duration/60) + return '%d.%.3d (minutes)' % jmod(m) diff --git a/pym/calculate/contrib/suds/metrics.pyc b/pym/calculate/contrib/suds/metrics.pyc new file mode 100644 index 0000000..f406642 Binary files /dev/null and b/pym/calculate/contrib/suds/metrics.pyc differ diff --git a/pym/calculate/contrib/suds/mx/__init__.py b/pym/calculate/contrib/suds/mx/__init__.py new file mode 100644 index 0000000..c64c0f9 --- /dev/null +++ b/pym/calculate/contrib/suds/mx/__init__.py @@ -0,0 +1,60 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides modules containing classes to support marshalling to XML. + +""" + +from suds.sudsobject import Object + + +class Content(Object): + """ + Marshaller content. + + @ivar tag: The content tag. + @type tag: str + @ivar value: The content's value. + @type value: I{any} + + """ + + extensions = [] + + def __init__(self, tag=None, value=None, **kwargs): + """ + @param tag: The content tag. + @type tag: str + @param value: The content's value. + @type value: I{any} + + """ + Object.__init__(self) + self.tag = tag + self.value = value + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + def __getattr__(self, name): + try: + return self.__dict__[name] + except KeyError: + pass + if name in self.extensions: + value = None + setattr(self, name, value) + return value + raise AttributeError("Content has no attribute %s" % (name,)) diff --git a/pym/calculate/contrib/suds/mx/__init__.pyc b/pym/calculate/contrib/suds/mx/__init__.pyc new file mode 100644 index 0000000..672f818 Binary files /dev/null and b/pym/calculate/contrib/suds/mx/__init__.pyc differ diff --git a/pym/calculate/contrib/suds/mx/appender.py b/pym/calculate/contrib/suds/mx/appender.py new file mode 100644 index 0000000..3736441 --- /dev/null +++ b/pym/calculate/contrib/suds/mx/appender.py @@ -0,0 +1,282 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides appender classes for I{marshalling}. +""" + +from suds import * +from suds.mx import * +from suds.sudsobject import Object, Property +from suds.sax.element import Element +from suds.sax.text import Text + + +class Matcher: + """ + Appender matcher. + @ivar cls: A class object. + @type cls: I{classobj} + """ + + def __init__(self, cls): + """ + @param cls: A class object. + @type cls: I{classobj} + """ + self.cls = cls + + def __eq__(self, x): + if self.cls is None: + return x is None + return isinstance(x, self.cls) + + +class ContentAppender: + """ + Appender used to add content to marshalled objects. + @ivar default: The default appender. + @type default: L{Appender} + @ivar appenders: A I{table} of appenders mapped by class. + @type appenders: I{table} + """ + + def __init__(self, marshaller): + """ + @param marshaller: A marshaller. + @type marshaller: L{suds.mx.core.Core} + """ + self.default = PrimitiveAppender(marshaller) + self.appenders = ( + (Matcher(None), NoneAppender(marshaller)), + (Matcher(null), NoneAppender(marshaller)), + (Matcher(Property), PropertyAppender(marshaller)), + (Matcher(Object), ObjectAppender(marshaller)), + (Matcher(Element), ElementAppender(marshaller)), + (Matcher(Text), TextAppender(marshaller)), + (Matcher(list), ListAppender(marshaller)), + (Matcher(tuple), ListAppender(marshaller))) + + def append(self, parent, content): + """ + Select an appender and append the content to parent. + @param parent: A parent node. + @type parent: L{Element} + @param content: The content to append. + @type content: L{Content} + """ + appender = self.default + for matcher, candidate_appender in self.appenders: + if matcher == content.value: + appender = candidate_appender + break + appender.append(parent, content) + + +class Appender: + """ + An appender used by the marshaller to append content. + @ivar marshaller: A marshaller. + @type marshaller: L{suds.mx.core.Core} + """ + + def __init__(self, marshaller): + """ + @param marshaller: A marshaller. + @type marshaller: L{suds.mx.core.Core} + """ + self.marshaller = marshaller + + def node(self, content): + """ + Create and return an XML node that is qualified + using the I{type}. Also, make sure all referenced namespace + prefixes are declared. + @param content: The content for which processing has ended. + @type content: L{Object} + @return: A new node. + @rtype: L{Element} + """ + return self.marshaller.node(content) + + def setnil(self, node, content): + """ + Set the value of the I{node} to nill. + @param node: A I{nil} node. + @type node: L{Element} + @param content: The content for which processing has ended. + @type content: L{Object} + """ + self.marshaller.setnil(node, content) + + def setdefault(self, node, content): + """ + Set the value of the I{node} to a default value. + @param node: A I{nil} node. + @type node: L{Element} + @param content: The content for which processing has ended. + @type content: L{Object} + @return: The default. + """ + return self.marshaller.setdefault(node, content) + + def optional(self, content): + """ + Get whether the specified content is optional. + @param content: The content which to check. + @type content: L{Content} + """ + return self.marshaller.optional(content) + + def suspend(self, content): + """ + Notify I{marshaller} that appending this content has suspended. + @param content: The content for which processing has been suspended. + @type content: L{Object} + """ + self.marshaller.suspend(content) + + def resume(self, content): + """ + Notify I{marshaller} that appending this content has resumed. + @param content: The content for which processing has been resumed. + @type content: L{Object} + """ + self.marshaller.resume(content) + + def append(self, parent, content): + """ + Append the specified L{content} to the I{parent}. + @param content: The content to append. + @type content: L{Object} + """ + self.marshaller.append(parent, content) + + +class PrimitiveAppender(Appender): + """ + An appender for python I{primitive} types. + """ + + def append(self, parent, content): + if content.tag.startswith('_'): + attr = content.tag[1:] + value = tostr(content.value) + if value: + parent.set(attr, value) + else: + child = self.node(content) + child.setText(tostr(content.value)) + parent.append(child) + + +class NoneAppender(Appender): + """ + An appender for I{None} values. + """ + + def append(self, parent, content): + child = self.node(content) + default = self.setdefault(child, content) + if default is None: + self.setnil(child, content) + parent.append(child) + + +class PropertyAppender(Appender): + """ + A L{Property} appender. + """ + + def append(self, parent, content): + p = content.value + child = self.node(content) + child.setText(p.get()) + parent.append(child) + for item in p.items(): + cont = Content(tag=item[0], value=item[1]) + Appender.append(self, child, cont) + + +class ObjectAppender(Appender): + """ + An L{Object} appender. + """ + + def append(self, parent, content): + object = content.value + child = self.node(content) + parent.append(child) + for item in object: + cont = Content(tag=item[0], value=item[1]) + Appender.append(self, child, cont) + + +class ElementWrapper(Element): + """ + Element wrapper. + """ + + def __init__(self, content): + Element.__init__(self, content.name, content.parent) + self.__content = content + + def str(self, indent=0): + return self.__content.str(indent) + + +class ElementAppender(Appender): + """ + An appender for I{Element} types. + """ + + def append(self, parent, content): + if content.tag.startswith('_'): + raise Exception('raw XML not valid as attribute value') + child = ElementWrapper(content.value) + parent.append(child) + + +class ListAppender(Appender): + """ + A list/tuple appender. + """ + + def append(self, parent, content): + collection = content.value + if len(collection): + self.suspend(content) + for item in collection: + cont = Content(tag=content.tag, value=item) + Appender.append(self, parent, cont) + self.resume(content) + + +class TextAppender(Appender): + """ + An appender for I{Text} values. + """ + + def append(self, parent, content): + if content.tag.startswith('_'): + attr = content.tag[1:] + value = tostr(content.value) + if value: + parent.set(attr, value) + else: + child = self.node(content) + child.setText(content.value) + parent.append(child) diff --git a/pym/calculate/contrib/suds/mx/appender.pyc b/pym/calculate/contrib/suds/mx/appender.pyc new file mode 100644 index 0000000..406baa6 Binary files /dev/null and b/pym/calculate/contrib/suds/mx/appender.pyc differ diff --git a/pym/calculate/contrib/suds/mx/basic.py b/pym/calculate/contrib/suds/mx/basic.py new file mode 100644 index 0000000..b2de161 --- /dev/null +++ b/pym/calculate/contrib/suds/mx/basic.py @@ -0,0 +1,45 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides basic I{marshaller} classes. +""" + +from suds import * +from suds.mx import * +from suds.mx.core import Core + + +class Basic(Core): + """ + A I{basic} (untyped) marshaller. + """ + + def process(self, value, tag=None): + """ + Process (marshal) the tag with the specified value using the + optional type information. + @param value: The value (content) of the XML node. + @type value: (L{Object}|any) + @param tag: The (optional) tag name for the value. The default is + value.__class__.__name__ + @type tag: str + @return: An xml node. + @rtype: L{Element} + """ + content = Content(tag=tag, value=value) + result = Core.process(self, content) + return result diff --git a/pym/calculate/contrib/suds/mx/basic.pyc b/pym/calculate/contrib/suds/mx/basic.pyc new file mode 100644 index 0000000..3f450c2 Binary files /dev/null and b/pym/calculate/contrib/suds/mx/basic.pyc differ diff --git a/pym/calculate/contrib/suds/mx/core.py b/pym/calculate/contrib/suds/mx/core.py new file mode 100644 index 0000000..5bcd73a --- /dev/null +++ b/pym/calculate/contrib/suds/mx/core.py @@ -0,0 +1,150 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides I{marshaller} core classes. +""" + +from suds import * +from suds.mx import * +from suds.mx.appender import ContentAppender +from suds.sax.document import Document + +from logging import getLogger +log = getLogger(__name__) + + +class Core: + """ + An I{abstract} marshaller. This class implement the core + functionality of the marshaller. + @ivar appender: A content appender. + @type appender: L{ContentAppender} + """ + + def __init__(self): + """ + """ + self.appender = ContentAppender(self) + + def process(self, content): + """ + Process (marshal) the tag with the specified value using the + optional type information. + @param content: The content to process. + @type content: L{Object} + """ + log.debug('processing:\n%s', content) + self.reset() + if content.tag is None: + content.tag = content.value.__class__.__name__ + document = Document() + self.append(document, content) + return document.root() + + def append(self, parent, content): + """ + Append the specified L{content} to the I{parent}. + @param parent: The parent node to append to. + @type parent: L{Element} + @param content: The content to append. + @type content: L{Object} + """ + log.debug('appending parent:\n%s\ncontent:\n%s', parent, content) + if self.start(content): + self.appender.append(parent, content) + self.end(parent, content) + + def reset(self): + """ + Reset the marshaller. + """ + pass + + def node(self, content): + """ + Create and return an XML node. + @param content: Content information for the new node. + @type content: L{Content} + @return: An element. + @rtype: L{Element} + """ + raise NotImplementedError + + def start(self, content): + """ + Appending this content has started. + @param content: The content for which processing has started. + @type content: L{Content} + @return: True to continue appending + @rtype: boolean + """ + return True + + def suspend(self, content): + """ + Appending this content has suspended. + @param content: The content for which processing has been suspended. + @type content: L{Content} + """ + pass + + def resume(self, content): + """ + Appending this content has resumed. + @param content: The content for which processing has been resumed. + @type content: L{Content} + """ + pass + + def end(self, parent, content): + """ + Appending this content has ended. + @param parent: The parent node ending. + @type parent: L{Element} + @param content: The content for which processing has ended. + @type content: L{Content} + """ + pass + + def setnil(self, node, content): + """ + Set the value of the I{node} to nill. + @param node: A I{nil} node. + @type node: L{Element} + @param content: The content to set nil. + @type content: L{Content} + """ + pass + + def setdefault(self, node, content): + """ + Set the value of the I{node} to a default value. + @param node: A I{nil} node. + @type node: L{Element} + @param content: The content to set the default value. + @type content: L{Content} + @return: The default. + """ + pass + + def optional(self, content): + """ + Get whether the specified content is optional. + @param content: The content which to check. + @type content: L{Content} + """ + return False diff --git a/pym/calculate/contrib/suds/mx/core.pyc b/pym/calculate/contrib/suds/mx/core.pyc new file mode 100644 index 0000000..7407ad9 Binary files /dev/null and b/pym/calculate/contrib/suds/mx/core.pyc differ diff --git a/pym/calculate/contrib/suds/mx/encoded.py b/pym/calculate/contrib/suds/mx/encoded.py new file mode 100644 index 0000000..ec09536 --- /dev/null +++ b/pym/calculate/contrib/suds/mx/encoded.py @@ -0,0 +1,131 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides encoded I{marshaller} classes. +""" + +from suds import * +from suds.mx import * +from suds.mx.literal import Literal +from suds.mx.typer import Typer +from suds.sudsobject import Factory, Object +from suds.xsd.query import TypeQuery + + +# +# Add encoded extensions +# aty = The soap (section 5) encoded array type. +# +Content.extensions.append('aty') + + +class Encoded(Literal): + """ + A SOAP section (5) encoding marshaller. + This marshaller supports rpc/encoded soap styles. + """ + + def start(self, content): + # + # For soap encoded arrays, the 'aty' (array type) information + # is extracted and added to the 'content'. Then, the content.value + # is replaced with an object containing an 'item=[]' attribute + # containing values that are 'typed' suds objects. + # + start = Literal.start(self, content) + if start and isinstance(content.value, (list,tuple)): + resolved = content.type.resolve() + for c in resolved: + if hasattr(c[0], 'aty'): + content.aty = (content.tag, c[0].aty) + self.cast(content) + break + return start + + def end(self, parent, content): + # + # For soap encoded arrays, the soapenc:arrayType attribute is + # added with proper type and size information. + # Eg: soapenc:arrayType="xs:int[3]" + # + Literal.end(self, parent, content) + if content.aty is None: + return + tag, aty = content.aty + ns0 = ('at0', aty[1]) + ns1 = ('at1', 'http://schemas.xmlsoap.org/soap/encoding/') + array = content.value.item + child = parent.getChild(tag) + child.addPrefix(ns0[0], ns0[1]) + child.addPrefix(ns1[0], ns1[1]) + name = '%s:arrayType' % ns1[0] + value = '%s:%s[%d]' % (ns0[0], aty[0], len(array)) + child.set(name, value) + + def encode(self, node, content): + if content.type.any(): + Typer.auto(node, content.value) + return + if content.real.any(): + Typer.auto(node, content.value) + return + ns = None + name = content.real.name + if self.xstq: + ns = content.real.namespace() + Typer.manual(node, name, ns) + + def cast(self, content): + """ + Cast the I{untyped} list items found in content I{value}. + Each items contained in the list is checked for XSD type information. + Items (values) that are I{untyped}, are replaced with suds objects and + type I{metadata} is added. + @param content: The content holding the collection. + @type content: L{Content} + @return: self + @rtype: L{Encoded} + """ + aty = content.aty[1] + resolved = content.type.resolve() + array = Factory.object(resolved.name) + array.item = [] + query = TypeQuery(aty) + ref = query.execute(self.schema) + if ref is None: + raise TypeNotFound(qref) + for x in content.value: + if isinstance(x, (list, tuple)): + array.item.append(x) + continue + if isinstance(x, Object): + md = x.__metadata__ + md.sxtype = ref + array.item.append(x) + continue + if isinstance(x, dict): + x = Factory.object(ref.name, x) + md = x.__metadata__ + md.sxtype = ref + array.item.append(x) + continue + x = Factory.property(ref.name, x) + md = x.__metadata__ + md.sxtype = ref + array.item.append(x) + content.value = array + return self diff --git a/pym/calculate/contrib/suds/mx/encoded.pyc b/pym/calculate/contrib/suds/mx/encoded.pyc new file mode 100644 index 0000000..8a57c2e Binary files /dev/null and b/pym/calculate/contrib/suds/mx/encoded.pyc differ diff --git a/pym/calculate/contrib/suds/mx/literal.py b/pym/calculate/contrib/suds/mx/literal.py new file mode 100644 index 0000000..a7e5e38 --- /dev/null +++ b/pym/calculate/contrib/suds/mx/literal.py @@ -0,0 +1,311 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides literal I{marshaller} classes. + +""" + +from suds import * +from suds.mx import * +from suds.mx.core import Core +from suds.mx.typer import Typer +from suds.resolver import Frame, GraphResolver +from suds.sax.element import Element +from suds.sudsobject import Factory + +from logging import getLogger +log = getLogger(__name__) + + +# add typed extensions +Content.extensions.append("type") # The expected xsd type +Content.extensions.append("real") # The 'true' XSD type +Content.extensions.append("ancestry") # The 'type' ancestry + + +class Typed(Core): + """ + A I{typed} marshaller. + + This marshaller is semi-typed as needed to support both I{document/literal} + and I{rpc/literal} SOAP message styles. + + @ivar schema: An XSD schema. + @type schema: L{xsd.schema.Schema} + @ivar resolver: A schema type resolver. + @type resolver: L{GraphResolver} + + """ + + def __init__(self, schema, xstq=True): + """ + @param schema: A schema object + @type schema: L{xsd.schema.Schema} + @param xstq: The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates + that the I{xsi:type} attribute values should be qualified by + namespace. + @type xstq: bool + + """ + Core.__init__(self) + self.schema = schema + self.xstq = xstq + self.resolver = GraphResolver(self.schema) + + def reset(self): + self.resolver.reset() + + def start(self, content): + """ + Start marshalling the 'content' by ensuring that both the 'content' + _and_ the resolver are primed with the XSD type information. The + 'content' value is both translated and sorted based on the XSD type. + Only values that are objects have their attributes sorted. + + """ + log.debug("starting content:\n%s", content) + if content.type is None: + name = content.tag + if name.startswith("_"): + name = "@" + name[1:] + content.type = self.resolver.find(name, content.value) + if content.type is None: + raise TypeNotFound(content.tag) + else: + known = None + if isinstance(content.value, Object): + known = self.resolver.known(content.value) + if known is None: + log.debug("object %s has no type information", + content.value) + known = content.type + frame = Frame(content.type, resolved=known) + self.resolver.push(frame) + frame = self.resolver.top() + content.real = frame.resolved + content.ancestry = frame.ancestry + self.translate(content) + self.sort(content) + if self.skip(content): + log.debug("skipping (optional) content:\n%s", content) + self.resolver.pop() + return False + return True + + def suspend(self, content): + """ + Suspend to process list content. + + Primarily, this involves popping the 'list' content off the resolver's + stack so its list items can be marshalled. + + """ + self.resolver.pop() + + def resume(self, content): + """ + Resume processing list content. + + To do this, we really need to simply push the 'list' content back onto + the resolver stack. + + """ + self.resolver.push(Frame(content.type)) + + def end(self, parent, content): + """ + End processing the content. + + Make sure the content ending matches the top of the resolver stack + since for list processing we play games with the resolver stack. + + """ + log.debug("ending content:\n%s", content) + current = self.resolver.top().type + if current != content.type: + raise Exception("content (end) mismatch: top=(%s) cont=(%s)" % ( + current, content)) + self.resolver.pop() + + def node(self, content): + """ + Create an XML node. + + The XML node is namespace qualified as defined by the corresponding + schema element. + + """ + ns = content.type.namespace() + if content.type.form_qualified: + node = Element(content.tag, ns=ns) + if ns[0]: + node.addPrefix(ns[0], ns[1]) + else: + node = Element(content.tag) + self.encode(node, content) + log.debug("created - node:\n%s", node) + return node + + def setnil(self, node, content): + """ + Set the 'node' nil only if the XSD type specifies that it is permitted. + + """ + if content.type.nillable: + node.setnil() + + def setdefault(self, node, content): + """Set the node to the default value specified by the XSD type.""" + default = content.type.default + if default is not None: + node.setText(default) + return default + + def optional(self, content): + if content.type.optional(): + return True + for a in content.ancestry: + if a.optional(): + return True + return False + + def encode(self, node, content): + """ + Add (SOAP) encoding information if needed. + + The encoding information is added only if the resolved type is derived + by extension. Furthermore, the xsi:type value is qualified by namespace + only if the content (tag) and referenced type are in different + namespaces. + + """ + if content.type.any(): + return + if not content.real.extension(): + return + if content.type.resolve() == content.real: + return + ns = None + name = content.real.name + if self.xstq: + ns = content.real.namespace("ns1") + Typer.manual(node, name, ns) + + def skip(self, content): + """ + Get whether to skip this I{content}. + + Should be skipped when the content is optional and value is either None + or an empty list. + + @param content: Content to skip. + @type content: L{Object} + @return: True if content is to be skipped. + @rtype: bool + + """ + if self.optional(content): + v = content.value + if v is None: + return True + if isinstance(v, (list, tuple)) and not v: + return True + return False + + def optional(self, content): + if content.type.optional(): + return True + for a in content.ancestry: + if a.optional(): + return True + return False + + def translate(self, content): + """ + Translate using the XSD type information. + + Python I{dict} is translated to a suds object. Most importantly, + primitive values are translated from python to XML types using the XSD + type. + + @param content: Content to translate. + @type content: L{Object} + @return: self + @rtype: L{Typed} + + """ + v = content.value + if v is None: + return + if isinstance(v, dict): + cls = content.real.name + content.value = Factory.object(cls, v) + md = content.value.__metadata__ + md.sxtype = content.type + return + v = content.real.translate(v, False) + content.value = v + return self + + def sort(self, content): + """ + Sort suds object attributes. + + The attributes are sorted based on the ordering defined in the XSD type + information. + + @param content: Content to sort. + @type content: L{Object} + @return: self + @rtype: L{Typed} + + """ + v = content.value + if isinstance(v, Object): + md = v.__metadata__ + md.ordering = self.ordering(content.real) + return self + + def ordering(self, type): + """ + Attribute ordering defined in the specified XSD type information. + + @param type: XSD type object. + @type type: L{SchemaObject} + @return: An ordered list of attribute names. + @rtype: list + + """ + result = [] + for child, ancestry in type.resolve(): + name = child.name + if child.name is None: + continue + if child.isattr(): + name = "_%s" % (child.name,) + result.append(name) + return result + + +class Literal(Typed): + """ + A I{literal} marshaller. + + This marshaller is semi-typed as needed to support both I{document/literal} + and I{rpc/literal} soap message styles. + + """ + pass diff --git a/pym/calculate/contrib/suds/mx/literal.pyc b/pym/calculate/contrib/suds/mx/literal.pyc new file mode 100644 index 0000000..44d2d3c Binary files /dev/null and b/pym/calculate/contrib/suds/mx/literal.pyc differ diff --git a/pym/calculate/contrib/suds/mx/typer.py b/pym/calculate/contrib/suds/mx/typer.py new file mode 100644 index 0000000..3533ddc --- /dev/null +++ b/pym/calculate/contrib/suds/mx/typer.py @@ -0,0 +1,126 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides XSD typing classes. + +""" + +from suds.sax import Namespace +from suds.sax.text import Text +from suds.sudsobject import Object + + +class Typer: + """ + Provides XML node typing as either automatic or manual. + + @cvar types: Class to XSD type mapping. + @type types: dict + + """ + + types = { + bool: ("boolean", Namespace.xsdns), + float: ("float", Namespace.xsdns), + int: ("int", Namespace.xsdns), + long: ("long", Namespace.xsdns), + str: ("string", Namespace.xsdns), + Text: ("string", Namespace.xsdns), + unicode: ("string", Namespace.xsdns)} + + @classmethod + def auto(cls, node, value=None): + """ + Automatically set the node's xsi:type attribute based on either + I{value}'s or the node text's class. When I{value} is an unmapped + class, the default type (xs:any) is set. + + @param node: XML node. + @type node: L{sax.element.Element} + @param value: Object that is or would be the node's text. + @type value: I{any} + @return: Specified node. + @rtype: L{sax.element.Element} + + """ + if value is None: + value = node.getText() + if isinstance(value, Object): + known = cls.known(value) + if known.name is None: + return node + tm = known.name, known.namespace() + else: + tm = cls.types.get(value.__class__, cls.types.get(str)) + cls.manual(node, *tm) + return node + + @classmethod + def manual(cls, node, tval, ns=None): + """ + Set the node's xsi:type attribute based on either I{value}'s or the + node text's class. Then adds the referenced prefix(s) to the node's + prefix mapping. + + @param node: XML node. + @type node: L{sax.element.Element} + @param tval: XSD schema type name. + @type tval: str + @param ns: I{tval} XML namespace. + @type ns: (prefix, URI) + @return: Specified node. + @rtype: L{sax.element.Element} + + """ + xta = ":".join((Namespace.xsins[0], "type")) + node.addPrefix(Namespace.xsins[0], Namespace.xsins[1]) + if ns is None: + node.set(xta, tval) + else: + ns = cls.genprefix(node, ns) + qname = ":".join((ns[0], tval)) + node.set(xta, qname) + node.addPrefix(ns[0], ns[1]) + return node + + @classmethod + def genprefix(cls, node, ns): + """ + Generate a prefix. + + @param node: XML node on which the prefix will be used. + @type node: L{sax.element.Element} + @param ns: Namespace needing a unique prefix. + @type ns: (prefix, URI) + @return: I{ns} with a new prefix. + @rtype: (prefix, URI) + + """ + for i in range(1, 1024): + prefix = "ns%d" % (i,) + uri = node.resolvePrefix(prefix, default=None) + if uri in (None, ns[1]): + return prefix, ns[1] + raise Exception("auto prefix, exhausted") + + @classmethod + def known(cls, object): + try: + md = object.__metadata__ + known = md.sxtype + return known + except Exception: + pass diff --git a/pym/calculate/contrib/suds/mx/typer.pyc b/pym/calculate/contrib/suds/mx/typer.pyc new file mode 100644 index 0000000..3d6e554 Binary files /dev/null and b/pym/calculate/contrib/suds/mx/typer.pyc differ diff --git a/pym/calculate/contrib/suds/options.py b/pym/calculate/contrib/suds/options.py new file mode 100644 index 0000000..a296ad5 --- /dev/null +++ b/pym/calculate/contrib/suds/options.py @@ -0,0 +1,162 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Suds basic options classes. +""" + +from suds.cache import Cache, NoCache +from suds.properties import * +from suds.store import DocumentStore, defaultDocumentStore +from suds.transport import Transport +from suds.wsse import Security +from suds.xsd.doctor import Doctor + + +class TpLinker(AutoLinker): + """ + Transport (auto) linker used to manage linkage between + transport objects Properties and those Properties that contain them. + """ + + def updated(self, properties, prev, next): + if isinstance(prev, Transport): + tp = Unskin(prev.options) + properties.unlink(tp) + if isinstance(next, Transport): + tp = Unskin(next.options) + properties.link(tp) + + +class Options(Skin): + """ + Options: + - B{cache} - The XML document cache. May be set to None for no caching. + - type: L{Cache} + - default: L{NoCache()} + - B{documentStore} - The XML document store used to access locally + stored documents without having to download them from an external + location. May be set to None for no internal suds library document + store. + - type: L{DocumentStore} + - default: L{defaultDocumentStore} + - B{extraArgumentErrors} - Raise exceptions when unknown message parts + are detected when receiving a web service reply, compared to the + operation's WSDL schema definition. + - type: I{bool} + - default: True + - B{allowUnknownMessageParts} - Raise exceptions when extra arguments are + detected when invoking a web service operation, compared to the + operation's WSDL schema definition. + - type: I{bool} + - default: False + - B{faults} - Raise faults raised by server, else return tuple from + service method invocation as (httpcode, object). + - type: I{bool} + - default: True + - B{service} - The default service name. + - type: I{str} + - default: None + - B{port} - The default service port name, not tcp port. + - type: I{str} + - default: None + - B{location} - This overrides the service port address I{URL} defined + in the WSDL. + - type: I{str} + - default: None + - B{transport} - The message transport. + - type: L{Transport} + - default: None + - B{soapheaders} - The soap headers to be included in the soap message. + - type: I{any} + - default: None + - B{wsse} - The web services I{security} provider object. + - type: L{Security} + - default: None + - B{doctor} - A schema I{doctor} object. + - type: L{Doctor} + - default: None + - B{xstq} - The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates + that the I{xsi:type} attribute values should be qualified by + namespace. + - type: I{bool} + - default: True + - B{prefixes} - Elements of the soap message should be qualified (when + needed) using XML prefixes as opposed to xmlns="" syntax. + - type: I{bool} + - default: True + - B{retxml} - Flag that causes the I{raw} soap envelope to be returned + instead of the python object graph. + - type: I{bool} + - default: False + - B{prettyxml} - Flag that causes I{pretty} xml to be rendered when + generating the outbound soap envelope. + - type: I{bool} + - default: False + - B{autoblend} - Flag that ensures that the schema(s) defined within + the WSDL import each other. + - type: I{bool} + - default: False + - B{cachingpolicy} - The caching policy. + - type: I{int} + - 0 = Cache XML documents. + - 1 = Cache WSDL (pickled) object. + - default: 0 + - B{plugins} - A plugin container. + - type: I{list} + - default: I{list()} + - B{nosend} - Create the soap envelope but do not send. + When specified, method invocation returns a I{RequestContext} + instead of sending it. + - type: I{bool} + - default: False + - B{unwrap} - Enable automatic parameter unwrapping when possible. + Enabled by default. If disabled, no input or output parameters are + ever automatically unwrapped. + - type: I{bool} + - default: True + - B{sortNamespaces} - Namespaces are sorted alphabetically. If disabled, + namespaces are left in the order they are received from the source. + Enabled by default for historical purposes. + - type: I{bool} + - default: True + """ + def __init__(self, **kwargs): + domain = __name__ + definitions = [ + Definition('cache', Cache, NoCache()), + Definition('documentStore', DocumentStore, defaultDocumentStore), + Definition('extraArgumentErrors', bool, True), + Definition('allowUnknownMessageParts', bool, False), + Definition('faults', bool, True), + Definition('transport', Transport, None, TpLinker()), + Definition('service', (int, basestring), None), + Definition('port', (int, basestring), None), + Definition('location', basestring, None), + Definition('soapheaders', (), ()), + Definition('wsse', Security, None), + Definition('doctor', Doctor, None), + Definition('xstq', bool, True), + Definition('prefixes', bool, True), + Definition('retxml', bool, False), + Definition('prettyxml', bool, False), + Definition('autoblend', bool, False), + Definition('cachingpolicy', int, 0), + Definition('plugins', (list, tuple), []), + Definition('nosend', bool, False), + Definition('unwrap', bool, True), + Definition('sortNamespaces', bool, True)] + Skin.__init__(self, domain, definitions, kwargs) diff --git a/pym/calculate/contrib/suds/options.pyc b/pym/calculate/contrib/suds/options.pyc new file mode 100644 index 0000000..40214ad Binary files /dev/null and b/pym/calculate/contrib/suds/options.pyc differ diff --git a/pym/calculate/contrib/suds/plugin.py b/pym/calculate/contrib/suds/plugin.py new file mode 100644 index 0000000..b957897 --- /dev/null +++ b/pym/calculate/contrib/suds/plugin.py @@ -0,0 +1,276 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The plugin module provides suds plugin implementation classes. + +""" + +from suds import * + +from logging import getLogger +log = getLogger(__name__) + + +class Context(object): + """Plugin context.""" + pass + + +class InitContext(Context): + """ + Init Context. + + @ivar wsdl: The WSDL. + @type wsdl: L{wsdl.Definitions} + + """ + pass + + +class DocumentContext(Context): + """ + The XML document load context. + + @ivar url: The URL. + @type url: str + @ivar document: Either the XML text or the B{parsed} document root. + @type document: (str|L{sax.element.Element}) + + """ + pass + + +class MessageContext(Context): + """ + The context for sending the SOAP envelope. + + @ivar envelope: The SOAP envelope to be sent. + @type envelope: (str|L{sax.element.Element}) + @ivar reply: The reply. + @type reply: (str|L{sax.element.Element}|object) + + """ + pass + + +class Plugin: + """Plugin base.""" + pass + + +class InitPlugin(Plugin): + """Base class for all suds I{init} plugins.""" + + def initialized(self, context): + """ + Suds client initialization. + + Called after WSDL the has been loaded. Provides the plugin with the + opportunity to inspect/modify the WSDL. + + @param context: The init context. + @type context: L{InitContext} + + """ + pass + + +class DocumentPlugin(Plugin): + """Base class for suds I{document} plugins.""" + + def loaded(self, context): + """ + Suds has loaded a WSDL/XSD document. + + Provides the plugin with an opportunity to inspect/modify the unparsed + document. Called after each WSDL/XSD document is loaded. + + @param context: The document context. + @type context: L{DocumentContext} + + """ + pass + + def parsed(self, context): + """ + Suds has parsed a WSDL/XSD document. + + Provides the plugin with an opportunity to inspect/modify the parsed + document. Called after each WSDL/XSD document is parsed. + + @param context: The document context. + @type context: L{DocumentContext} + + """ + pass + + +class MessagePlugin(Plugin): + """Base class for suds I{SOAP message} plugins.""" + + def marshalled(self, context): + """ + Suds is about to send the specified SOAP envelope. + + Provides the plugin with the opportunity to inspect/modify the envelope + Document before it is sent. + + @param context: The send context. + The I{envelope} is the envelope document. + @type context: L{MessageContext} + + """ + pass + + def sending(self, context): + """ + Suds is about to send the specified SOAP envelope. + + Provides the plugin with the opportunity to inspect/modify the message + text before it is sent. + + @param context: The send context. + The I{envelope} is the envelope text. + @type context: L{MessageContext} + + """ + pass + + def received(self, context): + """ + Suds has received the specified reply. + + Provides the plugin with the opportunity to inspect/modify the received + XML text before it is SAX parsed. + + @param context: The reply context. + The I{reply} is the raw text. + @type context: L{MessageContext} + + """ + pass + + def parsed(self, context): + """ + Suds has SAX parsed the received reply. + + Provides the plugin with the opportunity to inspect/modify the SAX + parsed DOM tree for the reply before it is unmarshalled. + + @param context: The reply context. + The I{reply} is DOM tree. + @type context: L{MessageContext} + + """ + pass + + def unmarshalled(self, context): + """ + Suds has unmarshalled the received reply. + + Provides the plugin with the opportunity to inspect/modify the + unmarshalled reply object before it is returned. + + @param context: The reply context. + The I{reply} is unmarshalled suds object. + @type context: L{MessageContext} + + """ + pass + + +class PluginContainer: + """ + Plugin container provides easy method invocation. + + @ivar plugins: A list of plugin objects. + @type plugins: [L{Plugin},] + @cvar ctxclass: A dict of plugin method / context classes. + @type ctxclass: dict + + """ + + domains = { + 'init': (InitContext, InitPlugin), + 'document': (DocumentContext, DocumentPlugin), + 'message': (MessageContext, MessagePlugin)} + + def __init__(self, plugins): + """ + @param plugins: A list of plugin objects. + @type plugins: [L{Plugin},] + + """ + self.plugins = plugins + + def __getattr__(self, name): + domain = self.domains.get(name) + if not domain: + raise Exception, 'plugin domain (%s), invalid' % (name,) + ctx, pclass = domain + plugins = [p for p in self.plugins if isinstance(p, pclass)] + return PluginDomain(ctx, plugins) + + +class PluginDomain: + """ + The plugin domain. + + @ivar ctx: A context. + @type ctx: L{Context} + @ivar plugins: A list of plugins (targets). + @type plugins: list + + """ + + def __init__(self, ctx, plugins): + self.ctx = ctx + self.plugins = plugins + + def __getattr__(self, name): + return Method(name, self) + + +class Method: + """ + Plugin method. + + @ivar name: The method name. + @type name: str + @ivar domain: The plugin domain. + @type domain: L{PluginDomain} + + """ + + def __init__(self, name, domain): + """ + @param name: The method name. + @type name: str + @param domain: A plugin domain. + @type domain: L{PluginDomain} + + """ + self.name = name + self.domain = domain + + def __call__(self, **kwargs): + ctx = self.domain.ctx() + ctx.__dict__.update(kwargs) + for plugin in self.domain.plugins: + method = getattr(plugin, self.name, None) + if method and callable(method): + method(ctx) + return ctx diff --git a/pym/calculate/contrib/suds/plugin.pyc b/pym/calculate/contrib/suds/plugin.pyc new file mode 100644 index 0000000..c3789c4 Binary files /dev/null and b/pym/calculate/contrib/suds/plugin.pyc differ diff --git a/pym/calculate/contrib/suds/properties.py b/pym/calculate/contrib/suds/properties.py new file mode 100644 index 0000000..5907d94 --- /dev/null +++ b/pym/calculate/contrib/suds/properties.py @@ -0,0 +1,539 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Properties classes. +""" + + +class AutoLinker(object): + """ + Base class, provides interface for I{automatic} link + management between a L{Properties} object and the L{Properties} + contained within I{values}. + """ + def updated(self, properties, prev, next): + """ + Notification that a values was updated and the linkage + between the I{properties} contained with I{prev} need to + be relinked to the L{Properties} contained within the + I{next} value. + """ + pass + + +class Link(object): + """ + Property link object. + @ivar endpoints: A tuple of the (2) endpoints of the link. + @type endpoints: tuple(2) + """ + def __init__(self, a, b): + """ + @param a: Property (A) to link. + @type a: L{Property} + @param b: Property (B) to link. + @type b: L{Property} + """ + pA = Endpoint(self, a) + pB = Endpoint(self, b) + self.endpoints = (pA, pB) + self.validate(a, b) + a.links.append(pB) + b.links.append(pA) + + def validate(self, pA, pB): + """ + Validate that the two properties may be linked. + @param pA: Endpoint (A) to link. + @type pA: L{Endpoint} + @param pB: Endpoint (B) to link. + @type pB: L{Endpoint} + @return: self + @rtype: L{Link} + """ + if pA in pB.links or \ + pB in pA.links: + raise Exception, 'Already linked' + dA = pA.domains() + dB = pB.domains() + for d in dA: + if d in dB: + raise Exception, 'Duplicate domain "%s" found' % d + for d in dB: + if d in dA: + raise Exception, 'Duplicate domain "%s" found' % d + kA = pA.keys() + kB = pB.keys() + for k in kA: + if k in kB: + raise Exception, 'Duplicate key %s found' % k + for k in kB: + if k in kA: + raise Exception, 'Duplicate key %s found' % k + return self + + def teardown(self): + """ + Teardown the link. + Removes endpoints from properties I{links} collection. + @return: self + @rtype: L{Link} + """ + pA, pB = self.endpoints + if pA in pB.links: + pB.links.remove(pA) + if pB in pA.links: + pA.links.remove(pB) + return self + + +class Endpoint(object): + """ + Link endpoint (wrapper). + @ivar link: The associated link. + @type link: L{Link} + @ivar target: The properties object. + @type target: L{Property} + """ + def __init__(self, link, target): + self.link = link + self.target = target + + def teardown(self): + return self.link.teardown() + + def __eq__(self, rhs): + return ( self.target == rhs ) + + def __hash__(self): + return hash(self.target) + + def __getattr__(self, name): + return getattr(self.target, name) + + +class Definition: + """ + Property definition. + @ivar name: The property name. + @type name: str + @ivar classes: The (class) list of permitted values + @type classes: tuple + @ivar default: The default value. + @ivar type: any + """ + def __init__(self, name, classes, default, linker=AutoLinker()): + """ + @param name: The property name. + @type name: str + @param classes: The (class) list of permitted values + @type classes: tuple + @param default: The default value. + @type default: any + """ + if not isinstance(classes, (list, tuple)): + classes = (classes,) + self.name = name + self.classes = classes + self.default = default + self.linker = linker + + def nvl(self, value=None): + """ + Convert the I{value} into the default when I{None}. + @param value: The proposed value. + @type value: any + @return: The I{default} when I{value} is I{None}, else I{value}. + @rtype: any + """ + if value is None: + return self.default + else: + return value + + def validate(self, value): + """ + Validate the I{value} is of the correct class. + @param value: The value to validate. + @type value: any + @raise AttributeError: When I{value} is invalid. + """ + if value is None: + return + if len(self.classes) and \ + not isinstance(value, self.classes): + msg = '"%s" must be: %s' % (self.name, self.classes) + raise AttributeError,msg + + + def __repr__(self): + return '%s: %s' % (self.name, str(self)) + + def __str__(self): + s = [] + if len(self.classes): + s.append('classes=%s' % str(self.classes)) + else: + s.append('classes=*') + s.append("default=%s" % str(self.default)) + return ', '.join(s) + + +class Properties: + """ + Represents basic application properties. + Provides basic type validation, default values and + link/synchronization behavior. + @ivar domain: The domain name. + @type domain: str + @ivar definitions: A table of property definitions. + @type definitions: {name: L{Definition}} + @ivar links: A list of linked property objects used to create + a network of properties. + @type links: [L{Property},..] + @ivar defined: A dict of property values. + @type defined: dict + """ + def __init__(self, domain, definitions, kwargs): + """ + @param domain: The property domain name. + @type domain: str + @param definitions: A table of property definitions. + @type definitions: {name: L{Definition}} + @param kwargs: A list of property name/values to set. + @type kwargs: dict + """ + self.definitions = {} + for d in definitions: + self.definitions[d.name] = d + self.domain = domain + self.links = [] + self.defined = {} + self.modified = set() + self.prime() + self.update(kwargs) + + def definition(self, name): + """ + Get the definition for the property I{name}. + @param name: The property I{name} to find the definition for. + @type name: str + @return: The property definition + @rtype: L{Definition} + @raise AttributeError: On not found. + """ + d = self.definitions.get(name) + if d is None: + raise AttributeError(name) + return d + + def update(self, other): + """ + Update the property values as specified by keyword/value. + @param other: An object to update from. + @type other: (dict|L{Properties}) + @return: self + @rtype: L{Properties} + """ + if isinstance(other, Properties): + other = other.defined + for n,v in other.items(): + self.set(n, v) + return self + + def notset(self, name): + """ + Get whether a property has never been set by I{name}. + @param name: A property name. + @type name: str + @return: True if never been set. + @rtype: bool + """ + self.provider(name).__notset(name) + + def set(self, name, value): + """ + Set the I{value} of a property by I{name}. + The value is validated against the definition and set + to the default when I{value} is None. + @param name: The property name. + @type name: str + @param value: The new property value. + @type value: any + @return: self + @rtype: L{Properties} + """ + self.provider(name).__set(name, value) + return self + + def unset(self, name): + """ + Unset a property by I{name}. + @param name: A property name. + @type name: str + @return: self + @rtype: L{Properties} + """ + self.provider(name).__set(name, None) + return self + + def get(self, name, *df): + """ + Get the value of a property by I{name}. + @param name: The property name. + @type name: str + @param df: An optional value to be returned when the value + is not set + @type df: [1]. + @return: The stored value, or I{df[0]} if not set. + @rtype: any + """ + return self.provider(name).__get(name, *df) + + def link(self, other): + """ + Link (associate) this object with anI{other} properties object + to create a network of properties. Links are bidirectional. + @param other: The object to link. + @type other: L{Properties} + @return: self + @rtype: L{Properties} + """ + Link(self, other) + return self + + def unlink(self, *others): + """ + Unlink (disassociate) the specified properties object. + @param others: The list object to unlink. Unspecified means unlink all. + @type others: [L{Properties},..] + @return: self + @rtype: L{Properties} + """ + if not len(others): + others = self.links[:] + for p in self.links[:]: + if p in others: + p.teardown() + return self + + def provider(self, name, history=None): + """ + Find the provider of the property by I{name}. + @param name: The property name. + @type name: str + @param history: A history of nodes checked to prevent + circular hunting. + @type history: [L{Properties},..] + @return: The provider when found. Otherwise, None (when nested) + and I{self} when not nested. + @rtype: L{Properties} + """ + if history is None: + history = [] + history.append(self) + if name in self.definitions: + return self + for x in self.links: + if x in history: + continue + provider = x.provider(name, history) + if provider is not None: + return provider + history.remove(self) + if len(history): + return None + return self + + def keys(self, history=None): + """ + Get the set of I{all} property names. + @param history: A history of nodes checked to prevent + circular hunting. + @type history: [L{Properties},..] + @return: A set of property names. + @rtype: list + """ + if history is None: + history = [] + history.append(self) + keys = set() + keys.update(self.definitions.keys()) + for x in self.links: + if x in history: + continue + keys.update(x.keys(history)) + history.remove(self) + return keys + + def domains(self, history=None): + """ + Get the set of I{all} domain names. + @param history: A history of nodes checked to prevent + circular hunting. + @type history: [L{Properties},..] + @return: A set of domain names. + @rtype: list + """ + if history is None: + history = [] + history.append(self) + domains = set() + domains.add(self.domain) + for x in self.links: + if x in history: + continue + domains.update(x.domains(history)) + history.remove(self) + return domains + + def prime(self): + """ + Prime the stored values based on default values + found in property definitions. + @return: self + @rtype: L{Properties} + """ + for d in self.definitions.values(): + self.defined[d.name] = d.default + return self + + def __notset(self, name): + return not (name in self.modified) + + def __set(self, name, value): + d = self.definition(name) + d.validate(value) + value = d.nvl(value) + prev = self.defined[name] + self.defined[name] = value + self.modified.add(name) + d.linker.updated(self, prev, value) + + def __get(self, name, *df): + d = self.definition(name) + value = self.defined.get(name) + if value == d.default and len(df): + value = df[0] + return value + + def str(self, history): + s = [] + s.append('Definitions:') + for d in self.definitions.values(): + s.append('\t%s' % repr(d)) + s.append('Content:') + for d in self.defined.items(): + s.append('\t%s' % str(d)) + if self not in history: + history.append(self) + s.append('Linked:') + for x in self.links: + s.append(x.str(history)) + history.remove(self) + return '\n'.join(s) + + def __repr__(self): + return str(self) + + def __str__(self): + return self.str([]) + + +class Skin(object): + """ + The meta-programming I{skin} around the L{Properties} object. + @ivar __pts__: The wrapped object. + @type __pts__: L{Properties}. + """ + def __init__(self, domain, definitions, kwargs): + self.__pts__ = Properties(domain, definitions, kwargs) + + def __setattr__(self, name, value): + builtin = name.startswith('__') and name.endswith('__') + if builtin: + self.__dict__[name] = value + return + self.__pts__.set(name, value) + + def __getattr__(self, name): + return self.__pts__.get(name) + + def __repr__(self): + return str(self) + + def __str__(self): + return str(self.__pts__) + + +class Unskin(object): + def __new__(self, *args, **kwargs): + return args[0].__pts__ + + +class Inspector: + """ + Wrapper inspector. + """ + def __init__(self, options): + self.properties = options.__pts__ + + def get(self, name, *df): + """ + Get the value of a property by I{name}. + @param name: The property name. + @type name: str + @param df: An optional value to be returned when the value + is not set + @type df: [1]. + @return: The stored value, or I{df[0]} if not set. + @rtype: any + """ + return self.properties.get(name, *df) + + def update(self, **kwargs): + """ + Update the property values as specified by keyword/value. + @param kwargs: A list of property name/values to set. + @type kwargs: dict + @return: self + @rtype: L{Properties} + """ + return self.properties.update(**kwargs) + + def link(self, other): + """ + Link (associate) this object with anI{other} properties object + to create a network of properties. Links are bidirectional. + @param other: The object to link. + @type other: L{Properties} + @return: self + @rtype: L{Properties} + """ + p = other.__pts__ + return self.properties.link(p) + + def unlink(self, other): + """ + Unlink (disassociate) the specified properties object. + @param other: The object to unlink. + @type other: L{Properties} + @return: self + @rtype: L{Properties} + """ + p = other.__pts__ + return self.properties.unlink(p) diff --git a/pym/calculate/contrib/suds/properties.pyc b/pym/calculate/contrib/suds/properties.pyc new file mode 100644 index 0000000..bfaaf1d Binary files /dev/null and b/pym/calculate/contrib/suds/properties.pyc differ diff --git a/pym/calculate/contrib/suds/reader.py b/pym/calculate/contrib/suds/reader.py new file mode 100644 index 0000000..31c5ee7 --- /dev/null +++ b/pym/calculate/contrib/suds/reader.py @@ -0,0 +1,197 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +XML document reader classes providing integration with the suds library's +caching system. + +""" + +import suds.cache +import suds.plugin +import suds.sax.parser +import suds.transport + +try: + from hashlib import md5 +except ImportError: + # 'hashlib' package added in Python 2.5 so use the now deprecated/removed + # 'md5' package in older Python versions. + from md5 import md5 + + +class Reader(object): + """ + Provides integration with the cache. + + @ivar options: An options object. + @type options: I{Options} + + """ + + def __init__(self, options): + """ + @param options: An options object. + @type options: I{Options} + + """ + self.options = options + self.plugins = suds.plugin.PluginContainer(options.plugins) + + def mangle(self, name, x): + """ + Mangle the name by hashing the I{name} and appending I{x}. + + @return: The mangled name. + @rtype: str + + """ + h = md5(name.encode()).hexdigest() + return '%s-%s' % (h, x) + + +class DefinitionsReader(Reader): + """ + Integrates between the WSDL Definitions object and the object cache. + + @ivar fn: A factory function used to create objects not found in the cache. + @type fn: I{Constructor} + + """ + + def __init__(self, options, fn): + """ + @param options: An options object. + @type options: I{Options} + @param fn: A factory function used to create objects not found in the + cache. + @type fn: I{Constructor} + + """ + super(DefinitionsReader, self).__init__(options) + self.fn = fn + + def open(self, url): + """ + Open a WSDL schema at the specified I{URL}. + + First, the WSDL schema is looked up in the I{object cache}. If not + found, a new one constructed using the I{fn} factory function and the + result is cached for the next open(). + + @param url: A WSDL URL. + @type url: str. + @return: The WSDL object. + @rtype: I{Definitions} + + """ + cache = self.__cache() + id = self.mangle(url, "wsdl") + wsdl = cache.get(id) + if wsdl is None: + wsdl = self.fn(url, self.options) + cache.put(id, wsdl) + else: + # Cached WSDL Definitions objects may have been created with + # different options so we update them here with our current ones. + wsdl.options = self.options + for imp in wsdl.imports: + imp.imported.options = self.options + return wsdl + + def __cache(self): + """ + Get the I{object cache}. + + @return: The I{cache} when I{cachingpolicy} = B{1}. + @rtype: L{Cache} + + """ + if self.options.cachingpolicy == 1: + return self.options.cache + return suds.cache.NoCache() + + +class DocumentReader(Reader): + """Integrates between the SAX L{Parser} and the document cache.""" + + def open(self, url): + """ + Open an XML document at the specified I{URL}. + + First, a preparsed document is looked up in the I{object cache}. If not + found, its content is fetched from an external source and parsed using + the SAX parser. The result is cached for the next open(). + + @param url: A document URL. + @type url: str. + @return: The specified XML document. + @rtype: I{Document} + + """ + cache = self.__cache() + id = self.mangle(url, "document") + xml = cache.get(id) + if xml is None: + xml = self.__fetch(url) + cache.put(id, xml) + self.plugins.document.parsed(url=url, document=xml.root()) + return xml + + def __cache(self): + """ + Get the I{object cache}. + + @return: The I{cache} when I{cachingpolicy} = B{0}. + @rtype: L{Cache} + + """ + if self.options.cachingpolicy == 0: + return self.options.cache + return suds.cache.NoCache() + + def __fetch(self, url): + """ + Fetch document content from an external source. + + The document content will first be looked up in the registered document + store, and if not found there, downloaded using the registered + transport system. + + Before being returned, the fetched document content first gets + processed by all the registered 'loaded' plugins. + + @param url: A document URL. + @type url: str. + @return: A file pointer to the fetched document content. + @rtype: file-like + + """ + content = None + store = self.options.documentStore + if store is not None: + content = store.open(url) + if content is None: + request = suds.transport.Request(url) + request.headers = self.options.headers + fp = self.options.transport.open(request) + try: + content = fp.read() + finally: + fp.close() + ctx = self.plugins.document.loaded(url=url, document=content) + content = ctx.document + sax = suds.sax.parser.Parser() + return sax.parse(string=content) diff --git a/pym/calculate/contrib/suds/reader.pyc b/pym/calculate/contrib/suds/reader.pyc new file mode 100644 index 0000000..f41035e Binary files /dev/null and b/pym/calculate/contrib/suds/reader.pyc differ diff --git a/pym/calculate/contrib/suds/resolver.py b/pym/calculate/contrib/suds/resolver.py new file mode 100644 index 0000000..82014f6 --- /dev/null +++ b/pym/calculate/contrib/suds/resolver.py @@ -0,0 +1,493 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{resolver} module provides a collection of classes that +provide wsdl/xsd named type resolution. +""" + +from suds import * +from suds.sax import splitPrefix, Namespace +from suds.sudsobject import Object +from suds.xsd.query import BlindQuery, TypeQuery, qualify + +import re + +from logging import getLogger +log = getLogger(__name__) + + +class Resolver: + """ + An I{abstract} schema-type resolver. + @ivar schema: A schema object. + @type schema: L{xsd.schema.Schema} + """ + + def __init__(self, schema): + """ + @param schema: A schema object. + @type schema: L{xsd.schema.Schema} + """ + self.schema = schema + + def find(self, name, resolved=True): + """ + Get the definition object for the schema object by name. + @param name: The name of a schema object. + @type name: basestring + @param resolved: A flag indicating that the fully resolved type + should be returned. + @type resolved: boolean + @return: The found schema I{type} + @rtype: L{xsd.sxbase.SchemaObject} + """ + log.debug('searching schema for (%s)', name) + qref = qualify(name, self.schema.root, self.schema.tns) + query = BlindQuery(qref) + result = query.execute(self.schema) + if result is None: + log.error('(%s) not-found', name) + return None + log.debug('found (%s) as (%s)', name, Repr(result)) + if resolved: + result = result.resolve() + return result + + +class PathResolver(Resolver): + """ + Resolves the definition object for the schema type located at a given path. + The path may contain (.) dot notation to specify nested types. + @ivar wsdl: A wsdl object. + @type wsdl: L{wsdl.Definitions} + """ + + def __init__(self, wsdl, ps='.'): + """ + @param wsdl: A schema object. + @type wsdl: L{wsdl.Definitions} + @param ps: The path separator character + @type ps: char + """ + Resolver.__init__(self, wsdl.schema) + self.wsdl = wsdl + self.altp = re.compile('({)(.+)(})(.+)') + self.splitp = re.compile('({.+})*[^\\%s]+' % ps[0]) + + def find(self, path, resolved=True): + """ + Get the definition object for the schema type located at the specified path. + The path may contain (.) dot notation to specify nested types. + Actually, the path separator is usually a (.) but can be redefined + during contruction. + @param path: A (.) separated path to a schema type. + @type path: basestring + @param resolved: A flag indicating that the fully resolved type + should be returned. + @type resolved: boolean + @return: The found schema I{type} + @rtype: L{xsd.sxbase.SchemaObject} + """ + result = None + parts = self.split(path) + try: + result = self.root(parts) + if len(parts) > 1: + result = result.resolve(nobuiltin=True) + result = self.branch(result, parts) + result = self.leaf(result, parts) + if resolved: + result = result.resolve(nobuiltin=True) + except PathResolver.BadPath: + log.error('path: "%s", not-found' % path) + return result + + def root(self, parts): + """ + Find the path root. + @param parts: A list of path parts. + @type parts: [str,..] + @return: The root. + @rtype: L{xsd.sxbase.SchemaObject} + """ + result = None + name = parts[0] + log.debug('searching schema for (%s)', name) + qref = self.qualify(parts[0]) + query = BlindQuery(qref) + result = query.execute(self.schema) + if result is None: + log.error('(%s) not-found', name) + raise PathResolver.BadPath(name) + log.debug('found (%s) as (%s)', name, Repr(result)) + return result + + def branch(self, root, parts): + """ + Traverse the path until a leaf is reached. + @param parts: A list of path parts. + @type parts: [str,..] + @param root: The root. + @type root: L{xsd.sxbase.SchemaObject} + @return: The end of the branch. + @rtype: L{xsd.sxbase.SchemaObject} + """ + result = root + for part in parts[1:-1]: + name = splitPrefix(part)[1] + log.debug('searching parent (%s) for (%s)', Repr(result), name) + result, ancestry = result.get_child(name) + if result is None: + log.error('(%s) not-found', name) + raise PathResolver.BadPath(name) + result = result.resolve(nobuiltin=True) + log.debug('found (%s) as (%s)', name, Repr(result)) + return result + + def leaf(self, parent, parts): + """ + Find the leaf. + @param parts: A list of path parts. + @type parts: [str,..] + @param parent: The leaf's parent. + @type parent: L{xsd.sxbase.SchemaObject} + @return: The leaf. + @rtype: L{xsd.sxbase.SchemaObject} + """ + name = splitPrefix(parts[-1])[1] + if name.startswith('@'): + result, path = parent.get_attribute(name[1:]) + else: + result, ancestry = parent.get_child(name) + if result is None: + raise PathResolver.BadPath(name) + return result + + def qualify(self, name): + """ + Qualify the name as either: + - plain name + - ns prefixed name (eg: ns0:Person) + - fully ns qualified name (eg: {http://myns-uri}Person) + @param name: The name of an object in the schema. + @type name: str + @return: A qualified name. + @rtype: qname + """ + m = self.altp.match(name) + if m is None: + return qualify(name, self.wsdl.root, self.wsdl.tns) + else: + return (m.group(4), m.group(2)) + + def split(self, s): + """ + Split the string on (.) while preserving any (.) inside the + '{}' alternalte syntax for full ns qualification. + @param s: A plain or qualified name. + @type s: str + @return: A list of the name's parts. + @rtype: [str,..] + """ + parts = [] + b = 0 + while 1: + m = self.splitp.match(s, b) + if m is None: + break + b,e = m.span() + parts.append(s[b:e]) + b = e+1 + return parts + + class BadPath(Exception): pass + + +class TreeResolver(Resolver): + """ + The tree resolver is a I{stateful} tree resolver + used to resolve each node in a tree. As such, it mirrors + the tree structure to ensure that nodes are resolved in + context. + @ivar stack: The context stack. + @type stack: list + """ + + def __init__(self, schema): + """ + @param schema: A schema object. + @type schema: L{xsd.schema.Schema} + """ + Resolver.__init__(self, schema) + self.stack = Stack() + + def reset(self): + """ + Reset the resolver's state. + """ + self.stack = Stack() + + def push(self, x): + """ + Push an I{object} onto the stack. + @param x: An object to push. + @type x: L{Frame} + @return: The pushed frame. + @rtype: L{Frame} + """ + if isinstance(x, Frame): + frame = x + else: + frame = Frame(x) + self.stack.append(frame) + log.debug('push: (%s)\n%s', Repr(frame), Repr(self.stack)) + return frame + + def top(self): + """ + Get the I{frame} at the top of the stack. + @return: The top I{frame}, else None. + @rtype: L{Frame} + """ + if len(self.stack): + return self.stack[-1] + else: + return Frame.Empty() + + def pop(self): + """ + Pop the frame at the top of the stack. + @return: The popped frame, else None. + @rtype: L{Frame} + """ + if len(self.stack): + popped = self.stack.pop() + log.debug('pop: (%s)\n%s', Repr(popped), Repr(self.stack)) + return popped + log.debug('stack empty, not-popped') + return None + + def depth(self): + """ + Get the current stack depth. + @return: The current stack depth. + @rtype: int + """ + return len(self.stack) + + def getchild(self, name, parent): + """Get a child by name.""" + log.debug('searching parent (%s) for (%s)', Repr(parent), name) + if name.startswith('@'): + return parent.get_attribute(name[1:]) + return parent.get_child(name) + + +class NodeResolver(TreeResolver): + """ + The node resolver is a I{stateful} XML document resolver + used to resolve each node in a tree. As such, it mirrors + the tree structure to ensure that nodes are resolved in + context. + """ + + def __init__(self, schema): + """ + @param schema: A schema object. + @type schema: L{xsd.schema.Schema} + """ + TreeResolver.__init__(self, schema) + + def find(self, node, resolved=False, push=True): + """ + @param node: An xml node to be resolved. + @type node: L{sax.element.Element} + @param resolved: A flag indicating that the fully resolved type should be + returned. + @type resolved: boolean + @param push: Indicates that the resolved type should be + pushed onto the stack. + @type push: boolean + @return: The found schema I{type} + @rtype: L{xsd.sxbase.SchemaObject} + """ + name = node.name + parent = self.top().resolved + if parent is None: + result, ancestry = self.query(name, node) + else: + result, ancestry = self.getchild(name, parent) + known = self.known(node) + if result is None: + return result + if push: + frame = Frame(result, resolved=known, ancestry=ancestry) + pushed = self.push(frame) + if resolved: + result = result.resolve() + return result + + def findattr(self, name, resolved=True): + """ + Find an attribute type definition. + @param name: An attribute name. + @type name: basestring + @param resolved: A flag indicating that the fully resolved type should be + returned. + @type resolved: boolean + @return: The found schema I{type} + @rtype: L{xsd.sxbase.SchemaObject} + """ + name = '@%s'%name + parent = self.top().resolved + if parent is None: + result, ancestry = self.query(name, node) + else: + result, ancestry = self.getchild(name, parent) + if result is None: + return result + if resolved: + result = result.resolve() + return result + + def query(self, name, node): + """Blindly query the schema by name.""" + log.debug('searching schema for (%s)', name) + qref = qualify(name, node, node.namespace()) + query = BlindQuery(qref) + result = query.execute(self.schema) + return (result, []) + + def known(self, node): + """Resolve type referenced by @xsi:type.""" + ref = node.get('type', Namespace.xsins) + if ref is None: + return None + qref = qualify(ref, node, node.namespace()) + query = BlindQuery(qref) + return query.execute(self.schema) + + +class GraphResolver(TreeResolver): + """ + The graph resolver is a I{stateful} L{Object} graph resolver + used to resolve each node in a tree. As such, it mirrors + the tree structure to ensure that nodes are resolved in + context. + """ + + def __init__(self, schema): + """ + @param schema: A schema object. + @type schema: L{xsd.schema.Schema} + """ + TreeResolver.__init__(self, schema) + + def find(self, name, object, resolved=False, push=True): + """ + @param name: The name of the object to be resolved. + @type name: basestring + @param object: The name's value. + @type object: (any|L{Object}) + @param resolved: A flag indicating that the fully resolved type + should be returned. + @type resolved: boolean + @param push: Indicates that the resolved type should be + pushed onto the stack. + @type push: boolean + @return: The found schema I{type} + @rtype: L{xsd.sxbase.SchemaObject} + """ + known = None + parent = self.top().resolved + if parent is None: + result, ancestry = self.query(name) + else: + result, ancestry = self.getchild(name, parent) + if result is None: + return None + if isinstance(object, Object): + known = self.known(object) + if push: + frame = Frame(result, resolved=known, ancestry=ancestry) + pushed = self.push(frame) + if resolved: + if known is None: + result = result.resolve() + else: + result = known + return result + + def query(self, name): + """Blindly query the schema by name.""" + log.debug('searching schema for (%s)', name) + schema = self.schema + wsdl = self.wsdl() + if wsdl is None: + qref = qualify(name, schema.root, schema.tns) + else: + qref = qualify(name, wsdl.root, wsdl.tns) + query = BlindQuery(qref) + result = query.execute(schema) + return (result, []) + + def wsdl(self): + """Get the wsdl.""" + container = self.schema.container + if container is None: + return None + else: + return container.wsdl + + def known(self, object): + """Get the type specified in the object's metadata.""" + try: + md = object.__metadata__ + known = md.sxtype + return known + except Exception: + pass + + +class Frame: + def __init__(self, type, resolved=None, ancestry=()): + self.type = type + if resolved is None: + resolved = type.resolve() + self.resolved = resolved.resolve() + self.ancestry = ancestry + + def __str__(self): + return '%s\n%s\n%s' % \ + (Repr(self.type), + Repr(self.resolved), + [Repr(t) for t in self.ancestry]) + + class Empty: + def __getattr__(self, name): + if name == 'ancestry': + return () + else: + return None + + +class Stack(list): + def __repr__(self): + result = [] + for item in self: + result.append(repr(item)) + return '\n'.join(result) diff --git a/pym/calculate/contrib/suds/resolver.pyc b/pym/calculate/contrib/suds/resolver.pyc new file mode 100644 index 0000000..1485599 Binary files /dev/null and b/pym/calculate/contrib/suds/resolver.pyc differ diff --git a/pym/calculate/contrib/suds/sax/__init__.py b/pym/calculate/contrib/suds/sax/__init__.py new file mode 100644 index 0000000..c75b320 --- /dev/null +++ b/pym/calculate/contrib/suds/sax/__init__.py @@ -0,0 +1,104 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The sax module contains a collection of classes that provide a (D)ocument +(O)bject (M)odel representation of an XML document. The goal is to provide an +easy, intuitive interface for managing XML documents. Although the term DOM is +used here, this model is B{far} better. + +XML namespaces in suds are represented using a (2) element tuple containing the +prefix and the URI, e.g. I{('tns', 'http://myns')} + +@var encoder: A I{pluggable} XML special character processor used to encode/ + decode strings. +@type encoder: L{Encoder} + +""" + +from suds.sax.enc import Encoder + +# pluggable XML special character encoder. +encoder = Encoder() + + +def splitPrefix(name): + """ + Split the name into a tuple (I{prefix}, I{name}). The first element in the + tuple is I{None} when the name does not have a prefix. + + @param name: A node name containing an optional prefix. + @type name: basestring + @return: A tuple containing the (2) parts of I{name}. + @rtype: (I{prefix}, I{name}) + + """ + if isinstance(name, basestring) and ":" in name: + return tuple(name.split(":", 1)) + return None, name + + +class Namespace: + """XML namespace.""" + + default = (None, None) + xmlns = ("xml", "http://www.w3.org/XML/1998/namespace") + xsdns = ("xs", "http://www.w3.org/2001/XMLSchema") + xsins = ("xsi", "http://www.w3.org/2001/XMLSchema-instance") + all = (xsdns, xsins) + + @classmethod + def create(cls, p=None, u=None): + return p, u + + @classmethod + def none(cls, ns): + return ns == cls.default + + @classmethod + def xsd(cls, ns): + try: + return cls.w3(ns) and ns[1].endswith("XMLSchema") + except Exception: + pass + return False + + @classmethod + def xsi(cls, ns): + try: + return cls.w3(ns) and ns[1].endswith("XMLSchema-instance") + except Exception: + pass + return False + + @classmethod + def xs(cls, ns): + return cls.xsd(ns) or cls.xsi(ns) + + @classmethod + def w3(cls, ns): + try: + return ns[1].startswith("http://www.w3.org") + except Exception: + pass + return False + + @classmethod + def isns(cls, ns): + try: + return isinstance(ns, tuple) and len(ns) == len(cls.default) + except Exception: + pass + return False diff --git a/pym/calculate/contrib/suds/sax/__init__.pyc b/pym/calculate/contrib/suds/sax/__init__.pyc new file mode 100644 index 0000000..e2eb920 Binary files /dev/null and b/pym/calculate/contrib/suds/sax/__init__.pyc differ diff --git a/pym/calculate/contrib/suds/sax/attribute.py b/pym/calculate/contrib/suds/sax/attribute.py new file mode 100644 index 0000000..e8c4247 --- /dev/null +++ b/pym/calculate/contrib/suds/sax/attribute.py @@ -0,0 +1,173 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides XML I{attribute} classes. + +""" + +from suds import UnicodeMixin +from suds.sax import splitPrefix, Namespace +from suds.sax.text import Text + + +class Attribute(UnicodeMixin): + """ + An XML attribute object. + + @ivar parent: The node containing this attribute. + @type parent: L{element.Element} + @ivar prefix: The I{optional} namespace prefix. + @type prefix: basestring + @ivar name: The I{unqualified} attribute name. + @type name: basestring + @ivar value: The attribute's value. + @type value: basestring + + """ + + def __init__(self, name, value=None): + """ + @param name: The attribute's name with I{optional} namespace prefix. + @type name: basestring + @param value: The attribute's value. + @type value: basestring + + """ + self.parent = None + self.prefix, self.name = splitPrefix(name) + self.setValue(value) + + def clone(self, parent=None): + """ + Clone this object. + + @param parent: The parent for the clone. + @type parent: L{element.Element} + @return: A copy of this object assigned to the new parent. + @rtype: L{Attribute} + + """ + a = Attribute(self.qname(), self.value) + a.parent = parent + return a + + def qname(self): + """ + Get this attribute's B{fully} qualified name. + + @return: The fully qualified name. + @rtype: basestring + + """ + if self.prefix is None: + return self.name + return ":".join((self.prefix, self.name)) + + def setValue(self, value): + """ + Set the attribute's value. + + @param value: The new value (may be None). + @type value: basestring + @return: self + @rtype: L{Attribute} + + """ + if isinstance(value, Text): + self.value = value + else: + self.value = Text(value) + return self + + def getValue(self, default=Text("")): + """ + Get the attributes value with optional default. + + @param default: An optional value to return when the attribute's value + has not been set. + @type default: basestring + @return: The attribute's value, or I{default}. + @rtype: L{Text} + + """ + return self.value or default + + def hasText(self): + """ + Get whether the attribute has a non-empty I{text} string value. + + @return: True when has I{text}. + @rtype: boolean + + """ + return bool(self.value) + + def namespace(self): + """ + Get the attribute's namespace. This may either be the namespace defined + by an optional prefix, or the default namespace. + + @return: The attribute's namespace. + @rtype: (I{prefix}, I{name}) + + """ + if self.prefix is None: + return Namespace.default + return self.resolvePrefix(self.prefix) + + def resolvePrefix(self, prefix): + """ + Resolve the specified prefix to a known namespace. + + @param prefix: A declared prefix. + @type prefix: basestring + @return: The namespace mapped to I{prefix}. + @rtype: (I{prefix}, I{name}) + + """ + if self.parent is None: + return Namespace.default + return self.parent.resolvePrefix(prefix) + + def match(self, name=None, ns=None): + """ + Match by (optional) name and/or (optional) namespace. + + @param name: The optional attribute tag name. + @type name: str + @param ns: An optional namespace. + @type ns: (I{prefix}, I{name}) + @return: True if matched. + @rtype: boolean + + """ + byname = name is None or (self.name == name) + byns = ns is None or (self.namespace()[1] == ns[1]) + return byname and byns + + def __eq__(self, rhs): + """Equals operator.""" + return (isinstance(rhs, Attribute) and self.prefix == rhs.name and + self.name == rhs.name) + + def __repr__(self): + """Programmer friendly string representation.""" + return "attr (prefix=%s, name=%s, value=(%s))" % (self.prefix, + self.name, self.value) + + def __unicode__(self): + """XML string representation.""" + return u'%s="%s"' % (self.qname(), self.value and self.value.escape()) diff --git a/pym/calculate/contrib/suds/sax/attribute.pyc b/pym/calculate/contrib/suds/sax/attribute.pyc new file mode 100644 index 0000000..8ff5655 Binary files /dev/null and b/pym/calculate/contrib/suds/sax/attribute.pyc differ diff --git a/pym/calculate/contrib/suds/sax/date.py b/pym/calculate/contrib/suds/sax/date.py new file mode 100644 index 0000000..6418796 --- /dev/null +++ b/pym/calculate/contrib/suds/sax/date.py @@ -0,0 +1,460 @@ +# -*- coding: utf-8 -*- + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) +# based on code by: Glen Walker +# based on code by: Nathan Van Gheem ( vangheem@gmail.com ) + +"""Classes for conversion between XML dates and Python objects.""" + +from suds import UnicodeMixin + +import datetime +import re +import time + + +_SNIPPET_DATE = \ + r"(?P\d{1,})-(?P\d{1,2})-(?P\d{1,2})" +_SNIPPET_TIME = \ + r"(?P\d{1,2}):(?P[0-5]?[0-9]):(?P[0-5]?[0-9])" \ + r"(?:\.(?P\d+))?" +_SNIPPET_ZONE = \ + r"(?:(?P[-+])(?P\d{1,2})" \ + r"(?::(?P[0-5]?[0-9]))?)" \ + r"|(?P[Zz])" + +_PATTERN_DATE = r"^%s(?:%s)?$" % (_SNIPPET_DATE, _SNIPPET_ZONE) +_PATTERN_TIME = r"^%s(?:%s)?$" % (_SNIPPET_TIME, _SNIPPET_ZONE) +_PATTERN_DATETIME = r"^%s[T ]%s(?:%s)?$" % (_SNIPPET_DATE, _SNIPPET_TIME, + _SNIPPET_ZONE) + +_RE_DATE = re.compile(_PATTERN_DATE) +_RE_TIME = re.compile(_PATTERN_TIME) +_RE_DATETIME = re.compile(_PATTERN_DATETIME) + + +class Date(UnicodeMixin): + """ + An XML date object supporting the xsd:date datatype. + + @ivar value: The object value. + @type value: B{datetime}.I{date} + + """ + + def __init__(self, value): + """ + @param value: The date value of the object. + @type value: (datetime.date|str) + @raise ValueError: When I{value} is invalid. + + """ + if isinstance(value, datetime.datetime): + self.value = value.date() + elif isinstance(value, datetime.date): + self.value = value + elif isinstance(value, basestring): + self.value = self.__parse(value) + else: + raise ValueError("invalid type for Date(): %s" % type(value)) + + @staticmethod + def __parse(value): + """ + Parse the string date. + + Supports the subset of ISO8601 used by xsd:date, but is lenient with + what is accepted, handling most reasonable syntax. + + Any timezone is parsed but ignored because a) it is meaningless without + a time and b) B{datetime}.I{date} does not support timezone + information. + + @param value: A date string. + @type value: str + @return: A date object. + @rtype: B{datetime}.I{date} + + """ + match_result = _RE_DATE.match(value) + if match_result is None: + raise ValueError("date data has invalid format '%s'" % (value,)) + return _date_from_match(match_result) + + def __unicode__(self): + return self.value.isoformat() + + +class DateTime(UnicodeMixin): + """ + An XML datetime object supporting the xsd:dateTime datatype. + + @ivar value: The object value. + @type value: B{datetime}.I{datetime} + + """ + + def __init__(self, value): + """ + @param value: The datetime value of the object. + @type value: (datetime.datetime|str) + @raise ValueError: When I{value} is invalid. + + """ + if isinstance(value, datetime.datetime): + self.value = value + elif isinstance(value, basestring): + self.value = self.__parse(value) + else: + raise ValueError("invalid type for DateTime(): %s" % type(value)) + + @staticmethod + def __parse(value): + """ + Parse the string datetime. + + Supports the subset of ISO8601 used by xsd:dateTime, but is lenient + with what is accepted, handling most reasonable syntax. + + Subsecond information is rounded to microseconds due to a restriction + in the python datetime.datetime/time implementation. + + @param value: A datetime string. + @type value: str + @return: A datetime object. + @rtype: B{datetime}.I{datetime} + + """ + match_result = _RE_DATETIME.match(value) + if match_result is None: + raise ValueError("date data has invalid format '%s'" % (value,)) + + date = _date_from_match(match_result) + time, round_up = _time_from_match(match_result) + tzinfo = _tzinfo_from_match(match_result) + + value = datetime.datetime.combine(date, time) + value = value.replace(tzinfo=tzinfo) + if round_up: + value += datetime.timedelta(microseconds=1) + return value + + def __unicode__(self): + return self.value.isoformat() + + +class Time(UnicodeMixin): + """ + An XML time object supporting the xsd:time datatype. + + @ivar value: The object value. + @type value: B{datetime}.I{time} + + """ + + def __init__(self, value): + """ + @param value: The time value of the object. + @type value: (datetime.time|str) + @raise ValueError: When I{value} is invalid. + + """ + if isinstance(value, datetime.time): + self.value = value + elif isinstance(value, basestring): + self.value = self.__parse(value) + else: + raise ValueError("invalid type for Time(): %s" % type(value)) + + @staticmethod + def __parse(value): + """ + Parse the string date. + + Supports the subset of ISO8601 used by xsd:time, but is lenient with + what is accepted, handling most reasonable syntax. + + Subsecond information is rounded to microseconds due to a restriction + in the python datetime.time implementation. + + @param value: A time string. + @type value: str + @return: A time object. + @rtype: B{datetime}.I{time} + + """ + match_result = _RE_TIME.match(value) + if match_result is None: + raise ValueError("date data has invalid format '%s'" % (value,)) + + time, round_up = _time_from_match(match_result) + tzinfo = _tzinfo_from_match(match_result) + if round_up: + time = _bump_up_time_by_microsecond(time) + return time.replace(tzinfo=tzinfo) + + def __unicode__(self): + return self.value.isoformat() + + +class FixedOffsetTimezone(datetime.tzinfo, UnicodeMixin): + """ + A timezone with a fixed offset and no daylight savings adjustment. + + http://docs.python.org/library/datetime.html#datetime.tzinfo + + """ + + def __init__(self, offset): + """ + @param offset: The fixed offset of the timezone. + @type offset: I{int} or B{datetime}.I{timedelta} + + """ + if type(offset) == int: + offset = datetime.timedelta(hours=offset) + elif type(offset) != datetime.timedelta: + raise TypeError("timezone offset must be an int or " + "datetime.timedelta") + if offset.microseconds or (offset.seconds % 60 != 0): + raise ValueError("timezone offset must have minute precision") + self.__offset = offset + + def dst(self, dt): + """ + http://docs.python.org/library/datetime.html#datetime.tzinfo.dst + + """ + return datetime.timedelta(0) + + def utcoffset(self, dt): + """ + http://docs.python.org/library/datetime.html#datetime.tzinfo.utcoffset + + """ + return self.__offset + + def tzname(self, dt): + """ + http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname + + """ + # total_seconds was introduced in Python 2.7 + if hasattr(self.__offset, "total_seconds"): + total_seconds = self.__offset.total_seconds() + else: + total_seconds = (self.__offset.days * 24 * 60 * 60) + \ + (self.__offset.seconds) + + hours = total_seconds // (60 * 60) + total_seconds -= hours * 60 * 60 + + minutes = total_seconds // 60 + total_seconds -= minutes * 60 + + seconds = total_seconds // 1 + total_seconds -= seconds + + if seconds: + return "%+03d:%02d:%02d" % (hours, minutes, seconds) + return "%+03d:%02d" % (hours, minutes) + + def __unicode__(self): + return "FixedOffsetTimezone %s" % (self.tzname(None),) + + +class UtcTimezone(FixedOffsetTimezone): + """ + The UTC timezone. + + http://docs.python.org/library/datetime.html#datetime.tzinfo + + """ + + def __init__(self): + FixedOffsetTimezone.__init__(self, datetime.timedelta(0)) + + def tzname(self, dt): + """ + http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname + + """ + return "UTC" + + def __unicode__(self): + return "UtcTimezone" + + +class LocalTimezone(datetime.tzinfo): + """ + The local timezone of the operating system. + + http://docs.python.org/library/datetime.html#datetime.tzinfo + + """ + + def __init__(self): + self.__offset = datetime.timedelta(seconds=-time.timezone) + self.__dst_offset = None + if time.daylight: + self.__dst_offset = datetime.timedelta(seconds=-time.altzone) + + def dst(self, dt): + """ + http://docs.python.org/library/datetime.html#datetime.tzinfo.dst + + """ + if self.__is_daylight_time(dt): + return self.__dst_offset - self.__offset + return datetime.timedelta(0) + + def tzname(self, dt): + """ + http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname + + """ + if self.__is_daylight_time(dt): + return time.tzname[1] + return time.tzname[0] + + def utcoffset(self, dt): + """ + http://docs.python.org/library/datetime.html#datetime.tzinfo.utcoffset + + """ + if self.__is_daylight_time(dt): + return self.__dst_offset + return self.__offset + + def __is_daylight_time(self, dt): + if not time.daylight: + return False + time_tuple = dt.replace(tzinfo=None).timetuple() + time_tuple = time.localtime(time.mktime(time_tuple)) + return time_tuple.tm_isdst > 0 + + def __unicode__(self): + dt = datetime.datetime.now() + return "LocalTimezone %s offset: %s dst: %s" % (self.tzname(dt), + self.utcoffset(dt), self.dst(dt)) + + +def _bump_up_time_by_microsecond(time): + """ + Helper function bumping up the given datetime.time by a microsecond, + cycling around silently to 00:00:00.0 in case of an overflow. + + @param time: Time object. + @type time: B{datetime}.I{time} + @return: Time object. + @rtype: B{datetime}.I{time} + + """ + dt = datetime.datetime(2000, 1, 1, time.hour, time.minute, + time.second, time.microsecond) + dt += datetime.timedelta(microseconds=1) + return dt.time() + + +def _date_from_match(match_object): + """ + Create a date object from a regular expression match. + + The regular expression match is expected to be from _RE_DATE or + _RE_DATETIME. + + @param match_object: The regular expression match. + @type match_object: B{re}.I{MatchObject} + @return: A date object. + @rtype: B{datetime}.I{date} + + """ + year = int(match_object.group("year")) + month = int(match_object.group("month")) + day = int(match_object.group("day")) + return datetime.date(year, month, day) + + +def _time_from_match(match_object): + """ + Create a time object from a regular expression match. + + Returns the time object and information whether the resulting time should + be bumped up by one microsecond due to microsecond rounding. + + Subsecond information is rounded to microseconds due to a restriction in + the python datetime.datetime/time implementation. + + The regular expression match is expected to be from _RE_DATETIME or + _RE_TIME. + + @param match_object: The regular expression match. + @type match_object: B{re}.I{MatchObject} + @return: Time object + rounding flag. + @rtype: tuple of B{datetime}.I{time} and bool + + """ + hour = int(match_object.group('hour')) + minute = int(match_object.group('minute')) + second = int(match_object.group('second')) + subsecond = match_object.group('subsecond') + + round_up = False + microsecond = 0 + if subsecond: + round_up = len(subsecond) > 6 and int(subsecond[6]) >= 5 + subsecond = subsecond[:6] + microsecond = int(subsecond + "0" * (6 - len(subsecond))) + return datetime.time(hour, minute, second, microsecond), round_up + + +def _tzinfo_from_match(match_object): + """ + Create a timezone information object from a regular expression match. + + The regular expression match is expected to be from _RE_DATE, _RE_DATETIME + or _RE_TIME. + + @param match_object: The regular expression match. + @type match_object: B{re}.I{MatchObject} + @return: A timezone information object. + @rtype: B{datetime}.I{tzinfo} + + """ + tz_utc = match_object.group("tz_utc") + if tz_utc: + return UtcTimezone() + + tz_sign = match_object.group("tz_sign") + if not tz_sign: + return + + h = int(match_object.group("tz_hour") or 0) + m = int(match_object.group("tz_minute") or 0) + if h == 0 and m == 0: + return UtcTimezone() + + # Python limitation - timezone offsets larger than one day (in absolute) + # will cause operations depending on tzinfo.utcoffset() to fail, e.g. + # comparing two timezone aware datetime.datetime/time objects. + if h >= 24: + raise ValueError("timezone indicator too large") + + tz_delta = datetime.timedelta(hours=h, minutes=m) + if tz_sign == "-": + tz_delta *= -1 + return FixedOffsetTimezone(tz_delta) diff --git a/pym/calculate/contrib/suds/sax/date.pyc b/pym/calculate/contrib/suds/sax/date.pyc new file mode 100644 index 0000000..0362118 Binary files /dev/null and b/pym/calculate/contrib/suds/sax/date.pyc differ diff --git a/pym/calculate/contrib/suds/sax/document.py b/pym/calculate/contrib/suds/sax/document.py new file mode 100644 index 0000000..1a70a61 --- /dev/null +++ b/pym/calculate/contrib/suds/sax/document.py @@ -0,0 +1,176 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides XML I{document} classes. +""" + +from suds import * +from suds.sax import * +from suds.sax.element import Element + + +class Document(UnicodeMixin): + """ An XML Document """ + + DECL = '' + + def __init__(self, root=None): + """ + @param root: A root L{Element} or name used to build + the document root element. + @type root: (L{Element}|str|None) + """ + self.__root = None + self.append(root) + + def root(self): + """ + Get the document root element (can be None) + @return: The document root. + @rtype: L{Element} + """ + return self.__root + + def append(self, node): + """ + Append (set) the document root. + @param node: A root L{Element} or name used to build + the document root element. + @type node: (L{Element}|str|None) + """ + if isinstance(node, basestring): + self.__root = Element(node) + return + if isinstance(node, Element): + self.__root = node + return + + def getChild(self, name, ns=None, default=None): + """ + Get a child by (optional) name and/or (optional) namespace. + @param name: The name of a child element (may contain prefix). + @type name: basestring + @param ns: An optional namespace used to match the child. + @type ns: (I{prefix}, I{name}) + @param default: Returned when child not-found. + @type default: L{Element} + @return: The requested child, or I{default} when not-found. + @rtype: L{Element} + """ + if self.__root is None: + return default + if ns is None: + prefix, name = splitPrefix(name) + if prefix is None: + ns = None + else: + ns = self.__root.resolvePrefix(prefix) + if self.__root.match(name, ns): + return self.__root + else: + return default + + def childAtPath(self, path): + """ + Get a child at I{path} where I{path} is a (/) separated + list of element names that are expected to be children. + @param path: A (/) separated list of element names. + @type path: basestring + @return: The leaf node at the end of I{path} + @rtype: L{Element} + """ + if self.__root is None: + return None + if path[0] == '/': + path = path[1:] + path = path.split('/',1) + if self.getChild(path[0]) is None: + return None + if len(path) > 1: + return self.__root.childAtPath(path[1]) + else: + return self.__root + + def childrenAtPath(self, path): + """ + Get a list of children at I{path} where I{path} is a (/) separated + list of element names that are expected to be children. + @param path: A (/) separated list of element names. + @type path: basestring + @return: The collection leaf nodes at the end of I{path} + @rtype: [L{Element},...] + """ + if self.__root is None: + return [] + if path[0] == '/': + path = path[1:] + path = path.split('/',1) + if self.getChild(path[0]) is None: + return [] + if len(path) > 1: + return self.__root.childrenAtPath(path[1]) + else: + return [self.__root,] + + def getChildren(self, name=None, ns=None): + """ + Get a list of children by (optional) name and/or (optional) namespace. + @param name: The name of a child element (may contain prefix). + @type name: basestring + @param ns: An optional namespace used to match the child. + @type ns: (I{prefix}, I{name}) + @return: The list of matching children. + @rtype: [L{Element},...] + """ + if name is None: + matched = self.__root + else: + matched = self.getChild(name, ns) + if matched is None: + return [] + else: + return [matched,] + + def str(self): + """ + Get a string representation of this XML document. + @return: A I{pretty} string. + @rtype: basestring + """ + s = [] + s.append(self.DECL) + root = self.root() + if root is not None: + s.append('\n') + s.append(root.str()) + return ''.join(s) + + def plain(self): + """ + Get a string representation of this XML document. + @return: A I{plain} string. + @rtype: basestring + """ + s = [] + s.append(self.DECL) + root = self.root() + if root is not None: + s.append(root.plain()) + return ''.join(s) + + def __unicode__(self): + return self.str() diff --git a/pym/calculate/contrib/suds/sax/document.pyc b/pym/calculate/contrib/suds/sax/document.pyc new file mode 100644 index 0000000..0932b1d Binary files /dev/null and b/pym/calculate/contrib/suds/sax/document.pyc differ diff --git a/pym/calculate/contrib/suds/sax/element.py b/pym/calculate/contrib/suds/sax/element.py new file mode 100644 index 0000000..06e4e5b --- /dev/null +++ b/pym/calculate/contrib/suds/sax/element.py @@ -0,0 +1,1205 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +XML I{element} classes. + +""" + +from suds import * +from suds.sax import * +from suds.sax.text import Text +from suds.sax.attribute import Attribute + + +class Element(UnicodeMixin): + """ + An XML element object. + + @ivar parent: The node containing this attribute. + @type parent: L{Element} + @ivar prefix: The I{optional} namespace prefix. + @type prefix: basestring + @ivar name: The I{unqualified} name of the attribute. + @type name: basestring + @ivar expns: An explicit namespace (xmlns="..."). + @type expns: (I{prefix}, I{name}) + @ivar nsprefixes: A mapping of prefixes to namespaces. + @type nsprefixes: dict + @ivar attributes: A list of XML attributes. + @type attributes: [I{Attribute},...] + @ivar text: The element's I{text} content. + @type text: basestring + @ivar children: A list of child elements. + @type children: [I{Element},...] + @cvar matcher: A collection of I{lambda} for string matching. + @cvar specialprefixes: A dictionary of builtin-special prefixes. + + """ + + matcher = { + "eq": lambda a, b: a == b, + "startswith": lambda a, b: a.startswith(b), + "endswith": lambda a, b: a.endswith(b), + "contains": lambda a, b: b in a} + + specialprefixes = {Namespace.xmlns[0]: Namespace.xmlns[1]} + + @classmethod + def buildPath(self, parent, path): + """ + Build the specified path as a/b/c. + + Any missing intermediate nodes are built automatically. + + @param parent: A parent element on which the path is built. + @type parent: I{Element} + @param path: A simple path separated by (/). + @type path: basestring + @return: The leaf node of I{path}. + @rtype: L{Element} + + """ + for tag in path.split("/"): + child = parent.getChild(tag) + if child is None: + child = Element(tag, parent) + parent = child + return child + + def __init__(self, name, parent=None, ns=None): + """ + @param name: The element's (tag) name. May contain a prefix. + @type name: basestring + @param parent: An optional parent element. + @type parent: I{Element} + @param ns: An optional namespace. + @type ns: (I{prefix}, I{name}) + + """ + self.rename(name) + self.expns = None + self.nsprefixes = {} + self.attributes = [] + self.text = None + if parent is not None and not isinstance(parent, Element): + raise Exception("parent (%s) not-valid" % + (parent.__class__.__name__,)) + self.parent = parent + self.children = [] + self.applyns(ns) + + def rename(self, name): + """ + Rename the element. + + @param name: A new name for the element. + @type name: basestring + + """ + if name is None: + raise Exception("name (%s) not-valid" % (name,)) + self.prefix, self.name = splitPrefix(name) + + def setPrefix(self, p, u=None): + """ + Set the element namespace prefix. + + @param p: A new prefix for the element. + @type p: basestring + @param u: A namespace URI to be mapped to the prefix. + @type u: basestring + @return: self + @rtype: L{Element} + + """ + self.prefix = p + if p is not None and u is not None: + self.expns = None + self.addPrefix(p, u) + return self + + def qname(self): + """ + Get this element's B{fully} qualified name. + + @return: The fully qualified name. + @rtype: basestring + + """ + if self.prefix is None: + return self.name + return "%s:%s" % (self.prefix, self.name) + + def getRoot(self): + """ + Get the root (top) node of the tree. + + @return: The I{top} node of this tree. + @rtype: I{Element} + + """ + if self.parent is None: + return self + return self.parent.getRoot() + + def clone(self, parent=None): + """ + Deep clone of this element and children. + + @param parent: An optional parent for the copied fragment. + @type parent: I{Element} + @return: A deep copy parented by I{parent} + @rtype: I{Element} + + """ + root = Element(self.qname(), parent, self.namespace()) + for a in self.attributes: + root.append(a.clone(self)) + for c in self.children: + root.append(c.clone(self)) + for ns in self.nsprefixes.items(): + root.addPrefix(ns[0], ns[1]) + return root + + def detach(self): + """ + Detach from parent. + + @return: This element removed from its parent's child list and + I{parent}=I{None}. + @rtype: L{Element} + + """ + if self.parent is not None: + if self in self.parent.children: + self.parent.children.remove(self) + self.parent = None + return self + + def set(self, name, value): + """ + Set an attribute's value. + + @param name: The name of the attribute. + @type name: basestring + @param value: The attribute value. + @type value: basestring + @see: __setitem__() + + """ + attr = self.getAttribute(name) + if attr is None: + attr = Attribute(name, value) + self.append(attr) + else: + attr.setValue(value) + + def unset(self, name): + """ + Unset (remove) an attribute. + + @param name: The attribute name. + @type name: str + @return: self + @rtype: L{Element} + + """ + try: + attr = self.getAttribute(name) + self.attributes.remove(attr) + except Exception: + pass + return self + + def get(self, name, ns=None, default=None): + """ + Get the value of an attribute by name. + + @param name: The name of the attribute. + @type name: basestring + @param ns: The optional attribute's namespace. + @type ns: (I{prefix}, I{name}) + @param default: An optional value to be returned when either the + attribute does not exist or has no value. + @type default: basestring + @return: The attribute's value or I{default}. + @rtype: basestring + @see: __getitem__() + + """ + attr = self.getAttribute(name, ns) + if attr is None or attr.value is None: + return default + return attr.getValue() + + def setText(self, value): + """ + Set the element's L{Text} content. + + @param value: The element's text value. + @type value: basestring + @return: self + @rtype: I{Element} + + """ + if not isinstance(value, Text): + value = Text(value) + self.text = value + return self + + def getText(self, default=None): + """ + Get the element's L{Text} content with optional default. + + @param default: A value to be returned when no text content exists. + @type default: basestring + @return: The text content, or I{default}. + @rtype: L{Text} + + """ + if self.hasText(): + return self.text + return default + + def trim(self): + """ + Trim leading and trailing whitespace. + + @return: self + @rtype: L{Element} + + """ + if self.hasText(): + self.text = self.text.trim() + return self + + def hasText(self): + """ + Get whether the element has non-empty I{text} string. + + @return: True when has I{text}. + @rtype: boolean + + """ + return bool(self.text) + + def namespace(self): + """ + Get the element's namespace. + + @return: The element's namespace by resolving the prefix, the explicit + namespace or the inherited namespace. + @rtype: (I{prefix}, I{name}) + + """ + if self.prefix is None: + return self.defaultNamespace() + return self.resolvePrefix(self.prefix) + + def defaultNamespace(self): + """ + Get the default (unqualified namespace). + + This is the expns of the first node (looking up the tree) that has it + set. + + @return: The namespace of a node when not qualified. + @rtype: (I{prefix}, I{name}) + + """ + p = self + while p is not None: + if p.expns is not None: + return None, p.expns + p = p.parent + return Namespace.default + + def append(self, objects): + """ + Append the specified child based on whether it is an element or an + attribute. + + @param objects: A (single|collection) of attribute(s) or element(s) to + be added as children. + @type objects: (L{Element}|L{Attribute}) + @return: self + @rtype: L{Element} + + """ + if not isinstance(objects, (list, tuple)): + objects = (objects,) + for child in objects: + if isinstance(child, Element): + self.children.append(child) + child.parent = self + continue + if isinstance(child, Attribute): + self.attributes.append(child) + child.parent = self + continue + raise Exception("append %s not-valid" % + (child.__class__.__name__,)) + return self + + def insert(self, objects, index=0): + """ + Insert an L{Element} content at the specified index. + + @param objects: A (single|collection) of attribute(s) or element(s) to + be added as children. + @type objects: (L{Element}|L{Attribute}) + @param index: The position in the list of children to insert. + @type index: int + @return: self + @rtype: L{Element} + + """ + objects = (objects,) + for child in objects: + if not isinstance(child, Element): + raise Exception("append %s not-valid" % + (child.__class__.__name__,)) + self.children.insert(index, child) + child.parent = self + return self + + def remove(self, child): + """ + Remove the specified child element or attribute. + + @param child: A child to remove. + @type child: L{Element}|L{Attribute} + @return: The detached I{child} when I{child} is an element, else None. + @rtype: L{Element}|None + + """ + if isinstance(child, Element): + return child.detach() + if isinstance(child, Attribute): + self.attributes.remove(child) + + def replaceChild(self, child, content): + """ + Replace I{child} with the specified I{content}. + + @param child: A child element. + @type child: L{Element} + @param content: An element or collection of elements. + @type content: L{Element} or [L{Element},...] + + """ + if child not in self.children: + raise Exception("child not-found") + index = self.children.index(child) + self.remove(child) + if not isinstance(content, (list, tuple)): + content = (content,) + for node in content: + self.children.insert(index, node.detach()) + node.parent = self + index += 1 + + def getAttribute(self, name, ns=None, default=None): + """ + Get an attribute by name and (optional) namespace. + + @param name: The name of a contained attribute (may contain prefix). + @type name: basestring + @param ns: An optional namespace + @type ns: (I{prefix}, I{name}) + @param default: Returned when attribute not-found. + @type default: L{Attribute} + @return: The requested attribute object. + @rtype: L{Attribute} + + """ + if ns is None: + prefix, name = splitPrefix(name) + if prefix is not None: + ns = self.resolvePrefix(prefix) + for a in self.attributes: + if a.match(name, ns): + return a + return default + + def getChild(self, name, ns=None, default=None): + """ + Get a child by (optional) name and/or (optional) namespace. + + @param name: The name of a child element (may contain prefix). + @type name: basestring + @param ns: An optional namespace used to match the child. + @type ns: (I{prefix}, I{name}) + @param default: Returned when child not-found. + @type default: L{Element} + @return: The requested child, or I{default} when not-found. + @rtype: L{Element} + + """ + if ns is None: + prefix, name = splitPrefix(name) + if prefix is not None: + ns = self.resolvePrefix(prefix) + for c in self.children: + if c.match(name, ns): + return c + return default + + def childAtPath(self, path): + """ + Get a child at I{path} where I{path} is a (/) separated list of element + names that are expected to be children. + + @param path: A (/) separated list of element names. + @type path: basestring + @return: The leaf node at the end of I{path}. + @rtype: L{Element} + + """ + result = None + node = self + for name in path.split("/"): + if not name: + continue + ns = None + prefix, name = splitPrefix(name) + if prefix is not None: + ns = node.resolvePrefix(prefix) + result = node.getChild(name, ns) + if result is None: + return + node = result + return result + + def childrenAtPath(self, path): + """ + Get a list of children at I{path} where I{path} is a (/) separated list + of element names expected to be children. + + @param path: A (/) separated list of element names. + @type path: basestring + @return: The collection leaf nodes at the end of I{path}. + @rtype: [L{Element},...] + + """ + parts = [p for p in path.split("/") if p] + if len(parts) == 1: + return self.getChildren(path) + return self.__childrenAtPath(parts) + + def getChildren(self, name=None, ns=None): + """ + Get a list of children by (optional) name and/or (optional) namespace. + + @param name: The name of a child element (may contain a prefix). + @type name: basestring + @param ns: An optional namespace used to match the child. + @type ns: (I{prefix}, I{name}) + @return: The list of matching children. + @rtype: [L{Element},...] + + """ + if ns is None: + if name is None: + return self.children + prefix, name = splitPrefix(name) + if prefix is not None: + ns = self.resolvePrefix(prefix) + return [c for c in self.children if c.match(name, ns)] + + def detachChildren(self): + """ + Detach and return this element's children. + + @return: The element's children (detached). + @rtype: [L{Element},...] + + """ + detached = self.children + self.children = [] + for child in detached: + child.parent = None + return detached + + def resolvePrefix(self, prefix, default=Namespace.default): + """ + Resolve the specified prefix to a namespace. The I{nsprefixes} is + searched. If not found, walk up the tree until either resolved or the + top of the tree is reached. Searching up the tree provides for + inherited mappings. + + @param prefix: A namespace prefix to resolve. + @type prefix: basestring + @param default: An optional value to be returned when the prefix cannot + be resolved. + @type default: (I{prefix}, I{URI}) + @return: The namespace that is mapped to I{prefix} in this context. + @rtype: (I{prefix}, I{URI}) + + """ + n = self + while n is not None: + if prefix in n.nsprefixes: + return prefix, n.nsprefixes[prefix] + if prefix in self.specialprefixes: + return prefix, self.specialprefixes[prefix] + n = n.parent + return default + + def addPrefix(self, p, u): + """ + Add or update a prefix mapping. + + @param p: A prefix. + @type p: basestring + @param u: A namespace URI. + @type u: basestring + @return: self + @rtype: L{Element} + """ + self.nsprefixes[p] = u + return self + + def updatePrefix(self, p, u): + """ + Update (redefine) a prefix mapping for the branch. + + @param p: A prefix. + @type p: basestring + @param u: A namespace URI. + @type u: basestring + @return: self + @rtype: L{Element} + @note: This method traverses down the entire branch! + + """ + if p in self.nsprefixes: + self.nsprefixes[p] = u + for c in self.children: + c.updatePrefix(p, u) + return self + + def clearPrefix(self, prefix): + """ + Clear the specified prefix from the prefix mappings. + + @param prefix: A prefix to clear. + @type prefix: basestring + @return: self + @rtype: L{Element} + + """ + if prefix in self.nsprefixes: + del self.nsprefixes[prefix] + return self + + def findPrefix(self, uri, default=None): + """ + Find the first prefix that has been mapped to a namespace URI. + + The local mapping is searched, then walks up the tree until it reaches + the top or finds a match. + + @param uri: A namespace URI. + @type uri: basestring + @param default: A default prefix when not found. + @type default: basestring + @return: A mapped prefix. + @rtype: basestring + + """ + for item in self.nsprefixes.items(): + if item[1] == uri: + return item[0] + for item in self.specialprefixes.items(): + if item[1] == uri: + return item[0] + if self.parent is not None: + return self.parent.findPrefix(uri, default) + return default + + def findPrefixes(self, uri, match="eq"): + """ + Find all prefixes that have been mapped to a namespace URI. + + The local mapping is searched, then walks up the tree until it reaches + the top, collecting all matches. + + @param uri: A namespace URI. + @type uri: basestring + @param match: A matching function L{Element.matcher}. + @type match: basestring + @return: A list of mapped prefixes. + @rtype: [basestring,...] + + """ + result = [] + for item in self.nsprefixes.items(): + if self.matcher[match](item[1], uri): + prefix = item[0] + result.append(prefix) + for item in self.specialprefixes.items(): + if self.matcher[match](item[1], uri): + prefix = item[0] + result.append(prefix) + if self.parent is not None: + result += self.parent.findPrefixes(uri, match) + return result + + def promotePrefixes(self): + """ + Push prefix declarations up the tree as far as possible. + + Prefix mapping are pushed to its parent unless the parent has the + prefix mapped to another URI or the parent has the prefix. This is + propagated up the tree until the top is reached. + + @return: self + @rtype: L{Element} + + """ + for c in self.children: + c.promotePrefixes() + if self.parent is None: + return + for p, u in self.nsprefixes.items(): + if p in self.parent.nsprefixes: + pu = self.parent.nsprefixes[p] + if pu == u: + del self.nsprefixes[p] + continue + if p != self.parent.prefix: + self.parent.nsprefixes[p] = u + del self.nsprefixes[p] + return self + + def refitPrefixes(self): + """ + Refit namespace qualification by replacing prefixes with explicit + namespaces. Also purges prefix mapping table. + + @return: self + @rtype: L{Element} + + """ + for c in self.children: + c.refitPrefixes() + if self.prefix is not None: + ns = self.resolvePrefix(self.prefix) + if ns[1] is not None: + self.expns = ns[1] + self.prefix = None + self.nsprefixes = {} + return self + + def normalizePrefixes(self): + """ + Normalize the namespace prefixes. + + This generates unique prefixes for all namespaces. Then retrofits all + prefixes and prefix mappings. Further, it will retrofix attribute + values that have values containing (:). + + @return: self + @rtype: L{Element} + + """ + PrefixNormalizer.apply(self) + return self + + def isempty(self, content=True): + """ + Get whether the element has no children. + + @param content: Test content (children & text) only. + @type content: boolean + @return: True when element has not children. + @rtype: boolean + + """ + nochildren = not self.children + notext = self.text is None + nocontent = nochildren and notext + if content: + return nocontent + noattrs = not len(self.attributes) + return nocontent and noattrs + + def isnil(self): + """ + Get whether the element is I{nil} as defined by having an + I{xsi:nil="true"} attribute. + + @return: True if I{nil}, else False + @rtype: boolean + + """ + nilattr = self.getAttribute("nil", ns=Namespace.xsins) + return nilattr is not None and (nilattr.getValue().lower() == "true") + + def setnil(self, flag=True): + """ + Set this node to I{nil} as defined by having an I{xsi:nil}=I{flag} + attribute. + + @param flag: A flag indicating how I{xsi:nil} will be set. + @type flag: boolean + @return: self + @rtype: L{Element} + + """ + p, u = Namespace.xsins + name = ":".join((p, "nil")) + self.set(name, str(flag).lower()) + self.addPrefix(p, u) + if flag: + self.text = None + return self + + def applyns(self, ns): + """ + Apply the namespace to this node. + + If the prefix is I{None} then this element's explicit namespace + I{expns} is set to the URI defined by I{ns}. Otherwise, the I{ns} is + simply mapped. + + @param ns: A namespace. + @type ns: (I{prefix}, I{URI}) + + """ + if ns is None: + return + if not isinstance(ns, (list, tuple)): + raise Exception("namespace must be a list or a tuple") + if ns[0] is None: + self.expns = ns[1] + else: + self.prefix = ns[0] + self.nsprefixes[ns[0]] = ns[1] + + def str(self, indent=0): + """ + Get a string representation of this XML fragment. + + @param indent: The indent to be used in formatting the output. + @type indent: int + @return: A I{pretty} string. + @rtype: basestring + + """ + tab = "%*s" % (indent * 3, "") + result = [] + result.append("%s<%s" % (tab, self.qname())) + result.append(self.nsdeclarations()) + for a in self.attributes: + result.append(" %s" % (unicode(a),)) + if self.isempty(): + result.append("/>") + return "".join(result) + result.append(">") + if self.hasText(): + result.append(self.text.escape()) + for c in self.children: + result.append("\n") + result.append(c.str(indent + 1)) + if len(self.children): + result.append("\n%s" % (tab,)) + result.append("" % (self.qname(),)) + return "".join(result) + + def plain(self): + """ + Get a string representation of this XML fragment. + + @return: A I{plain} string. + @rtype: basestring + + """ + result = ["<%s" % (self.qname(),), self.nsdeclarations()] + for a in self.attributes: + result.append(" %s" % (unicode(a),)) + if self.isempty(): + result.append("/>") + return "".join(result) + result.append(">") + if self.hasText(): + result.append(self.text.escape()) + for c in self.children: + result.append(c.plain()) + result.append("" % (self.qname(),)) + return "".join(result) + + def nsdeclarations(self): + """ + Get a string representation for all namespace declarations as xmlns="" + and xmlns:p="". + + @return: A separated list of declarations. + @rtype: basestring + + """ + s = [] + myns = None, self.expns + if self.parent is None: + pns = Namespace.default + else: + pns = None, self.parent.expns + if myns[1] != pns[1]: + if self.expns is not None: + s.append(' xmlns="%s"' % (self.expns,)) + for item in self.nsprefixes.items(): + p, u = item + if self.parent is not None: + ns = self.parent.resolvePrefix(p) + if ns[1] == u: + continue + s.append(' xmlns:%s="%s"' % (p, u)) + return "".join(s) + + def match(self, name=None, ns=None): + """ + Match by (optional) name and/or (optional) namespace. + + @param name: The optional element tag name. + @type name: str + @param ns: An optional namespace. + @type ns: (I{prefix}, I{name}) + @return: True if matched. + @rtype: boolean + + """ + byname = name is None or (self.name == name) + byns = ns is None or (self.namespace()[1] == ns[1]) + return byname and byns + + def branch(self): + """ + Get a flattened representation of the branch. + + @return: A flat list of nodes. + @rtype: [L{Element},...] + + """ + branch = [self] + for c in self.children: + branch += c.branch() + return branch + + def ancestors(self): + """ + Get a list of ancestors. + + @return: A list of ancestors. + @rtype: [L{Element},...] + + """ + ancestors = [] + p = self.parent + while p is not None: + ancestors.append(p) + p = p.parent + return ancestors + + def walk(self, visitor): + """ + Walk the branch and call the visitor function on each node. + + @param visitor: A function. + @type visitor: single argument function + @return: self + @rtype: L{Element} + + """ + visitor(self) + for c in self.children: + c.walk(visitor) + return self + + def prune(self): + """Prune the branch of empty nodes.""" + pruned = [] + for c in self.children: + c.prune() + if c.isempty(False): + pruned.append(c) + for p in pruned: + self.children.remove(p) + + def __childrenAtPath(self, parts): + result = [] + node = self + ancestors = parts[:-1] + leaf = parts[-1] + for name in ancestors: + ns = None + prefix, name = splitPrefix(name) + if prefix is not None: + ns = node.resolvePrefix(prefix) + child = node.getChild(name, ns) + if child is None: + break + node = child + if child is not None: + ns = None + prefix, leaf = splitPrefix(leaf) + if prefix is not None: + ns = node.resolvePrefix(prefix) + result = child.getChildren(leaf) + return result + + def __len__(self): + return len(self.children) + + def __getitem__(self, index): + if isinstance(index, basestring): + return self.get(index) + if index < len(self.children): + return self.children[index] + + def __setitem__(self, index, value): + if isinstance(index, basestring): + self.set(index, value) + else: + if index < len(self.children) and isinstance(value, Element): + self.children.insert(index, value) + + def __eq__(self, rhs): + return (isinstance(rhs, Element) and + self.match(rhs.name, rhs.namespace())) + + def __repr__(self): + return "Element (prefix=%s, name=%s)" % (self.prefix, self.name) + + def __unicode__(self): + return self.str() + + def __iter__(self): + return NodeIterator(self) + + +class NodeIterator: + """ + The L{Element} child node iterator. + + @ivar pos: The current position + @type pos: int + @ivar children: A list of a child nodes. + @type children: [L{Element},...] + + """ + + def __init__(self, parent): + """ + @param parent: An element to iterate. + @type parent: L{Element} + + """ + self.pos = 0 + self.children = parent.children + + def next(self): + """ + Get the next child. + + @return: The next child. + @rtype: L{Element} + @raise StopIterator: At the end. + + """ + try: + child = self.children[self.pos] + self.pos += 1 + return child + except Exception: + raise StopIteration() + + +class PrefixNormalizer: + """ + The prefix normalizer provides namespace prefix normalization. + + @ivar node: A node to normalize. + @type node: L{Element} + @ivar branch: The nodes flattened branch. + @type branch: [L{Element},...] + @ivar namespaces: A unique list of namespaces (URI). + @type namespaces: [str,...] + @ivar prefixes: A reverse dict of prefixes. + @type prefixes: {u: p} + + """ + + @classmethod + def apply(cls, node): + """ + Normalize the specified node. + + @param node: A node to normalize. + @type node: L{Element} + @return: The normalized node. + @rtype: L{Element} + + """ + return PrefixNormalizer(node).refit() + + def __init__(self, node): + """ + @param node: A node to normalize. + @type node: L{Element} + + """ + self.node = node + self.branch = node.branch() + self.namespaces = self.getNamespaces() + self.prefixes = self.genPrefixes() + + def getNamespaces(self): + """ + Get the I{unique} set of namespaces referenced in the branch. + + @return: A set of namespaces. + @rtype: set + + """ + s = set() + for n in self.branch + self.node.ancestors(): + if self.permit(n.expns): + s.add(n.expns) + s = s.union(self.pset(n)) + return s + + def pset(self, n): + """ + Convert the nodes nsprefixes into a set. + + @param n: A node. + @type n: L{Element} + @return: A set of namespaces. + @rtype: set + + """ + s = set() + for ns in n.nsprefixes.items(): + if self.permit(ns): + s.add(ns[1]) + return s + + def genPrefixes(self): + """ + Generate a I{reverse} mapping of unique prefixes for all namespaces. + + @return: A reverse dict of prefixes. + @rtype: {u: p} + + """ + prefixes = {} + n = 0 + for u in self.namespaces: + prefixes[u] = "ns%d" % (n,) + n += 1 + return prefixes + + def refit(self): + """Refit (normalize) the prefixes in the node.""" + self.refitNodes() + self.refitMappings() + + def refitNodes(self): + """Refit (normalize) all of the nodes in the branch.""" + for n in self.branch: + if n.prefix is not None: + ns = n.namespace() + if self.permit(ns): + n.prefix = self.prefixes[ns[1]] + self.refitAttrs(n) + + def refitAttrs(self, n): + """ + Refit (normalize) all of the attributes in the node. + + @param n: A node. + @type n: L{Element} + + """ + for a in n.attributes: + self.refitAddr(a) + + def refitAddr(self, a): + """ + Refit (normalize) the attribute. + + @param a: An attribute. + @type a: L{Attribute} + + """ + if a.prefix is not None: + ns = a.namespace() + if self.permit(ns): + a.prefix = self.prefixes[ns[1]] + self.refitValue(a) + + def refitValue(self, a): + """ + Refit (normalize) the attribute's value. + + @param a: An attribute. + @type a: L{Attribute} + + """ + p, name = splitPrefix(a.getValue()) + if p is None: + return + ns = a.resolvePrefix(p) + if self.permit(ns): + p = self.prefixes[ns[1]] + a.setValue(":".join((p, name))) + + def refitMappings(self): + """Refit (normalize) all of the nsprefix mappings.""" + for n in self.branch: + n.nsprefixes = {} + n = self.node + for u, p in self.prefixes.items(): + n.addPrefix(p, u) + + def permit(self, ns): + """ + Get whether the I{ns} is to be normalized. + + @param ns: A namespace. + @type ns: (p, u) + @return: True if to be included. + @rtype: boolean + + """ + return not self.skip(ns) + + def skip(self, ns): + """ + Get whether the I{ns} is to B{not} be normalized. + + @param ns: A namespace. + @type ns: (p, u) + @return: True if to be skipped. + @rtype: boolean + + """ + return ns is None or ns in ( + Namespace.default, + Namespace.xsdns, + Namespace.xsins, + Namespace.xmlns) diff --git a/pym/calculate/contrib/suds/sax/element.pyc b/pym/calculate/contrib/suds/sax/element.pyc new file mode 100644 index 0000000..7e9ec7a Binary files /dev/null and b/pym/calculate/contrib/suds/sax/element.pyc differ diff --git a/pym/calculate/contrib/suds/sax/enc.py b/pym/calculate/contrib/suds/sax/enc.py new file mode 100644 index 0000000..f750556 --- /dev/null +++ b/pym/calculate/contrib/suds/sax/enc.py @@ -0,0 +1,94 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides XML I{special character} encoder classes. + +""" + +import re + + +class Encoder: + """ + An XML special character encoder/decoder. + + @cvar encodings: A mapping of special characters encoding. + @type encodings: [(str, str),...] + @cvar decodings: A mapping of special characters decoding. + @type decodings: [(str, str),...] + @cvar special: A list of special characters. + @type special: [char,...] + + """ + + encodings = ( + ("&(?!(amp|lt|gt|quot|apos);)", "&"), + ("<", "<"), + (">", ">"), + ('"', """), + ("'", "'")) + decodings = ( + ("<", "<"), + (">", ">"), + (""", '"'), + ("'", "'"), + ("&", "&")) + special = ("&", "<", ">", '"', "'") + + def encode(self, s): + """ + Encode special characters found in string I{s}. + + @param s: A string to encode. + @type s: str + @return: The encoded string. + @rtype: str + + """ + if isinstance(s, basestring) and self.__needs_encoding(s): + for x in self.encodings: + s = re.sub(x[0], x[1], s) + return s + + def decode(self, s): + """ + Decode special characters encodings found in string I{s}. + + @param s: A string to decode. + @type s: str + @return: The decoded string. + @rtype: str + + """ + if isinstance(s, basestring) and "&" in s: + for x in self.decodings: + s = s.replace(x[0], x[1]) + return s + + def __needs_encoding(self, s): + """ + Get whether string I{s} contains special characters. + + @param s: A string to check. + @type s: str + @return: True if needs encoding. + @rtype: boolean + + """ + if isinstance(s, basestring): + for c in self.special: + if c in s: + return True diff --git a/pym/calculate/contrib/suds/sax/enc.pyc b/pym/calculate/contrib/suds/sax/enc.pyc new file mode 100644 index 0000000..897ef97 Binary files /dev/null and b/pym/calculate/contrib/suds/sax/enc.pyc differ diff --git a/pym/calculate/contrib/suds/sax/parser.py b/pym/calculate/contrib/suds/sax/parser.py new file mode 100644 index 0000000..d532c7f --- /dev/null +++ b/pym/calculate/contrib/suds/sax/parser.py @@ -0,0 +1,137 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Classes providing a (D)ocument (O)bject (M)odel representation of an XML +document. + +The goal is to provide an easy, intuitive interface for managing XML documents. +Although the term DOM is used above, this model is B{far} better. + +XML namespaces in suds are represented using a (2) element tuple containing the +prefix and the URI, e.g. I{('tns', 'http://myns')}. + +""" + +import suds +from suds import * +from suds.sax import * +from suds.sax.attribute import Attribute +from suds.sax.document import Document +from suds.sax.element import Element +from suds.sax.text import Text + +import sys +from xml.sax import make_parser, InputSource, ContentHandler +from xml.sax.handler import feature_external_ges + + +class Handler(ContentHandler): + """SAX handler.""" + + def __init__(self): + self.nodes = [Document()] + + def startElement(self, name, attrs): + top = self.top() + node = Element(unicode(name)) + for a in attrs.getNames(): + n = unicode(a) + v = unicode(attrs.getValue(a)) + attribute = Attribute(n, v) + if self.mapPrefix(node, attribute): + continue + node.append(attribute) + node.charbuffer = [] + top.append(node) + self.push(node) + + def mapPrefix(self, node, attribute): + if attribute.name == "xmlns": + if len(attribute.value): + node.expns = unicode(attribute.value) + return True + if attribute.prefix == "xmlns": + prefix = attribute.name + node.nsprefixes[prefix] = unicode(attribute.value) + return True + return False + + def endElement(self, name): + name = unicode(name) + current = self.pop() + if name != current.qname(): + raise Exception("malformed document") + if current.charbuffer: + current.text = Text(u"".join(current.charbuffer)) + del current.charbuffer + if current: + current.trim() + + def characters(self, content): + text = unicode(content) + node = self.top() + node.charbuffer.append(text) + + def push(self, node): + self.nodes.append(node) + return node + + def pop(self): + return self.nodes.pop() + + def top(self): + return self.nodes[-1] + + +class Parser: + """SAX parser.""" + + @classmethod + def saxparser(cls): + p = make_parser() + p.setFeature(feature_external_ges, 0) + h = Handler() + p.setContentHandler(h) + return p, h + + def parse(self, file=None, string=None): + """ + SAX parse XML text. + + @param file: Parse a python I{file-like} object. + @type file: I{file-like} object + @param string: Parse string XML. + @type string: str + @return: Parsed XML document. + @rtype: L{Document} + + """ + if file is None and string is None: + return + timer = suds.metrics.Timer() + timer.start() + source = file + if file is None: + source = InputSource(None) + source.setByteStream(suds.BytesIO(string)) + sax, handler = self.saxparser() + sax.parse(source) + timer.stop() + if file is None: + suds.metrics.log.debug("%s\nsax duration: %s", string, timer) + else: + suds.metrics.log.debug("sax (%s) duration: %s", file, timer) + return handler.nodes[0] diff --git a/pym/calculate/contrib/suds/sax/parser.pyc b/pym/calculate/contrib/suds/sax/parser.pyc new file mode 100644 index 0000000..3516c20 Binary files /dev/null and b/pym/calculate/contrib/suds/sax/parser.pyc differ diff --git a/pym/calculate/contrib/suds/sax/text.py b/pym/calculate/contrib/suds/sax/text.py new file mode 100644 index 0000000..985386e --- /dev/null +++ b/pym/calculate/contrib/suds/sax/text.py @@ -0,0 +1,116 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Contains XML text classes. +""" + +from suds import * +from suds.sax import * + + +class Text(unicode): + """ + An XML text object used to represent text content. + @ivar lang: The (optional) language flag. + @type lang: bool + @ivar escaped: The (optional) XML special character escaped flag. + @type escaped: bool + """ + __slots__ = ('lang', 'escaped') + + @classmethod + def __valid(cls, *args): + return len(args) and args[0] is not None + + def __new__(cls, *args, **kwargs): + if cls.__valid(*args): + lang = kwargs.pop('lang', None) + escaped = kwargs.pop('escaped', False) + result = super(Text, cls).__new__(cls, *args, **kwargs) + result.lang = lang + result.escaped = escaped + else: + result = None + return result + + def escape(self): + """ + Encode (escape) special XML characters. + @return: The text with XML special characters escaped. + @rtype: L{Text} + """ + if not self.escaped: + post = sax.encoder.encode(self) + escaped = ( post != self ) + return Text(post, lang=self.lang, escaped=escaped) + return self + + def unescape(self): + """ + Decode (unescape) special XML characters. + @return: The text with escaped XML special characters decoded. + @rtype: L{Text} + """ + if self.escaped: + post = sax.encoder.decode(self) + return Text(post, lang=self.lang) + return self + + def trim(self): + post = self.strip() + return Text(post, lang=self.lang, escaped=self.escaped) + + def __add__(self, other): + joined = u''.join((self, other)) + result = Text(joined, lang=self.lang, escaped=self.escaped) + if isinstance(other, Text): + result.escaped = self.escaped or other.escaped + return result + + def __repr__(self): + s = [self] + if self.lang is not None: + s.append(' [%s]' % self.lang) + if self.escaped: + s.append(' ') + return ''.join(s) + + def __getstate__(self): + state = {} + for k in self.__slots__: + state[k] = getattr(self, k) + return state + + def __setstate__(self, state): + for k in self.__slots__: + setattr(self, k, state[k]) + + +class Raw(Text): + """ + Raw text which is not XML escaped. + This may include I{string} XML. + """ + def escape(self): + return self + + def unescape(self): + return self + + def __add__(self, other): + joined = u''.join((self, other)) + return Raw(joined, lang=self.lang) diff --git a/pym/calculate/contrib/suds/sax/text.pyc b/pym/calculate/contrib/suds/sax/text.pyc new file mode 100644 index 0000000..f6cac33 Binary files /dev/null and b/pym/calculate/contrib/suds/sax/text.pyc differ diff --git a/pym/calculate/contrib/suds/servicedefinition.py b/pym/calculate/contrib/suds/servicedefinition.py new file mode 100644 index 0000000..44bc52e --- /dev/null +++ b/pym/calculate/contrib/suds/servicedefinition.py @@ -0,0 +1,246 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{service definition} provides a textual representation of a service. +""" + +from suds import * +import suds.metrics as metrics +from suds.sax import Namespace + +from logging import getLogger +log = getLogger(__name__) + + +class ServiceDefinition(UnicodeMixin): + """ + A service definition provides an object used to generate a textual description + of a service. + @ivar wsdl: A wsdl. + @type wsdl: L{wsdl.Definitions} + @ivar sort_namespaces: Whether to sort namespaces on storing them. + @ivar service: The service object. + @type service: L{suds.wsdl.Service} + @ivar ports: A list of port-tuple: (port, [(method-name, pdef)]) + @type ports: [port-tuple,..] + @ivar prefixes: A list of remapped prefixes. + @type prefixes: [(prefix,uri),..] + @ivar types: A list of type definitions + @type types: [I{Type},..] + """ + + def __init__(self, wsdl, service): + """ + @param wsdl: A WSDL object + @type wsdl: L{Definitions} + @param service: A service B{name}. + @type service: str + @param service: A service B{name}. + @param sort_namespaces: Whether to sort namespaces on storing them. + """ + self.wsdl = wsdl + self.service = service + self.ports = [] + self.params = [] + self.types = [] + self.prefixes = [] + self.addports() + self.paramtypes() + self.publictypes() + self.getprefixes() + self.pushprefixes() + + def pushprefixes(self): + """ + Add our prefixes to the WSDL so that when users invoke methods + and reference the prefixes, they will resolve properly. + """ + for ns in self.prefixes: + self.wsdl.root.addPrefix(ns[0], ns[1]) + + def addports(self): + """ + Look through the list of service ports and construct a list of tuples + where each tuple is used to describe a port and its list of methods as: + (port, [method]). Each method is a tuple: (name, [pdef,..]) where each + pdef is a tuple: (param-name, type). + """ + timer = metrics.Timer() + timer.start() + for port in self.service.ports: + p = self.findport(port) + for op in port.binding.operations.values(): + m = p[0].method(op.name) + binding = m.binding.input + method = (m.name, binding.param_defs(m)) + p[1].append(method) + metrics.log.debug("method '%s' created: %s", m.name, timer) + p[1].sort() + timer.stop() + + def findport(self, port): + """ + Find and return a port tuple for the specified port. + Created and added when not found. + @param port: A port. + @type port: I{service.Port} + @return: A port tuple. + @rtype: (port, [method]) + """ + for p in self.ports: + if p[0] == p: return p + p = (port, []) + self.ports.append(p) + return p + + def getprefixes(self): + """Add prefixes for each namespace referenced by parameter types.""" + namespaces = [] + for l in (self.params, self.types): + for t,r in l: + ns = r.namespace() + if ns[1] is None: continue + if ns[1] in namespaces: continue + if Namespace.xs(ns) or Namespace.xsd(ns): + continue + namespaces.append(ns[1]) + if t == r: continue + ns = t.namespace() + if ns[1] is None: continue + if ns[1] in namespaces: continue + namespaces.append(ns[1]) + i = 0 + + if self.wsdl.options.sortNamespaces: + namespaces.sort() + + for u in namespaces: + p = self.nextprefix() + ns = (p, u) + self.prefixes.append(ns) + + def paramtypes(self): + """Get all parameter types.""" + for m in [p[1] for p in self.ports]: + for p in [p[1] for p in m]: + for pd in p: + if pd[1] in self.params: continue + item = (pd[1], pd[1].resolve()) + self.params.append(item) + + def publictypes(self): + """Get all public types.""" + for t in self.wsdl.schema.types.values(): + if t in self.params: continue + if t in self.types: continue + item = (t, t) + self.types.append(item) + self.types.sort(key=lambda x: x[0].name) + + def nextprefix(self): + """ + Get the next available prefix. This means a prefix starting with 'ns' with + a number appended as (ns0, ns1, ..) that is not already defined in the + WSDL document. + """ + used = [ns[0] for ns in self.prefixes] + used += [ns[0] for ns in self.wsdl.root.nsprefixes.items()] + for n in range(0,1024): + p = 'ns%d'%n + if p not in used: + return p + raise Exception('prefixes exhausted') + + def getprefix(self, u): + """ + Get the prefix for the specified namespace (URI) + @param u: A namespace URI. + @type u: str + @return: The namspace. + @rtype: (prefix, uri). + """ + for ns in Namespace.all: + if u == ns[1]: return ns[0] + for ns in self.prefixes: + if u == ns[1]: return ns[0] + raise Exception('ns (%s) not mapped' % u) + + def xlate(self, type): + """ + Get a (namespace) translated I{qualified} name for specified type. + @param type: A schema type. + @type type: I{suds.xsd.sxbasic.SchemaObject} + @return: A translated I{qualified} name. + @rtype: str + """ + resolved = type.resolve() + name = resolved.name + if type.multi_occurrence(): + name += '[]' + ns = resolved.namespace() + if ns[1] == self.wsdl.tns[1]: + return name + prefix = self.getprefix(ns[1]) + return ':'.join((prefix, name)) + + def description(self): + """ + Get a textual description of the service for which this object represents. + @return: A textual description. + @rtype: str + """ + s = [] + indent = (lambda n : '\n%*s'%(n*3,' ')) + s.append('Service ( %s ) tns="%s"' % (self.service.name, self.wsdl.tns[1])) + s.append(indent(1)) + s.append('Prefixes (%d)' % len(self.prefixes)) + for p in self.prefixes: + s.append(indent(2)) + s.append('%s = "%s"' % p) + s.append(indent(1)) + s.append('Ports (%d):' % len(self.ports)) + for p in self.ports: + s.append(indent(2)) + s.append('(%s)' % p[0].name) + s.append(indent(3)) + s.append('Methods (%d):' % len(p[1])) + for m in p[1]: + sig = [] + s.append(indent(4)) + sig.append(m[0]) + sig.append('(') + sig.append(', '.join("%s %s" % (self.xlate(p[1]), p[0]) for p + in m[1])) + sig.append(')') + try: + s.append(''.join(sig)) + except Exception: + pass + s.append(indent(3)) + s.append('Types (%d):' % len(self.types)) + for t in self.types: + s.append(indent(4)) + s.append(self.xlate(t[0])) + s.append('\n\n') + return ''.join(s) + + def __unicode__(self): + try: + return self.description() + except Exception as e: + log.exception(e) + return tostr(e) diff --git a/pym/calculate/contrib/suds/servicedefinition.pyc b/pym/calculate/contrib/suds/servicedefinition.pyc new file mode 100644 index 0000000..29d38ee Binary files /dev/null and b/pym/calculate/contrib/suds/servicedefinition.pyc differ diff --git a/pym/calculate/contrib/suds/serviceproxy.py b/pym/calculate/contrib/suds/serviceproxy.py new file mode 100644 index 0000000..278c189 --- /dev/null +++ b/pym/calculate/contrib/suds/serviceproxy.py @@ -0,0 +1,80 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The service proxy provides access to web services. + +Replaced by: L{client.Client} +""" + +from suds import * +from suds.client import Client + + +class ServiceProxy(UnicodeMixin): + + """ + A lightweight soap based web service proxy. + @ivar __client__: A client. + Everything is delegated to the 2nd generation API. + @type __client__: L{Client} + @note: Deprecated, replaced by L{Client}. + """ + + def __init__(self, url, **kwargs): + """ + @param url: The URL for the WSDL. + @type url: str + @param kwargs: keyword arguments. + @keyword faults: Raise faults raised by server (default:True), + else return tuple from service method invocation as (http code, object). + @type faults: boolean + @keyword proxy: An http proxy to be specified on requests (default:{}). + The proxy is defined as {protocol:proxy,} + @type proxy: dict + """ + client = Client(url, **kwargs) + self.__client__ = client + + def get_instance(self, name): + """ + Get an instance of a WSDL type by name + @param name: The name of a type defined in the WSDL. + @type name: str + @return: An instance on success, else None + @rtype: L{sudsobject.Object} + """ + return self.__client__.factory.create(name) + + def get_enum(self, name): + """ + Get an instance of an enumeration defined in the WSDL by name. + @param name: The name of a enumeration defined in the WSDL. + @type name: str + @return: An instance on success, else None + @rtype: L{sudsobject.Object} + """ + return self.__client__.factory.create(name) + + def __unicode__(self): + return unicode(self.__client__) + + def __getattr__(self, name): + builtin = name.startswith('__') and name.endswith('__') + if builtin: + return self.__dict__[name] + else: + return getattr(self.__client__.service, name) diff --git a/pym/calculate/contrib/suds/serviceproxy.pyc b/pym/calculate/contrib/suds/serviceproxy.pyc new file mode 100644 index 0000000..4676812 Binary files /dev/null and b/pym/calculate/contrib/suds/serviceproxy.pyc differ diff --git a/pym/calculate/contrib/suds/soaparray.py b/pym/calculate/contrib/suds/soaparray.py new file mode 100644 index 0000000..ea04fa7 --- /dev/null +++ b/pym/calculate/contrib/suds/soaparray.py @@ -0,0 +1,71 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{soaparray} module provides XSD extensions for handling +soap (section 5) encoded arrays. +""" + +from suds import * +from logging import getLogger +from suds.xsd.sxbasic import Factory as SXFactory +from suds.xsd.sxbasic import Attribute as SXAttribute + + +class Attribute(SXAttribute): + """ + Represents an XSD that handles special + attributes that are extensions for WSDLs. + @ivar aty: Array type information. + @type aty: The value of wsdl:arrayType. + """ + + def __init__(self, schema, root, aty): + """ + @param aty: Array type information. + @type aty: The value of wsdl:arrayType. + """ + SXAttribute.__init__(self, schema, root) + if aty.endswith('[]'): + self.aty = aty[:-2] + else: + self.aty = aty + + def autoqualified(self): + aqs = SXAttribute.autoqualified(self) + aqs.append('aty') + return aqs + + def description(self): + d = SXAttribute.description(self) + d = d+('aty',) + return d + +# +# Builder function, only builds Attribute when arrayType +# attribute is defined on root. +# +def __fn(x, y): + ns = (None, "http://schemas.xmlsoap.org/wsdl/") + aty = y.get('arrayType', ns=ns) + if aty is None: + return SXAttribute(x, y) + return Attribute(x, y, aty) + +# +# Remap tags to __fn() builder. +# +SXFactory.maptag('attribute', __fn) diff --git a/pym/calculate/contrib/suds/soaparray.pyc b/pym/calculate/contrib/suds/soaparray.pyc new file mode 100644 index 0000000..5644beb Binary files /dev/null and b/pym/calculate/contrib/suds/soaparray.pyc differ diff --git a/pym/calculate/contrib/suds/store.py b/pym/calculate/contrib/suds/store.py new file mode 100644 index 0000000..156e022 --- /dev/null +++ b/pym/calculate/contrib/suds/store.py @@ -0,0 +1,600 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Support for holding XML document content that may then be accessed internally +by suds without having to download them from an external source. Also contains +XML document content to be distributed alongside the suds library. + +""" + +import suds + + +soap5_encoding_schema = suds.byte_str("""\ + + + + + + + 'root' can be used to distinguish serialization roots from other + elements that are present in a serialization but are not roots of + a serialized value graph + + + + + + + + + + + + + Attributes common to all elements that function as accessors or + represent independent (multi-ref) values. The href attribute is + intended to be used in a manner like CONREF. That is, the element + content should be empty iff the href attribute appears + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'Array' is a complex type for accessors identified by position + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""") + + +class DocumentStore(object): + """ + Local XML document content repository. + + Each XML document is identified by its location, i.e. URL without any + protocol identifier. Contained XML documents can be looked up using any URL + referencing that same location. + + """ + + def __init__(self, *args, **kwargs): + self.__store = { + 'schemas.xmlsoap.org/soap/encoding/': soap5_encoding_schema} + self.update = self.__store.update + self.update(*args, **kwargs) + + def __len__(self): + return len(self.__store) + + def open(self, url): + """ + Open a document at the specified URL. + + The document URL's needs not contain a protocol identifier, and if it + does, that protocol identifier is ignored when looking up the store + content. + + Missing documents referenced using the internal 'suds' protocol are + reported by raising an exception. For other protocols, None is returned + instead. + + @param url: A document URL. + @type url: str + @return: Document content or None if not found. + @rtype: bytes + + """ + protocol, location = self.__split(url) + content = self.__find(location) + if protocol == 'suds' and content is None: + raise Exception, 'location "%s" not in document store' % location + return content + + def __find(self, location): + """ + Find the specified location in the store. + + @param location: The I{location} part of a URL. + @type location: str + @return: Document content or None if not found. + @rtype: bytes + + """ + return self.__store.get(location) + + def __split(self, url): + """ + Split the given URL into its I{protocol} & I{location} components. + + @param url: A URL. + @param url: str + @return: (I{protocol}, I{location}) + @rtype: (str, str) + + """ + parts = url.split('://', 1) + if len(parts) == 2: + return parts + return None, url + + +defaultDocumentStore = DocumentStore() diff --git a/pym/calculate/contrib/suds/store.pyc b/pym/calculate/contrib/suds/store.pyc new file mode 100644 index 0000000..28fd9b2 Binary files /dev/null and b/pym/calculate/contrib/suds/store.pyc differ diff --git a/pym/calculate/contrib/suds/sudsobject.py b/pym/calculate/contrib/suds/sudsobject.py new file mode 100644 index 0000000..0c18d5a --- /dev/null +++ b/pym/calculate/contrib/suds/sudsobject.py @@ -0,0 +1,391 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides a collection of suds objects primarily used for highly dynamic +interactions with WSDL/XSD defined types. + +""" + +from suds import * + +from logging import getLogger +log = getLogger(__name__) + + +def items(sobject): + """ + Extract the I{items} from a suds object. + + Much like the items() method works on I{dict}. + + @param sobject: A suds object + @type sobject: L{Object} + @return: A list of items contained in I{sobject}. + @rtype: [(key, value),...] + + """ + for item in sobject: + yield item + + +def asdict(sobject): + """ + Convert a sudsobject into a dictionary. + + @param sobject: A suds object + @type sobject: L{Object} + @return: A python dictionary containing the items contained in I{sobject}. + @rtype: dict + + """ + return dict(items(sobject)) + +def merge(a, b): + """ + Merge all attributes and metadata from I{a} to I{b}. + + @param a: A I{source} object + @type a: L{Object} + @param b: A I{destination} object + @type b: L{Object} + + """ + for item in a: + setattr(b, item[0], item[1]) + b.__metadata__ = b.__metadata__ + return b + +def footprint(sobject): + """ + Get the I{virtual footprint} of the object. + + This is really a count of all the significant value attributes in the + branch. + + @param sobject: A suds object. + @type sobject: L{Object} + @return: The branch footprint. + @rtype: int + + """ + n = 0 + for a in sobject.__keylist__: + v = getattr(sobject, a) + if v is None: + continue + if isinstance(v, Object): + n += footprint(v) + continue + if hasattr(v, "__len__"): + if len(v): + n += 1 + continue + n += 1 + return n + + +class Factory: + + cache = {} + + @classmethod + def subclass(cls, name, bases, dict={}): + if not isinstance(bases, tuple): + bases = (bases,) + # name is of type unicode in python 2 -> not accepted by type() + name = str(name) + key = ".".join((name, str(bases))) + subclass = cls.cache.get(key) + if subclass is None: + subclass = type(name, bases, dict) + cls.cache[key] = subclass + return subclass + + @classmethod + def object(cls, classname=None, dict={}): + if classname is not None: + subclass = cls.subclass(classname, Object) + inst = subclass() + else: + inst = Object() + for a in dict.items(): + setattr(inst, a[0], a[1]) + return inst + + @classmethod + def metadata(cls): + return Metadata() + + @classmethod + def property(cls, name, value=None): + subclass = cls.subclass(name, Property) + return subclass(value) + + +class Object(UnicodeMixin): + + def __init__(self): + self.__keylist__ = [] + self.__printer__ = Printer() + self.__metadata__ = Metadata() + + def __setattr__(self, name, value): + builtin = name.startswith("__") and name.endswith("__") + if not builtin and name not in self.__keylist__: + self.__keylist__.append(name) + self.__dict__[name] = value + + def __delattr__(self, name): + try: + del self.__dict__[name] + builtin = name.startswith("__") and name.endswith("__") + if not builtin: + self.__keylist__.remove(name) + except Exception: + cls = self.__class__.__name__ + raise AttributeError, "%s has no attribute '%s'" % (cls, name) + + def __getitem__(self, name): + if isinstance(name, int): + name = self.__keylist__[int(name)] + return getattr(self, name) + + def __setitem__(self, name, value): + setattr(self, name, value) + + def __iter__(self): + return Iter(self) + + def __len__(self): + return len(self.__keylist__) + + def __contains__(self, name): + return name in self.__keylist__ + + def __repr__(self): + return str(self) + + def __unicode__(self): + return self.__printer__.tostr(self) + + +class Iter: + + def __init__(self, sobject): + self.sobject = sobject + self.keylist = self.__keylist(sobject) + self.index = 0 + + def next(self): + keylist = self.keylist + nkeys = len(self.keylist) + while self.index < nkeys: + k = keylist[self.index] + self.index += 1 + if hasattr(self.sobject, k): + v = getattr(self.sobject, k) + return (k, v) + raise StopIteration() + + def __keylist(self, sobject): + keylist = sobject.__keylist__ + try: + keyset = set(keylist) + ordering = sobject.__metadata__.ordering + ordered = set(ordering) + if not ordered.issuperset(keyset): + log.debug("%s must be superset of %s, ordering ignored", + keylist, ordering) + raise KeyError() + return ordering + except Exception: + return keylist + + def __iter__(self): + return self + + +class Metadata(Object): + def __init__(self): + self.__keylist__ = [] + self.__printer__ = Printer() + + +class Facade(Object): + def __init__(self, name): + Object.__init__(self) + md = self.__metadata__ + md.facade = name + + +class Property(Object): + + def __init__(self, value): + Object.__init__(self) + self.value = value + + def items(self): + for item in self: + if item[0] != "value": + yield item + + def get(self): + return self.value + + def set(self, value): + self.value = value + return self + + +class Printer: + """Pretty printing of a Object object.""" + + @classmethod + def indent(cls, n): + return "%*s" % (n * 3, " ") + + def tostr(self, object, indent=-2): + """Get s string representation of object.""" + history = [] + return self.process(object, history, indent) + + def process(self, object, h, n=0, nl=False): + """Print object using the specified indent (n) and newline (nl).""" + if object is None: + return "None" + if isinstance(object, Object): + if len(object) == 0: + return "" + return self.print_object(object, h, n + 2, nl) + if isinstance(object, dict): + if len(object) == 0: + return "" + return self.print_dictionary(object, h, n + 2, nl) + if isinstance(object, (list, tuple)): + if len(object) == 0: + return "" + return self.print_collection(object, h, n + 2) + if isinstance(object, basestring): + return '"%s"' % (tostr(object),) + return "%s" % (tostr(object),) + + def print_object(self, d, h, n, nl=False): + """Print complex using the specified indent (n) and newline (nl).""" + s = [] + cls = d.__class__ + if d in h: + s.append("(") + s.append(cls.__name__) + s.append(")") + s.append("...") + return "".join(s) + h.append(d) + if nl: + s.append("\n") + s.append(self.indent(n)) + if cls != Object: + s.append("(") + if isinstance(d, Facade): + s.append(d.__metadata__.facade) + else: + s.append(cls.__name__) + s.append(")") + s.append("{") + for item in d: + if self.exclude(d, item): + continue + item = self.unwrap(d, item) + s.append("\n") + s.append(self.indent(n+1)) + if isinstance(item[1], (list,tuple)): + s.append(item[0]) + s.append("[]") + else: + s.append(item[0]) + s.append(" = ") + s.append(self.process(item[1], h, n, True)) + s.append("\n") + s.append(self.indent(n)) + s.append("}") + h.pop() + return "".join(s) + + def print_dictionary(self, d, h, n, nl=False): + """Print complex using the specified indent (n) and newline (nl).""" + if d in h: + return "{}..." + h.append(d) + s = [] + if nl: + s.append("\n") + s.append(self.indent(n)) + s.append("{") + for item in d.items(): + s.append("\n") + s.append(self.indent(n+1)) + if isinstance(item[1], (list,tuple)): + s.append(tostr(item[0])) + s.append("[]") + else: + s.append(tostr(item[0])) + s.append(" = ") + s.append(self.process(item[1], h, n, True)) + s.append("\n") + s.append(self.indent(n)) + s.append("}") + h.pop() + return "".join(s) + + def print_collection(self, c, h, n): + """Print collection using the specified indent (n) and newline (nl).""" + if c in h: + return "[]..." + h.append(c) + s = [] + for item in c: + s.append("\n") + s.append(self.indent(n)) + s.append(self.process(item, h, n - 2)) + s.append(",") + h.pop() + return "".join(s) + + def unwrap(self, d, item): + """Translate (unwrap) using an optional wrapper function.""" + try: + md = d.__metadata__ + pmd = getattr(md, "__print__", None) + if pmd is None: + return item + wrappers = getattr(pmd, "wrappers", {}) + fn = wrappers.get(item[0], lambda x: x) + return (item[0], fn(item[1])) + except Exception: + pass + return item + + def exclude(self, d, item): + """Check metadata for excluded items.""" + try: + md = d.__metadata__ + pmd = getattr(md, "__print__", None) + if pmd is None: + return False + excludes = getattr(pmd, "excludes", []) + return item[0] in excludes + except Exception: + pass + return False diff --git a/pym/calculate/contrib/suds/sudsobject.pyc b/pym/calculate/contrib/suds/sudsobject.pyc new file mode 100644 index 0000000..72326aa Binary files /dev/null and b/pym/calculate/contrib/suds/sudsobject.pyc differ diff --git a/pym/calculate/contrib/suds/transport/__init__.py b/pym/calculate/contrib/suds/transport/__init__.py new file mode 100644 index 0000000..e45b66a --- /dev/null +++ b/pym/calculate/contrib/suds/transport/__init__.py @@ -0,0 +1,166 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Contains transport interface (classes). + +""" + +from suds import UnicodeMixin + +import sys + + +class TransportError(Exception): + def __init__(self, reason, httpcode, fp=None): + Exception.__init__(self, reason) + self.httpcode = httpcode + self.fp = fp + + +class Request(UnicodeMixin): + """ + A transport request. + + Request URL input data may be given as either a byte or a unicode string, + but it may not under any circumstances contain non-ASCII characters. The + URL value is stored as a str value internally. With Python versions prior + to 3.0, str is the byte string type, while with later Python versions it is + the unicode string type. + + @ivar url: The URL for the request. + @type url: str + @ivar message: The optional message to be sent in the request body. + @type message: bytes|None + @type timeout: int|None + @ivar headers: The HTTP headers to be used for the request. + @type headers: dict + + """ + + def __init__(self, url, message=None, timeout=None): + """ + Raised exception in case of detected non-ASCII URL characters may be + either UnicodeEncodeError or UnicodeDecodeError, depending on the used + Python version's str type and the exact value passed as URL input data. + + @param url: The URL for the request. + @type url: bytes|str|unicode + @param message: The optional message to be sent in the request body. + @type message: bytes|None + + """ + self.__set_URL(url) + self.headers = {} + self.message = message + self.timeout = timeout + + def __unicode__(self): + result = [u"URL: %s\nHEADERS: %s" % (self.url, self.headers)] + if self.message is not None: + result.append(u"MESSAGE:") + result.append(self.message.decode("raw_unicode_escape")) + return u"\n".join(result) + + def __set_URL(self, url): + """ + URL is stored as a str internally and must not contain ASCII chars. + + Raised exception in case of detected non-ASCII URL characters may be + either UnicodeEncodeError or UnicodeDecodeError, depending on the used + Python version's str type and the exact value passed as URL input data. + + """ + if isinstance(url, str): + url.encode("ascii") # Check for non-ASCII characters. + self.url = url + elif sys.version_info < (3, 0): + self.url = url.encode("ascii") + else: + self.url = url.decode("ascii") + + +class Reply(UnicodeMixin): + """ + A transport reply. + + @ivar code: The HTTP code returned. + @type code: int + @ivar headers: The HTTP headers included in the received reply. + @type headers: dict + @ivar message: The message received as a reply. + @type message: bytes + + """ + + def __init__(self, code, headers, message): + """ + @param code: The HTTP code returned. + @type code: int + @param headers: The HTTP headers included in the received reply. + @type headers: dict + @param message: The (optional) message received as a reply. + @type message: bytes + + """ + self.code = code + self.headers = headers + self.message = message + + def __unicode__(self): + return u"""\ +CODE: %s +HEADERS: %s +MESSAGE: +%s""" % (self.code, self.headers, self.message.decode("raw_unicode_escape")) + + +class Transport(object): + """The transport I{interface}.""" + + def __init__(self): + from suds.transport.options import Options + self.options = Options() + + def open(self, request): + """ + Open the URL in the specified request. + + @param request: A transport request. + @type request: L{Request} + @return: An input stream. + @rtype: stream + @raise TransportError: On all transport errors. + + """ + raise Exception('not-implemented') + + def send(self, request): + """ + Send SOAP message. Implementations are expected to handle: + - proxies + - I{HTTP} headers + - cookies + - sending message + - brokering exceptions into L{TransportError} + + @param request: A transport request. + @type request: L{Request} + @return: The reply + @rtype: L{Reply} + @raise TransportError: On all transport errors. + + """ + raise Exception('not-implemented') diff --git a/pym/calculate/contrib/suds/transport/__init__.pyc b/pym/calculate/contrib/suds/transport/__init__.pyc new file mode 100644 index 0000000..820f726 Binary files /dev/null and b/pym/calculate/contrib/suds/transport/__init__.pyc differ diff --git a/pym/calculate/contrib/suds/transport/http.py b/pym/calculate/contrib/suds/transport/http.py new file mode 100644 index 0000000..f1dc8ce --- /dev/null +++ b/pym/calculate/contrib/suds/transport/http.py @@ -0,0 +1,249 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Basic HTTP transport implementation classes. + +""" + +from suds.properties import Unskin +from suds.transport import * + +import base64 +from cookielib import CookieJar +import httplib +import socket +import sys +import urllib2 +import gzip +import zlib + +from logging import getLogger +log = getLogger(__name__) + + +class HttpTransport(Transport): + """ + Basic HTTP transport implemented using using urllib2, that provides for + cookies & proxies but no authentication. + + """ + + def __init__(self, **kwargs): + """ + @param kwargs: Keyword arguments. + - B{proxy} - An HTTP proxy to be specified on requests. + The proxy is defined as {protocol:proxy,} + - type: I{dict} + - default: {} + - B{timeout} - Set the URL open timeout (seconds). + - type: I{float} + - default: 90 + + """ + Transport.__init__(self) + Unskin(self.options).update(kwargs) + self.cookiejar = CookieJar() + self.proxy = {} + self.urlopener = None + + def open(self, request): + try: + url = self.__get_request_url_for_urllib(request) + headers = request.headers + log.debug('opening (%s)', url) + u2request = urllib2.Request(url, headers=headers) + self.proxy = self.options.proxy + return self.u2open(u2request) + except urllib2.HTTPError as e: + raise TransportError(str(e), e.code, e.fp) + + def send(self, request): + url = self.__get_request_url_for_urllib(request) + msg = request.message + headers = request.headers + if 'Content-Encoding' in headers: + encoding = headers['Content-Encoding'] + if encoding == 'gzip': + msg = gzip.compress(msg) + elif encoding == 'deflate': + msg = zlib.compress(msg) + try: + u2request = urllib2.Request(url, msg, headers) + self.addcookies(u2request) + self.proxy = self.options.proxy + request.headers.update(u2request.headers) + log.debug('sending:\n%s', request) + fp = self.u2open(u2request, timeout=request.timeout) + self.getcookies(fp, u2request) + headers = fp.headers + if sys.version_info < (3, 0): + headers = headers.dict + message = fp.read() + if 'Content-Encoding' in headers: + encoding = headers['Content-Encoding'] + if encoding == 'gzip': + message = gzip.decompress(message) + elif encoding == 'deflate': + message = zlib.decompress(message) + reply = Reply(httplib.OK, headers, message) + log.debug('received:\n%s', reply) + return reply + except urllib2.HTTPError as e: + if e.code not in (httplib.ACCEPTED, httplib.NO_CONTENT): + raise TransportError(e.msg, e.code, e.fp) + + def addcookies(self, u2request): + """ + Add cookies in the cookiejar to the request. + + @param u2request: A urllib2 request. + @rtype: u2request: urllib2.Request. + + """ + self.cookiejar.add_cookie_header(u2request) + + def getcookies(self, fp, u2request): + """ + Add cookies in the request to the cookiejar. + + @param u2request: A urllib2 request. + @rtype: u2request: urllib2.Request. + + """ + self.cookiejar.extract_cookies(fp, u2request) + + def u2open(self, u2request, timeout=None): + """ + Open a connection. + + @param u2request: A urllib2 request. + @type u2request: urllib2.Request. + @return: The opened file-like urllib2 object. + @rtype: fp + + """ + tm = timeout or self.options.timeout + url = self.u2opener() + if (sys.version_info < (3, 0)) and (self.u2ver() < 2.6): + socket.setdefaulttimeout(tm) + return url.open(u2request) + return url.open(u2request, timeout=tm) + + def u2opener(self): + """ + Create a urllib opener. + + @return: An opener. + @rtype: I{OpenerDirector} + + """ + if self.urlopener is None: + return urllib2.build_opener(*self.u2handlers()) + return self.urlopener + + def u2handlers(self): + """ + Get a collection of urllib handlers. + + @return: A list of handlers to be installed in the opener. + @rtype: [Handler,...] + + """ + return [urllib2.ProxyHandler(self.proxy)] + + def u2ver(self): + """ + Get the major/minor version of the urllib2 lib. + + @return: The urllib2 version. + @rtype: float + + """ + try: + part = urllib2.__version__.split('.', 1) + return float('.'.join(part)) + except Exception as e: + log.exception(e) + return 0 + + def __deepcopy__(self, memo={}): + clone = self.__class__() + p = Unskin(self.options) + cp = Unskin(clone.options) + cp.update(p) + return clone + + @staticmethod + def __get_request_url_for_urllib(request): + """ + Returns the given request's URL, properly encoded for use with urllib. + + We expect that the given request object already verified that the URL + contains ASCII characters only and stored it as a native str value. + + urllib accepts URL information as a native str value and may break + unexpectedly if given URL information in another format. + + Python 3.x httplib.client implementation must be given a unicode string + and not a bytes object and the given string is internally converted to + a bytes object using an explicitly specified ASCII encoding. + + Python 2.7 httplib implementation expects the URL passed to it to not + be a unicode string. If it is, then passing it to the underlying + httplib Request object will cause that object to forcefully convert all + of its data to unicode, assuming that data contains ASCII data only and + raising a UnicodeDecodeError exception if it does not (caused by simple + unicode + string concatenation). + + Python 2.4 httplib implementation does not really care about this as it + does not use the internal optimization present in the Python 2.7 + implementation causing all the requested data to be converted to + unicode. + + """ + assert isinstance(request.url, str) + return request.url + + +class HttpAuthenticated(HttpTransport): + """ + Provides basic HTTP authentication for servers that do not follow the + specified challenge/response model. Appends the I{Authorization} HTTP + header with base64 encoded credentials on every HTTP request. + + """ + + def open(self, request): + self.addcredentials(request) + return HttpTransport.open(self, request) + + def send(self, request): + self.addcredentials(request) + return HttpTransport.send(self, request) + + def addcredentials(self, request): + credentials = self.credentials() + if None not in credentials: + credentials = ':'.join(credentials) + if sys.version_info < (3, 0): + encodedString = base64.b64encode(credentials) + else: + encodedBytes = base64.urlsafe_b64encode(credentials.encode()) + encodedString = encodedBytes.decode() + request.headers['Authorization'] = 'Basic %s' % encodedString + + def credentials(self): + return self.options.username, self.options.password diff --git a/pym/calculate/contrib/suds/transport/http.pyc b/pym/calculate/contrib/suds/transport/http.pyc new file mode 100644 index 0000000..ed26bb8 Binary files /dev/null and b/pym/calculate/contrib/suds/transport/http.pyc differ diff --git a/pym/calculate/contrib/suds/transport/https.py b/pym/calculate/contrib/suds/transport/https.py new file mode 100644 index 0000000..cdff55a --- /dev/null +++ b/pym/calculate/contrib/suds/transport/https.py @@ -0,0 +1,99 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Contains classes for authenticated HTTP transport implementations. + +""" + +from suds.transport import * +from suds.transport.http import HttpTransport + +import urllib2 + + +class HttpAuthenticated(HttpTransport): + """ + Provides basic HTTP authentication that follows the RFC-2617 specification. + + As defined by specifications, credentials are provided to the server upon + request (HTTP/1.0 401 Authorization Required) by the server only. + + @ivar pm: The password manager. + @ivar handler: The authentication handler. + + """ + + def __init__(self, **kwargs): + """ + @param kwargs: Keyword arguments. + - B{proxy} - An HTTP proxy to be specified on requests. + The proxy is defined as {protocol:proxy,} + - type: I{dict} + - default: {} + - B{timeout} - Set the URL open timeout (seconds). + - type: I{float} + - default: 90 + - B{username} - The username used for HTTP authentication. + - type: I{str} + - default: None + - B{password} - The password used for HTTP authentication. + - type: I{str} + - default: None + + """ + HttpTransport.__init__(self, **kwargs) + self.pm = urllib2.HTTPPasswordMgrWithDefaultRealm() + + def open(self, request): + self.addcredentials(request) + return HttpTransport.open(self, request) + + def send(self, request): + self.addcredentials(request) + return HttpTransport.send(self, request) + + def addcredentials(self, request): + credentials = self.credentials() + if None not in credentials: + u = credentials[0] + p = credentials[1] + self.pm.add_password(None, request.url, u, p) + + def credentials(self): + return self.options.username, self.options.password + + def u2handlers(self): + handlers = HttpTransport.u2handlers(self) + handlers.append(urllib2.HTTPBasicAuthHandler(self.pm)) + return handlers + + +class WindowsHttpAuthenticated(HttpAuthenticated): + """ + Provides Windows (NTLM) based HTTP authentication. + + @author: Christopher Bess + + """ + + def u2handlers(self): + try: + from ntlm import HTTPNtlmAuthHandler + except ImportError: + raise Exception("Cannot import python-ntlm module") + handlers = HttpTransport.u2handlers(self) + handlers.append(HTTPNtlmAuthHandler.HTTPNtlmAuthHandler(self.pm)) + return handlers diff --git a/pym/calculate/contrib/suds/transport/https.pyc b/pym/calculate/contrib/suds/transport/https.pyc new file mode 100644 index 0000000..2c4d434 Binary files /dev/null and b/pym/calculate/contrib/suds/transport/https.pyc differ diff --git a/pym/calculate/contrib/suds/transport/options.py b/pym/calculate/contrib/suds/transport/options.py new file mode 100644 index 0000000..f6a071e --- /dev/null +++ b/pym/calculate/contrib/suds/transport/options.py @@ -0,0 +1,58 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Classes modeling transport options. + +""" + + +from suds.transport import * +from suds.properties import * + + +class Options(Skin): + """ + Options: + - B{proxy} - An HTTP proxy to be specified on requests, defined as + {protocol:proxy, ...}. + - type: I{dict} + - default: {} + - B{timeout} - Set the URL open timeout (seconds). + - type: I{float} + - default: 90 + - B{headers} - Extra HTTP headers. + - type: I{dict} + - I{str} B{http} - The I{HTTP} protocol proxy URL. + - I{str} B{https} - The I{HTTPS} protocol proxy URL. + - default: {} + - B{username} - The username used for HTTP authentication. + - type: I{str} + - default: None + - B{password} - The password used for HTTP authentication. + - type: I{str} + - default: None + + """ + + def __init__(self, **kwargs): + domain = __name__ + definitions = [ + Definition('proxy', dict, {}), + Definition('timeout', (int,float), 90), + Definition('headers', dict, {}), + Definition('username', basestring, None), + Definition('password', basestring, None)] + Skin.__init__(self, domain, definitions, kwargs) diff --git a/pym/calculate/contrib/suds/transport/options.pyc b/pym/calculate/contrib/suds/transport/options.pyc new file mode 100644 index 0000000..500e914 Binary files /dev/null and b/pym/calculate/contrib/suds/transport/options.pyc differ diff --git a/pym/calculate/contrib/suds/umx/__init__.py b/pym/calculate/contrib/suds/umx/__init__.py new file mode 100644 index 0000000..ca65cad --- /dev/null +++ b/pym/calculate/contrib/suds/umx/__init__.py @@ -0,0 +1,56 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides modules containing classes to support +unmarshalling (XML). +""" + +from suds.sudsobject import Object + + + +class Content(Object): + """ + @ivar node: The content source node. + @type node: L{sax.element.Element} + @ivar data: The (optional) content data. + @type data: L{Object} + @ivar text: The (optional) content (xml) text. + @type text: basestring + """ + + extensions = [] + + def __init__(self, node, **kwargs): + Object.__init__(self) + self.node = node + self.data = None + self.text = None + for k,v in kwargs.items(): + setattr(self, k, v) + + def __getattr__(self, name): + if name not in self.__dict__: + if name in self.extensions: + v = None + setattr(self, name, v) + else: + raise AttributeError, \ + 'Content has no attribute %s' % name + else: + v = self.__dict__[name] + return v diff --git a/pym/calculate/contrib/suds/umx/__init__.pyc b/pym/calculate/contrib/suds/umx/__init__.pyc new file mode 100644 index 0000000..a9f958c Binary files /dev/null and b/pym/calculate/contrib/suds/umx/__init__.pyc differ diff --git a/pym/calculate/contrib/suds/umx/attrlist.py b/pym/calculate/contrib/suds/umx/attrlist.py new file mode 100644 index 0000000..df8da0b --- /dev/null +++ b/pym/calculate/contrib/suds/umx/attrlist.py @@ -0,0 +1,88 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides filtered attribute list classes. +""" + +from suds import * +from suds.umx import * +from suds.sax import Namespace + + +class AttrList: + """ + A filtered attribute list. + Items are included during iteration if they are in either the (xs) or + (xml) namespaces. + @ivar raw: The I{raw} attribute list. + @type raw: list + """ + def __init__(self, attributes): + """ + @param attributes: A list of attributes + @type attributes: list + """ + self.raw = attributes + + def real(self): + """ + Get list of I{real} attributes which exclude xs and xml attributes. + @return: A list of I{real} attributes. + @rtype: I{generator} + """ + for a in self.raw: + if self.skip(a): continue + yield a + + def rlen(self): + """ + Get the number of I{real} attributes which exclude xs and xml attributes. + @return: A count of I{real} attributes. + @rtype: L{int} + """ + n = 0 + for a in self.real(): + n += 1 + return n + + def lang(self): + """ + Get list of I{filtered} attributes which exclude xs. + @return: A list of I{filtered} attributes. + @rtype: I{generator} + """ + for a in self.raw: + if a.qname() == 'xml:lang': + return a.value + return None + + def skip(self, attr): + """ + Get whether to skip (filter-out) the specified attribute. + @param attr: An attribute. + @type attr: I{Attribute} + @return: True if should be skipped. + @rtype: bool + """ + ns = attr.namespace() + skip = ( + Namespace.xmlns[1], + 'http://schemas.xmlsoap.org/soap/encoding/', + 'http://schemas.xmlsoap.org/soap/envelope/', + 'http://www.w3.org/2003/05/soap-envelope', + ) + return ( Namespace.xs(ns) or ns[1] in skip ) diff --git a/pym/calculate/contrib/suds/umx/attrlist.pyc b/pym/calculate/contrib/suds/umx/attrlist.pyc new file mode 100644 index 0000000..8840971 Binary files /dev/null and b/pym/calculate/contrib/suds/umx/attrlist.pyc differ diff --git a/pym/calculate/contrib/suds/umx/basic.py b/pym/calculate/contrib/suds/umx/basic.py new file mode 100644 index 0000000..888a212 --- /dev/null +++ b/pym/calculate/contrib/suds/umx/basic.py @@ -0,0 +1,41 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides basic unmarshaller classes. +""" + +from logging import getLogger +from suds import * +from suds.umx import * +from suds.umx.core import Core + + +class Basic(Core): + """ + A object builder (unmarshaller). + """ + + def process(self, node): + """ + Process an object graph representation of the xml I{node}. + @param node: An XML tree. + @type node: L{sax.element.Element} + @return: A suds object. + @rtype: L{Object} + """ + content = Content(node) + return Core.process(self, content) diff --git a/pym/calculate/contrib/suds/umx/basic.pyc b/pym/calculate/contrib/suds/umx/basic.pyc new file mode 100644 index 0000000..2465c10 Binary files /dev/null and b/pym/calculate/contrib/suds/umx/basic.pyc differ diff --git a/pym/calculate/contrib/suds/umx/core.py b/pym/calculate/contrib/suds/umx/core.py new file mode 100644 index 0000000..4db8eaa --- /dev/null +++ b/pym/calculate/contrib/suds/umx/core.py @@ -0,0 +1,214 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides base classes for XML->object I{unmarshalling}. +""" + +from suds import * +from suds.umx import * +from suds.umx.attrlist import AttrList +from suds.sax.text import Text +from suds.sudsobject import Factory, merge + + +reserved = {'class':'cls', 'def':'dfn'} + + +class Core: + """ + The abstract XML I{node} unmarshaller. This class provides the + I{core} unmarshalling functionality. + """ + + def process(self, content): + """ + Process an object graph representation of the xml I{node}. + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: A suds object. + @rtype: L{Object} + """ + self.reset() + return self.append(content) + + def append(self, content): + """ + Process the specified node and convert the XML document into + a I{suds} L{object}. + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: A I{append-result} tuple as: (L{Object}, I{value}) + @rtype: I{append-result} + @note: This is not the proper entry point. + @see: L{process()} + """ + self.start(content) + self.append_attributes(content) + self.append_children(content) + self.append_text(content) + self.end(content) + return self.postprocess(content) + + def postprocess(self, content): + """ + Perform final processing of the resulting data structure as follows: + - Mixed values (children and text) will have a result of the I{content.node}. + - Simi-simple values (attributes, no-children and text) will have a result of a + property object. + - Simple values (no-attributes, no-children with text nodes) will have a string + result equal to the value of the content.node.getText(). + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: The post-processed result. + @rtype: I{any} + """ + node = content.node + if len(node.children) and node.hasText(): + return node + attributes = AttrList(node.attributes) + if attributes.rlen() and \ + not len(node.children) and \ + node.hasText(): + p = Factory.property(node.name, node.getText()) + return merge(content.data, p) + if len(content.data): + return content.data + lang = attributes.lang() + if content.node.isnil(): + return None + if not len(node.children) and content.text is None: + if self.nillable(content): + return None + else: + return Text('', lang=lang) + if isinstance(content.text, basestring): + return Text(content.text, lang=lang) + else: + return content.text + + def append_attributes(self, content): + """ + Append attribute nodes into L{Content.data}. + Attributes in the I{schema} or I{xml} namespaces are skipped. + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + attributes = AttrList(content.node.attributes) + for attr in attributes.real(): + name = attr.name + value = attr.value + self.append_attribute(name, value, content) + + def append_attribute(self, name, value, content): + """ + Append an attribute name/value into L{Content.data}. + @param name: The attribute name + @type name: basestring + @param value: The attribute's value + @type value: basestring + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + key = name + key = '_%s' % reserved.get(key, key) + setattr(content.data, key, value) + + def append_children(self, content): + """ + Append child nodes into L{Content.data} + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + for child in content.node: + cont = Content(child) + cval = self.append(cont) + key = reserved.get(child.name, child.name) + if key in content.data: + v = getattr(content.data, key) + if isinstance(v, list): + v.append(cval) + else: + setattr(content.data, key, [v, cval]) + continue + if self.multi_occurrence(cont): + if cval is None: + setattr(content.data, key, []) + else: + setattr(content.data, key, [cval,]) + else: + setattr(content.data, key, cval) + + def append_text(self, content): + """ + Append text nodes into L{Content.data} + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + if content.node.hasText(): + content.text = content.node.getText() + + def reset(self): + pass + + def start(self, content): + """ + Processing on I{node} has started. Build and return + the proper object. + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: A subclass of Object. + @rtype: L{Object} + """ + content.data = Factory.object(content.node.name) + + def end(self, content): + """ + Processing on I{node} has ended. + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + pass + + def single_occurrence(self, content): + """ + Get whether the content has at most a single occurrence (not a list). + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: True if content has at most a single occurrence, else False. + @rtype: boolean + '""" + return not self.multi_occurrence(content) + + def multi_occurrence(self, content): + """ + Get whether the content has more than one occurrence (a list). + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: True if content has more than one occurrence, else False. + @rtype: boolean + '""" + return False + + def nillable(self, content): + """ + Get whether the object is nillable. + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: True if nillable, else False + @rtype: boolean + '""" + return False diff --git a/pym/calculate/contrib/suds/umx/core.pyc b/pym/calculate/contrib/suds/umx/core.pyc new file mode 100644 index 0000000..7259828 Binary files /dev/null and b/pym/calculate/contrib/suds/umx/core.pyc differ diff --git a/pym/calculate/contrib/suds/umx/encoded.py b/pym/calculate/contrib/suds/umx/encoded.py new file mode 100644 index 0000000..bb454e1 --- /dev/null +++ b/pym/calculate/contrib/suds/umx/encoded.py @@ -0,0 +1,126 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides soap encoded unmarshaller classes. +""" + +from suds import * +from suds.umx import * +from suds.umx.typed import Typed +from suds.sax import Namespace + + +# +# Add encoded extensions +# aty = The soap (section 5) encoded array type. +# +Content.extensions.append('aty') + + +class Encoded(Typed): + """ + A SOAP section (5) encoding unmarshaller. + This marshaller supports rpc/encoded soap styles. + """ + + def start(self, content): + # + # Grab the array type and continue + # + self.setaty(content) + Typed.start(self, content) + + def end(self, content): + # + # Squash soap encoded arrays into python lists. This is + # also where we insure that empty arrays are represented + # as empty python lists. + # + aty = content.aty + if aty is not None: + self.promote(content) + return Typed.end(self, content) + + def postprocess(self, content): + # + # Ensure proper rendering of empty arrays. + # + if content.aty is None: + return Typed.postprocess(self, content) + else: + return content.data + + def setaty(self, content): + """ + Grab the (aty) soap-enc:arrayType and attach it to the + content for proper array processing later in end(). + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: self + @rtype: L{Encoded} + """ + name = 'arrayType' + ns = (None, 'http://schemas.xmlsoap.org/soap/encoding/') + aty = content.node.get(name, ns) + if aty is not None: + content.aty = aty + parts = aty.split('[') + ref = parts[0] + if len(parts) == 2: + self.applyaty(content, ref) + else: + pass # (2) dimensional array + return self + + def applyaty(self, content, xty): + """ + Apply the type referenced in the I{arrayType} to the content + (child nodes) of the array. Each element (node) in the array + that does not have an explicit xsi:type attribute is given one + based on the I{arrayType}. + @param content: An array content. + @type content: L{Content} + @param xty: The XSI type reference. + @type xty: str + @return: self + @rtype: L{Encoded} + """ + name = 'type' + ns = Namespace.xsins + parent = content.node + for child in parent.getChildren(): + ref = child.get(name, ns) + if ref is None: + parent.addPrefix(ns[0], ns[1]) + attr = ':'.join((ns[0], name)) + child.set(attr, xty) + return self + + def promote(self, content): + """ + Promote (replace) the content.data with the first attribute + of the current content.data that is a I{list}. Note: the + content.data may be empty or contain only _x attributes. + In either case, the content.data is assigned an empty list. + @param content: An array content. + @type content: L{Content} + """ + for n,v in content.data: + if isinstance(v, list): + content.data = v + return + content.data = [] diff --git a/pym/calculate/contrib/suds/umx/encoded.pyc b/pym/calculate/contrib/suds/umx/encoded.pyc new file mode 100644 index 0000000..3100629 Binary files /dev/null and b/pym/calculate/contrib/suds/umx/encoded.pyc differ diff --git a/pym/calculate/contrib/suds/umx/typed.py b/pym/calculate/contrib/suds/umx/typed.py new file mode 100644 index 0000000..f28cb92 --- /dev/null +++ b/pym/calculate/contrib/suds/umx/typed.py @@ -0,0 +1,140 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides typed unmarshaller classes. +""" + +from suds import * +from suds.umx import * +from suds.umx.core import Core +from suds.resolver import NodeResolver, Frame +from suds.sudsobject import Factory + +from logging import getLogger +log = getLogger(__name__) + + +# +# Add typed extensions +# type = The expected xsd type +# real = The 'true' XSD type +# +Content.extensions.append('type') +Content.extensions.append('real') + + +class Typed(Core): + """ + A I{typed} XML unmarshaller + @ivar resolver: A schema type resolver. + @type resolver: L{NodeResolver} + """ + + def __init__(self, schema): + """ + @param schema: A schema object. + @type schema: L{xsd.schema.Schema} + """ + self.resolver = NodeResolver(schema) + + def process(self, node, type): + """ + Process an object graph representation of the xml L{node}. + @param node: An XML tree. + @type node: L{sax.element.Element} + @param type: The I{optional} schema type. + @type type: L{xsd.sxbase.SchemaObject} + @return: A suds object. + @rtype: L{Object} + """ + content = Content(node) + content.type = type + return Core.process(self, content) + + def reset(self): + log.debug('reset') + self.resolver.reset() + + def start(self, content): + # + # Resolve to the schema type; build an object and setup metadata. + # + if content.type is None: + found = self.resolver.find(content.node) + if found is None: + log.error(self.resolver.schema) + raise TypeNotFound(content.node.qname()) + content.type = found + else: + known = self.resolver.known(content.node) + frame = Frame(content.type, resolved=known) + self.resolver.push(frame) + real = self.resolver.top().resolved + content.real = real + cls_name = real.name + if cls_name is None: + cls_name = content.node.name + content.data = Factory.object(cls_name) + md = content.data.__metadata__ + md.sxtype = real + + def end(self, content): + self.resolver.pop() + + def multi_occurrence(self, content): + return content.type.multi_occurrence() + + def nillable(self, content): + resolved = content.type.resolve() + return ( content.type.nillable or \ + (resolved.builtin() and resolved.nillable ) ) + + def append_attribute(self, name, value, content): + """ + Append an attribute name/value into L{Content.data}. + @param name: The attribute name + @type name: basestring + @param value: The attribute's value + @type value: basestring + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + type = self.resolver.findattr(name) + if type is None: + log.warning('attribute (%s) type, not-found', name) + else: + value = self.translated(value, type) + Core.append_attribute(self, name, value, content) + + def append_text(self, content): + """ + Append text nodes into L{Content.data} + Here is where the I{true} type is used to translate the value + into the proper python type. + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + Core.append_text(self, content) + known = self.resolver.top().resolved + content.text = self.translated(content.text, known) + + def translated(self, value, type): + """ translate using the schema type """ + if value is not None: + resolved = type.resolve() + return resolved.translate(value) + return value diff --git a/pym/calculate/contrib/suds/umx/typed.pyc b/pym/calculate/contrib/suds/umx/typed.pyc new file mode 100644 index 0000000..0558cd2 Binary files /dev/null and b/pym/calculate/contrib/suds/umx/typed.pyc differ diff --git a/pym/calculate/contrib/suds/version.py b/pym/calculate/contrib/suds/version.py new file mode 100644 index 0000000..9fdfb67 --- /dev/null +++ b/pym/calculate/contrib/suds/version.py @@ -0,0 +1,26 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +""" +Module containing the library's version information. + + This version information has been extracted into a separate file so it can be +read from the setup.py script without having to import the suds package itself. +See the setup.py script for more detailed information. + +""" + +__version__ = "0.8.5" +__build__ = "" diff --git a/pym/calculate/contrib/suds/version.pyc b/pym/calculate/contrib/suds/version.pyc new file mode 100644 index 0000000..1029c48 Binary files /dev/null and b/pym/calculate/contrib/suds/version.pyc differ diff --git a/pym/calculate/contrib/suds/wsdl.py b/pym/calculate/contrib/suds/wsdl.py new file mode 100644 index 0000000..f4fb7da --- /dev/null +++ b/pym/calculate/contrib/suds/wsdl.py @@ -0,0 +1,1005 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{wsdl} module provides an objectification of the WSDL. + +The primary class is I{Definitions}, representing the root element found in a +WSDL schema document. + +""" + +from suds import * +from suds.bindings.document import Document +from suds.bindings.rpc import RPC, Encoded +from suds.reader import DocumentReader +from suds.sax.element import Element +from suds.sudsobject import Object, Facade, Metadata +from suds.xsd import qualify, Namespace +from suds.xsd.query import ElementQuery +from suds.xsd.schema import Schema, SchemaCollection + +import re +import soaparray +from urlparse import urljoin + +from logging import getLogger +log = getLogger(__name__) + + +wsdlns = (None, "http://schemas.xmlsoap.org/wsdl/") +soapns = (None, "http://schemas.xmlsoap.org/wsdl/soap/") +soap12ns = (None, "http://schemas.xmlsoap.org/wsdl/soap12/") + + +class WObject(Object): + """ + Base object for WSDL types. + + @ivar root: The XML I{root} element. + @type root: L{Element} + + """ + + def __init__(self, root): + """ + @param root: An XML root element. + @type root: L{Element} + + """ + Object.__init__(self) + self.root = root + pmd = Metadata() + pmd.excludes = ["root"] + pmd.wrappers = dict(qname=repr) + self.__metadata__.__print__ = pmd + self.__resolved = False + + def resolve(self, definitions): + """ + Resolve named references to other WSDL objects. + + Can be safely called multiple times. + + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + if not self.__resolved: + self.do_resolve(definitions) + self.__resolved = True + + def do_resolve(self, definitions): + """ + Internal worker resolving named references to other WSDL objects. + + May only be called once per instance. + + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + pass + + +class NamedObject(WObject): + """ + A B{named} WSDL object. + + @ivar name: The name of the object. + @type name: str + @ivar qname: The I{qualified} name of the object. + @type qname: (name, I{namespace-uri}). + + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + WObject.__init__(self, root) + self.name = root.get("name") + self.qname = (self.name, definitions.tns[1]) + pmd = self.__metadata__.__print__ + pmd.wrappers["qname"] = repr + + +class Definitions(WObject): + """ + I{Root} container for all the WSDL objects defined by . + + @ivar id: The object id. + @type id: str + @ivar options: An options dictionary. + @type options: L{options.Options} + @ivar url: The URL used to load the object. + @type url: str + @ivar tns: The target namespace for the WSDL. + @type tns: str + @ivar schema: The collective WSDL schema object. + @type schema: L{SchemaCollection} + @ivar children: The raw list of child objects. + @type children: [L{WObject},...] + @ivar imports: The list of L{Import} children. + @type imports: [L{Import},...] + @ivar messages: The dictionary of L{Message} children keyed by I{qname}. + @type messages: [L{Message},...] + @ivar port_types: The dictionary of L{PortType} children keyed by I{qname}. + @type port_types: [L{PortType},...] + @ivar bindings: The dictionary of L{Binding} children keyed by I{qname}. + @type bindings: [L{Binding},...] + @ivar service: The service object. + @type service: L{Service} + + """ + + Tag = "definitions" + + def __init__(self, url, options, imported_definitions=None): + """ + @param url: A URL to the WSDL. + @type url: str + @param options: An options dictionary. + @type options: L{options.Options} + + """ + log.debug("reading WSDL at: %s ...", url) + reader = DocumentReader(options) + d = reader.open(url) + root = d.root() + WObject.__init__(self, root) + self.id = objid(self) + self.options = options + self.url = url + self.tns = self.mktns(root) + self.types = [] + self.schema = None + self.children = [] + self.imports = [] + self.messages = {} + self.port_types = {} + self.bindings = {} + self.services = [] + self.add_children(self.root) + self.children.sort() + pmd = self.__metadata__.__print__ + pmd.excludes.append("children") + pmd.excludes.append("wsdl") + pmd.wrappers["schema"] = repr + if imported_definitions is None: + imported_definitions = {} + imported_definitions[url] = self + self.open_imports(imported_definitions) + self.resolve() + self.build_schema() + self.set_wrapped() + for s in self.services: + self.add_methods(s) + log.debug("WSDL at '%s' loaded:\n%s", url, self) + + def mktns(self, root): + """Get/create the target namespace.""" + tns = root.get("targetNamespace") + prefix = root.findPrefix(tns) + if prefix is None: + log.debug("warning: tns (%s), not mapped to prefix", tns) + prefix = "tns" + return (prefix, tns) + + def add_children(self, root): + """Add child objects using the factory.""" + for c in root.getChildren(ns=wsdlns): + child = Factory.create(c, self) + if child is None: continue + self.children.append(child) + if isinstance(child, Import): + self.imports.append(child) + continue + if isinstance(child, Types): + self.types.append(child) + continue + if isinstance(child, Message): + self.messages[child.qname] = child + continue + if isinstance(child, PortType): + self.port_types[child.qname] = child + continue + if isinstance(child, Binding): + self.bindings[child.qname] = child + continue + if isinstance(child, Service): + self.services.append(child) + continue + + def open_imports(self, imported_definitions): + """Import the I{imported} WSDLs.""" + for imp in self.imports: + imp.load(self, imported_definitions) + + def resolve(self): + """Tell all children to resolve themselves.""" + for c in self.children: + c.resolve(self) + + def build_schema(self): + """Process L{Types} objects and create the schema collection.""" + loaded_schemata = {} + container = SchemaCollection(self) + for t in (t for t in self.types if t.local()): + for root in t.contents(): + schema = Schema(root, self.url, self.options, loaded_schemata, container) + container.add(schema) + if not container: + root = Element.buildPath(self.root, "types/schema") + schema = Schema(root, self.url, self.options, loaded_schemata, container) + container.add(schema) + self.schema = container.load(self.options, loaded_schemata) + #TODO: Recheck this XSD schema merging. XSD schema imports are not + # supposed to be transitive. They only allow the importing schema to + # reference entities from the imported schema, but do not include them + # as their own content. + for s in (t.schema() for t in self.types if t.imported()): + self.schema.merge(s) + return self.schema + + def add_methods(self, service): + """Build method view for service.""" + bindings = { + "document/literal": Document(self), + "rpc/literal": RPC(self), + "rpc/encoded": Encoded(self)} + for p in service.ports: + binding = p.binding + ptype = p.binding.type + operations = p.binding.type.operations.values() + for name in (op.name for op in operations): + m = Facade("Method") + m.name = name + m.location = p.location + m.binding = Facade("binding") + op = binding.operation(name) + m.soap = op.soap + key = "/".join((op.soap.style, op.soap.input.body.use)) + m.binding.input = bindings.get(key) + key = "/".join((op.soap.style, op.soap.output.body.use)) + m.binding.output = bindings.get(key) + p.methods[name] = m + + def set_wrapped(self): + """Set (wrapped|bare) flag on messages.""" + for b in self.bindings.values(): + for op in b.operations.values(): + for body in (op.soap.input.body, op.soap.output.body): + body.wrapped = False + if not self.options.unwrap: + continue + if len(body.parts) != 1: + continue + for p in body.parts: + if p.element is None: + continue + query = ElementQuery(p.element) + pt = query.execute(self.schema) + if pt is None: + raise TypeNotFound(query.ref) + resolved = pt.resolve() + if resolved.builtin(): + continue + body.wrapped = True + + def __getstate__(self): + nopickle = ("options",) + state = self.__dict__.copy() + for k in nopickle: + if k in state: + del state[k] + return state + + def __repr__(self): + return "Definitions (id=%s)" % (self.id,) + + +class Import(WObject): + """ + Represents the . + + @ivar location: The value of the I{location} attribute. + @type location: str + @ivar ns: The value of the I{namespace} attribute. + @type ns: str + @ivar imported: The imported object. + @type imported: L{Definitions} + + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + WObject.__init__(self, root) + self.location = root.get("location") + self.ns = root.get("namespace") + self.imported = None + pmd = self.__metadata__.__print__ + pmd.wrappers["imported"] = repr + + def load(self, definitions, imported_definitions): + """Load the object by opening the URL.""" + url = self.location + log.debug("importing (%s)", url) + if "://" not in url: + url = urljoin(definitions.url, url) + d = imported_definitions.get(url) + if not d: + d = Definitions(url, definitions.options, imported_definitions) + if d.root.match(Definitions.Tag, wsdlns): + self.import_definitions(definitions, d) + return + if d.root.match(Schema.Tag, Namespace.xsdns): + self.import_schema(definitions, d) + return + raise Exception("document at '%s' is unknown" % url) + + def import_definitions(self, definitions, d): + """Import/merge WSDL definitions.""" + definitions.types += d.types + definitions.messages.update(d.messages) + definitions.port_types.update(d.port_types) + definitions.bindings.update(d.bindings) + self.imported = d + log.debug("imported (WSDL):\n%s", d) + + def import_schema(self, definitions, d): + """Import schema as content.""" + if not definitions.types: + root = Element("types", ns=wsdlns) + definitions.root.insert(root) + types = Types(root, definitions) + definitions.types.append(types) + else: + types = definitions.types[-1] + types.root.append(d.root) + log.debug("imported (XSD):\n%s", d.root) + + def __gt__(self, other): + return False + + +class Types(WObject): + """Represents .""" + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + WObject.__init__(self, root) + self.definitions = definitions + + def contents(self): + return self.root.getChildren("schema", Namespace.xsdns) + + def schema(self): + return self.definitions.schema + + def local(self): + return self.definitions.schema is None + + def imported(self): + return not self.local() + + def __gt__(self, other): + return isinstance(other, Import) + + +class Part(NamedObject): + """ + Represents . + + @ivar element: The value of the {element} attribute. Stored as a I{qref} as + converted by L{suds.xsd.qualify}. + @type element: str + @ivar type: The value of the {type} attribute. Stored as a I{qref} as + converted by L{suds.xsd.qualify}. + @type type: str + + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + NamedObject.__init__(self, root, definitions) + pmd = Metadata() + pmd.wrappers = dict(element=repr, type=repr) + self.__metadata__.__print__ = pmd + tns = definitions.tns + self.element = self.__getref("element", tns) + self.type = self.__getref("type", tns) + + def __getref(self, a, tns): + """Get the qualified value of attribute named 'a'.""" + s = self.root.get(a) + if s is not None: + return qualify(s, self.root, tns) + + +class Message(NamedObject): + """ + Represents . + + @ivar parts: A list of message parts. + @type parts: [I{Part},...] + + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + NamedObject.__init__(self, root, definitions) + self.parts = [] + for p in root.getChildren("part"): + part = Part(p, definitions) + self.parts.append(part) + + def __gt__(self, other): + return isinstance(other, (Import, Types)) + + +class PortType(NamedObject): + """ + Represents . + + @ivar operations: A list of contained operations. + @type operations: list + + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + NamedObject.__init__(self, root, definitions) + self.operations = {} + for c in root.getChildren("operation"): + op = Facade("Operation") + op.name = c.get("name") + op.tns = definitions.tns + input = c.getChild("input") + if input is None: + op.input = None + else: + op.input = input.get("message") + output = c.getChild("output") + if output is None: + op.output = None + else: + op.output = output.get("message") + faults = [] + for fault in c.getChildren("fault"): + f = Facade("Fault") + f.name = fault.get("name") + f.message = fault.get("message") + faults.append(f) + op.faults = faults + self.operations[op.name] = op + + def do_resolve(self, definitions): + """ + Resolve named references to other WSDL objects. + + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + for op in self.operations.values(): + if op.input is None: + op.input = Message(Element("no-input"), definitions) + else: + qref = qualify(op.input, self.root, definitions.tns) + msg = definitions.messages.get(qref) + if msg is None: + raise Exception("msg '%s', not-found" % (op.input,)) + op.input = msg + if op.output is None: + op.output = Message(Element("no-output"), definitions) + else: + qref = qualify(op.output, self.root, definitions.tns) + msg = definitions.messages.get(qref) + if msg is None: + raise Exception("msg '%s', not-found" % (op.output,)) + op.output = msg + for f in op.faults: + qref = qualify(f.message, self.root, definitions.tns) + msg = definitions.messages.get(qref) + if msg is None: + raise Exception("msg '%s', not-found" % (f.message,)) + f.message = msg + + def operation(self, name): + """ + Shortcut used to get a contained operation by name. + + @param name: An operation name. + @type name: str + @return: The named operation. + @rtype: Operation + @raise L{MethodNotFound}: When not found. + + """ + try: + return self.operations[name] + except Exception as e: + raise MethodNotFound(name) + + def __gt__(self, other): + return isinstance(other, (Import, Types, Message)) + + +class Binding(NamedObject): + """ + Represents . + + @ivar operations: A list of contained operations. + @type operations: list + + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + NamedObject.__init__(self, root, definitions) + self.operations = {} + self.type = root.get("type") + sr = self.soaproot() + if sr is None: + self.soap = None + log.debug("binding: '%s' not a SOAP binding", self.name) + return + soap = Facade("soap") + self.soap = soap + self.soap.style = sr.get("style", default="document") + self.add_operations(self.root, definitions) + + def soaproot(self): + """Get the soap:binding.""" + for ns in (soapns, soap12ns): + sr = self.root.getChild("binding", ns=ns) + if sr is not None: + return sr + + def add_operations(self, root, definitions): + """Add children.""" + dsop = Element("operation", ns=soapns) + for c in root.getChildren("operation"): + op = Facade("Operation") + op.name = c.get("name") + sop = c.getChild("operation", default=dsop) + soap = Facade("soap") + soap.action = '"%s"' % (sop.get("soapAction", default=""),) + soap.style = sop.get("style", default=self.soap.style) + soap.input = Facade("Input") + soap.input.body = Facade("Body") + soap.input.headers = [] + soap.output = Facade("Output") + soap.output.body = Facade("Body") + soap.output.headers = [] + op.soap = soap + input = c.getChild("input") + if input is None: + input = Element("input", ns=wsdlns) + body = input.getChild("body") + self.body(definitions, soap.input.body, body) + for header in input.getChildren("header"): + self.header(definitions, soap.input, header) + output = c.getChild("output") + if output is None: + output = Element("output", ns=wsdlns) + body = output.getChild("body") + self.body(definitions, soap.output.body, body) + for header in output.getChildren("header"): + self.header(definitions, soap.output, header) + faults = [] + for fault in c.getChildren("fault"): + sf = fault.getChild("fault") + if sf is None: + continue + fn = fault.get("name") + f = Facade("Fault") + f.name = sf.get("name", default=fn) + f.use = sf.get("use", default="literal") + faults.append(f) + soap.faults = faults + self.operations[op.name] = op + + def body(self, definitions, body, root): + """Add the input/output body properties.""" + if root is None: + body.use = "literal" + body.namespace = definitions.tns + body.parts = () + return + parts = root.get("parts") + if parts is None: + body.parts = () + else: + body.parts = re.split("[\\s,]", parts) + body.use = root.get("use", default="literal") + ns = root.get("namespace") + if ns is None: + body.namespace = definitions.tns + else: + prefix = root.findPrefix(ns, "b0") + body.namespace = (prefix, ns) + + def header(self, definitions, parent, root): + """Add the input/output header properties.""" + if root is None: + return + header = Facade("Header") + parent.headers.append(header) + header.use = root.get("use", default="literal") + ns = root.get("namespace") + if ns is None: + header.namespace = definitions.tns + else: + prefix = root.findPrefix(ns, "h0") + header.namespace = (prefix, ns) + msg = root.get("message") + if msg is not None: + header.message = msg + part = root.get("part") + if part is not None: + header.part = part + + def do_resolve(self, definitions): + """ + Resolve named references to other WSDL objects. This includes + cross-linking information (from) the portType (to) the I{SOAP} protocol + information on the binding for each operation. + + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + self.__resolveport(definitions) + for op in self.operations.values(): + self.__resolvesoapbody(definitions, op) + self.__resolveheaders(definitions, op) + self.__resolvefaults(definitions, op) + + def __resolveport(self, definitions): + """ + Resolve port_type reference. + + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + ref = qualify(self.type, self.root, definitions.tns) + port_type = definitions.port_types.get(ref) + if port_type is None: + raise Exception("portType '%s', not-found" % (self.type,)) + # Later on we will require access to the message data referenced by + # this port_type instance, and in order for those data references to be + # available, port_type first needs to dereference its message + # identification string. The only scenario where the port_type might + # possibly not have already resolved its references, and where this + # explicit resolve() call is required, is if we are dealing with a + # recursive WSDL import chain. + port_type.resolve(definitions) + self.type = port_type + + def __resolvesoapbody(self, definitions, op): + """ + Resolve SOAP body I{message} parts by cross-referencing with operation + defined in port type. + + @param definitions: A definitions object. + @type definitions: L{Definitions} + @param op: An I{operation} object. + @type op: I{operation} + + """ + ptop = self.type.operation(op.name) + if ptop is None: + raise Exception("operation '%s' not defined in portType" % ( + op.name,)) + soap = op.soap + parts = soap.input.body.parts + if parts: + pts = [] + for p in ptop.input.parts: + if p.name in parts: + pts.append(p) + soap.input.body.parts = pts + else: + soap.input.body.parts = ptop.input.parts + parts = soap.output.body.parts + if parts: + pts = [] + for p in ptop.output.parts: + if p.name in parts: + pts.append(p) + soap.output.body.parts = pts + else: + soap.output.body.parts = ptop.output.parts + + def __resolveheaders(self, definitions, op): + """ + Resolve SOAP header I{message} references. + + @param definitions: A definitions object. + @type definitions: L{Definitions} + @param op: An I{operation} object. + @type op: I{operation} + + """ + soap = op.soap + headers = soap.input.headers + soap.output.headers + for header in headers: + mn = header.message + ref = qualify(mn, self.root, definitions.tns) + message = definitions.messages.get(ref) + if message is None: + raise Exception("message '%s', not-found" % (mn,)) + pn = header.part + for p in message.parts: + if p.name == pn: + header.part = p + break + if pn == header.part: + raise Exception("message '%s' has not part named '%s'" % ( + ref, pn)) + + def __resolvefaults(self, definitions, op): + """ + Resolve SOAP fault I{message} references by cross-referencing with + operations defined in the port type. + + @param definitions: A definitions object. + @type definitions: L{Definitions} + @param op: An I{operation} object. + @type op: I{operation} + + """ + ptop = self.type.operation(op.name) + if ptop is None: + raise Exception("operation '%s' not defined in portType" % ( + op.name,)) + soap = op.soap + for fault in soap.faults: + for f in ptop.faults: + if f.name == fault.name: + fault.parts = f.message.parts + continue + if hasattr(fault, "parts"): + continue + raise Exception("fault '%s' not defined in portType '%s'" % ( + fault.name, self.type.name)) + + def operation(self, name): + """ + Shortcut used to get a contained operation by name. + + @param name: An operation name. + @type name: str + @return: The named operation. + @rtype: Operation + @raise L{MethodNotFound}: When not found. + + """ + try: + return self.operations[name] + except Exception: + raise MethodNotFound(name) + + def __gt__(self, other): + return not isinstance(other, Service) + + +class Port(NamedObject): + """ + Represents a service port. + + @ivar service: A service. + @type service: L{Service} + @ivar binding: A binding name. + @type binding: str + @ivar location: The service location (URL). + @type location: str + + """ + + def __init__(self, root, definitions, service): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + @param service: A service object. + @type service: L{Service} + + """ + NamedObject.__init__(self, root, definitions) + self.__service = service + self.binding = root.get("binding") + address = root.getChild("address") + self.location = address is not None and address.get("location") + self.methods = {} + + def method(self, name): + """ + Get a method defined in this portType by name. + + @param name: A method name. + @type name: str + @return: The requested method object. + @rtype: I{Method} + + """ + return self.methods.get(name) + + +class Service(NamedObject): + """ + Represents . + + @ivar port: The contained ports. + @type port: [Port,..] + @ivar methods: The contained methods for all ports. + @type methods: [Method,..] + + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + NamedObject.__init__(self, root, definitions) + self.ports = [] + for p in root.getChildren("port"): + port = Port(p, definitions, self) + self.ports.append(port) + + def port(self, name): + """ + Locate a port by name. + + @param name: A port name. + @type name: str + @return: The port object. + @rtype: L{Port} + + """ + for p in self.ports: + if p.name == name: + return p + + def setlocation(self, url, names=None): + """ + Override the invocation location (URL) for service method. + + @param url: A URL location. + @type url: A URL. + @param names: A list of method names. None=ALL + @type names: [str,..] + + """ + for p in self.ports: + for m in p.methods.values(): + if names is None or m.name in names: + m.location = url + + def do_resolve(self, definitions): + """ + Resolve named references to other WSDL objects. Ports without SOAP + bindings are discarded. + + @param definitions: A definitions object. + @type definitions: L{Definitions} + + """ + filtered = [] + for p in self.ports: + ref = qualify(p.binding, self.root, definitions.tns) + binding = definitions.bindings.get(ref) + if binding is None: + raise Exception("binding '%s', not-found" % (p.binding,)) + if binding.soap is None: + log.debug("binding '%s' - not a SOAP binding, discarded", + binding.name) + continue + # After we have been resolved, our caller will expect that the + # binding we are referencing has been fully constructed, i.e. + # resolved, as well. The only scenario where the operations binding + # might possibly not have already resolved its references, and + # where this explicit resolve() call is required, is if we are + # dealing with a recursive WSDL import chain. + binding.resolve(definitions) + p.binding = binding + filtered.append(p) + self.ports = filtered + + def __gt__(self, other): + return True + + +class Factory: + """ + Simple WSDL object factory. + + @cvar tags: Dictionary of tag-->constructor mappings. + @type tags: dict + + """ + + tags = { + "import": Import, + "types": Types, + "message": Message, + "portType": PortType, + "binding": Binding, + "service": Service} + + @classmethod + def create(cls, root, definitions): + """ + Create an object based on the root tag name. + + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + @return: The created object. + @rtype: L{WObject} + + """ + fn = cls.tags.get(root.name) + if fn is not None: + return fn(root, definitions) diff --git a/pym/calculate/contrib/suds/wsdl.pyc b/pym/calculate/contrib/suds/wsdl.pyc new file mode 100644 index 0000000..81772d0 Binary files /dev/null and b/pym/calculate/contrib/suds/wsdl.pyc differ diff --git a/pym/calculate/contrib/suds/wsse.py b/pym/calculate/contrib/suds/wsse.py new file mode 100644 index 0000000..96d9eb6 --- /dev/null +++ b/pym/calculate/contrib/suds/wsse.py @@ -0,0 +1,236 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{wsse} module provides WS-Security. +""" + +from logging import getLogger +from suds import * +from suds.sudsobject import Object +from suds.sax.element import Element +from suds.sax.date import DateTime, UtcTimezone +from datetime import datetime, timedelta + +try: + from hashlib import md5 +except ImportError: + # Python 2.4 compatibility + from md5 import md5 + + +dsns = \ + ('ds', + 'http://www.w3.org/2000/09/xmldsig#') +wssens = \ + ('wsse', + 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd') +wsuns = \ + ('wsu', + 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd') +wsencns = \ + ('wsenc', + 'http://www.w3.org/2001/04/xmlenc#') + +nonce_encoding_type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" +username_token_profile = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0" +wsdigest = "%s#PasswordDigest" % username_token_profile +wstext = "%s#PasswordText" % username_token_profile + + +class Security(Object): + """ + WS-Security object. + @ivar tokens: A list of security tokens + @type tokens: [L{Token},...] + @ivar signatures: A list of signatures. + @type signatures: TBD + @ivar references: A list of references. + @type references: TBD + @ivar keys: A list of encryption keys. + @type keys: TBD + """ + + def __init__(self): + """ """ + Object.__init__(self) + self.mustUnderstand = True + self.tokens = [] + self.signatures = [] + self.references = [] + self.keys = [] + + def xml(self): + """ + Get xml representation of the object. + @return: The root node. + @rtype: L{Element} + """ + root = Element('Security', ns=wssens) + root.set('mustUnderstand', str(self.mustUnderstand).lower()) + for t in self.tokens: + root.append(t.xml()) + return root + + +class Token(Object): + """ I{Abstract} security token. """ + + @classmethod + def now(cls): + return datetime.now() + + @classmethod + def utc(cls): + return datetime.utcnow().replace(tzinfo=UtcTimezone()) + + @classmethod + def sysdate(cls): + utc = DateTime(cls.utc()) + return str(utc) + + def __init__(self): + Object.__init__(self) + + +class UsernameToken(Token): + """ + Represents a basic I{UsernameToken} WS-Secuirty token. + @ivar username: A username. + @type username: str + @ivar password: A password. + @type password: str + @type password_digest: A password digest + @ivar nonce: A set of bytes to prevent replay attacks. + @type nonce: str + @ivar created: The token created. + @type created: L{datetime} + """ + + def __init__(self, username=None, password=None): + """ + @param username: A username. + @type username: str + @param password: A password. + @type password: str + """ + Token.__init__(self) + self.username = username + self.password = password + self.nonce = None + self.created = None + self.password_digest = None + self.nonce_has_encoding = False + + def setnonceencoding(self, value=False): + self.nonce_has_encoding = value + + def setpassworddigest(self, passwd_digest): + """ + Set password digest which is a text returned by + auth WS. + """ + self.password_digest = passwd_digest + + def setnonce(self, text=None): + """ + Set I{nonce} which is an arbitrary set of bytes to prevent replay + attacks. + @param text: The nonce text value. + Generated when I{None}. + @type text: str + """ + if text is None: + s = [] + s.append(self.username) + s.append(self.password) + s.append(Token.sysdate()) + m = md5() + m.update(':'.join(s).encode('utf-8')) + self.nonce = m.hexdigest() + else: + self.nonce = text + + def setcreated(self, dt=None): + """ + Set I{created}. + @param dt: The created date & time. + Set as datetime.utc() when I{None}. + @type dt: L{datetime} + """ + if dt is None: + self.created = Token.utc() + else: + self.created = dt + + def xml(self): + """ + Get xml representation of the object. + @return: The root node. + @rtype: L{Element} + """ + root = Element('UsernameToken', ns=wssens) + u = Element('Username', ns=wssens) + u.setText(self.username) + root.append(u) + p = Element('Password', ns=wssens) + p.setText(self.password) + if self.password_digest: + p.set("Type", wsdigest) + p.setText(self.password_digest) + else: + p.set("Type", wstext) + root.append(p) + if self.nonce is not None: + n = Element('Nonce', ns=wssens) + if self.nonce_has_encoding: + n.set("EncodingType", nonce_encoding_type) + n.setText(self.nonce) + root.append(n) + if self.created is not None: + n = Element('Created', ns=wsuns) + n.setText(str(DateTime(self.created))) + root.append(n) + return root + + +class Timestamp(Token): + """ + Represents the I{Timestamp} WS-Secuirty token. + @ivar created: The token created. + @type created: L{datetime} + @ivar expires: The token expires. + @type expires: L{datetime} + """ + + def __init__(self, validity=90): + """ + @param validity: The time in seconds. + @type validity: int + """ + Token.__init__(self) + self.created = Token.utc() + self.expires = self.created + timedelta(seconds=validity) + + def xml(self): + root = Element("Timestamp", ns=wsuns) + created = Element('Created', ns=wsuns) + created.setText(str(DateTime(self.created))) + expires = Element('Expires', ns=wsuns) + expires.setText(str(DateTime(self.expires))) + root.append(created) + root.append(expires) + return root diff --git a/pym/calculate/contrib/suds/wsse.pyc b/pym/calculate/contrib/suds/wsse.pyc new file mode 100644 index 0000000..5eaa922 Binary files /dev/null and b/pym/calculate/contrib/suds/wsse.pyc differ diff --git a/pym/calculate/contrib/suds/xsd/__init__.py b/pym/calculate/contrib/suds/xsd/__init__.py new file mode 100644 index 0000000..c5d8015 --- /dev/null +++ b/pym/calculate/contrib/suds/xsd/__init__.py @@ -0,0 +1,75 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + + +from suds import * +from suds.sax import Namespace, splitPrefix + + +def qualify(ref, resolvers, defns=Namespace.default): + """ + Get a reference that is I{qualified} by namespace. + @param ref: A referenced schema type name. + @type ref: str + @param resolvers: A list of objects to be used to resolve types. + @type resolvers: [L{sax.element.Element},] + @param defns: An optional target namespace used to qualify references + when no prefix is specified. + @type defns: A default namespace I{tuple: (prefix,uri)} used when ref not prefixed. + @return: A qualified reference. + @rtype: (name, namespace-uri) + """ + ns = None + p, n = splitPrefix(ref) + if p is not None: + if not isinstance(resolvers, (list, tuple)): + resolvers = (resolvers,) + for r in resolvers: + resolved = r.resolvePrefix(p) + if resolved[1] is not None: + ns = resolved + break + if ns is None: + raise Exception('prefix (%s) not resolved' % p) + else: + ns = defns + return (n, ns[1]) + +def isqref(object): + """ + Get whether the object is a I{qualified reference}. + @param object: An object to be tested. + @type object: I{any} + @rtype: boolean + @see: L{qualify} + """ + return (\ + isinstance(object, tuple) and \ + len(object) == 2 and \ + isinstance(object[0], basestring) and \ + isinstance(object[1], basestring)) + + +class Filter: + def __init__(self, inclusive=False, *items): + self.inclusive = inclusive + self.items = items + def __contains__(self, x): + if self.inclusive: + result = ( x in self.items ) + else: + result = ( x not in self.items ) + return result diff --git a/pym/calculate/contrib/suds/xsd/__init__.pyc b/pym/calculate/contrib/suds/xsd/__init__.pyc new file mode 100644 index 0000000..093e4cc Binary files /dev/null and b/pym/calculate/contrib/suds/xsd/__init__.pyc differ diff --git a/pym/calculate/contrib/suds/xsd/depsort.py b/pym/calculate/contrib/suds/xsd/depsort.py new file mode 100644 index 0000000..002d755 --- /dev/null +++ b/pym/calculate/contrib/suds/xsd/depsort.py @@ -0,0 +1,71 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Dependency/topological sort implementation. + +""" + +from suds import * + +from logging import getLogger +log = getLogger(__name__) + + +def dependency_sort(dependency_tree): + """ + Sorts items 'dependencies first' in a given dependency tree. + + A dependency tree is a dictionary mapping an object to a collection its + dependency objects. + + Result is a properly sorted list of items, where each item is a 2-tuple + containing an object and its dependency list, as given in the input + dependency tree. + + If B is directly or indirectly dependent on A and they are not both a part + of the same dependency cycle (i.e. then A is neither directly nor + indirectly dependent on B) then A needs to come before B. + + If A and B are a part of the same dependency cycle, i.e. if they are both + directly or indirectly dependent on each other, then it does not matter + which comes first. + + Any entries found listed as dependencies, but that do not have their own + dependencies listed as well, are logged & ignored. + + @return: The sorted items. + @rtype: list + + """ + sorted = [] + processed = set() + for key, deps in dependency_tree.iteritems(): + _sort_r(sorted, processed, key, deps, dependency_tree) + return sorted + + +def _sort_r(sorted, processed, key, deps, dependency_tree): + """Recursive topological sort implementation.""" + if key in processed: + return + processed.add(key) + for dep_key in deps: + dep_deps = dependency_tree.get(dep_key) + if dep_deps is None: + log.debug('"%s" not found, skipped', Repr(dep_key)) + continue + _sort_r(sorted, processed, dep_key, dep_deps, dependency_tree) + sorted.append((key, deps)) diff --git a/pym/calculate/contrib/suds/xsd/depsort.pyc b/pym/calculate/contrib/suds/xsd/depsort.pyc new file mode 100644 index 0000000..9932d27 Binary files /dev/null and b/pym/calculate/contrib/suds/xsd/depsort.pyc differ diff --git a/pym/calculate/contrib/suds/xsd/doctor.py b/pym/calculate/contrib/suds/xsd/doctor.py new file mode 100644 index 0000000..5a52e76 --- /dev/null +++ b/pym/calculate/contrib/suds/xsd/doctor.py @@ -0,0 +1,223 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{doctor} module provides classes for fixing broken (sick) +schema(s). +""" + +from suds.sax import Namespace +from suds.sax.element import Element +from suds.plugin import DocumentPlugin, DocumentContext + +from logging import getLogger +log = getLogger(__name__) + + +class Doctor: + """ + Schema Doctor. + """ + def examine(self, root): + """ + Examine and repair the schema (if necessary). + @param root: A schema root element. + @type root: L{Element} + """ + pass + + +class Practice(Doctor): + """ + A collection of doctors. + @ivar doctors: A list of doctors. + @type doctors: list + """ + + def __init__(self): + self.doctors = [] + + def add(self, doctor): + """ + Add a doctor to the practice + @param doctor: A doctor to add. + @type doctor: L{Doctor} + """ + self.doctors.append(doctor) + + def examine(self, root): + for d in self.doctors: + d.examine(root) + return root + + +class TnsFilter: + """ + Target Namespace filter. + @ivar tns: A list of target namespaces. + @type tns: [str,...] + """ + + def __init__(self, *tns): + """ + @param tns: A list of target namespaces. + @type tns: [str,...] + """ + self.tns = [] + self.add(*tns) + + def add(self, *tns): + """ + Add I{targetNamespaces} to be added. + @param tns: A list of target namespaces. + @type tns: [str,...] + """ + self.tns += tns + + def match(self, root, ns): + """ + Match by I{targetNamespace} excluding those that + are equal to the specified namespace to prevent + adding an import to itself. + @param root: A schema root. + @type root: L{Element} + """ + tns = root.get('targetNamespace') + if len(self.tns): + matched = ( tns in self.tns ) + else: + matched = 1 + itself = ( ns == tns ) + return ( matched and not itself ) + + +class Import: + """ + An to be applied. + @cvar xsdns: The XSD namespace. + @type xsdns: (p,u) + @ivar ns: An import namespace. + @type ns: str + @ivar location: An optional I{schemaLocation}. + @type location: str + @ivar filter: A filter used to restrict application to + a particular schema. + @type filter: L{TnsFilter} + """ + + xsdns = Namespace.xsdns + + def __init__(self, ns, location=None): + """ + @param ns: An import namespace. + @type ns: str + @param location: An optional I{schemaLocation}. + @type location: str + """ + self.ns = ns + self.location = location + self.filter = TnsFilter() + + def setfilter(self, filter): + """ + Set the filter. + @param filter: A filter to set. + @type filter: L{TnsFilter} + """ + self.filter = filter + + def apply(self, root): + """ + Apply the import (rule) to the specified schema. + If the schema does not already contain an import for the + I{namespace} specified here, it is added. + @param root: A schema root. + @type root: L{Element} + """ + if not self.filter.match(root, self.ns): + return + if self.exists(root): + return + node = Element('import', ns=self.xsdns) + node.set('namespace', self.ns) + if self.location is not None: + node.set('schemaLocation', self.location) + log.debug('inserting: %s', node) + root.insert(node) + + def add(self, root): + """ + Add an to the specified schema root. + @param root: A schema root. + @type root: L{Element} + """ + node = Element('import', ns=self.xsdns) + node.set('namespace', self.ns) + if self.location is not None: + node.set('schemaLocation', self.location) + log.debug('%s inserted', node) + root.insert(node) + + def exists(self, root): + """ + Check to see if the already exists + in the specified schema root by matching I{namespace}. + @param root: A schema root. + @type root: L{Element} + """ + for node in root.children: + if node.name != 'import': + continue + ns = node.get('namespace') + if self.ns == ns: + return 1 + return 0 + + +class ImportDoctor(Doctor, DocumentPlugin): + """ + Doctor used to fix missing imports. + @ivar imports: A list of imports to apply. + @type imports: [L{Import},...] + """ + + def __init__(self, *imports): + self.imports = [] + self.add(*imports) + + def add(self, *imports): + """ + Add a namespace to be checked. + @param imports: A list of L{Import} objects. + @type imports: [L{Import},..] + """ + self.imports += imports + + def examine(self, node): + for imp in self.imports: + imp.apply(node) + + def parsed(self, context): + node = context.document + # xsd root + if node.name == 'schema' and Namespace.xsd(node.namespace()): + self.examine(node) + return + # look deeper + context = DocumentContext() + for child in node: + context.document = child + self.parsed(context) diff --git a/pym/calculate/contrib/suds/xsd/doctor.pyc b/pym/calculate/contrib/suds/xsd/doctor.pyc new file mode 100644 index 0000000..2bf4857 Binary files /dev/null and b/pym/calculate/contrib/suds/xsd/doctor.pyc differ diff --git a/pym/calculate/contrib/suds/xsd/query.py b/pym/calculate/contrib/suds/xsd/query.py new file mode 100644 index 0000000..8f2266b --- /dev/null +++ b/pym/calculate/contrib/suds/xsd/query.py @@ -0,0 +1,208 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{query} module defines a class for performing schema queries. +""" + +from suds import * +from suds.sudsobject import * +from suds.xsd import qualify, isqref +from suds.xsd.sxbuiltin import Factory + +from logging import getLogger +log = getLogger(__name__) + + +class Query(Object): + """ + Schema query base class. + + """ + def __init__(self, ref=None): + """ + @param ref: The schema reference being queried. + @type ref: qref + """ + Object.__init__(self) + self.id = objid(self) + self.ref = ref + self.history = [] + self.resolved = False + if not isqref(self.ref): + raise Exception('%s, must be qref' % tostr(self.ref)) + + def execute(self, schema): + """ + Execute this query using the specified schema. + @param schema: The schema associated with the query. The schema is used + by the query to search for items. + @type schema: L{schema.Schema} + @return: The item matching the search criteria. + @rtype: L{sxbase.SchemaObject} + """ + raise Exception, 'not-implemented by subclass' + + def filter(self, result): + """ + Filter the specified result based on query criteria. + @param result: A potential result. + @type result: L{sxbase.SchemaObject} + @return: True if result should be excluded. + @rtype: boolean + """ + if result is None: + return True + reject = ( result in self.history ) + if reject: + log.debug('result %s, rejected by\n%s', Repr(result), self) + return reject + + def result(self, result): + """ + Query result post processing. + @param result: A query result. + @type result: L{sxbase.SchemaObject} + """ + if result is None: + log.debug('%s, not-found', self.ref) + return + if self.resolved: + result = result.resolve() + log.debug('%s, found as: %s', self.ref, Repr(result)) + self.history.append(result) + return result + + +class BlindQuery(Query): + """ + Schema query class that I{blindly} searches for a reference in the + specified schema. It may be used to find Elements and Types but will match + on an Element first. This query will also find builtins. + + """ + def execute(self, schema): + if schema.builtin(self.ref): + name = self.ref[0] + b = Factory.create(schema, name) + log.debug('%s, found builtin (%s)', self.id, name) + return b + result = None + for d in (schema.elements, schema.types): + result = d.get(self.ref) + if self.filter(result): + result = None + else: + break + if result is None: + eq = ElementQuery(self.ref) + eq.history = self.history + result = eq.execute(schema) + return self.result(result) + + +class TypeQuery(Query): + """ + Schema query class that searches for Type references in the specified + schema. Matches on root types only. + + """ + def execute(self, schema): + if schema.builtin(self.ref): + name = self.ref[0] + b = Factory.create(schema, name) + log.debug('%s, found builtin (%s)', self.id, name) + return b + result = schema.types.get(self.ref) + if self.filter(result): + result = None + return self.result(result) + + +class GroupQuery(Query): + """ + Schema query class that searches for Group references in the specified + schema. + + """ + def execute(self, schema): + result = schema.groups.get(self.ref) + if self.filter(result): + result = None + return self.result(result) + + +class AttrQuery(Query): + """ + Schema query class that searches for Attribute references in the specified + schema. Matches on root Attribute by qname first, then searches deeper into + the document. + + """ + def execute(self, schema): + result = schema.attributes.get(self.ref) + if self.filter(result): + result = self.__deepsearch(schema) + return self.result(result) + + def __deepsearch(self, schema): + from suds.xsd.sxbasic import Attribute + result = None + for e in schema.all: + result = e.find(self.ref, (Attribute,)) + if self.filter(result): + result = None + else: + break + return result + + +class AttrGroupQuery(Query): + """ + Schema query class that searches for attributeGroup references in the + specified schema. + + """ + def execute(self, schema): + result = schema.agrps.get(self.ref) + if self.filter(result): + result = None + return self.result(result) + + +class ElementQuery(Query): + """ + Schema query class that searches for Element references in the specified + schema. Matches on root Elements by qname first, then searches deeper into + the document. + + """ + def execute(self, schema): + result = schema.elements.get(self.ref) + if self.filter(result): + result = self.__deepsearch(schema) + return self.result(result) + + def __deepsearch(self, schema): + from suds.xsd.sxbasic import Element + result = None + for e in schema.all: + result = e.find(self.ref, (Element,)) + if self.filter(result): + result = None + else: + break + return result diff --git a/pym/calculate/contrib/suds/xsd/query.pyc b/pym/calculate/contrib/suds/xsd/query.pyc new file mode 100644 index 0000000..58704cf Binary files /dev/null and b/pym/calculate/contrib/suds/xsd/query.pyc differ diff --git a/pym/calculate/contrib/suds/xsd/schema.py b/pym/calculate/contrib/suds/xsd/schema.py new file mode 100644 index 0000000..d057024 --- /dev/null +++ b/pym/calculate/contrib/suds/xsd/schema.py @@ -0,0 +1,464 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{schema} module provides an intelligent representation of an XSD schema. +The I{raw} model is the XML tree and the I{model} is a denormalized, +objectified and intelligent view of the schema. Most of the I{value-add} +provided by the model is centered around transparent referenced type resolution +and targeted denormalization. + +""" + +from suds import * +from suds.xsd import * +from suds.xsd.depsort import dependency_sort +from suds.xsd.sxbuiltin import * +from suds.xsd.sxbase import SchemaObject +from suds.xsd.sxbasic import Factory as BasicFactory +from suds.xsd.sxbuiltin import Factory as BuiltinFactory +from suds.sax import splitPrefix, Namespace +from suds.sax.element import Element + +from logging import getLogger +log = getLogger(__name__) + + +class SchemaCollection(UnicodeMixin): + """ + A collection of schema objects. + + This class is needed because a WSDL may contain more then one + node. + + @ivar wsdl: A WSDL object. + @type wsdl: L{suds.wsdl.Definitions} + @ivar children: A list contained schemas. + @type children: [L{Schema},...] + @ivar namespaces: A dictionary of contained schemas by namespace. + @type namespaces: {str: L{Schema}} + + """ + + def __init__(self, wsdl): + """ + @param wsdl: A WSDL object. + @type wsdl: L{suds.wsdl.Definitions} + + """ + self.wsdl = wsdl + self.children = [] + self.namespaces = {} + + def add(self, schema): + """ + Add a schema node to the collection. Schema(s) within the same target + namespace are consolidated. + + @param schema: A schema object. + @type schema: (L{Schema}) + + """ + key = schema.tns[1] + existing = self.namespaces.get(key) + if existing is None: + self.children.append(schema) + self.namespaces[key] = schema + else: + existing.root.children += schema.root.children + existing.root.nsprefixes.update(schema.root.nsprefixes) + + def load(self, options, loaded_schemata): + """ + Load schema objects for the root nodes. + - de-reference schemas + - merge schemas + + @param options: An options dictionary. + @type options: L{options.Options} + @param loaded_schemata: Already loaded schemata cache (URL --> Schema). + @type loaded_schemata: dict + @return: The merged schema. + @rtype: L{Schema} + + """ + if options.autoblend: + self.autoblend() + for child in self.children: + child.build() + for child in self.children: + child.open_imports(options, loaded_schemata) + for child in self.children: + child.dereference() + log.debug("loaded:\n%s", self) + merged = self.merge() + log.debug("MERGED:\n%s", merged) + return merged + + def autoblend(self): + """ + Ensure that all schemas within the collection import each other which + has a blending effect. + + @return: self + @rtype: L{SchemaCollection} + + """ + namespaces = self.namespaces.keys() + for s in self.children: + for ns in namespaces: + tns = s.root.get("targetNamespace") + if tns == ns: + continue + for imp in s.root.getChildren("import"): + if imp.get("namespace") == ns: + continue + imp = Element("import", ns=Namespace.xsdns) + imp.set("namespace", ns) + s.root.append(imp) + return self + + def locate(self, ns): + """ + Find a schema by namespace. Only the URI portion of the namespace is + compared to each schema's I{targetNamespace}. + + @param ns: A namespace. + @type ns: (prefix, URI) + @return: The schema matching the namespace, else None. + @rtype: L{Schema} + + """ + return self.namespaces.get(ns[1]) + + def merge(self): + """ + Merge contained schemas into one. + + @return: The merged schema. + @rtype: L{Schema} + + """ + if self.children: + schema = self.children[0] + for s in self.children[1:]: + schema.merge(s) + return schema + + def __len__(self): + return len(self.children) + + def __unicode__(self): + result = ["\nschema collection"] + for s in self.children: + result.append(s.str(1)) + return "\n".join(result) + + +class Schema(UnicodeMixin): + """ + The schema is an objectification of a (XSD) definition. It + provides inspection, lookup and type resolution. + + @ivar root: The root node. + @type root: L{sax.element.Element} + @ivar baseurl: The I{base} URL for this schema. + @type baseurl: str + @ivar container: A schema collection containing this schema. + @type container: L{SchemaCollection} + @ivar children: A list of direct top level children. + @type children: [L{SchemaObject},...] + @ivar all: A list of all (includes imported) top level children. + @type all: [L{SchemaObject},...] + @ivar types: A schema types cache. + @type types: {name:L{SchemaObject}} + @ivar imports: A list of import objects. + @type imports: [L{SchemaObject},...] + @ivar elements: A list of objects. + @type elements: [L{SchemaObject},...] + @ivar attributes: A list of objects. + @type attributes: [L{SchemaObject},...] + @ivar groups: A list of group objects. + @type groups: [L{SchemaObject},...] + @ivar agrps: A list of attribute group objects. + @type agrps: [L{SchemaObject},...] + @ivar form_qualified: The flag indicating: (@elementFormDefault). + @type form_qualified: bool + + """ + + Tag = "schema" + + def __init__(self, root, baseurl, options, loaded_schemata=None, + container=None): + """ + @param root: The XML root. + @type root: L{sax.element.Element} + @param baseurl: The base URL used for importing. + @type baseurl: basestring + @param options: An options dictionary. + @type options: L{options.Options} + @param loaded_schemata: An optional already loaded schemata cache (URL + --> Schema). + @type loaded_schemata: dict + @param container: An optional container. + @type container: L{SchemaCollection} + + """ + self.root = root + self.id = objid(self) + self.tns = self.mktns() + self.baseurl = baseurl + self.container = container + self.children = [] + self.all = [] + self.types = {} + self.imports = [] + self.elements = {} + self.attributes = {} + self.groups = {} + self.agrps = {} + if options.doctor is not None: + options.doctor.examine(root) + form = self.root.get("elementFormDefault") + self.form_qualified = form == "qualified" + + # If we have a container, that container is going to take care of + # finishing our build for us in parallel with building all the other + # schemata in that container. That allows the different schema within + # the same container to freely reference each other. + #TODO: check whether this container content build parallelization is + # really necessary or if we can simply build our top-level WSDL + # contained schemata one by one as they are loaded + if container is None: + if loaded_schemata is None: + loaded_schemata = {} + loaded_schemata[baseurl] = self + #TODO: It seems like this build() step can be done for each schema + # on its own instead of letting the container do it. Building our + # XSD schema objects should not require any external schema + # information and even references between XSD schema objects within + # the same schema can not be established until all the XSD schema + # objects have been built. The only reason I can see right now why + # this step has been placed under container control is so our + # container (a SchemaCollection instance) can add some additional + # XML elements to our schema before our XSD schema object entities + # get built, but there is bound to be a cleaner way to do this, + # similar to how we support such XML modifications in suds plugins. + self.build() + self.open_imports(options, loaded_schemata) + log.debug("built:\n%s", self) + self.dereference() + log.debug("dereferenced:\n%s", self) + + def mktns(self): + """ + Make the schema's target namespace. + + @return: namespace representation of the schema's targetNamespace + value. + @rtype: (prefix, URI) + + """ + tns = self.root.get("targetNamespace") + tns_prefix = None + if tns is not None: + tns_prefix = self.root.findPrefix(tns) + return tns_prefix, tns + + def build(self): + """ + Build the schema (object graph) using the root node using the factory. + - Build the graph. + - Collate the children. + + """ + self.children = BasicFactory.build(self.root, self) + collated = BasicFactory.collate(self.children) + self.children = collated[0] + self.attributes = collated[2] + self.imports = collated[1] + self.elements = collated[3] + self.types = collated[4] + self.groups = collated[5] + self.agrps = collated[6] + + def merge(self, schema): + """ + Merge the schema contents. + + Only objects not already contained in this schema's collections are + merged. This provides support for bidirectional imports producing + cyclic includes. + + @returns: self + @rtype: L{Schema} + + """ + for item in schema.attributes.items(): + if item[0] in self.attributes: + continue + self.all.append(item[1]) + self.attributes[item[0]] = item[1] + for item in schema.elements.items(): + if item[0] in self.elements: + continue + self.all.append(item[1]) + self.elements[item[0]] = item[1] + for item in schema.types.items(): + if item[0] in self.types: + continue + self.all.append(item[1]) + self.types[item[0]] = item[1] + for item in schema.groups.items(): + if item[0] in self.groups: + continue + self.all.append(item[1]) + self.groups[item[0]] = item[1] + for item in schema.agrps.items(): + if item[0] in self.agrps: + continue + self.all.append(item[1]) + self.agrps[item[0]] = item[1] + schema.merged = True + return self + + def open_imports(self, options, loaded_schemata): + """ + Instruct all contained L{sxbasic.Import} children to import all of + their referenced schemas. The imported schema contents are I{merged} + in. + + @param options: An options dictionary. + @type options: L{options.Options} + @param loaded_schemata: Already loaded schemata cache (URL --> Schema). + @type loaded_schemata: dict + + """ + for imp in self.imports: + imported = imp.open(options, loaded_schemata) + if imported is None: + continue + imported.open_imports(options, loaded_schemata) + log.debug("imported:\n%s", imported) + self.merge(imported) + + def dereference(self): + """Instruct all children to perform dereferencing.""" + all = [] + indexes = {} + for child in self.children: + child.content(all) + dependencies = {} + for x in all: + x.qualify() + midx, deps = x.dependencies() + dependencies[x] = deps + indexes[x] = midx + for x, deps in dependency_sort(dependencies): + midx = indexes.get(x) + if midx is None: + continue + d = deps[midx] + log.debug("(%s) merging %s <== %s", self.tns[1], Repr(x), Repr(d)) + x.merge(d) + + def locate(self, ns): + """ + Find a schema by namespace. Only the URI portion of the namespace is + compared to each schema's I{targetNamespace}. The request is passed on + to the container. + + @param ns: A namespace. + @type ns: (prefix, URI) + @return: The schema matching the namespace, else None. + @rtype: L{Schema} + + """ + if self.container is not None: + return self.container.locate(ns) + + def custom(self, ref, context=None): + """ + Get whether the specified reference is B{not} an (xs) builtin. + + @param ref: A str or qref. + @type ref: (str|qref) + @return: True if B{not} a builtin, else False. + @rtype: bool + + """ + return ref is None or not self.builtin(ref, context) + + def builtin(self, ref, context=None): + """ + Get whether the specified reference is an (xs) builtin. + + @param ref: A str or qref. + @type ref: (str|qref) + @return: True if builtin, else False. + @rtype: bool + + """ + w3 = "http://www.w3.org" + try: + if isqref(ref): + ns = ref[1] + return ref[0] in Factory.tags and ns.startswith(w3) + if context is None: + context = self.root + prefix = splitPrefix(ref)[0] + prefixes = context.findPrefixes(w3, "startswith") + return prefix in prefixes and ref[0] in Factory.tags + except Exception: + return False + + def instance(self, root, baseurl, loaded_schemata, options): + """ + Create and return an new schema object using the specified I{root} and + I{URL}. + + @param root: A schema root node. + @type root: L{sax.element.Element} + @param baseurl: A base URL. + @type baseurl: str + @param loaded_schemata: Already loaded schemata cache (URL --> Schema). + @type loaded_schemata: dict + @param options: An options dictionary. + @type options: L{options.Options} + @return: The newly created schema object. + @rtype: L{Schema} + @note: This is only used by Import children. + + """ + return Schema(root, baseurl, options, loaded_schemata) + + def str(self, indent=0): + tab = "%*s" % (indent * 3, "") + result = [] + result.append("%s%s" % (tab, self.id)) + result.append("%s(raw)" % (tab,)) + result.append(self.root.str(indent + 1)) + result.append("%s(model)" % (tab,)) + for c in self.children: + result.append(c.str(indent + 1)) + result.append("") + return "\n".join(result) + + def __repr__(self): + return '<%s tns="%s"/>' % (self.id, self.tns[1]) + + def __unicode__(self): + return self.str() diff --git a/pym/calculate/contrib/suds/xsd/schema.pyc b/pym/calculate/contrib/suds/xsd/schema.pyc new file mode 100644 index 0000000..56a3503 Binary files /dev/null and b/pym/calculate/contrib/suds/xsd/schema.pyc differ diff --git a/pym/calculate/contrib/suds/xsd/sxbase.py b/pym/calculate/contrib/suds/xsd/sxbase.py new file mode 100644 index 0000000..95e98c5 --- /dev/null +++ b/pym/calculate/contrib/suds/xsd/sxbase.py @@ -0,0 +1,748 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +"""I{Base} classes representing XSD schema objects.""" + +from suds import * +from suds.xsd import * +from suds.sax.element import Element +from suds.sax import Namespace + +from logging import getLogger +log = getLogger(__name__) + + +class SchemaObject(UnicodeMixin): + """ + A schema object is an extension to object with schema awareness. + + @ivar root: The XML root element. + @type root: L{Element} + @ivar schema: The schema containing this object. + @type schema: L{schema.Schema} + @ivar form_qualified: A flag indicating that @elementFormDefault has a + value of I{qualified}. + @type form_qualified: boolean + @ivar nillable: A flag indicating that @nillable has a value of I{true}. + @type nillable: boolean + @ivar default: The default value. + @type default: object + @ivar rawchildren: A list raw of all children. + @type rawchildren: [L{SchemaObject},...] + + """ + + @classmethod + def prepend(cls, d, s, filter=Filter()): + """ + Prepend B{s}ource XSD schema objects to the B{d}estination list. + + B{filter} is used to decide which objects to prepend and which to skip. + + @param d: The destination list. + @type d: list + @param s: The source list. + @type s: list + @param filter: A filter allowing items to be prepended. + @type filter: L{Filter} + + """ + i = 0 + for x in s: + if x in filter: + d.insert(i, x) + i += 1 + + @classmethod + def append(cls, d, s, filter=Filter()): + """ + Append B{s}ource XSD schema objects to the B{d}estination list. + + B{filter} is used to decide which objects to append and which to skip. + + @param d: The destination list. + @type d: list + @param s: The source list. + @type s: list + @param filter: A filter that allows items to be appended. + @type filter: L{Filter} + + """ + for item in s: + if item in filter: + d.append(item) + + def __init__(self, schema, root): + """ + @param schema: The containing schema. + @type schema: L{schema.Schema} + @param root: The XML root node. + @type root: L{Element} + + """ + self.schema = schema + self.root = root + self.id = objid(self) + self.name = root.get("name") + self.qname = (self.name, schema.tns[1]) + self.min = root.get("minOccurs") + self.max = root.get("maxOccurs") + self.type = root.get("type") + self.ref = root.get("ref") + self.form_qualified = schema.form_qualified + self.nillable = False + self.default = root.get("default") + self.rawchildren = [] + + def attributes(self, filter=Filter()): + """ + Get only the attribute content. + + @param filter: A filter to constrain the result. + @type filter: L{Filter} + @return: A list of (attr, ancestry) tuples. + @rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..] + + """ + result = [] + for child, ancestry in self: + if child.isattr() and child in filter: + result.append((child, ancestry)) + return result + + def children(self, filter=Filter()): + """ + Get only the I{direct} or non-attribute content. + + @param filter: A filter to constrain the result. + @type filter: L{Filter} + @return: A list tuples: (child, ancestry) + @rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..] + + """ + result = [] + for child, ancestry in self: + if not child.isattr() and child in filter: + result.append((child, ancestry)) + return result + + def get_attribute(self, name): + """ + Get (find) an attribute by name. + + @param name: A attribute name. + @type name: str + @return: A tuple: the requested (attribute, ancestry). + @rtype: (L{SchemaObject}, [L{SchemaObject},..]) + + """ + for child, ancestry in self.attributes(): + if child.name == name: + return child, ancestry + return None, [] + + def get_child(self, name): + """ + Get (find) a I{non-attribute} child by name. + + @param name: A child name. + @type name: str + @return: A tuple: the requested (child, ancestry). + @rtype: (L{SchemaObject}, [L{SchemaObject},..]) + + """ + for child, ancestry in self.children(): + if child.any() or child.name == name: + return child, ancestry + return None, [] + + def namespace(self, prefix=None): + """ + Get this property's namespace. + + @param prefix: The default prefix. + @type prefix: str + @return: The schema's target namespace. + @rtype: (I{prefix}, I{URI}) + + """ + ns = self.schema.tns + if ns[0] is None: + ns = (prefix, ns[1]) + return ns + + def default_namespace(self): + return self.root.defaultNamespace() + + def multi_occurrence(self): + """ + Get whether the node has multiple occurrences, i.e. is a I{collection}. + + @return: True if it has, False if it has at most 1 occurrence. + @rtype: boolean + + """ + max = self.max + if max is None: + return False + if max.isdigit(): + return int(max) > 1 + return max == "unbounded" + + def optional(self): + """ + Get whether this type is optional. + + @return: True if optional, else False. + @rtype: boolean + + """ + return self.min == "0" + + def required(self): + """ + Get whether this type is required. + + @return: True if required, else False. + @rtype: boolean + + """ + return not self.optional() + + def resolve(self, nobuiltin=False): + """ + Resolve the node's type reference and return the referenced type node. + + Only XSD schema objects that actually support 'having a type' custom + implement this interface while others simply resolve as themselves. + + @param nobuiltin: Flag indicating whether resolving to an external XSD + built-in type should not be allowed. + @return: The resolved (true) type. + @rtype: L{SchemaObject} + """ + return self + + def sequence(self): + """ + Get whether this is an . + + @return: True if , else False. + @rtype: boolean + + """ + return False + + def xslist(self): + """ + Get whether this is an . + + @return: True if , else False. + @rtype: boolean + + """ + return False + + def all(self): + """ + Get whether this is an . + + @return: True if , else False. + @rtype: boolean + + """ + return False + + def choice(self): + """ + Get whether this is an . + + @return: True if , else False. + @rtype: boolean + + """ + return False + + def any(self): + """ + Get whether this is an . + + @return: True if , else False. + @rtype: boolean + + """ + return False + + def builtin(self): + """ + Get whether this is a built-in schema-instance XSD type. + + @return: True if a built-in type, else False. + @rtype: boolean + + """ + return False + + def enum(self): + """ + Get whether this is a simple-type containing an enumeration. + + @return: True if enumeration, else False. + @rtype: boolean + + """ + return False + + def isattr(self): + """ + Get whether the object is a schema I{attribute} definition. + + @return: True if an attribute, else False. + @rtype: boolean + + """ + return False + + def extension(self): + """ + Get whether the object is an extension of another type. + + @return: True if an extension, else False. + @rtype: boolean + + """ + return False + + def restriction(self): + """ + Get whether the object is an restriction of another type. + + @return: True if a restriction, else False. + @rtype: boolean + + """ + return False + + def mixed(self): + """Get whether the object has I{mixed} content.""" + return False + + def find(self, qref, classes=[], ignore=None): + """ + Find a referenced type in self or children. Return None if not found. + + Qualified references for all schema objects checked in this search will + be added to the set of ignored qualified references to avoid the find + operation going into an infinite loop in case of recursively defined + structures. + + @param qref: A qualified reference. + @type qref: qref + @param classes: A collection of classes used to qualify the match. + @type classes: Collection(I{class},...), e.g. [I(class),...] + @param ignore: A set of qualified references to ignore in this search. + @type ignore: {qref,...} + @return: The referenced type. + @rtype: L{SchemaObject} + @see: L{qualify()} + + """ + if not len(classes): + classes = (self.__class__,) + if ignore is None: + ignore = set() + if self.qname in ignore: + return + ignore.add(self.qname) + if self.qname == qref and self.__class__ in classes: + return self + for c in self.rawchildren: + p = c.find(qref, classes, ignore=ignore) + if p is not None: + return p + + def translate(self, value, topython=True): + """ + Translate between an XSD type values and Python objects. + + When converting a Python object to an XSD type value the operation may + return any Python object whose string representation matches the + desired XSD type value. + + @param value: A value to translate. + @type value: str if topython is True; any Python object otherwise + @param topython: Flag indicating the translation direction. + @type topython: bool + @return: The converted I{language} type. + + """ + return value + + def childtags(self): + """ + Get a list of valid child tag names. + + @return: A list of child tag names. + @rtype: [str,...] + + """ + return () + + def dependencies(self): + """ + Get a list of dependencies for dereferencing. + + @return: A merge dependency index and a list of dependencies. + @rtype: (int, [L{SchemaObject},...]) + + """ + return None, [] + + def autoqualified(self): + """ + The list of I{auto} qualified attribute values. + + Qualification means to convert values into I{qref}. + + @return: A list of attribute names. + @rtype: list + + """ + return ["type", "ref"] + + def qualify(self): + """ + Convert reference attribute values into a I{qref}. + + Constructed I{qref} uses the default document namespace. Since many + WSDL schemas are written improperly: when the document does not define + its default namespace, the schema target namespace is used to qualify + references. + + """ + defns = self.root.defaultNamespace() + if Namespace.none(defns): + defns = self.schema.tns + for a in self.autoqualified(): + ref = getattr(self, a) + if ref is None: + continue + if isqref(ref): + continue + qref = qualify(ref, self.root, defns) + log.debug("%s, convert %s='%s' to %s", self.id, a, ref, qref) + setattr(self, a, qref) + + def merge(self, other): + """Merge another object as needed.""" + other.qualify() + for n in ("default", "max", "min", "name", "nillable", "qname", + "type"): + if getattr(self, n) is not None: + continue + v = getattr(other, n) + if v is None: + continue + setattr(self, n, v) + + def content(self, collection=None, filter=Filter(), history=None): + """ + Get a I{flattened} list of this node's contents. + + @param collection: A list to fill. + @type collection: list + @param filter: A filter used to constrain the result. + @type filter: L{Filter} + @param history: The history list used to prevent cyclic dependency. + @type history: list + @return: The filled list. + @rtype: list + + """ + if collection is None: + collection = [] + if history is None: + history = [] + if self in history: + return collection + history.append(self) + if self in filter: + collection.append(self) + for c in self.rawchildren: + c.content(collection, filter, history) + history.pop() + return collection + + def str(self, indent=0, history=None): + """ + Get a string representation of this object. + + @param indent: The indent. + @type indent: int + @return: A string. + @rtype: str + + """ + if history is None: + history = [] + if self in history: + return "%s ..." % Repr(self) + history.append(self) + tab = "%*s" % (indent * 3, "") + result = ["%s<%s" % (tab, self.id)] + for n in self.description(): + if not hasattr(self, n): + continue + v = getattr(self, n) + if v is None: + continue + result.append(' %s="%s"' % (n, v)) + if len(self): + result.append(">") + for c in self.rawchildren: + result.append("\n") + result.append(c.str(indent+1, history[:])) + if c.isattr(): + result.append("@") + result.append("\n%s" % (tab,)) + result.append("" % (self.__class__.__name__,)) + else: + result.append(" />") + return "".join(result) + + def description(self): + """ + Get the names used for repr() and str() description. + + @return: A dictionary of relevant attributes. + @rtype: [str,...] + + """ + return () + + def __unicode__(self): + return unicode(self.str()) + + def __repr__(self): + s = [] + s.append("<%s" % (self.id,)) + for n in self.description(): + if not hasattr(self, n): + continue + v = getattr(self, n) + if v is None: + continue + s.append(' %s="%s"' % (n, v)) + s.append(" />") + return "".join(s) + + def __len__(self): + n = 0 + for x in self: + n += 1 + return n + + def __iter__(self): + return Iter(self) + + def __getitem__(self, index): + """ + Returns a contained schema object referenced by its 0-based index. + + Returns None if such an object does not exist. + + """ + i = 0 + for c in self: + if i == index: + return c + i += 1 + + +class Iter: + """ + The content iterator - used to iterate the L{Content} children. + + The iterator provides a I{view} of the children that is free of container + elements such as , or . + + @ivar stack: A stack used to control nesting. + @type stack: list + + """ + + class Frame: + """A content iterator frame.""" + + def __init__(self, sx): + """ + @param sx: A schema object. + @type sx: L{SchemaObject} + + """ + self.sx = sx + self.items = sx.rawchildren + self.index = 0 + + def next(self): + """ + Get the I{next} item in the frame's collection. + + @return: The next item or None + @rtype: L{SchemaObject} + + """ + if self.index < len(self.items): + result = self.items[self.index] + self.index += 1 + return result + + def __init__(self, sx): + """ + @param sx: A schema object. + @type sx: L{SchemaObject} + + """ + self.stack = [] + self.push(sx) + + def push(self, sx): + """ + Create a frame and push the specified object. + + @param sx: A schema object to push. + @type sx: L{SchemaObject} + + """ + self.stack.append(Iter.Frame(sx)) + + def pop(self): + """ + Pop the I{top} frame. + + @return: The popped frame. + @rtype: L{Frame} + @raise StopIteration: when stack is empty. + + """ + if self.stack: + return self.stack.pop() + raise StopIteration() + + def top(self): + """ + Get the I{top} frame. + + @return: The top frame. + @rtype: L{Frame} + @raise StopIteration: when stack is empty. + + """ + if self.stack: + return self.stack[-1] + raise StopIteration() + + def next(self): + """ + Get the next item. + + @return: A tuple: the next (child, ancestry). + @rtype: (L{SchemaObject}, [L{SchemaObject},..]) + @raise StopIteration: A the end. + + """ + frame = self.top() + while True: + result = frame.next() + if result is None: + self.pop() + return self.next() + if isinstance(result, Content): + ancestry = [f.sx for f in self.stack] + return result, ancestry + self.push(result) + return self.next() + + def __iter__(self): + return self + + +class XBuiltin(SchemaObject): + """Represents a built-in XSD schema node.""" + + def __init__(self, schema, name): + """ + @param schema: The containing schema. + @type schema: L{schema.Schema} + + """ + root = Element(name) + SchemaObject.__init__(self, schema, root) + self.name = name + self.nillable = True + + def namespace(self, prefix=None): + return Namespace.xsdns + + def builtin(self): + return True + + +class Content(SchemaObject): + """XSD schema objects representing real XML document content.""" + pass + + +class NodeFinder: + """ + Find nodes based on flexable criteria. + + I{matcher} may be any object implementing a match(n) method. + + @ivar matcher: An object used as criteria for match. + @type matcher: I{any}.match(n) + @ivar limit: Limit the number of matches. 0=unlimited. + @type limit: int + + """ + def __init__(self, matcher, limit=0): + """ + @param matcher: An object used as criteria for match. + @type matcher: I{any}.match(n) + @param limit: Limit the number of matches. 0=unlimited. + @type limit: int + + """ + self.matcher = matcher + self.limit = limit + + def find(self, node, list): + """ + Traverse the tree looking for matches. + + @param node: A node to match on. + @type node: L{SchemaObject} + @param list: A list to fill. + @type list: list + + """ + if self.matcher.match(node): + list.append(node) + self.limit -= 1 + if self.limit == 0: + return + for c in node.rawchildren: + self.find(c, list) + return self diff --git a/pym/calculate/contrib/suds/xsd/sxbase.pyc b/pym/calculate/contrib/suds/xsd/sxbase.pyc new file mode 100644 index 0000000..30dd780 Binary files /dev/null and b/pym/calculate/contrib/suds/xsd/sxbase.pyc differ diff --git a/pym/calculate/contrib/suds/xsd/sxbasic.py b/pym/calculate/contrib/suds/xsd/sxbasic.py new file mode 100644 index 0000000..0f659ba --- /dev/null +++ b/pym/calculate/contrib/suds/xsd/sxbasic.py @@ -0,0 +1,862 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +"""Classes representing I{basic} XSD schema objects.""" + +from suds import * +from suds.reader import DocumentReader +from suds.sax import Namespace +from suds.transport import TransportError +from suds.xsd import * +from suds.xsd.query import * +from suds.xsd.sxbase import * + +from urlparse import urljoin + +from logging import getLogger +log = getLogger(__name__) + + +class RestrictionMatcher: + """For use with L{NodeFinder} to match restriction.""" + def match(self, n): + return isinstance(n, Restriction) + + +class TypedContent(Content): + """Represents any I{typed} content.""" + + def __init__(self, *args, **kwargs): + Content.__init__(self, *args, **kwargs) + self.resolved_cache = {} + + def resolve(self, nobuiltin=False): + """ + Resolve the node's type reference and return the referenced type node. + + Returns self if the type is defined locally, e.g. as a + subnode. Otherwise returns the referenced external node. + + @param nobuiltin: Flag indicating whether resolving to XSD built-in + types should not be allowed. + @return: The resolved (true) type. + @rtype: L{SchemaObject} + + """ + cached = self.resolved_cache.get(nobuiltin) + if cached is not None: + return cached + resolved = self.__resolve_type(nobuiltin) + self.resolved_cache[nobuiltin] = resolved + return resolved + + def __resolve_type(self, nobuiltin=False): + """ + Private resolve() worker without any result caching. + + @param nobuiltin: Flag indicating whether resolving to XSD built-in + types should not be allowed. + @return: The resolved (true) type. + @rtype: L{SchemaObject} + + """ + # There is no need for a recursive implementation here since a node can + # reference an external type node but XSD specification explicitly + # states that that external node must not be a reference to yet another + # node. + qref = self.qref() + if qref is None: + return self + query = TypeQuery(qref) + query.history = [self] + log.debug("%s, resolving: %s\n using:%s", self.id, qref, query) + resolved = query.execute(self.schema) + if resolved is None: + log.debug(self.schema) + raise TypeNotFound(qref) + if resolved.builtin() and nobuiltin: + return self + return resolved + + def qref(self): + """ + Get the I{type} qualified reference to the referenced XSD type. + + This method takes into account simple types defined through restriction + which are detected by determining that self is simple (len == 0) and by + finding a restriction child. + + @return: The I{type} qualified reference. + @rtype: qref + + """ + qref = self.type + if qref is None and len(self) == 0: + ls = [] + m = RestrictionMatcher() + finder = NodeFinder(m, 1) + finder.find(self, ls) + if ls: + return ls[0].ref + return qref + + +class Complex(SchemaObject): + """ + Represents an XSD schema node. + + @cvar childtags: A list of valid child node names. + @type childtags: (I{str},...) + + """ + + def childtags(self): + return ("all", "any", "attribute", "attributeGroup", "choice", + "complexContent", "group", "sequence", "simpleContent") + + def description(self): + return ("name",) + + def extension(self): + for c in self.rawchildren: + if c.extension(): + return True + return False + + def mixed(self): + for c in self.rawchildren: + if isinstance(c, SimpleContent) and c.mixed(): + return True + return False + + +class Group(SchemaObject): + """ + Represents an XSD schema node. + + @cvar childtags: A list of valid child node names. + @type childtags: (I{str},...) + + """ + + def childtags(self): + return "all", "choice", "sequence" + + def dependencies(self): + deps = [] + midx = None + if self.ref is not None: + query = GroupQuery(self.ref) + g = query.execute(self.schema) + if g is None: + log.debug(self.schema) + raise TypeNotFound(self.ref) + deps.append(g) + midx = 0 + return midx, deps + + def merge(self, other): + SchemaObject.merge(self, other) + self.rawchildren = other.rawchildren + + def description(self): + return "name", "ref" + + +class AttributeGroup(SchemaObject): + """ + Represents an XSD schema node. + + @cvar childtags: A list of valid child node names. + @type childtags: (I{str},...) + + """ + + def childtags(self): + return "attribute", "attributeGroup" + + def dependencies(self): + deps = [] + midx = None + if self.ref is not None: + query = AttrGroupQuery(self.ref) + ag = query.execute(self.schema) + if ag is None: + log.debug(self.schema) + raise TypeNotFound(self.ref) + deps.append(ag) + midx = 0 + return midx, deps + + def merge(self, other): + SchemaObject.merge(self, other) + self.rawchildren = other.rawchildren + + def description(self): + return "name", "ref" + + +class Simple(SchemaObject): + """Represents an XSD schema node.""" + + def childtags(self): + return "any", "list", "restriction" + + def enum(self): + for child, ancestry in self.children(): + if isinstance(child, Enumeration): + return True + return False + + def mixed(self): + return len(self) + + def description(self): + return ("name",) + + def extension(self): + for c in self.rawchildren: + if c.extension(): + return True + return False + + def restriction(self): + for c in self.rawchildren: + if c.restriction(): + return True + return False + + +class List(SchemaObject): + """Represents an XSD schema node.""" + + def childtags(self): + return () + + def description(self): + return ("name",) + + def xslist(self): + return True + + +class Restriction(SchemaObject): + """Represents an XSD schema node.""" + + def __init__(self, schema, root): + SchemaObject.__init__(self, schema, root) + self.ref = root.get("base") + + def childtags(self): + return "attribute", "attributeGroup", "enumeration" + + def dependencies(self): + deps = [] + midx = None + if self.ref is not None: + query = TypeQuery(self.ref) + super = query.execute(self.schema) + if super is None: + log.debug(self.schema) + raise TypeNotFound(self.ref) + if not super.builtin(): + deps.append(super) + midx = 0 + return midx, deps + + def restriction(self): + return True + + def merge(self, other): + SchemaObject.merge(self, other) + filter = Filter(False, self.rawchildren) + self.prepend(self.rawchildren, other.rawchildren, filter) + + def description(self): + return ("ref",) + + +class Collection(SchemaObject): + """Represents an XSD schema collection (a.k.a. order indicator) node.""" + + def childtags(self): + return "all", "any", "choice", "element", "group", "sequence" + + +class All(Collection): + """Represents an XSD schema node.""" + def all(self): + return True + + +class Choice(Collection): + """Represents an XSD schema node.""" + def choice(self): + return True + + +class Sequence(Collection): + """Represents an XSD schema node.""" + def sequence(self): + return True + + +class ComplexContent(SchemaObject): + """Represents an XSD schema node.""" + + def childtags(self): + return "attribute", "attributeGroup", "extension", "restriction" + + def extension(self): + for c in self.rawchildren: + if c.extension(): + return True + return False + + def restriction(self): + for c in self.rawchildren: + if c.restriction(): + return True + return False + + +class SimpleContent(SchemaObject): + """Represents an XSD schema node.""" + + def childtags(self): + return "extension", "restriction" + + def extension(self): + for c in self.rawchildren: + if c.extension(): + return True + return False + + def restriction(self): + for c in self.rawchildren: + if c.restriction(): + return True + return False + + def mixed(self): + return len(self) + + +class Enumeration(Content): + """Represents an XSD schema node.""" + + def __init__(self, schema, root): + Content.__init__(self, schema, root) + self.name = root.get("value") + + def description(self): + return ("name",) + + def enum(self): + return True + + +class Element(TypedContent): + """Represents an XSD schema node.""" + + def __init__(self, schema, root): + TypedContent.__init__(self, schema, root) + is_reference = self.ref is not None + is_top_level = root.parent is schema.root + if is_reference or is_top_level: + self.form_qualified = True + else: + form = root.get("form") + if form is not None: + self.form_qualified = (form == "qualified") + nillable = self.root.get("nillable") + if nillable is not None: + self.nillable = (nillable in ("1", "true")) + self.implany() + + def implany(self): + """ + Set the type to when implicit. + + An element has an implicit type when it has no body and no + explicitly defined type. + + @return: self + @rtype: L{Element} + + """ + if self.type is None and self.ref is None and self.root.isempty(): + self.type = self.anytype() + + def childtags(self): + return "any", "attribute", "complexType", "simpleType" + + def extension(self): + for c in self.rawchildren: + if c.extension(): + return True + return False + + def restriction(self): + for c in self.rawchildren: + if c.restriction(): + return True + return False + + def dependencies(self): + deps = [] + midx = None + e = self.__deref() + if e is not None: + deps.append(e) + midx = 0 + return midx, deps + + def merge(self, other): + SchemaObject.merge(self, other) + self.rawchildren = other.rawchildren + + def description(self): + return "name", "ref", "type" + + def anytype(self): + """Create an xsd:anyType reference.""" + p, u = Namespace.xsdns + mp = self.root.findPrefix(u) + if mp is None: + mp = p + self.root.addPrefix(p, u) + return ":".join((mp, "anyType")) + + def namespace(self, prefix=None): + """ + Get this schema element's target namespace. + + In case of reference elements, the target namespace is defined by the + referenced and not the referencing element node. + + @param prefix: The default prefix. + @type prefix: str + @return: The schema element's target namespace + @rtype: (I{prefix},I{URI}) + + """ + e = self.__deref() + if e is not None: + return e.namespace(prefix) + return super(Element, self).namespace() + + def __deref(self): + if self.ref is None: + return + query = ElementQuery(self.ref) + e = query.execute(self.schema) + if e is None: + log.debug(self.schema) + raise TypeNotFound(self.ref) + return e + + +class Extension(SchemaObject): + """Represents an XSD schema node.""" + + def __init__(self, schema, root): + SchemaObject.__init__(self, schema, root) + self.ref = root.get("base") + + def childtags(self): + return ("all", "attribute", "attributeGroup", "choice", "group", + "sequence") + + def dependencies(self): + deps = [] + midx = None + if self.ref is not None: + query = TypeQuery(self.ref) + super = query.execute(self.schema) + if super is None: + log.debug(self.schema) + raise TypeNotFound(self.ref) + if not super.builtin(): + deps.append(super) + midx = 0 + return midx, deps + + def merge(self, other): + SchemaObject.merge(self, other) + filter = Filter(False, self.rawchildren) + self.prepend(self.rawchildren, other.rawchildren, filter) + + def extension(self): + return self.ref is not None + + def description(self): + return ("ref",) + + +class Import(SchemaObject): + """ + Represents an XSD schema node. + + @cvar locations: A dictionary of namespace locations. + @type locations: dict + @ivar ns: The imported namespace. + @type ns: str + @ivar location: The (optional) location. + @type location: namespace-uri + @ivar opened: Opened and I{imported} flag. + @type opened: boolean + + """ + + locations = {} + + @classmethod + def bind(cls, ns, location=None): + """ + Bind a namespace to a schema location (URI). + + This is used for imports that do not specify a schemaLocation. + + @param ns: A namespace-uri. + @type ns: str + @param location: The (optional) schema location for the namespace. + (default=ns) + @type location: str + + """ + if location is None: + location = ns + cls.locations[ns] = location + + def __init__(self, schema, root): + SchemaObject.__init__(self, schema, root) + self.ns = (None, root.get("namespace")) + self.location = root.get("schemaLocation") + if self.location is None: + self.location = self.locations.get(self.ns[1]) + self.opened = False + + def open(self, options, loaded_schemata): + """ + Open and import the referenced schema. + + @param options: An options dictionary. + @type options: L{options.Options} + @param loaded_schemata: Already loaded schemata cache (URL --> Schema). + @type loaded_schemata: dict + @return: The referenced schema. + @rtype: L{Schema} + + """ + if self.opened: + return + self.opened = True + log.debug("%s, importing ns='%s', location='%s'", self.id, self.ns[1], + self.location) + result = self.__locate() + if result is None: + if self.location is None: + log.debug("imported schema (%s) not-found", self.ns[1]) + else: + url = self.location + if "://" not in url: + url = urljoin(self.schema.baseurl, url) + result = (loaded_schemata.get(url) or + self.__download(url, loaded_schemata, options)) + log.debug("imported:\n%s", result) + return result + + def __locate(self): + """Find the schema locally.""" + if self.ns[1] != self.schema.tns[1]: + return self.schema.locate(self.ns) + + def __download(self, url, loaded_schemata, options): + """Download the schema.""" + try: + reader = DocumentReader(options) + d = reader.open(url) + root = d.root() + root.set("url", url) + return self.schema.instance(root, url, loaded_schemata, options) + except TransportError: + msg = "import schema (%s) at (%s), failed" % (self.ns[1], url) + log.error("%s, %s", self.id, msg, exc_info=True) + raise Exception(msg) + + def description(self): + return "ns", "location" + + +class Include(SchemaObject): + """ + Represents an XSD schema node. + + @ivar location: The (optional) location. + @type location: namespace-uri + @ivar opened: Opened and I{imported} flag. + @type opened: boolean + + """ + + locations = {} + + def __init__(self, schema, root): + SchemaObject.__init__(self, schema, root) + self.location = root.get("schemaLocation") + if self.location is None: + self.location = self.locations.get(self.ns[1]) + self.opened = False + + def open(self, options, loaded_schemata): + """ + Open and include the referenced schema. + + @param options: An options dictionary. + @type options: L{options.Options} + @param loaded_schemata: Already loaded schemata cache (URL --> Schema). + @type loaded_schemata: dict + @return: The referenced schema. + @rtype: L{Schema} + + """ + if self.opened: + return + self.opened = True + log.debug("%s, including location='%s'", self.id, self.location) + url = self.location + if "://" not in url: + url = urljoin(self.schema.baseurl, url) + result = (loaded_schemata.get(url) or + self.__download(url, loaded_schemata, options)) + log.debug("included:\n%s", result) + return result + + def __download(self, url, loaded_schemata, options): + """Download the schema.""" + try: + reader = DocumentReader(options) + d = reader.open(url) + root = d.root() + root.set("url", url) + self.__applytns(root) + return self.schema.instance(root, url, loaded_schemata, options) + except TransportError: + msg = "include schema at (%s), failed" % url + log.error("%s, %s", self.id, msg, exc_info=True) + raise Exception(msg) + + def __applytns(self, root): + """Make sure included schema has the same target namespace.""" + TNS = "targetNamespace" + tns = root.get(TNS) + if tns is None: + tns = self.schema.tns[1] + root.set(TNS, tns) + else: + if self.schema.tns[1] != tns: + raise Exception, "%s mismatch" % TNS + + def description(self): + return "location" + + +class Attribute(TypedContent): + """Represents an XSD schema node.""" + + def __init__(self, schema, root): + TypedContent.__init__(self, schema, root) + self.use = root.get("use", default="") + + def childtags(self): + return ("restriction",) + + def isattr(self): + return True + + def get_default(self): + """ + Gets the attribute value. + + @return: The default value for the attribute + @rtype: str + + """ + return self.root.get("default", default="") + + def optional(self): + return self.use != "required" + + def dependencies(self): + deps = [] + midx = None + if self.ref is not None: + query = AttrQuery(self.ref) + a = query.execute(self.schema) + if a is None: + log.debug(self.schema) + raise TypeNotFound(self.ref) + deps.append(a) + midx = 0 + return midx, deps + + def description(self): + return "name", "ref", "type" + + +class Any(Content): + """Represents an XSD schema node.""" + + def get_child(self, name): + root = self.root.clone() + root.set("note", "synthesized (any) child") + child = Any(self.schema, root) + return child, [] + + def get_attribute(self, name): + root = self.root.clone() + root.set("note", "synthesized (any) attribute") + attribute = Any(self.schema, root) + return attribute, [] + + def any(self): + return True + + +class Factory: + """ + @cvar tags: A factory to create object objects based on tag. + @type tags: {tag:fn,} + + """ + + tags = { + "all": All, + "any": Any, + "attribute": Attribute, + "attributeGroup": AttributeGroup, + "choice": Choice, + "complexContent": ComplexContent, + "complexType": Complex, + "element": Element, + "enumeration": Enumeration, + "extension": Extension, + "group": Group, + "import": Import, + "include": Include, + "list": List, + "restriction": Restriction, + "simpleContent": SimpleContent, + "simpleType": Simple, + "sequence": Sequence, + } + + @classmethod + def maptag(cls, tag, fn): + """ + Map (override) tag => I{class} mapping. + + @param tag: An XSD tag name. + @type tag: str + @param fn: A function or class. + @type fn: fn|class. + + """ + cls.tags[tag] = fn + + @classmethod + def create(cls, root, schema): + """ + Create an object based on the root tag name. + + @param root: An XML root element. + @type root: L{Element} + @param schema: A schema object. + @type schema: L{schema.Schema} + @return: The created object. + @rtype: L{SchemaObject} + + """ + fn = cls.tags.get(root.name) + if fn is not None: + return fn(schema, root) + + @classmethod + def build(cls, root, schema, filter=("*",)): + """ + Build an xsobject representation. + + @param root: An schema XML root. + @type root: L{sax.element.Element} + @param filter: A tag filter. + @type filter: [str,...] + @return: A schema object graph. + @rtype: L{sxbase.SchemaObject} + + """ + children = [] + for node in root.getChildren(ns=Namespace.xsdns): + if "*" in filter or node.name in filter: + child = cls.create(node, schema) + if child is None: + continue + children.append(child) + c = cls.build(node, schema, child.childtags()) + child.rawchildren = c + return children + + @classmethod + def collate(cls, children): + imports = [] + elements = {} + attributes = {} + types = {} + groups = {} + agrps = {} + for c in children: + if isinstance(c, (Import, Include)): + imports.append(c) + continue + if isinstance(c, Attribute): + attributes[c.qname] = c + continue + if isinstance(c, Element): + elements[c.qname] = c + continue + if isinstance(c, Group): + groups[c.qname] = c + continue + if isinstance(c, AttributeGroup): + agrps[c.qname] = c + continue + types[c.qname] = c + for i in imports: + children.remove(i) + return children, imports, attributes, elements, types, groups, agrps + + +####################################################### +# Static Import Bindings :-( +####################################################### +Import.bind( + "http://schemas.xmlsoap.org/soap/encoding/", + "suds://schemas.xmlsoap.org/soap/encoding/") +Import.bind( + "http://www.w3.org/XML/1998/namespace", + "http://www.w3.org/2001/xml.xsd") +Import.bind( + "http://www.w3.org/2001/XMLSchema", + "http://www.w3.org/2001/XMLSchema.xsd") diff --git a/pym/calculate/contrib/suds/xsd/sxbasic.pyc b/pym/calculate/contrib/suds/xsd/sxbasic.pyc new file mode 100644 index 0000000..d3bf658 Binary files /dev/null and b/pym/calculate/contrib/suds/xsd/sxbasic.pyc differ diff --git a/pym/calculate/contrib/suds/xsd/sxbuiltin.py b/pym/calculate/contrib/suds/xsd/sxbuiltin.py new file mode 100644 index 0000000..3537022 --- /dev/null +++ b/pym/calculate/contrib/suds/xsd/sxbuiltin.py @@ -0,0 +1,347 @@ +# -*- coding: utf-8 -*- + +# This program is free software; you can redistribute it and/or modify it under +# the terms of the (LGPL) GNU Lesser General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 Library Lesser General Public License +# for more details at ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +"""Classes representing I{built-in} XSD schema objects.""" + +from suds import * +from suds.xsd import * +from suds.sax.date import * +from suds.xsd.sxbase import XBuiltin + +import datetime +import decimal +import sys + + +class XAny(XBuiltin): + """Represents an XSD node.""" + + def __init__(self, schema, name): + XBuiltin.__init__(self, schema, name) + self.nillable = False + + def get_child(self, name): + child = XAny(self.schema, name) + return child, [] + + def any(self): + return True + + +class XBoolean(XBuiltin): + """Represents an XSD boolean built-in type.""" + + _xml_to_python = {"1": True, "true": True, "0": False, "false": False} + _python_to_xml = {True: "true", 1: "true", False: "false", 0: "false"} + + @staticmethod + def translate(value, topython=True): + if topython: + if isinstance(value, basestring): + return XBoolean._xml_to_python.get(value) + else: + if isinstance(value, (bool, int)): + return XBoolean._python_to_xml.get(value) + return value + + +class XDate(XBuiltin): + """Represents an XSD built-in type.""" + + @staticmethod + def translate(value, topython=True): + if topython: + if isinstance(value, basestring) and value: + return Date(value).value + else: + if isinstance(value, datetime.date): + return Date(value) + return value + + +class XDateTime(XBuiltin): + """Represents an XSD built-in type.""" + + @staticmethod + def translate(value, topython=True): + if topython: + if isinstance(value, basestring) and value: + return DateTime(value).value + else: + if isinstance(value, datetime.datetime): + return DateTime(value) + return value + + +class XDecimal(XBuiltin): + """ + Represents an XSD built-in type. + + Excerpt from the XSD datatype specification + (http://www.w3.org/TR/2004/REC-xmlschema-2-20041028): + + > 3.2.3 decimal + > + > [Definition:] decimal represents a subset of the real numbers, which can + > be represented by decimal numerals. The ·value space· of decimal is the + > set of numbers that can be obtained by multiplying an integer by a + > non-positive power of ten, i.e., expressible as i × 10^-n where i and n + > are integers and n >= 0. Precision is not reflected in this value space; + > the number 2.0 is not distinct from the number 2.00. The ·order-relation· + > on decimal is the order relation on real numbers, restricted to this + > subset. + > + > 3.2.3.1 Lexical representation + > + > decimal has a lexical representation consisting of a finite-length + > sequence of decimal digits (#x30-#x39) separated by a period as a decimal + > indicator. An optional leading sign is allowed. If the sign is omitted, + > "+" is assumed. Leading and trailing zeroes are optional. If the + > fractional part is zero, the period and following zero(es) can be + > omitted. For example: -1.23, 12678967.543233, +100000.00, 210. + + """ + + # Python versions before 2.7 do not support the decimal.Decimal.canonical() + # method but they maintain their decimal.Decimal encoded in canonical + # format internally so we can easily emulate that function by simply + # returning the same decimal instance. + if sys.version_info < (2, 7): + _decimal_canonical = staticmethod(lambda decimal: decimal) + else: + _decimal_canonical = decimal.Decimal.canonical + + @staticmethod + def _decimal_to_xsd_format(value): + """ + Converts a decimal.Decimal value to its XSD decimal type value. + + Result is a string containing the XSD decimal type's lexical value + representation. The conversion is done without any precision loss. + + Note that Python's native decimal.Decimal string representation will + not do here as the lexical representation desired here does not allow + representing decimal values using float-like `E' + format, e.g. 12E+30 or 0.10006E-12. + + """ + value = XDecimal._decimal_canonical(value) + negative, digits, exponent = value.as_tuple() + + # The following implementation assumes the following tuple decimal + # encoding (part of the canonical decimal value encoding): + # - digits must contain at least one element + # - no leading integral 0 digits except a single one in 0 (if a non-0 + # decimal value has leading integral 0 digits they must be encoded + # in its 'exponent' value and not included explicitly in its + # 'digits' tuple) + assert digits + assert digits[0] != 0 or len(digits) == 1 + + result = [] + if negative: + result.append("-") + + # No fractional digits. + if exponent >= 0: + result.extend(str(x) for x in digits) + result.extend("0" * exponent) + return "".join(result) + + digit_count = len(digits) + + # Decimal point offset from the given digit start. + point_offset = digit_count + exponent + + # Trim trailing fractional 0 digits. + fractional_digit_count = min(digit_count, -exponent) + while fractional_digit_count and digits[digit_count - 1] == 0: + digit_count -= 1 + fractional_digit_count -= 1 + + # No trailing fractional 0 digits and a decimal point coming not after + # the given digits, meaning there is no need to add additional trailing + # integral 0 digits. + if point_offset <= 0: + # No integral digits. + result.append("0") + if digit_count > 0: + result.append(".") + result.append("0" * -point_offset) + result.extend(str(x) for x in digits[:digit_count]) + else: + # Have integral and possibly some fractional digits. + result.extend(str(x) for x in digits[:point_offset]) + if point_offset < digit_count: + result.append(".") + result.extend(str(x) for x in digits[point_offset:digit_count]) + return "".join(result) + + @classmethod + def translate(cls, value, topython=True): + if topython: + if isinstance(value, basestring) and value: + return decimal.Decimal(value) + else: + if isinstance(value, decimal.Decimal): + return cls._decimal_to_xsd_format(value) + return value + + +class XFloat(XBuiltin): + """Represents an XSD built-in type.""" + + @staticmethod + def translate(value, topython=True): + if topython: + if isinstance(value, basestring) and value: + return float(value) + else: + return value + + +class XInteger(XBuiltin): + """Represents an XSD built-in type.""" + + @staticmethod + def translate(value, topython=True): + if topython: + if isinstance(value, basestring) and value: + return int(value) + else: + return value + + +class XLong(XBuiltin): + """Represents an XSD built-in type.""" + + @staticmethod + def translate(value, topython=True): + if topython: + if isinstance(value, basestring) and value: + return long(value) + else: + return value + + +class XString(XBuiltin): + """Represents an XSD node.""" + pass + + +class XTime(XBuiltin): + """Represents an XSD built-in type.""" + + @staticmethod + def translate(value, topython=True): + if topython: + if isinstance(value, basestring) and value: + return Time(value).value + else: + if isinstance(value, datetime.time): + return Time(value) + return value + + +class Factory: + + tags = { + # any + "anyType": XAny, + # strings + "string": XString, + "normalizedString": XString, + "ID": XString, + "Name": XString, + "QName": XString, + "NCName": XString, + "anySimpleType": XString, + "anyURI": XString, + "NOTATION": XString, + "token": XString, + "language": XString, + "IDREFS": XString, + "ENTITIES": XString, + "IDREF": XString, + "ENTITY": XString, + "NMTOKEN": XString, + "NMTOKENS": XString, + # binary + "hexBinary": XString, + "base64Binary": XString, + # integers + "int": XInteger, + "integer": XInteger, + "unsignedInt": XInteger, + "positiveInteger": XInteger, + "negativeInteger": XInteger, + "nonPositiveInteger": XInteger, + "nonNegativeInteger": XInteger, + # longs + "long": XLong, + "unsignedLong": XLong, + # shorts + "short": XInteger, + "unsignedShort": XInteger, + "byte": XInteger, + "unsignedByte": XInteger, + # floats + "float": XFloat, + "double": XFloat, + "decimal": XDecimal, + # dates & times + "date": XDate, + "time": XTime, + "dateTime": XDateTime, + "duration": XString, + "gYearMonth": XString, + "gYear": XString, + "gMonthDay": XString, + "gDay": XString, + "gMonth": XString, + # boolean + "boolean": XBoolean, + } + + @classmethod + def maptag(cls, tag, fn): + """ + Map (override) tag => I{class} mapping. + + @param tag: An XSD tag name. + @type tag: str + @param fn: A function or class. + @type fn: fn|class. + + """ + cls.tags[tag] = fn + + @classmethod + def create(cls, schema, name): + """ + Create an object based on the root tag name. + + @param schema: A schema object. + @type schema: L{schema.Schema} + @param name: The name. + @type name: str + @return: The created object. + @rtype: L{XBuiltin} + + """ + fn = cls.tags.get(name, XBuiltin) + return fn(schema, name) diff --git a/pym/calculate/contrib/suds/xsd/sxbuiltin.pyc b/pym/calculate/contrib/suds/xsd/sxbuiltin.pyc new file mode 100644 index 0000000..f136dfc Binary files /dev/null and b/pym/calculate/contrib/suds/xsd/sxbuiltin.pyc differ