Add spyne suds to contrib

legacy27 3.6.10
idziubenko 3 years ago
parent 416cd40e26
commit 919113f3e0

@ -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()

@ -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

@ -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

@ -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

@ -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)

@ -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)

@ -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)

@ -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

@ -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)

@ -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)

@ -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)

@ -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)

@ -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)

@ -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

@ -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()

@ -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),
".",
)
))

@ -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

@ -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

@ -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,), {})()

@ -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)

@ -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

@ -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()

@ -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)

@ -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

@ -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.')

@ -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

@ -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),
]

@ -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:
# <?xml-stylesheet type="text/xsl" href="wsdl-viewer.xsl"?>"
# 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

@ -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

@ -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

@ -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)

@ -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)

@ -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

@ -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 <union> is not "
"implemented", name)
def process_simple_type(self, s, name=None):
"""Returns the simple Spyne type from `<simpleType>` 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 <include> or <import>
# 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

@ -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

File diff suppressed because it is too large Load Diff

@ -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))),
]

@ -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)

File diff suppressed because it is too large Load Diff

@ -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

@ -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 <http://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383507>`_,
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/
#
# <xs:element name="faultcode" type="xs:QName"/>
# <xs:element name="faultstring" type="xs:string"/>
# <xs:element name="faultactor" type="xs:anyURI" minOccurs="0"/>
# <xs:element name="detail" type="tns:detail" minOccurs="0"/>
#
@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

@ -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

@ -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,
})

@ -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<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})'
TIME_PATTERN = r'(?P<hr>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})(?P<sec_frac>\.\d+)?'
OFFSET_PATTERN = r'(?P<tz_hr>[+-]\d{2}):(?P<tz_min>\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=<as_timezone>)`` 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 <https://pypi.python.org/pypi/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,
})

@ -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)

@ -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

@ -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

@ -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 <a> 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,
})

@ -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})*'

@ -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

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save