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()
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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)
|
Binary file not shown.
@ -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)
|
Binary file not shown.
@ -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)
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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)
|
Binary file not shown.
@ -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)
|
Binary file not shown.
@ -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)
|
Binary file not shown.
@ -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)
|
Binary file not shown.
@ -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)
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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()
|
Binary file not shown.
@ -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),
|
||||||
|
".",
|
||||||
|
)
|
||||||
|
))
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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,), {})()
|
Binary file not shown.
@ -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)
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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()
|
Binary file not shown.
@ -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)
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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.')
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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),
|
||||||
|
]
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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)
|
Binary file not shown.
@ -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)
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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))),
|
||||||
|
]
|
Binary file not shown.
@ -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)
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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,
|
||||||
|
})
|
Binary file not shown.
@ -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,
|
||||||
|
})
|
Binary file not shown.
@ -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)
|
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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
|
||||||
|
|
Binary file not shown.
@ -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,
|
||||||
|
})
|
Binary file not shown.
@ -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})*'
|
Binary file not shown.
@ -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
|
||||||
|
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue