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