parent
8b8b88253b
commit
7b8d2939e7
@ -1,164 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Lightweight SOAP Python client providing a Web Service proxy.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
#
|
||||
# Project properties
|
||||
#
|
||||
|
||||
from .version import __build__, __version__
|
||||
|
||||
|
||||
#
|
||||
# Exceptions
|
||||
#
|
||||
|
||||
class MethodNotFound(Exception):
|
||||
def __init__(self, name):
|
||||
Exception.__init__(self, u"Method not found: '%s'" % (name,))
|
||||
|
||||
class PortNotFound(Exception):
|
||||
def __init__(self, name):
|
||||
Exception.__init__(self, u"Port not found: '%s'" % (name,))
|
||||
|
||||
class ServiceNotFound(Exception):
|
||||
def __init__(self, name):
|
||||
Exception.__init__(self, u"Service not found: '%s'" % (name,))
|
||||
|
||||
class TypeNotFound(Exception):
|
||||
def __init__(self, name):
|
||||
Exception.__init__(self, u"Type not found: '%s'" % (tostr(name),))
|
||||
|
||||
class BuildError(Exception):
|
||||
def __init__(self, name, exception):
|
||||
Exception.__init__(self, u"An error occurred while building an "
|
||||
"instance of (%s). As a result the object you requested could not "
|
||||
"be constructed. It is recommended that you construct the type "
|
||||
"manually using a Suds object. Please open a ticket with a "
|
||||
"description of this error. Reason: %s" % (name, exception))
|
||||
|
||||
class WebFault(Exception):
|
||||
def __init__(self, fault, document):
|
||||
if hasattr(fault, "faultstring"):
|
||||
Exception.__init__(self, u"Server raised fault: '%s'" %
|
||||
(fault.faultstring,))
|
||||
self.fault = fault
|
||||
self.document = document
|
||||
|
||||
|
||||
#
|
||||
# Logging
|
||||
#
|
||||
|
||||
class Repr:
|
||||
def __init__(self, x):
|
||||
self.x = x
|
||||
def __str__(self):
|
||||
return repr(self.x)
|
||||
|
||||
|
||||
#
|
||||
# Utility
|
||||
#
|
||||
|
||||
class null:
|
||||
"""I{null} object used to pass NULL for optional XML nodes."""
|
||||
pass
|
||||
|
||||
def objid(obj):
|
||||
return obj.__class__.__name__ + ":" + hex(id(obj))
|
||||
|
||||
def tostr(object, encoding=None):
|
||||
"""Get a unicode safe string representation of an object."""
|
||||
if isinstance(object, basestring):
|
||||
if encoding is None:
|
||||
return object
|
||||
return object.encode(encoding)
|
||||
if isinstance(object, tuple):
|
||||
s = ["("]
|
||||
for item in object:
|
||||
s.append(tostr(item))
|
||||
s.append(", ")
|
||||
s.append(")")
|
||||
return "".join(s)
|
||||
if isinstance(object, list):
|
||||
s = ["["]
|
||||
for item in object:
|
||||
s.append(tostr(item))
|
||||
s.append(", ")
|
||||
s.append("]")
|
||||
return "".join(s)
|
||||
if isinstance(object, dict):
|
||||
s = ["{"]
|
||||
for item in object.items():
|
||||
s.append(tostr(item[0]))
|
||||
s.append(" = ")
|
||||
s.append(tostr(item[1]))
|
||||
s.append(", ")
|
||||
s.append("}")
|
||||
return "".join(s)
|
||||
try:
|
||||
return unicode(object)
|
||||
except Exception:
|
||||
return str(object)
|
||||
|
||||
|
||||
#
|
||||
# Python 3 compatibility
|
||||
#
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
from cStringIO import StringIO as BytesIO
|
||||
else:
|
||||
from io import BytesIO
|
||||
|
||||
# Idea from 'http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python'.
|
||||
class UnicodeMixin(object):
|
||||
if sys.version_info >= (3, 0):
|
||||
# For Python 3, __str__() and __unicode__() should be identical.
|
||||
__str__ = lambda x: x.__unicode__()
|
||||
else:
|
||||
__str__ = lambda x: unicode(x).encode("utf-8")
|
||||
|
||||
# Used instead of byte literals as they are not supported on Python versions
|
||||
# prior to 2.6.
|
||||
def byte_str(s="", encoding="utf-8", input_encoding="utf-8", errors="strict"):
|
||||
"""
|
||||
Returns a byte string version of 's', encoded as specified in 'encoding'.
|
||||
|
||||
Accepts str & unicode objects, interpreting non-unicode strings as byte
|
||||
strings encoded using the given input encoding.
|
||||
|
||||
"""
|
||||
assert isinstance(s, basestring)
|
||||
if isinstance(s, unicode):
|
||||
return s.encode(encoding, errors)
|
||||
if s and encoding != input_encoding:
|
||||
return s.decode(input_encoding, errors).encode(encoding, errors)
|
||||
return s
|
||||
|
||||
# Class used to represent a byte string. Useful for asserting that correct
|
||||
# string types are being passed around where needed.
|
||||
if sys.version_info >= (3, 0):
|
||||
byte_str_class = bytes
|
||||
else:
|
||||
byte_str_class = str
|
@ -1,419 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr )
|
||||
|
||||
"""
|
||||
Suds web service operation invocation function argument parser.
|
||||
|
||||
See the parse_args() function description for more detailed information.
|
||||
|
||||
"""
|
||||
|
||||
__all__ = ["parse_args"]
|
||||
|
||||
|
||||
def parse_args(method_name, param_defs, args, kwargs, external_param_processor,
|
||||
extra_parameter_errors):
|
||||
"""
|
||||
Parse arguments for suds web service operation invocation functions.
|
||||
|
||||
Suds prepares Python function objects for invoking web service operations.
|
||||
This function implements generic binding agnostic part of processing the
|
||||
arguments passed when calling those function objects.
|
||||
|
||||
Argument parsing rules:
|
||||
* Each input parameter element should be represented by single regular
|
||||
Python function argument.
|
||||
* At most one input parameter belonging to a single choice parameter
|
||||
structure may have its value specified as something other than None.
|
||||
* Positional arguments are mapped to choice group input parameters the
|
||||
same as is done for a simple all/sequence group - each in turn.
|
||||
|
||||
Expects to be passed the web service operation's parameter definitions
|
||||
(parameter name, type & optional ancestry information) in order and, based
|
||||
on that, extracts the values for those parameter from the arguments
|
||||
provided in the web service operation invocation call.
|
||||
|
||||
Ancestry information describes parameters constructed based on suds
|
||||
library's automatic input parameter structure unwrapping. It is expected to
|
||||
include the parameter's XSD schema 'ancestry' context, i.e. a list of all
|
||||
the parent XSD schema tags containing the parameter's <element> tag. Such
|
||||
ancestry context provides detailed information about how the parameter's
|
||||
value is expected to be used, especially in relation to other input
|
||||
parameters, e.g. at most one parameter value may be specified for
|
||||
parameters directly belonging to the same choice input group.
|
||||
|
||||
Rules on acceptable ancestry items:
|
||||
* Ancestry item's choice() method must return whether the item
|
||||
represents a <choice> XSD schema tag.
|
||||
* Passed ancestry items are used 'by address' internally and the same XSD
|
||||
schema tag is expected to be identified by the exact same ancestry item
|
||||
object during the whole argument processing.
|
||||
|
||||
During processing, each parameter's definition and value, together with any
|
||||
additional pertinent information collected from the encountered parameter
|
||||
definition structure, is passed on to the provided external parameter
|
||||
processor function. There that information is expected to be used to
|
||||
construct the actual binding specific web service operation invocation
|
||||
request.
|
||||
|
||||
Raises a TypeError exception in case any argument related errors are
|
||||
detected. The exceptions raised have been constructed to make them as
|
||||
similar as possible to their respective exceptions raised during regular
|
||||
Python function argument checking.
|
||||
|
||||
Does not support multiple same-named input parameters.
|
||||
|
||||
"""
|
||||
arg_parser = _ArgParser(method_name, param_defs, external_param_processor)
|
||||
return arg_parser(args, kwargs, extra_parameter_errors)
|
||||
|
||||
|
||||
class _ArgParser:
|
||||
"""Internal argument parser implementation function object."""
|
||||
|
||||
def __init__(self, method_name, param_defs, external_param_processor):
|
||||
self.__method_name = method_name
|
||||
self.__param_defs = param_defs
|
||||
self.__external_param_processor = external_param_processor
|
||||
self.__stack = []
|
||||
|
||||
def __call__(self, args, kwargs, extra_parameter_errors):
|
||||
"""
|
||||
Runs the main argument parsing operation.
|
||||
|
||||
Passed args & kwargs objects are not modified during parsing.
|
||||
|
||||
Returns an informative 2-tuple containing the number of required &
|
||||
allowed arguments.
|
||||
|
||||
"""
|
||||
assert not self.active(), "recursive argument parsing not allowed"
|
||||
self.__init_run(args, kwargs, extra_parameter_errors)
|
||||
try:
|
||||
self.__process_parameters()
|
||||
return self.__all_parameters_processed()
|
||||
finally:
|
||||
self.__cleanup_run()
|
||||
assert not self.active()
|
||||
|
||||
def active(self):
|
||||
"""
|
||||
Return whether this object is currently running argument processing.
|
||||
|
||||
Used to avoid recursively entering argument processing from within an
|
||||
external parameter processor.
|
||||
|
||||
"""
|
||||
return bool(self.__stack)
|
||||
|
||||
def __all_parameters_processed(self):
|
||||
"""
|
||||
Finish the argument processing.
|
||||
|
||||
Should be called after all the web service operation's parameters have
|
||||
been successfully processed and, afterwards, no further parameter
|
||||
processing is allowed.
|
||||
|
||||
Returns a 2-tuple containing the number of required & allowed
|
||||
arguments.
|
||||
|
||||
See the _ArgParser class description for more detailed information.
|
||||
|
||||
"""
|
||||
assert self.active()
|
||||
sentinel_frame = self.__stack[0]
|
||||
self.__pop_frames_above(sentinel_frame)
|
||||
assert len(self.__stack) == 1
|
||||
self.__pop_top_frame()
|
||||
assert not self.active()
|
||||
args_required = sentinel_frame.args_required()
|
||||
args_allowed = sentinel_frame.args_allowed()
|
||||
self.__check_for_extra_arguments(args_required, args_allowed)
|
||||
return args_required, args_allowed
|
||||
|
||||
def __check_for_extra_arguments(self, args_required, args_allowed):
|
||||
"""
|
||||
Report an error in case any extra arguments are detected.
|
||||
|
||||
Does nothing if reporting extra arguments as exceptions has not been
|
||||
enabled.
|
||||
|
||||
May only be called after the argument processing has been completed.
|
||||
|
||||
"""
|
||||
assert not self.active()
|
||||
if not self.__extra_parameter_errors:
|
||||
return
|
||||
|
||||
if self.__kwargs:
|
||||
param_name = self.__kwargs.keys()[0]
|
||||
if param_name in self.__params_with_arguments:
|
||||
msg = "got multiple values for parameter '%s'"
|
||||
else:
|
||||
msg = "got an unexpected keyword argument '%s'"
|
||||
self.__error(msg % (param_name,))
|
||||
|
||||
if self.__args:
|
||||
def plural_suffix(count):
|
||||
if count == 1:
|
||||
return ""
|
||||
return "s"
|
||||
def plural_was_were(count):
|
||||
if count == 1:
|
||||
return "was"
|
||||
return "were"
|
||||
expected = args_required
|
||||
if args_required != args_allowed:
|
||||
expected = "%d to %d" % (args_required, args_allowed)
|
||||
given = self.__args_count
|
||||
msg_parts = ["takes %s positional argument" % (expected,),
|
||||
plural_suffix(expected), " but %d " % (given,),
|
||||
plural_was_were(given), " given"]
|
||||
self.__error("".join(msg_parts))
|
||||
|
||||
def __cleanup_run(self):
|
||||
"""Cleans up after a completed argument parsing run."""
|
||||
self.__stack = []
|
||||
assert not self.active()
|
||||
|
||||
def __error(self, message):
|
||||
"""Report an argument processing error."""
|
||||
raise TypeError("%s() %s" % (self.__method_name, message))
|
||||
|
||||
def __frame_factory(self, ancestry_item):
|
||||
"""Construct a new frame representing the given ancestry item."""
|
||||
frame_class = Frame
|
||||
if ancestry_item is not None and ancestry_item.choice():
|
||||
frame_class = ChoiceFrame
|
||||
return frame_class(ancestry_item, self.__error,
|
||||
self.__extra_parameter_errors)
|
||||
|
||||
def __get_param_value(self, name):
|
||||
"""
|
||||
Extract a parameter value from the remaining given arguments.
|
||||
|
||||
Returns a 2-tuple consisting of the following:
|
||||
* Boolean indicating whether an argument has been specified for the
|
||||
requested input parameter.
|
||||
* Parameter value.
|
||||
|
||||
"""
|
||||
if self.__args:
|
||||
return True, self.__args.pop(0)
|
||||
try:
|
||||
value = self.__kwargs.pop(name)
|
||||
except KeyError:
|
||||
return False, None
|
||||
return True, value
|
||||
|
||||
def __in_choice_context(self):
|
||||
"""
|
||||
Whether we are currently processing a choice parameter group.
|
||||
|
||||
This includes processing a parameter defined directly or indirectly
|
||||
within such a group.
|
||||
|
||||
May only be called during parameter processing or the result will be
|
||||
calculated based on the context left behind by the previous parameter
|
||||
processing if any.
|
||||
|
||||
"""
|
||||
for x in self.__stack:
|
||||
if x.__class__ is ChoiceFrame:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __init_run(self, args, kwargs, extra_parameter_errors):
|
||||
"""Initializes data for a new argument parsing run."""
|
||||
assert not self.active()
|
||||
self.__args = list(args)
|
||||
self.__kwargs = dict(kwargs)
|
||||
self.__extra_parameter_errors = extra_parameter_errors
|
||||
self.__args_count = len(args) + len(kwargs)
|
||||
self.__params_with_arguments = set()
|
||||
self.__stack = []
|
||||
self.__push_frame(None)
|
||||
|
||||
def __match_ancestry(self, ancestry):
|
||||
"""
|
||||
Find frames matching the given ancestry.
|
||||
|
||||
Returns a tuple containing the following:
|
||||
* Topmost frame matching the given ancestry or the bottom-most sentry
|
||||
frame if no frame matches.
|
||||
* Unmatched ancestry part.
|
||||
|
||||
"""
|
||||
stack = self.__stack
|
||||
if len(stack) == 1:
|
||||
return stack[0], ancestry
|
||||
previous = stack[0]
|
||||
for frame, n in zip(stack[1:], xrange(len(ancestry))):
|
||||
if frame.id() is not ancestry[n]:
|
||||
return previous, ancestry[n:]
|
||||
previous = frame
|
||||
return frame, ancestry[n + 1:]
|
||||
|
||||
def __pop_frames_above(self, frame):
|
||||
"""Pops all the frames above, but not including the given frame."""
|
||||
while self.__stack[-1] is not frame:
|
||||
self.__pop_top_frame()
|
||||
assert self.__stack
|
||||
|
||||
def __pop_top_frame(self):
|
||||
"""Pops the top frame off the frame stack."""
|
||||
popped = self.__stack.pop()
|
||||
if self.__stack:
|
||||
self.__stack[-1].process_subframe(popped)
|
||||
|
||||
def __process_parameter(self, param_name, param_type, ancestry=None):
|
||||
"""Collect values for a given web service operation input parameter."""
|
||||
assert self.active()
|
||||
param_optional = param_type.optional()
|
||||
has_argument, value = self.__get_param_value(param_name)
|
||||
if has_argument:
|
||||
self.__params_with_arguments.add(param_name)
|
||||
self.__update_context(ancestry)
|
||||
self.__stack[-1].process_parameter(param_optional, value is not None)
|
||||
self.__external_param_processor(param_name, param_type,
|
||||
self.__in_choice_context(), value)
|
||||
|
||||
def __process_parameters(self):
|
||||
"""Collect values for given web service operation input parameters."""
|
||||
for pdef in self.__param_defs:
|
||||
self.__process_parameter(*pdef)
|
||||
|
||||
def __push_frame(self, ancestry_item):
|
||||
"""Push a new frame on top of the frame stack."""
|
||||
frame = self.__frame_factory(ancestry_item)
|
||||
self.__stack.append(frame)
|
||||
|
||||
def __push_frames(self, ancestry):
|
||||
"""
|
||||
Push new frames representing given ancestry items.
|
||||
|
||||
May only be given ancestry items other than None. Ancestry item None
|
||||
represents the internal sentinel item and should never appear in a
|
||||
given parameter's ancestry information.
|
||||
|
||||
"""
|
||||
for x in ancestry:
|
||||
assert x is not None
|
||||
self.__push_frame(x)
|
||||
|
||||
def __update_context(self, ancestry):
|
||||
if not ancestry:
|
||||
return
|
||||
match_result = self.__match_ancestry(ancestry)
|
||||
last_matching_frame, unmatched_ancestry = match_result
|
||||
self.__pop_frames_above(last_matching_frame)
|
||||
self.__push_frames(unmatched_ancestry)
|
||||
|
||||
|
||||
class Frame:
|
||||
"""
|
||||
Base _ArgParser context frame.
|
||||
|
||||
When used directly, as opposed to using a derived class, may represent any
|
||||
input parameter context/ancestry item except a choice order indicator.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, id, error, extra_parameter_errors):
|
||||
"""
|
||||
Construct a new Frame instance.
|
||||
|
||||
Passed error function is used to report any argument checking errors.
|
||||
|
||||
"""
|
||||
assert self.__class__ != Frame or not id or not id.choice()
|
||||
self.__id = id
|
||||
self._error = error
|
||||
self._extra_parameter_errors = extra_parameter_errors
|
||||
self._args_allowed = 0
|
||||
self._args_required = 0
|
||||
self._has_value = False
|
||||
|
||||
def args_allowed(self):
|
||||
return self._args_allowed
|
||||
|
||||
def args_required(self):
|
||||
return self._args_required
|
||||
|
||||
def has_value(self):
|
||||
return self._has_value
|
||||
|
||||
def id(self):
|
||||
return self.__id
|
||||
|
||||
def process_parameter(self, optional, has_value):
|
||||
args_required = 1
|
||||
if optional:
|
||||
args_required = 0
|
||||
self._process_item(has_value, 1, args_required)
|
||||
|
||||
def process_subframe(self, subframe):
|
||||
self._process_item(
|
||||
subframe.has_value(),
|
||||
subframe.args_allowed(),
|
||||
subframe.args_required())
|
||||
|
||||
def _process_item(self, has_value, args_allowed, args_required):
|
||||
self._args_allowed += args_allowed
|
||||
self._args_required += args_required
|
||||
if has_value:
|
||||
self._has_value = True
|
||||
|
||||
|
||||
class ChoiceFrame(Frame):
|
||||
"""
|
||||
_ArgParser context frame representing a choice order indicator.
|
||||
|
||||
A choice requires as many input arguments as are needed to satisfy the
|
||||
least requiring of its items. For example, if we use I(n) to identify an
|
||||
item requiring n parameter, then a choice containing I(2), I(3) & I(7)
|
||||
requires 2 arguments while a choice containing I(5) & I(4) requires 4.
|
||||
|
||||
Accepts an argument for each of its contained elements but allows at most
|
||||
one of its directly contained items to have a defined value.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, id, error, extra_parameter_errors):
|
||||
assert id.choice()
|
||||
Frame.__init__(self, id, error, extra_parameter_errors)
|
||||
self.__has_item = False
|
||||
|
||||
def _process_item(self, has_value, args_allowed, args_required):
|
||||
self._args_allowed += args_allowed
|
||||
self.__update_args_required_for_item(args_required)
|
||||
self.__update_has_value_for_item(has_value)
|
||||
|
||||
def __update_args_required_for_item(self, item_args_required):
|
||||
if not self.__has_item:
|
||||
self.__has_item = True
|
||||
self._args_required = item_args_required
|
||||
return
|
||||
self._args_required = min(self.args_required(), item_args_required)
|
||||
|
||||
def __update_has_value_for_item(self, item_has_value):
|
||||
if item_has_value:
|
||||
if self.has_value() and self._extra_parameter_errors:
|
||||
self._error("got multiple values for a single choice "
|
||||
"parameter")
|
||||
self._has_value = True
|
@ -1,18 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides modules containing classes to support Web Services (SOAP) bindings.
|
||||
"""
|
@ -1,510 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
(WS) SOAP binding classes.
|
||||
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.sax import Namespace
|
||||
from suds.sax.document import Document
|
||||
from suds.sax.element import Element
|
||||
from suds.sudsobject import Factory
|
||||
from suds.mx import Content
|
||||
from suds.mx.literal import Literal as MxLiteral
|
||||
from suds.umx.typed import Typed as UmxTyped
|
||||
from suds.bindings.multiref import MultiRef
|
||||
from suds.xsd.query import TypeQuery, ElementQuery
|
||||
from suds.xsd.sxbasic import Element as SchemaElement
|
||||
from suds.options import Options
|
||||
from suds.plugin import PluginContainer
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
envns = ("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/")
|
||||
|
||||
|
||||
class Binding(object):
|
||||
"""
|
||||
The SOAP binding class used to process outgoing and incoming SOAP messages
|
||||
per the WSDL port binding.
|
||||
|
||||
@ivar wsdl: The WSDL.
|
||||
@type wsdl: L{suds.wsdl.Definitions}
|
||||
@ivar schema: The collective schema contained within the WSDL.
|
||||
@type schema: L{xsd.schema.Schema}
|
||||
@ivar options: A dictionary options.
|
||||
@type options: L{Options}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, wsdl):
|
||||
"""
|
||||
@param wsdl: A WSDL.
|
||||
@type wsdl: L{wsdl.Definitions}
|
||||
|
||||
"""
|
||||
self.wsdl = wsdl
|
||||
self.multiref = MultiRef()
|
||||
|
||||
def schema(self):
|
||||
return self.wsdl.schema
|
||||
|
||||
def options(self):
|
||||
return self.wsdl.options
|
||||
|
||||
def unmarshaller(self):
|
||||
"""
|
||||
Get the appropriate schema based XML decoder.
|
||||
|
||||
@return: Typed unmarshaller.
|
||||
@rtype: L{UmxTyped}
|
||||
|
||||
"""
|
||||
return UmxTyped(self.schema())
|
||||
|
||||
def marshaller(self):
|
||||
"""
|
||||
Get the appropriate XML encoder.
|
||||
|
||||
@return: An L{MxLiteral} marshaller.
|
||||
@rtype: L{MxLiteral}
|
||||
|
||||
"""
|
||||
return MxLiteral(self.schema(), self.options().xstq)
|
||||
|
||||
def param_defs(self, method):
|
||||
"""
|
||||
Get parameter definitions.
|
||||
|
||||
Each I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple.
|
||||
|
||||
@param method: A service method.
|
||||
@type method: I{service.Method}
|
||||
@return: A collection of parameter definitions
|
||||
@rtype: [I{pdef},...]
|
||||
|
||||
"""
|
||||
raise Exception("not implemented")
|
||||
|
||||
def get_message(self, method, args, kwargs):
|
||||
"""
|
||||
Get a SOAP message for the specified method, args and SOAP headers.
|
||||
|
||||
This is the entry point for creating an outbound SOAP message.
|
||||
|
||||
@param method: The method being invoked.
|
||||
@type method: I{service.Method}
|
||||
@param args: A list of args for the method invoked.
|
||||
@type args: list
|
||||
@param kwargs: Named (keyword) args for the method invoked.
|
||||
@type kwargs: dict
|
||||
@return: The SOAP envelope.
|
||||
@rtype: L{Document}
|
||||
|
||||
"""
|
||||
content = self.headercontent(method)
|
||||
header = self.header(content)
|
||||
content = self.bodycontent(method, args, kwargs)
|
||||
body = self.body(content)
|
||||
env = self.envelope(header, body)
|
||||
if self.options().prefixes:
|
||||
body.normalizePrefixes()
|
||||
env.promotePrefixes()
|
||||
else:
|
||||
env.refitPrefixes()
|
||||
return Document(env)
|
||||
|
||||
def get_reply(self, method, replyroot):
|
||||
"""
|
||||
Process the I{reply} for the specified I{method} by unmarshalling it
|
||||
into into Python object(s).
|
||||
|
||||
@param method: The name of the invoked method.
|
||||
@type method: str
|
||||
@param replyroot: The reply XML root node received after invoking the
|
||||
specified method.
|
||||
@type replyroot: L{Element}
|
||||
@return: The unmarshalled reply. The returned value is an L{Object} or
|
||||
a I{list} depending on whether the service returns a single object
|
||||
or a collection.
|
||||
@rtype: L{Object} or I{list}
|
||||
|
||||
"""
|
||||
soapenv = replyroot.getChild("Envelope", envns)
|
||||
soapenv.promotePrefixes()
|
||||
soapbody = soapenv.getChild("Body", envns)
|
||||
soapbody = self.multiref.process(soapbody)
|
||||
nodes = self.replycontent(method, soapbody)
|
||||
rtypes = self.returned_types(method)
|
||||
if len(rtypes) > 1:
|
||||
return self.replycomposite(rtypes, nodes)
|
||||
if len(rtypes) == 0:
|
||||
return
|
||||
if rtypes[0].multi_occurrence():
|
||||
return self.replylist(rtypes[0], nodes)
|
||||
if len(nodes):
|
||||
resolved = rtypes[0].resolve(nobuiltin=True)
|
||||
return self.unmarshaller().process(nodes[0], resolved)
|
||||
|
||||
def replylist(self, rt, nodes):
|
||||
"""
|
||||
Construct a I{list} reply.
|
||||
|
||||
Called for replies with possible multiple occurrences.
|
||||
|
||||
@param rt: The return I{type}.
|
||||
@type rt: L{suds.xsd.sxbase.SchemaObject}
|
||||
@param nodes: A collection of XML nodes.
|
||||
@type nodes: [L{Element},...]
|
||||
@return: A list of I{unmarshalled} objects.
|
||||
@rtype: [L{Object},...]
|
||||
|
||||
"""
|
||||
resolved = rt.resolve(nobuiltin=True)
|
||||
unmarshaller = self.unmarshaller()
|
||||
return [unmarshaller.process(node, resolved) for node in nodes]
|
||||
|
||||
def replycomposite(self, rtypes, nodes):
|
||||
"""
|
||||
Construct a I{composite} reply.
|
||||
|
||||
Called for replies with multiple output nodes.
|
||||
|
||||
@param rtypes: A list of known return I{types}.
|
||||
@type rtypes: [L{suds.xsd.sxbase.SchemaObject},...]
|
||||
@param nodes: A collection of XML nodes.
|
||||
@type nodes: [L{Element},...]
|
||||
@return: The I{unmarshalled} composite object.
|
||||
@rtype: L{Object},...
|
||||
|
||||
"""
|
||||
dictionary = {}
|
||||
for rt in rtypes:
|
||||
dictionary[rt.name] = rt
|
||||
unmarshaller = self.unmarshaller()
|
||||
composite = Factory.object("reply")
|
||||
for node in nodes:
|
||||
tag = node.name
|
||||
rt = dictionary.get(tag)
|
||||
if rt is None:
|
||||
if node.get("id") is None and not self.options().allowUnknownMessageParts:
|
||||
message = "<%s/> not mapped to message part" % (tag,)
|
||||
raise Exception(message)
|
||||
continue
|
||||
resolved = rt.resolve(nobuiltin=True)
|
||||
sobject = unmarshaller.process(node, resolved)
|
||||
value = getattr(composite, tag, None)
|
||||
if value is None:
|
||||
if rt.multi_occurrence():
|
||||
value = []
|
||||
setattr(composite, tag, value)
|
||||
value.append(sobject)
|
||||
else:
|
||||
setattr(composite, tag, sobject)
|
||||
else:
|
||||
if not isinstance(value, list):
|
||||
value = [value,]
|
||||
setattr(composite, tag, value)
|
||||
value.append(sobject)
|
||||
return composite
|
||||
|
||||
def mkparam(self, method, pdef, object):
|
||||
"""
|
||||
Builds a parameter for the specified I{method} using the parameter
|
||||
definition (pdef) and the specified value (object).
|
||||
|
||||
@param method: A method name.
|
||||
@type method: str
|
||||
@param pdef: A parameter definition.
|
||||
@type pdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject})
|
||||
@param object: The parameter value.
|
||||
@type object: any
|
||||
@return: The parameter fragment.
|
||||
@rtype: L{Element}
|
||||
|
||||
"""
|
||||
marshaller = self.marshaller()
|
||||
content = Content(tag=pdef[0], value=object, type=pdef[1],
|
||||
real=pdef[1].resolve())
|
||||
return marshaller.process(content)
|
||||
|
||||
def mkheader(self, method, hdef, object):
|
||||
"""
|
||||
Builds a soapheader for the specified I{method} using the header
|
||||
definition (hdef) and the specified value (object).
|
||||
|
||||
@param method: A method name.
|
||||
@type method: str
|
||||
@param hdef: A header definition.
|
||||
@type hdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject})
|
||||
@param object: The header value.
|
||||
@type object: any
|
||||
@return: The parameter fragment.
|
||||
@rtype: L{Element}
|
||||
|
||||
"""
|
||||
marshaller = self.marshaller()
|
||||
if isinstance(object, (list, tuple)):
|
||||
return [self.mkheader(method, hdef, item) for item in object]
|
||||
content = Content(tag=hdef[0], value=object, type=hdef[1])
|
||||
return marshaller.process(content)
|
||||
|
||||
def envelope(self, header, body):
|
||||
"""
|
||||
Build the B{<Envelope/>} for a SOAP outbound message.
|
||||
|
||||
@param header: The SOAP message B{header}.
|
||||
@type header: L{Element}
|
||||
@param body: The SOAP message B{body}.
|
||||
@type body: L{Element}
|
||||
@return: The SOAP envelope containing the body and header.
|
||||
@rtype: L{Element}
|
||||
|
||||
"""
|
||||
env = Element("Envelope", ns=envns)
|
||||
env.addPrefix(Namespace.xsins[0], Namespace.xsins[1])
|
||||
env.append(header)
|
||||
env.append(body)
|
||||
return env
|
||||
|
||||
def header(self, content):
|
||||
"""
|
||||
Build the B{<Body/>} for a SOAP outbound message.
|
||||
|
||||
@param content: The header content.
|
||||
@type content: L{Element}
|
||||
@return: The SOAP body fragment.
|
||||
@rtype: L{Element}
|
||||
|
||||
"""
|
||||
header = Element("Header", ns=envns)
|
||||
header.append(content)
|
||||
return header
|
||||
|
||||
def bodycontent(self, method, args, kwargs):
|
||||
"""
|
||||
Get the content for the SOAP I{body} node.
|
||||
|
||||
@param method: A service method.
|
||||
@type method: I{service.Method}
|
||||
@param args: method parameter values.
|
||||
@type args: list
|
||||
@param kwargs: Named (keyword) args for the method invoked.
|
||||
@type kwargs: dict
|
||||
@return: The XML content for the <body/>.
|
||||
@rtype: [L{Element},...]
|
||||
|
||||
"""
|
||||
raise Exception("not implemented")
|
||||
|
||||
def headercontent(self, method):
|
||||
"""
|
||||
Get the content for the SOAP I{Header} node.
|
||||
|
||||
@param method: A service method.
|
||||
@type method: I{service.Method}
|
||||
@return: The XML content for the <body/>.
|
||||
@rtype: [L{Element},...]
|
||||
|
||||
"""
|
||||
content = []
|
||||
wsse = self.options().wsse
|
||||
if wsse is not None:
|
||||
content.append(wsse.xml())
|
||||
headers = self.options().soapheaders
|
||||
if not isinstance(headers, (tuple, list, dict)):
|
||||
headers = (headers,)
|
||||
elif not headers:
|
||||
return content
|
||||
pts = self.headpart_types(method)
|
||||
if isinstance(headers, (tuple, list)):
|
||||
n = 0
|
||||
for header in headers:
|
||||
if isinstance(header, Element):
|
||||
content.append(deepcopy(header))
|
||||
continue
|
||||
if len(pts) == n:
|
||||
break
|
||||
h = self.mkheader(method, pts[n], header)
|
||||
ns = pts[n][1].namespace("ns0")
|
||||
h.setPrefix(ns[0], ns[1])
|
||||
content.append(h)
|
||||
n += 1
|
||||
else:
|
||||
for pt in pts:
|
||||
header = headers.get(pt[0])
|
||||
if header is None:
|
||||
continue
|
||||
h = self.mkheader(method, pt, header)
|
||||
ns = pt[1].namespace("ns0")
|
||||
h.setPrefix(ns[0], ns[1])
|
||||
content.append(h)
|
||||
return content
|
||||
|
||||
def replycontent(self, method, body):
|
||||
"""
|
||||
Get the reply body content.
|
||||
|
||||
@param method: A service method.
|
||||
@type method: I{service.Method}
|
||||
@param body: The SOAP body.
|
||||
@type body: L{Element}
|
||||
@return: The body content.
|
||||
@rtype: [L{Element},...]
|
||||
|
||||
"""
|
||||
raise Exception("not implemented")
|
||||
|
||||
def body(self, content):
|
||||
"""
|
||||
Build the B{<Body/>} for a SOAP outbound message.
|
||||
|
||||
@param content: The body content.
|
||||
@type content: L{Element}
|
||||
@return: The SOAP body fragment.
|
||||
@rtype: L{Element}
|
||||
|
||||
"""
|
||||
body = Element("Body", ns=envns)
|
||||
body.append(content)
|
||||
return body
|
||||
|
||||
def bodypart_types(self, method, input=True):
|
||||
"""
|
||||
Get a list of I{parameter definitions} (pdefs) defined for the
|
||||
specified method.
|
||||
|
||||
An input I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple,
|
||||
while an output I{pdef} is a L{xsd.sxbase.SchemaObject}.
|
||||
|
||||
@param method: A service method.
|
||||
@type method: I{service.Method}
|
||||
@param input: Defines input/output message.
|
||||
@type input: boolean
|
||||
@return: A list of parameter definitions
|
||||
@rtype: [I{pdef},...]
|
||||
|
||||
"""
|
||||
if input:
|
||||
parts = method.soap.input.body.parts
|
||||
else:
|
||||
parts = method.soap.output.body.parts
|
||||
return [self.__part_type(p, input) for p in parts]
|
||||
|
||||
def headpart_types(self, method, input=True):
|
||||
"""
|
||||
Get a list of header I{parameter definitions} (pdefs) defined for the
|
||||
specified method.
|
||||
|
||||
An input I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple,
|
||||
while an output I{pdef} is a L{xsd.sxbase.SchemaObject}.
|
||||
|
||||
@param method: A service method.
|
||||
@type method: I{service.Method}
|
||||
@param input: Defines input/output message.
|
||||
@type input: boolean
|
||||
@return: A list of parameter definitions
|
||||
@rtype: [I{pdef},...]
|
||||
|
||||
"""
|
||||
if input:
|
||||
headers = method.soap.input.headers
|
||||
else:
|
||||
headers = method.soap.output.headers
|
||||
return [self.__part_type(h.part, input) for h in headers]
|
||||
|
||||
def returned_types(self, method):
|
||||
"""
|
||||
Get the I{method} return value type(s).
|
||||
|
||||
@param method: A service method.
|
||||
@type method: I{service.Method}
|
||||
@return: Method return value type.
|
||||
@rtype: [L{xsd.sxbase.SchemaObject},...]
|
||||
|
||||
"""
|
||||
return self.bodypart_types(method, input=False)
|
||||
|
||||
def __part_type(self, part, input):
|
||||
"""
|
||||
Get a I{parameter definition} (pdef) defined for a given body or header
|
||||
message part.
|
||||
|
||||
An input I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple,
|
||||
while an output I{pdef} is a L{xsd.sxbase.SchemaObject}.
|
||||
|
||||
@param part: A service method input or output part.
|
||||
@type part: I{suds.wsdl.Part}
|
||||
@param input: Defines input/output message.
|
||||
@type input: boolean
|
||||
@return: A list of parameter definitions
|
||||
@rtype: [I{pdef},...]
|
||||
|
||||
"""
|
||||
if part.element is None:
|
||||
query = TypeQuery(part.type)
|
||||
else:
|
||||
query = ElementQuery(part.element)
|
||||
part_type = query.execute(self.schema())
|
||||
if part_type is None:
|
||||
raise TypeNotFound(query.ref)
|
||||
if part.type is not None:
|
||||
part_type = PartElement(part.name, part_type)
|
||||
if not input:
|
||||
return part_type
|
||||
if part_type.name is None:
|
||||
return part.name, part_type
|
||||
return part_type.name, part_type
|
||||
|
||||
|
||||
class PartElement(SchemaElement):
|
||||
"""
|
||||
Message part referencing an XSD type and thus acting like an XSD element.
|
||||
|
||||
@ivar resolved: The part type.
|
||||
@type resolved: L{suds.xsd.sxbase.SchemaObject}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name, resolved):
|
||||
"""
|
||||
@param name: The part name.
|
||||
@type name: str
|
||||
@param resolved: The part type.
|
||||
@type resolved: L{suds.xsd.sxbase.SchemaObject}
|
||||
|
||||
"""
|
||||
root = Element("element", ns=Namespace.xsdns)
|
||||
SchemaElement.__init__(self, resolved.schema, root)
|
||||
self.__resolved = resolved
|
||||
self.name = name
|
||||
self.form_qualified = False
|
||||
|
||||
def implany(self):
|
||||
pass
|
||||
|
||||
def optional(self):
|
||||
return True
|
||||
|
||||
def namespace(self, prefix=None):
|
||||
return Namespace.default
|
||||
|
||||
def resolve(self, nobuiltin=False):
|
||||
if nobuiltin and self.__resolved.builtin():
|
||||
return self
|
||||
return self.__resolved
|
@ -1,143 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Classes for the (WS) SOAP I{document/literal} binding.
|
||||
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.argparser import parse_args
|
||||
from suds.bindings.binding import Binding
|
||||
from suds.sax.element import Element
|
||||
|
||||
|
||||
class Document(Binding):
|
||||
"""
|
||||
The document/literal style. Literal is the only (@use) supported since
|
||||
document/encoded is pretty much dead.
|
||||
|
||||
Although the SOAP specification supports multiple documents within the SOAP
|
||||
<body/>, it is very uncommon. As such, suds library supports presenting an
|
||||
I{RPC} view of service methods defined with only a single document
|
||||
parameter. To support the complete specification, service methods defined
|
||||
with multiple documents (multiple message parts), are still presented using
|
||||
a full I{document} view.
|
||||
|
||||
More detailed description:
|
||||
|
||||
An interface is considered I{wrapped} if:
|
||||
- There is exactly one message part in that interface.
|
||||
- The message part resolves to an element of a non-builtin type.
|
||||
Otherwise it is considered I{bare}.
|
||||
|
||||
I{Bare} interface is interpreted directly as specified in the WSDL schema,
|
||||
with each message part represented by a single parameter in the suds
|
||||
library web service operation proxy interface (input or output).
|
||||
|
||||
I{Wrapped} interface is interpreted without the external wrapping document
|
||||
structure, with each of its contained elements passed through suds
|
||||
library's web service operation proxy interface (input or output)
|
||||
individually instead of as a single I{document} object.
|
||||
|
||||
"""
|
||||
def bodycontent(self, method, args, kwargs):
|
||||
wrapped = method.soap.input.body.wrapped
|
||||
if wrapped:
|
||||
pts = self.bodypart_types(method)
|
||||
root = self.document(pts[0])
|
||||
else:
|
||||
root = []
|
||||
|
||||
def add_param(param_name, param_type, in_choice_context, value):
|
||||
"""
|
||||
Construct request data for the given input parameter.
|
||||
|
||||
Called by our argument parser for every input parameter, in order.
|
||||
|
||||
A parameter's type is identified by its corresponding XSD schema
|
||||
element.
|
||||
|
||||
"""
|
||||
# Do not construct request data for undefined input parameters
|
||||
# defined inside a choice order indicator. An empty choice
|
||||
# parameter can still be included in the constructed request by
|
||||
# explicitly providing an empty string value for it.
|
||||
#TODO: This functionality might be better placed inside the
|
||||
# mkparam() function but to do that we would first need to better
|
||||
# understand how different Binding subclasses in suds work and how
|
||||
# they would be affected by this change.
|
||||
if in_choice_context and value is None:
|
||||
return
|
||||
|
||||
# Construct request data for the current input parameter.
|
||||
pdef = (param_name, param_type)
|
||||
p = self.mkparam(method, pdef, value)
|
||||
if p is None:
|
||||
return
|
||||
if not wrapped:
|
||||
ns = param_type.namespace("ns0")
|
||||
p.setPrefix(ns[0], ns[1])
|
||||
root.append(p)
|
||||
|
||||
parse_args(method.name, self.param_defs(method), args, kwargs,
|
||||
add_param, self.options().extraArgumentErrors)
|
||||
|
||||
return root
|
||||
|
||||
def replycontent(self, method, body):
|
||||
if method.soap.output.body.wrapped:
|
||||
return body[0].children
|
||||
return body.children
|
||||
|
||||
def document(self, wrapper):
|
||||
"""
|
||||
Get the document root. For I{document/literal}, this is the name of the
|
||||
wrapper element qualified by the schema's target namespace.
|
||||
|
||||
@param wrapper: The method name.
|
||||
@type wrapper: L{xsd.sxbase.SchemaObject}
|
||||
@return: A root element.
|
||||
@rtype: L{Element}
|
||||
|
||||
"""
|
||||
tag = wrapper[1].name
|
||||
ns = wrapper[1].namespace("ns0")
|
||||
return Element(tag, ns=ns)
|
||||
|
||||
def mkparam(self, method, pdef, object):
|
||||
"""
|
||||
Expand list parameters into individual parameters each with the type
|
||||
information. This is because in document arrays are simply
|
||||
multi-occurrence elements.
|
||||
|
||||
"""
|
||||
if isinstance(object, (list, tuple)):
|
||||
return [self.mkparam(method, pdef, item) for item in object]
|
||||
return super(Document, self).mkparam(method, pdef, object)
|
||||
|
||||
def param_defs(self, method):
|
||||
"""Get parameter definitions for document literal."""
|
||||
pts = self.bodypart_types(method)
|
||||
if not method.soap.input.body.wrapped:
|
||||
return pts
|
||||
pt = pts[0][1].resolve()
|
||||
return [(c.name, c, a) for c, a in pt if not c.isattr()]
|
||||
|
||||
def returned_types(self, method):
|
||||
rts = super(Document, self).returned_types(method)
|
||||
if not method.soap.output.body.wrapped:
|
||||
return rts
|
||||
return [child for child, ancestry in rts[0].resolve(nobuiltin=True)]
|
@ -1,124 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides classes for handling soap multirefs.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.sax.element import Element
|
||||
|
||||
|
||||
soapenc = (None, 'http://schemas.xmlsoap.org/soap/encoding/')
|
||||
|
||||
class MultiRef:
|
||||
"""
|
||||
Resolves and replaces multirefs.
|
||||
@ivar nodes: A list of non-multiref nodes.
|
||||
@type nodes: list
|
||||
@ivar catalog: A dictionary of multiref nodes by id.
|
||||
@type catalog: dict
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.nodes = []
|
||||
self.catalog = {}
|
||||
|
||||
def process(self, body):
|
||||
"""
|
||||
Process the specified soap envelope body and replace I{multiref} node
|
||||
references with the contents of the referenced node.
|
||||
@param body: A soap envelope body node.
|
||||
@type body: L{Element}
|
||||
@return: The processed I{body}
|
||||
@rtype: L{Element}
|
||||
"""
|
||||
self.nodes = []
|
||||
self.catalog = {}
|
||||
self.build_catalog(body)
|
||||
self.update(body)
|
||||
body.children = self.nodes
|
||||
return body
|
||||
|
||||
def update(self, node):
|
||||
"""
|
||||
Update the specified I{node} by replacing the I{multiref} references with
|
||||
the contents of the referenced nodes and remove the I{href} attribute.
|
||||
@param node: A node to update.
|
||||
@type node: L{Element}
|
||||
@return: The updated node
|
||||
@rtype: L{Element}
|
||||
"""
|
||||
self.replace_references(node)
|
||||
for c in node.children:
|
||||
self.update(c)
|
||||
return node
|
||||
|
||||
def replace_references(self, node):
|
||||
"""
|
||||
Replacing the I{multiref} references with the contents of the
|
||||
referenced nodes and remove the I{href} attribute. Warning: since
|
||||
the I{ref} is not cloned,
|
||||
@param node: A node to update.
|
||||
@type node: L{Element}
|
||||
"""
|
||||
href = node.getAttribute('href')
|
||||
if href is None:
|
||||
return
|
||||
id = href.getValue()
|
||||
ref = self.catalog.get(id)
|
||||
if ref is None:
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
log.error('soap multiref: %s, not-resolved', id)
|
||||
return
|
||||
node.append(ref.children)
|
||||
node.setText(ref.getText())
|
||||
for a in ref.attributes:
|
||||
if a.name != 'id':
|
||||
node.append(a)
|
||||
node.remove(href)
|
||||
|
||||
def build_catalog(self, body):
|
||||
"""
|
||||
Create the I{catalog} of multiref nodes by id and the list of
|
||||
non-multiref nodes.
|
||||
@param body: A soap envelope body node.
|
||||
@type body: L{Element}
|
||||
"""
|
||||
for child in body.children:
|
||||
if self.soaproot(child):
|
||||
self.nodes.append(child)
|
||||
id = child.get('id')
|
||||
if id is None: continue
|
||||
key = '#%s' % id
|
||||
self.catalog[key] = child
|
||||
|
||||
def soaproot(self, node):
|
||||
"""
|
||||
Get whether the specified I{node} is a soap encoded root.
|
||||
This is determined by examining @soapenc:root='1'.
|
||||
The node is considered to be a root when the attribute
|
||||
is not specified.
|
||||
@param node: A node to evaluate.
|
||||
@type node: L{Element}
|
||||
@return: True if a soap encoded root.
|
||||
@rtype: bool
|
||||
"""
|
||||
root = node.getAttribute('root', ns=soapenc)
|
||||
if root is None:
|
||||
return True
|
||||
else:
|
||||
return ( root.value == '1' )
|
@ -1,91 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Classes for the (WS) SOAP I{rpc/literal} and I{rpc/encoded} bindings.
|
||||
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.mx.encoded import Encoded as MxEncoded
|
||||
from suds.umx.encoded import Encoded as UmxEncoded
|
||||
from suds.bindings.binding import Binding, envns
|
||||
from suds.sax.element import Element
|
||||
|
||||
|
||||
encns = ("SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/")
|
||||
|
||||
|
||||
class RPC(Binding):
|
||||
"""RPC/Literal binding style."""
|
||||
|
||||
def param_defs(self, method):
|
||||
return self.bodypart_types(method)
|
||||
|
||||
def envelope(self, header, body):
|
||||
env = super(RPC, self).envelope(header, body)
|
||||
env.addPrefix(encns[0], encns[1])
|
||||
env.set("%s:encodingStyle" % (envns[0],), encns[1])
|
||||
return env
|
||||
|
||||
def bodycontent(self, method, args, kwargs):
|
||||
n = 0
|
||||
root = self.method(method)
|
||||
for pd in self.param_defs(method):
|
||||
if n < len(args):
|
||||
value = args[n]
|
||||
else:
|
||||
value = kwargs.get(pd[0])
|
||||
p = self.mkparam(method, pd, value)
|
||||
if p is not None:
|
||||
root.append(p)
|
||||
n += 1
|
||||
return root
|
||||
|
||||
def replycontent(self, method, body):
|
||||
return body[0].children
|
||||
|
||||
def method(self, method):
|
||||
"""
|
||||
Get the document root. For I{rpc/(literal|encoded)}, this is the name
|
||||
of the method qualified by the schema tns.
|
||||
|
||||
@param method: A service method.
|
||||
@type method: I{service.Method}
|
||||
@return: A root element.
|
||||
@rtype: L{Element}
|
||||
|
||||
"""
|
||||
ns = method.soap.input.body.namespace
|
||||
if ns[0] is None:
|
||||
ns = ('ns0', ns[1])
|
||||
return Element(method.name, ns=ns)
|
||||
|
||||
|
||||
class Encoded(RPC):
|
||||
"""RPC/Encoded (section 5) binding style."""
|
||||
|
||||
def marshaller(self):
|
||||
return MxEncoded(self.schema())
|
||||
|
||||
def unmarshaller(self):
|
||||
"""
|
||||
Get the appropriate schema based XML decoder.
|
||||
|
||||
@return: Typed unmarshaller.
|
||||
@rtype: L{UmxTyped}
|
||||
|
||||
"""
|
||||
return UmxEncoded(self.schema())
|
@ -1,122 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The I{builder} module provides an wsdl/xsd defined types factory
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.sudsobject import Factory
|
||||
|
||||
|
||||
class Builder:
|
||||
""" Builder used to construct an object for types defined in the schema """
|
||||
|
||||
def __init__(self, resolver):
|
||||
"""
|
||||
@param resolver: A schema object name resolver.
|
||||
@type resolver: L{resolver.Resolver}
|
||||
"""
|
||||
self.resolver = resolver
|
||||
|
||||
def build(self, name):
|
||||
""" build a an object for the specified typename as defined in the schema """
|
||||
if isinstance(name, basestring):
|
||||
type = self.resolver.find(name)
|
||||
if type is None:
|
||||
raise TypeNotFound(name)
|
||||
else:
|
||||
type = name
|
||||
cls = type.name
|
||||
if type.mixed():
|
||||
data = Factory.property(cls)
|
||||
else:
|
||||
data = Factory.object(cls)
|
||||
resolved = type.resolve()
|
||||
md = data.__metadata__
|
||||
md.sxtype = resolved
|
||||
md.ordering = self.ordering(resolved)
|
||||
history = []
|
||||
self.add_attributes(data, resolved)
|
||||
for child, ancestry in type.children():
|
||||
if self.skip_child(child, ancestry):
|
||||
continue
|
||||
|
||||
self.process(data, child, history[:])
|
||||
return data
|
||||
|
||||
def process(self, data, type, history):
|
||||
""" process the specified type then process its children """
|
||||
if type in history:
|
||||
return
|
||||
if type.enum():
|
||||
return
|
||||
history.append(type)
|
||||
resolved = type.resolve()
|
||||
value = None
|
||||
|
||||
|
||||
|
||||
if type.multi_occurrence():
|
||||
value = []
|
||||
else:
|
||||
if len(resolved) > 0:
|
||||
if resolved.mixed():
|
||||
value = Factory.property(resolved.name)
|
||||
md = value.__metadata__
|
||||
md.sxtype = resolved
|
||||
else:
|
||||
value = Factory.object(resolved.name)
|
||||
md = value.__metadata__
|
||||
md.sxtype = resolved
|
||||
md.ordering = self.ordering(resolved)
|
||||
|
||||
setattr(data, type.name, value if not type.optional() or type.multi_occurrence() else None)
|
||||
if value is not None:
|
||||
data = value
|
||||
if not isinstance(data, list):
|
||||
self.add_attributes(data, resolved)
|
||||
for child, ancestry in resolved.children():
|
||||
if self.skip_child(child, ancestry):
|
||||
continue
|
||||
self.process(data, child, history[:])
|
||||
|
||||
def add_attributes(self, data, type):
|
||||
""" add required attributes """
|
||||
for attr, ancestry in type.attributes():
|
||||
name = '_%s' % attr.name
|
||||
value = attr.get_default()
|
||||
setattr(data, name, value)
|
||||
|
||||
def skip_child(self, child, ancestry):
|
||||
""" get whether or not to skip the specified child """
|
||||
if child.any(): return True
|
||||
for x in ancestry:
|
||||
if x.choice():
|
||||
return True
|
||||
return False
|
||||
|
||||
def ordering(self, type):
|
||||
""" get the ordering """
|
||||
result = []
|
||||
for child, ancestry in type.resolve():
|
||||
name = child.name
|
||||
if child.name is None:
|
||||
continue
|
||||
if child.isattr():
|
||||
name = '_%s' % child.name
|
||||
result.append(name)
|
||||
return result
|
@ -1,334 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Basic caching classes.
|
||||
|
||||
"""
|
||||
|
||||
import suds
|
||||
import suds.sax.element
|
||||
import suds.sax.parser
|
||||
|
||||
import datetime
|
||||
import os
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except Exception:
|
||||
import pickle
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Cache(object):
|
||||
"""An object cache."""
|
||||
|
||||
def get(self, id):
|
||||
"""
|
||||
Get an object from the cache by id.
|
||||
|
||||
@param id: The object id.
|
||||
@type id: str
|
||||
@return: The object, else None.
|
||||
@rtype: any
|
||||
|
||||
"""
|
||||
raise Exception("not-implemented")
|
||||
|
||||
def put(self, id, object):
|
||||
"""
|
||||
Put an object into the cache.
|
||||
|
||||
@param id: The object id.
|
||||
@type id: str
|
||||
@param object: The object to add.
|
||||
@type object: any
|
||||
|
||||
"""
|
||||
raise Exception("not-implemented")
|
||||
|
||||
def purge(self, id):
|
||||
"""
|
||||
Purge an object from the cache by id.
|
||||
|
||||
@param id: A object id.
|
||||
@type id: str
|
||||
|
||||
"""
|
||||
raise Exception("not-implemented")
|
||||
|
||||
def clear(self):
|
||||
"""Clear all objects from the cache."""
|
||||
raise Exception("not-implemented")
|
||||
|
||||
|
||||
class NoCache(Cache):
|
||||
"""The pass-through object cache."""
|
||||
|
||||
def get(self, id):
|
||||
return
|
||||
|
||||
def put(self, id, object):
|
||||
pass
|
||||
|
||||
|
||||
class FileCache(Cache):
|
||||
"""
|
||||
A file-based URL cache.
|
||||
|
||||
@cvar fnprefix: The file name prefix.
|
||||
@type fnprefix: str
|
||||
@cvar remove_default_location_on_exit: Whether to remove the default cache
|
||||
location on process exit (default=True).
|
||||
@type remove_default_location_on_exit: bool
|
||||
@ivar duration: The duration after which cached entries expire (0=never).
|
||||
@type duration: datetime.timedelta
|
||||
@ivar location: The cached file folder.
|
||||
@type location: str
|
||||
|
||||
"""
|
||||
fnprefix = "suds"
|
||||
__default_location = None
|
||||
remove_default_location_on_exit = True
|
||||
|
||||
def __init__(self, location=None, **duration):
|
||||
"""
|
||||
Initialized a new FileCache instance.
|
||||
|
||||
If no cache location is specified, a temporary default location will be
|
||||
used. Such default cache location will be shared by all FileCache
|
||||
instances with no explicitly specified location within the same
|
||||
process. The default cache location will be removed automatically on
|
||||
process exit unless user sets the remove_default_location_on_exit
|
||||
FileCache class attribute to False.
|
||||
|
||||
@param location: The cached file folder.
|
||||
@type location: str
|
||||
@param duration: The duration after which cached entries expire
|
||||
(default: 0=never).
|
||||
@type duration: keyword arguments for datetime.timedelta constructor
|
||||
|
||||
"""
|
||||
if location is None:
|
||||
location = self.__get_default_location()
|
||||
self.location = location
|
||||
self.duration = datetime.timedelta(**duration)
|
||||
self.__check_version()
|
||||
|
||||
def clear(self):
|
||||
for filename in os.listdir(self.location):
|
||||
path = os.path.join(self.location, filename)
|
||||
if os.path.isdir(path):
|
||||
continue
|
||||
if filename.startswith(self.fnprefix):
|
||||
os.remove(path)
|
||||
log.debug("deleted: %s", path)
|
||||
|
||||
def fnsuffix(self):
|
||||
"""
|
||||
Get the file name suffix.
|
||||
|
||||
@return: The suffix.
|
||||
@rtype: str
|
||||
|
||||
"""
|
||||
return "gcf"
|
||||
|
||||
def get(self, id):
|
||||
try:
|
||||
f = self._getf(id)
|
||||
try:
|
||||
return f.read()
|
||||
finally:
|
||||
f.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def purge(self, id):
|
||||
filename = self.__filename(id)
|
||||
try:
|
||||
os.remove(filename)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def put(self, id, data):
|
||||
try:
|
||||
filename = self.__filename(id)
|
||||
f = self.__open(filename, "wb")
|
||||
try:
|
||||
f.write(data)
|
||||
finally:
|
||||
f.close()
|
||||
return data
|
||||
except Exception:
|
||||
log.debug(id, exc_info=1)
|
||||
return data
|
||||
|
||||
def _getf(self, id):
|
||||
"""Open a cached file with the given id for reading."""
|
||||
try:
|
||||
filename = self.__filename(id)
|
||||
self.__remove_if_expired(filename)
|
||||
return self.__open(filename, "rb")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def __check_version(self):
|
||||
path = os.path.join(self.location, "version")
|
||||
try:
|
||||
f = self.__open(path)
|
||||
try:
|
||||
version = f.read()
|
||||
finally:
|
||||
f.close()
|
||||
if version != suds.__version__:
|
||||
raise Exception()
|
||||
except Exception:
|
||||
self.clear()
|
||||
f = self.__open(path, "w")
|
||||
try:
|
||||
f.write(suds.__version__)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
def __filename(self, id):
|
||||
"""Return the cache file name for an entry with a given id."""
|
||||
suffix = self.fnsuffix()
|
||||
filename = "%s-%s.%s" % (self.fnprefix, id, suffix)
|
||||
return os.path.join(self.location, filename)
|
||||
|
||||
@staticmethod
|
||||
def __get_default_location():
|
||||
"""
|
||||
Returns the current process's default cache location folder.
|
||||
|
||||
The folder is determined lazily on first call.
|
||||
|
||||
"""
|
||||
if not FileCache.__default_location:
|
||||
tmp = tempfile.mkdtemp("suds-default-cache")
|
||||
FileCache.__default_location = tmp
|
||||
import atexit
|
||||
atexit.register(FileCache.__remove_default_location)
|
||||
return FileCache.__default_location
|
||||
|
||||
def __mktmp(self):
|
||||
"""Create the I{location} folder if it does not already exist."""
|
||||
try:
|
||||
if not os.path.isdir(self.location):
|
||||
os.makedirs(self.location)
|
||||
except Exception:
|
||||
log.debug(self.location, exc_info=1)
|
||||
return self
|
||||
|
||||
def __open(self, filename, *args):
|
||||
"""Open cache file making sure the I{location} folder is created."""
|
||||
self.__mktmp()
|
||||
return open(filename, *args)
|
||||
|
||||
@staticmethod
|
||||
def __remove_default_location():
|
||||
"""
|
||||
Removes the default cache location folder.
|
||||
|
||||
This removal may be disabled by setting the
|
||||
remove_default_location_on_exit FileCache class attribute to False.
|
||||
|
||||
"""
|
||||
if FileCache.remove_default_location_on_exit:
|
||||
# We must not load shutil here on-demand as under some
|
||||
# circumstances this may cause the shutil.rmtree() operation to
|
||||
# fail due to not having some internal module loaded. E.g. this
|
||||
# happens if you run the project's test suite using the setup.py
|
||||
# test command on Python 2.4.x.
|
||||
shutil.rmtree(FileCache.__default_location, ignore_errors=True)
|
||||
|
||||
def __remove_if_expired(self, filename):
|
||||
"""
|
||||
Remove a cached file entry if it expired.
|
||||
|
||||
@param filename: The file name.
|
||||
@type filename: str
|
||||
|
||||
"""
|
||||
if not self.duration:
|
||||
return
|
||||
created = datetime.datetime.fromtimestamp(os.path.getctime(filename))
|
||||
expired = created + self.duration
|
||||
if expired < datetime.datetime.now():
|
||||
os.remove(filename)
|
||||
log.debug("%s expired, deleted", filename)
|
||||
|
||||
|
||||
class DocumentCache(FileCache):
|
||||
"""XML document file cache."""
|
||||
|
||||
def fnsuffix(self):
|
||||
return "xml"
|
||||
|
||||
def get(self, id):
|
||||
fp = None
|
||||
try:
|
||||
fp = self._getf(id)
|
||||
if fp is not None:
|
||||
p = suds.sax.parser.Parser()
|
||||
cached = p.parse(fp)
|
||||
fp.close()
|
||||
return cached
|
||||
except Exception:
|
||||
if fp is not None:
|
||||
fp.close()
|
||||
self.purge(id)
|
||||
|
||||
def put(self, id, object):
|
||||
if isinstance(object,
|
||||
(suds.sax.document.Document, suds.sax.element.Element)):
|
||||
super(DocumentCache, self).put(id, suds.byte_str(str(object)))
|
||||
return object
|
||||
|
||||
|
||||
class ObjectCache(FileCache):
|
||||
"""
|
||||
Pickled object file cache.
|
||||
|
||||
@cvar protocol: The pickling protocol.
|
||||
@type protocol: int
|
||||
|
||||
"""
|
||||
protocol = 2
|
||||
|
||||
def fnsuffix(self):
|
||||
return "px"
|
||||
|
||||
def get(self, id):
|
||||
fp = None
|
||||
try:
|
||||
fp = self._getf(id)
|
||||
if fp is not None:
|
||||
cached = pickle.load(fp)
|
||||
fp.close()
|
||||
return cached
|
||||
except Exception:
|
||||
if fp is not None:
|
||||
fp.close()
|
||||
self.purge(id)
|
||||
|
||||
def put(self, id, object):
|
||||
data = pickle.dumps(object, self.protocol)
|
||||
super(ObjectCache, self).put(id, data)
|
||||
return object
|
@ -1,952 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Service proxy implementation providing access to web services.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from future.utils import raise_
|
||||
import suds
|
||||
from suds import *
|
||||
import suds.bindings.binding
|
||||
from suds.builder import Builder
|
||||
import suds.cache
|
||||
import suds.metrics as metrics
|
||||
from suds.options import Options
|
||||
from suds.plugin import PluginContainer
|
||||
from suds.properties import Unskin
|
||||
from suds.reader import DefinitionsReader
|
||||
from suds.resolver import PathResolver
|
||||
from suds.sax.document import Document
|
||||
import suds.sax.parser
|
||||
from suds.servicedefinition import ServiceDefinition
|
||||
import suds.transport
|
||||
import suds.transport.https
|
||||
from suds.umx.basic import Basic as UmxBasic
|
||||
from suds.wsdl import Definitions
|
||||
from . import sudsobject
|
||||
|
||||
from cookielib import CookieJar
|
||||
from copy import deepcopy
|
||||
import httplib
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Client(UnicodeMixin):
|
||||
"""
|
||||
A lightweight web service client.
|
||||
|
||||
@ivar wsdl: The WSDL object.
|
||||
@type wsdl:L{Definitions}
|
||||
@ivar service: The service proxy used to invoke operations.
|
||||
@type service: L{Service}
|
||||
@ivar factory: The factory used to create objects.
|
||||
@type factory: L{Factory}
|
||||
@ivar sd: The service definition
|
||||
@type sd: L{ServiceDefinition}
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def items(cls, sobject):
|
||||
"""
|
||||
Extract I{items} from a suds object.
|
||||
|
||||
Much like the items() method works on I{dict}.
|
||||
|
||||
@param sobject: A suds object
|
||||
@type sobject: L{Object}
|
||||
@return: A list of items contained in I{sobject}.
|
||||
@rtype: [(key, value),...]
|
||||
|
||||
"""
|
||||
return sudsobject.items(sobject)
|
||||
|
||||
@classmethod
|
||||
def dict(cls, sobject):
|
||||
"""
|
||||
Convert a sudsobject into a dictionary.
|
||||
|
||||
@param sobject: A suds object
|
||||
@type sobject: L{Object}
|
||||
@return: A dictionary of items contained in I{sobject}.
|
||||
@rtype: dict
|
||||
|
||||
"""
|
||||
return sudsobject.asdict(sobject)
|
||||
|
||||
@classmethod
|
||||
def metadata(cls, sobject):
|
||||
"""
|
||||
Extract the metadata from a suds object.
|
||||
|
||||
@param sobject: A suds object
|
||||
@type sobject: L{Object}
|
||||
@return: The object's metadata
|
||||
@rtype: L{sudsobject.Metadata}
|
||||
|
||||
"""
|
||||
return sobject.__metadata__
|
||||
|
||||
def __init__(self, url, **kwargs):
|
||||
"""
|
||||
@param url: The URL for the WSDL.
|
||||
@type url: str
|
||||
@param kwargs: keyword arguments.
|
||||
@see: L{Options}
|
||||
|
||||
"""
|
||||
options = Options()
|
||||
options.transport = suds.transport.https.HttpAuthenticated()
|
||||
self.options = options
|
||||
if "cache" not in kwargs:
|
||||
kwargs["cache"] = suds.cache.ObjectCache(days=1)
|
||||
self.set_options(**kwargs)
|
||||
reader = DefinitionsReader(options, Definitions)
|
||||
self.wsdl = reader.open(url)
|
||||
plugins = PluginContainer(options.plugins)
|
||||
plugins.init.initialized(wsdl=self.wsdl)
|
||||
self.factory = Factory(self.wsdl)
|
||||
self.service = ServiceSelector(self, self.wsdl.services)
|
||||
self.sd = []
|
||||
for s in self.wsdl.services:
|
||||
sd = ServiceDefinition(self.wsdl, s)
|
||||
self.sd.append(sd)
|
||||
|
||||
def set_options(self, **kwargs):
|
||||
"""
|
||||
Set options.
|
||||
|
||||
@param kwargs: keyword arguments.
|
||||
@see: L{Options}
|
||||
|
||||
"""
|
||||
p = Unskin(self.options)
|
||||
p.update(kwargs)
|
||||
|
||||
def add_prefix(self, prefix, uri):
|
||||
"""
|
||||
Add I{static} mapping of an XML namespace prefix to a namespace.
|
||||
|
||||
Useful for cases when a WSDL and referenced XSD schemas make heavy use
|
||||
of namespaces and those namespaces are subject to change.
|
||||
|
||||
@param prefix: An XML namespace prefix.
|
||||
@type prefix: str
|
||||
@param uri: An XML namespace URI.
|
||||
@type uri: str
|
||||
@raise Exception: prefix already mapped.
|
||||
|
||||
"""
|
||||
root = self.wsdl.root
|
||||
mapped = root.resolvePrefix(prefix, None)
|
||||
if mapped is None:
|
||||
root.addPrefix(prefix, uri)
|
||||
return
|
||||
if mapped[1] != uri:
|
||||
raise Exception('"%s" already mapped as "%s"' % (prefix, mapped))
|
||||
|
||||
def clone(self):
|
||||
"""
|
||||
Get a shallow clone of this object.
|
||||
|
||||
The clone only shares the WSDL. All other attributes are unique to the
|
||||
cloned object including options.
|
||||
|
||||
@return: A shallow clone.
|
||||
@rtype: L{Client}
|
||||
|
||||
"""
|
||||
class Uninitialized(Client):
|
||||
def __init__(self):
|
||||
pass
|
||||
clone = Uninitialized()
|
||||
clone.options = Options()
|
||||
cp = Unskin(clone.options)
|
||||
mp = Unskin(self.options)
|
||||
cp.update(deepcopy(mp))
|
||||
clone.wsdl = self.wsdl
|
||||
clone.factory = self.factory
|
||||
clone.service = ServiceSelector(clone, self.wsdl.services)
|
||||
clone.sd = self.sd
|
||||
return clone
|
||||
|
||||
def __unicode__(self):
|
||||
s = ["\n"]
|
||||
s.append("Suds ( https://fedorahosted.org/suds/ )")
|
||||
s.append(" version: %s" % (suds.__version__,))
|
||||
if suds.__build__:
|
||||
s.append(" build: %s" % (suds.__build__,))
|
||||
for sd in self.sd:
|
||||
s.append("\n\n%s" % (unicode(sd),))
|
||||
return "".join(s)
|
||||
|
||||
|
||||
class Factory:
|
||||
"""
|
||||
A factory for instantiating types defined in the WSDL.
|
||||
|
||||
@ivar resolver: A schema type resolver.
|
||||
@type resolver: L{PathResolver}
|
||||
@ivar builder: A schema object builder.
|
||||
@type builder: L{Builder}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, wsdl):
|
||||
"""
|
||||
@param wsdl: A schema object.
|
||||
@type wsdl: L{wsdl.Definitions}
|
||||
|
||||
"""
|
||||
self.wsdl = wsdl
|
||||
self.resolver = PathResolver(wsdl)
|
||||
self.builder = Builder(self.resolver)
|
||||
|
||||
def create(self, name):
|
||||
"""
|
||||
Create a WSDL type by name.
|
||||
|
||||
@param name: The name of a type defined in the WSDL.
|
||||
@type name: str
|
||||
@return: The requested object.
|
||||
@rtype: L{Object}
|
||||
|
||||
"""
|
||||
timer = metrics.Timer()
|
||||
timer.start()
|
||||
type = self.resolver.find(name)
|
||||
if type is None:
|
||||
raise TypeNotFound(name)
|
||||
if type.enum():
|
||||
result = sudsobject.Factory.object(name)
|
||||
for e, a in type.children():
|
||||
setattr(result, e.name, e.name)
|
||||
else:
|
||||
try:
|
||||
result = self.builder.build(type)
|
||||
except Exception as e:
|
||||
log.error("create '%s' failed", name, exc_info=True)
|
||||
raise BuildError(name, e)
|
||||
timer.stop()
|
||||
metrics.log.debug("%s created: %s", name, timer)
|
||||
return result
|
||||
|
||||
def separator(self, ps):
|
||||
"""
|
||||
Set the path separator.
|
||||
|
||||
@param ps: The new path separator.
|
||||
@type ps: char
|
||||
|
||||
"""
|
||||
self.resolver = PathResolver(self.wsdl, ps)
|
||||
|
||||
|
||||
class ServiceSelector:
|
||||
"""
|
||||
The B{service} selector is used to select a web service.
|
||||
|
||||
Most WSDLs only define a single service in which case access by subscript
|
||||
is passed through to a L{PortSelector}. This is also the behavior when a
|
||||
I{default} service has been specified. In cases where multiple services
|
||||
have been defined and no default has been specified, the service is found
|
||||
by name (or index) and a L{PortSelector} for the service is returned. In
|
||||
all cases, attribute access is forwarded to the L{PortSelector} for either
|
||||
the I{first} service or the I{default} service (when specified).
|
||||
|
||||
@ivar __client: A suds client.
|
||||
@type __client: L{Client}
|
||||
@ivar __services: A list of I{WSDL} services.
|
||||
@type __services: list
|
||||
|
||||
"""
|
||||
def __init__(self, client, services):
|
||||
"""
|
||||
@param client: A suds client.
|
||||
@type client: L{Client}
|
||||
@param services: A list of I{WSDL} services.
|
||||
@type services: list
|
||||
|
||||
"""
|
||||
self.__client = client
|
||||
self.__services = services
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Attribute access is forwarded to the L{PortSelector}.
|
||||
|
||||
Uses the I{default} service if specified or the I{first} service
|
||||
otherwise.
|
||||
|
||||
@param name: Method name.
|
||||
@type name: str
|
||||
@return: A L{PortSelector}.
|
||||
@rtype: L{PortSelector}.
|
||||
|
||||
"""
|
||||
default = self.__ds()
|
||||
if default is None:
|
||||
port = self.__find(0)
|
||||
else:
|
||||
port = default
|
||||
return getattr(port, name)
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""
|
||||
Provides I{service} selection by name (string) or index (integer).
|
||||
|
||||
In cases where only a single service is defined or a I{default} has
|
||||
been specified, the request is forwarded to the L{PortSelector}.
|
||||
|
||||
@param name: The name (or index) of a service.
|
||||
@type name: int|str
|
||||
@return: A L{PortSelector} for the specified service.
|
||||
@rtype: L{PortSelector}.
|
||||
|
||||
"""
|
||||
if len(self.__services) == 1:
|
||||
port = self.__find(0)
|
||||
return port[name]
|
||||
default = self.__ds()
|
||||
if default is not None:
|
||||
port = default
|
||||
return port[name]
|
||||
return self.__find(name)
|
||||
|
||||
def __find(self, name):
|
||||
"""
|
||||
Find a I{service} by name (string) or index (integer).
|
||||
|
||||
@param name: The name (or index) of a service.
|
||||
@type name: int|str
|
||||
@return: A L{PortSelector} for the found service.
|
||||
@rtype: L{PortSelector}.
|
||||
|
||||
"""
|
||||
service = None
|
||||
if not self.__services:
|
||||
raise Exception("No services defined")
|
||||
if isinstance(name, int):
|
||||
try:
|
||||
service = self.__services[name]
|
||||
name = service.name
|
||||
except IndexError:
|
||||
raise_(ServiceNotFound, "at [%d]" % (name,))
|
||||
else:
|
||||
for s in self.__services:
|
||||
if name == s.name:
|
||||
service = s
|
||||
break
|
||||
if service is None:
|
||||
raise_(ServiceNotFound, name)
|
||||
return PortSelector(self.__client, service.ports, name)
|
||||
|
||||
def __ds(self):
|
||||
"""
|
||||
Get the I{default} service if defined in the I{options}.
|
||||
|
||||
@return: A L{PortSelector} for the I{default} service.
|
||||
@rtype: L{PortSelector}.
|
||||
|
||||
"""
|
||||
ds = self.__client.options.service
|
||||
if ds is not None:
|
||||
return self.__find(ds)
|
||||
|
||||
|
||||
class PortSelector:
|
||||
"""
|
||||
The B{port} selector is used to select a I{web service} B{port}.
|
||||
|
||||
In cases where multiple ports have been defined and no default has been
|
||||
specified, the port is found by name (or index) and a L{MethodSelector} for
|
||||
the port is returned. In all cases, attribute access is forwarded to the
|
||||
L{MethodSelector} for either the I{first} port or the I{default} port (when
|
||||
specified).
|
||||
|
||||
@ivar __client: A suds client.
|
||||
@type __client: L{Client}
|
||||
@ivar __ports: A list of I{service} ports.
|
||||
@type __ports: list
|
||||
@ivar __qn: The I{qualified} name of the port (used for logging).
|
||||
@type __qn: str
|
||||
|
||||
"""
|
||||
def __init__(self, client, ports, qn):
|
||||
"""
|
||||
@param client: A suds client.
|
||||
@type client: L{Client}
|
||||
@param ports: A list of I{service} ports.
|
||||
@type ports: list
|
||||
@param qn: The name of the service.
|
||||
@type qn: str
|
||||
|
||||
"""
|
||||
self.__client = client
|
||||
self.__ports = ports
|
||||
self.__qn = qn
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Attribute access is forwarded to the L{MethodSelector}.
|
||||
|
||||
Uses the I{default} port when specified or the I{first} port otherwise.
|
||||
|
||||
@param name: The name of a method.
|
||||
@type name: str
|
||||
@return: A L{MethodSelector}.
|
||||
@rtype: L{MethodSelector}.
|
||||
|
||||
"""
|
||||
default = self.__dp()
|
||||
if default is None:
|
||||
m = self.__find(0)
|
||||
else:
|
||||
m = default
|
||||
return getattr(m, name)
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""
|
||||
Provides I{port} selection by name (string) or index (integer).
|
||||
|
||||
In cases where only a single port is defined or a I{default} has been
|
||||
specified, the request is forwarded to the L{MethodSelector}.
|
||||
|
||||
@param name: The name (or index) of a port.
|
||||
@type name: int|str
|
||||
@return: A L{MethodSelector} for the specified port.
|
||||
@rtype: L{MethodSelector}.
|
||||
|
||||
"""
|
||||
default = self.__dp()
|
||||
if default is None:
|
||||
return self.__find(name)
|
||||
return default
|
||||
|
||||
def __find(self, name):
|
||||
"""
|
||||
Find a I{port} by name (string) or index (integer).
|
||||
|
||||
@param name: The name (or index) of a port.
|
||||
@type name: int|str
|
||||
@return: A L{MethodSelector} for the found port.
|
||||
@rtype: L{MethodSelector}.
|
||||
|
||||
"""
|
||||
port = None
|
||||
if not self.__ports:
|
||||
raise_(Exception, "No ports defined: %s" % (self.__qn,))
|
||||
if isinstance(name, int):
|
||||
qn = "%s[%d]" % (self.__qn, name)
|
||||
try:
|
||||
port = self.__ports[name]
|
||||
except IndexError:
|
||||
raise_(PortNotFound, qn)
|
||||
else:
|
||||
qn = ".".join((self.__qn, name))
|
||||
for p in self.__ports:
|
||||
if name == p.name:
|
||||
port = p
|
||||
break
|
||||
if port is None:
|
||||
raise_(PortNotFound, qn)
|
||||
qn = ".".join((self.__qn, port.name))
|
||||
return MethodSelector(self.__client, port.methods, qn)
|
||||
|
||||
def __dp(self):
|
||||
"""
|
||||
Get the I{default} port if defined in the I{options}.
|
||||
|
||||
@return: A L{MethodSelector} for the I{default} port.
|
||||
@rtype: L{MethodSelector}.
|
||||
|
||||
"""
|
||||
dp = self.__client.options.port
|
||||
if dp is not None:
|
||||
return self.__find(dp)
|
||||
|
||||
|
||||
class MethodSelector:
|
||||
"""
|
||||
The B{method} selector is used to select a B{method} by name.
|
||||
|
||||
@ivar __client: A suds client.
|
||||
@type __client: L{Client}
|
||||
@ivar __methods: A dictionary of methods.
|
||||
@type __methods: dict
|
||||
@ivar __qn: The I{qualified} name of the method (used for logging).
|
||||
@type __qn: str
|
||||
|
||||
"""
|
||||
def __init__(self, client, methods, qn):
|
||||
"""
|
||||
@param client: A suds client.
|
||||
@type client: L{Client}
|
||||
@param methods: A dictionary of methods.
|
||||
@type methods: dict
|
||||
@param qn: The I{qualified} name of the port.
|
||||
@type qn: str
|
||||
|
||||
"""
|
||||
self.__client = client
|
||||
self.__methods = methods
|
||||
self.__qn = qn
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Get a method by name and return it in an I{execution wrapper}.
|
||||
|
||||
@param name: The name of a method.
|
||||
@type name: str
|
||||
@return: An I{execution wrapper} for the specified method name.
|
||||
@rtype: L{Method}
|
||||
|
||||
"""
|
||||
return self[name]
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""
|
||||
Get a method by name and return it in an I{execution wrapper}.
|
||||
|
||||
@param name: The name of a method.
|
||||
@type name: str
|
||||
@return: An I{execution wrapper} for the specified method name.
|
||||
@rtype: L{Method}
|
||||
|
||||
"""
|
||||
m = self.__methods.get(name)
|
||||
if m is None:
|
||||
qn = ".".join((self.__qn, name))
|
||||
raise_(MethodNotFound, qn)
|
||||
return Method(self.__client, m)
|
||||
|
||||
|
||||
class Method:
|
||||
"""
|
||||
The I{method} (namespace) object.
|
||||
|
||||
@ivar client: A client object.
|
||||
@type client: L{Client}
|
||||
@ivar method: A I{WSDL} method.
|
||||
@type I{raw} Method.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, client, method):
|
||||
"""
|
||||
@param client: A client object.
|
||||
@type client: L{Client}
|
||||
@param method: A I{raw} method.
|
||||
@type I{raw} Method.
|
||||
|
||||
"""
|
||||
self.client = client
|
||||
self.method = method
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Invoke the method."""
|
||||
clientclass = self.clientclass(kwargs)
|
||||
client = clientclass(self.client, self.method)
|
||||
try:
|
||||
return client.invoke(args, kwargs)
|
||||
except WebFault as e:
|
||||
if self.faults():
|
||||
raise
|
||||
return httplib.INTERNAL_SERVER_ERROR, e
|
||||
|
||||
def faults(self):
|
||||
"""Get faults option."""
|
||||
return self.client.options.faults
|
||||
|
||||
def clientclass(self, kwargs):
|
||||
"""Get SOAP client class."""
|
||||
if _SimClient.simulation(kwargs):
|
||||
return _SimClient
|
||||
return _SoapClient
|
||||
|
||||
|
||||
class RequestContext:
|
||||
"""
|
||||
A request context.
|
||||
|
||||
Returned by a suds Client when invoking a web service operation with the
|
||||
``nosend`` enabled. Allows the caller to take care of sending the request
|
||||
himself and return back the reply data for further processing.
|
||||
|
||||
@ivar envelope: The SOAP request envelope.
|
||||
@type envelope: I{bytes}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, process_reply, envelope):
|
||||
"""
|
||||
@param process_reply: A callback for processing a user defined reply.
|
||||
@type process_reply: I{callable}
|
||||
@param envelope: The SOAP request envelope.
|
||||
@type envelope: I{bytes}
|
||||
|
||||
"""
|
||||
self.__process_reply = process_reply
|
||||
self.envelope = envelope
|
||||
|
||||
def process_reply(self, reply, status=None, description=None):
|
||||
"""
|
||||
Re-entry for processing a successful reply.
|
||||
|
||||
Depending on how the ``retxml`` option is set, may return the SOAP
|
||||
reply XML or process it and return the Python object representing the
|
||||
returned value.
|
||||
|
||||
@param reply: The SOAP reply envelope.
|
||||
@type reply: I{bytes}
|
||||
@param status: The HTTP status code.
|
||||
@type status: int
|
||||
@param description: Additional status description.
|
||||
@type description: I{bytes}
|
||||
@return: The invoked web service operation return value.
|
||||
@rtype: I{builtin}|I{subclass of} L{Object}|I{bytes}|I{None}
|
||||
|
||||
"""
|
||||
return self.__process_reply(reply, status, description)
|
||||
|
||||
|
||||
class _SoapClient:
|
||||
"""
|
||||
An internal lightweight SOAP based web service operation client.
|
||||
|
||||
Each instance is constructed for specific web service operation and knows
|
||||
how to:
|
||||
- Construct a SOAP request for it.
|
||||
- Transport a SOAP request for it using a configured transport.
|
||||
- Receive a SOAP reply using a configured transport.
|
||||
- Process the received SOAP reply.
|
||||
|
||||
Depending on the given suds options, may do all the tasks listed above or
|
||||
may stop the process at an earlier point and return some intermediate
|
||||
result, e.g. the constructed SOAP request or the raw received SOAP reply.
|
||||
See the invoke() method for more detailed information.
|
||||
|
||||
@ivar service: The target method.
|
||||
@type service: L{Service}
|
||||
@ivar method: A target method.
|
||||
@type method: L{Method}
|
||||
@ivar options: A dictonary of options.
|
||||
@type options: dict
|
||||
@ivar cookiejar: A cookie jar.
|
||||
@type cookiejar: libcookie.CookieJar
|
||||
|
||||
"""
|
||||
|
||||
TIMEOUT_ARGUMENT = "__timeout"
|
||||
|
||||
def __init__(self, client, method):
|
||||
"""
|
||||
@param client: A suds client.
|
||||
@type client: L{Client}
|
||||
@param method: A target method.
|
||||
@type method: L{Method}
|
||||
|
||||
"""
|
||||
self.client = client
|
||||
self.method = method
|
||||
self.options = client.options
|
||||
self.cookiejar = CookieJar()
|
||||
|
||||
def invoke(self, args, kwargs):
|
||||
"""
|
||||
Invoke a specified web service method.
|
||||
|
||||
Depending on how the ``nosend`` & ``retxml`` options are set, may do
|
||||
one of the following:
|
||||
* Return a constructed web service operation SOAP request without
|
||||
sending it to the web service.
|
||||
* Invoke the web service operation and return its SOAP reply XML.
|
||||
* Invoke the web service operation, process its results and return
|
||||
the Python object representing the returned value.
|
||||
|
||||
When returning a SOAP request, the request is wrapped inside a
|
||||
RequestContext object allowing the user to acquire a corresponding SOAP
|
||||
reply himself and then pass it back to suds for further processing.
|
||||
|
||||
Constructed request data is automatically processed using registered
|
||||
plugins and serialized into a byte-string. Exact request XML formatting
|
||||
may be affected by the ``prettyxml`` suds option.
|
||||
|
||||
@param args: A list of args for the method invoked.
|
||||
@type args: list|tuple
|
||||
@param kwargs: Named (keyword) args for the method invoked.
|
||||
@type kwargs: dict
|
||||
@return: SOAP request, SOAP reply or a web service return value.
|
||||
@rtype: L{RequestContext}|I{builtin}|I{subclass of} L{Object}|I{bytes}|
|
||||
I{None}
|
||||
|
||||
"""
|
||||
timer = metrics.Timer()
|
||||
timer.start()
|
||||
binding = self.method.binding.input
|
||||
timeout = kwargs.pop(_SoapClient.TIMEOUT_ARGUMENT, None)
|
||||
soapenv = binding.get_message(self.method, args, kwargs)
|
||||
timer.stop()
|
||||
method_name = self.method.name
|
||||
metrics.log.debug("message for '%s' created: %s", method_name, timer)
|
||||
timer.start()
|
||||
result = self.send(soapenv, timeout=timeout)
|
||||
timer.stop()
|
||||
metrics.log.debug("method '%s' invoked: %s", method_name, timer)
|
||||
return result
|
||||
|
||||
def send(self, soapenv, timeout=None):
|
||||
"""
|
||||
Send SOAP message.
|
||||
|
||||
Depending on how the ``nosend`` & ``retxml`` options are set, may do
|
||||
one of the following:
|
||||
* Return a constructed web service operation request without sending
|
||||
it to the web service.
|
||||
* Invoke the web service operation and return its SOAP reply XML.
|
||||
* Invoke the web service operation, process its results and return
|
||||
the Python object representing the returned value.
|
||||
|
||||
@param soapenv: A SOAP envelope to send.
|
||||
@type soapenv: L{Document}
|
||||
@return: SOAP request, SOAP reply or a web service return value.
|
||||
@rtype: L{RequestContext}|I{builtin}|I{subclass of} L{Object}|I{bytes}|
|
||||
I{None}
|
||||
|
||||
"""
|
||||
location = self.__location()
|
||||
log.debug("sending to (%s)\nmessage:\n%s", location, soapenv)
|
||||
plugins = PluginContainer(self.options.plugins)
|
||||
plugins.message.marshalled(envelope=soapenv.root())
|
||||
if self.options.prettyxml:
|
||||
soapenv = soapenv.str()
|
||||
else:
|
||||
soapenv = soapenv.plain()
|
||||
soapenv = soapenv.encode("utf-8")
|
||||
ctx = plugins.message.sending(envelope=soapenv)
|
||||
soapenv = ctx.envelope
|
||||
if self.options.nosend:
|
||||
return RequestContext(self.process_reply, soapenv)
|
||||
request = suds.transport.Request(location, soapenv, timeout)
|
||||
request.headers = self.__headers()
|
||||
try:
|
||||
timer = metrics.Timer()
|
||||
timer.start()
|
||||
reply = self.options.transport.send(request)
|
||||
timer.stop()
|
||||
metrics.log.debug("waited %s on server reply", timer)
|
||||
except suds.transport.TransportError as e:
|
||||
content = e.fp and e.fp.read() or ""
|
||||
return self.process_reply(content, e.httpcode, tostr(e))
|
||||
return self.process_reply(reply.message, None, None)
|
||||
|
||||
def process_reply(self, reply, status, description):
|
||||
"""
|
||||
Process a web service operation SOAP reply.
|
||||
|
||||
Depending on how the ``retxml`` option is set, may return the SOAP
|
||||
reply XML or process it and return the Python object representing the
|
||||
returned value.
|
||||
|
||||
@param reply: The SOAP reply envelope.
|
||||
@type reply: I{bytes}
|
||||
@param status: The HTTP status code (None indicates httplib.OK).
|
||||
@type status: int|I{None}
|
||||
@param description: Additional status description.
|
||||
@type description: str
|
||||
@return: The invoked web service operation return value.
|
||||
@rtype: I{builtin}|I{subclass of} L{Object}|I{bytes}|I{None}
|
||||
|
||||
"""
|
||||
if status is None:
|
||||
status = httplib.OK
|
||||
debug_message = "Reply HTTP status - %d" % (status,)
|
||||
if status in (httplib.ACCEPTED, httplib.NO_CONTENT):
|
||||
log.debug(debug_message)
|
||||
return
|
||||
#TODO: Consider whether and how to allow plugins to handle error,
|
||||
# httplib.ACCEPTED & httplib.NO_CONTENT replies as well as successful
|
||||
# ones.
|
||||
if status == httplib.OK:
|
||||
log.debug("%s\n%s", debug_message, reply)
|
||||
else:
|
||||
log.debug("%s - %s\n%s", debug_message, description, reply)
|
||||
|
||||
plugins = PluginContainer(self.options.plugins)
|
||||
ctx = plugins.message.received(reply=reply)
|
||||
reply = ctx.reply
|
||||
|
||||
# SOAP standard states that SOAP errors must be accompanied by HTTP
|
||||
# status code 500 - internal server error:
|
||||
#
|
||||
# From SOAP 1.1 specification:
|
||||
# In case of a SOAP error while processing the request, the SOAP HTTP
|
||||
# server MUST issue an HTTP 500 "Internal Server Error" response and
|
||||
# include a SOAP message in the response containing a SOAP Fault
|
||||
# element (see section 4.4) indicating the SOAP processing error.
|
||||
#
|
||||
# From WS-I Basic profile:
|
||||
# An INSTANCE MUST use a "500 Internal Server Error" HTTP status code
|
||||
# if the response message is a SOAP Fault.
|
||||
replyroot = None
|
||||
if status in (httplib.OK, httplib.INTERNAL_SERVER_ERROR):
|
||||
replyroot = _parse(reply)
|
||||
plugins.message.parsed(reply=replyroot)
|
||||
fault = self.__get_fault(replyroot)
|
||||
if fault:
|
||||
if status != httplib.INTERNAL_SERVER_ERROR:
|
||||
log.warning("Web service reported a SOAP processing fault "
|
||||
"using an unexpected HTTP status code %d. Reporting "
|
||||
"as an internal server error.", status)
|
||||
if self.options.faults:
|
||||
raise WebFault(fault, replyroot)
|
||||
return httplib.INTERNAL_SERVER_ERROR, fault
|
||||
if status != httplib.OK:
|
||||
if self.options.faults:
|
||||
#TODO: Use a more specific exception class here.
|
||||
raise Exception((status, description))
|
||||
return status, description
|
||||
|
||||
if self.options.retxml:
|
||||
return reply
|
||||
|
||||
result = replyroot and self.method.binding.output.get_reply(
|
||||
self.method, replyroot)
|
||||
ctx = plugins.message.unmarshalled(reply=result)
|
||||
result = ctx.reply
|
||||
if self.options.faults:
|
||||
return result
|
||||
return httplib.OK, result
|
||||
|
||||
def __get_fault(self, replyroot):
|
||||
"""
|
||||
Extract fault information from a SOAP reply.
|
||||
|
||||
Returns an I{unmarshalled} fault L{Object} or None in case the given
|
||||
XML document does not contain a SOAP <Fault> element.
|
||||
|
||||
@param replyroot: A SOAP reply message root XML element or None.
|
||||
@type replyroot: L{Element}|I{None}
|
||||
@return: A fault object.
|
||||
@rtype: L{Object}
|
||||
|
||||
"""
|
||||
envns = suds.bindings.binding.envns
|
||||
soapenv = replyroot and replyroot.getChild("Envelope", envns)
|
||||
soapbody = soapenv and soapenv.getChild("Body", envns)
|
||||
fault = soapbody and soapbody.getChild("Fault", envns)
|
||||
return fault is not None and UmxBasic().process(fault)
|
||||
|
||||
def __headers(self):
|
||||
"""
|
||||
Get HTTP headers for a HTTP/HTTPS SOAP request.
|
||||
|
||||
@return: A dictionary of header/values.
|
||||
@rtype: dict
|
||||
|
||||
"""
|
||||
action = self.method.soap.action
|
||||
if isinstance(action, unicode):
|
||||
action = action.encode("utf-8")
|
||||
result = {
|
||||
"Content-Type": "text/xml; charset=utf-8",
|
||||
"SOAPAction": action}
|
||||
result.update(**self.options.headers)
|
||||
log.debug("headers = %s", result)
|
||||
return result
|
||||
|
||||
def __location(self):
|
||||
"""Returns the SOAP request's target location URL."""
|
||||
return Unskin(self.options).get("location", self.method.location)
|
||||
|
||||
|
||||
class _SimClient(_SoapClient):
|
||||
"""
|
||||
Loopback _SoapClient used for SOAP request/reply simulation.
|
||||
|
||||
Used when a web service operation is invoked with injected SOAP request or
|
||||
reply data.
|
||||
|
||||
"""
|
||||
|
||||
__injkey = "__inject"
|
||||
|
||||
@classmethod
|
||||
def simulation(cls, kwargs):
|
||||
"""Get whether injected data has been specified in I{kwargs}."""
|
||||
return _SimClient.__injkey in kwargs
|
||||
|
||||
def invoke(self, args, kwargs):
|
||||
"""
|
||||
Invoke a specified web service method.
|
||||
|
||||
Uses an injected SOAP request/response instead of a regularly
|
||||
constructed/received one.
|
||||
|
||||
Depending on how the ``nosend`` & ``retxml`` options are set, may do
|
||||
one of the following:
|
||||
* Return a constructed web service operation request without sending
|
||||
it to the web service.
|
||||
* Invoke the web service operation and return its SOAP reply XML.
|
||||
* Invoke the web service operation, process its results and return
|
||||
the Python object representing the returned value.
|
||||
|
||||
@param args: Positional arguments for the method invoked.
|
||||
@type args: list|tuple
|
||||
@param kwargs: Keyword arguments for the method invoked.
|
||||
@type kwargs: dict
|
||||
@return: SOAP request, SOAP reply or a web service return value.
|
||||
@rtype: L{RequestContext}|I{builtin}|I{subclass of} L{Object}|I{bytes}|
|
||||
I{None}
|
||||
|
||||
"""
|
||||
simulation = kwargs.pop(self.__injkey)
|
||||
msg = simulation.get("msg")
|
||||
if msg is not None:
|
||||
assert msg.__class__ is suds.byte_str_class
|
||||
return self.send(_parse(msg))
|
||||
msg = self.method.binding.input.get_message(self.method, args, kwargs)
|
||||
log.debug("inject (simulated) send message:\n%s", msg)
|
||||
reply = simulation.get("reply")
|
||||
if reply is not None:
|
||||
assert reply.__class__ is suds.byte_str_class
|
||||
status = simulation.get("status")
|
||||
description = simulation.get("description")
|
||||
if description is None:
|
||||
description = "injected reply"
|
||||
return self.process_reply(reply, status, description)
|
||||
raise Exception("reply or msg injection parameter expected")
|
||||
|
||||
|
||||
def _parse(string):
|
||||
"""
|
||||
Parses given XML document content.
|
||||
|
||||
Returns the resulting root XML element node or None if the given XML
|
||||
content is empty.
|
||||
|
||||
@param string: XML document content to parse.
|
||||
@type string: I{bytes}
|
||||
@return: Resulting root XML element node or None.
|
||||
@rtype: L{Element}|I{None}
|
||||
|
||||
"""
|
||||
if string:
|
||||
return suds.sax.parser.Parser().parse(string=string)
|
@ -1,950 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Service proxy implementation providing access to web services.
|
||||
|
||||
"""
|
||||
|
||||
import suds
|
||||
from suds import *
|
||||
import suds.bindings.binding
|
||||
from suds.builder import Builder
|
||||
import suds.cache
|
||||
import suds.metrics as metrics
|
||||
from suds.options import Options
|
||||
from suds.plugin import PluginContainer
|
||||
from suds.properties import Unskin
|
||||
from suds.reader import DefinitionsReader
|
||||
from suds.resolver import PathResolver
|
||||
from suds.sax.document import Document
|
||||
import suds.sax.parser
|
||||
from suds.servicedefinition import ServiceDefinition
|
||||
import suds.transport
|
||||
import suds.transport.https
|
||||
from suds.umx.basic import Basic as UmxBasic
|
||||
from suds.wsdl import Definitions
|
||||
import sudsobject
|
||||
|
||||
from cookielib import CookieJar
|
||||
from copy import deepcopy
|
||||
import httplib
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Client(UnicodeMixin):
|
||||
"""
|
||||
A lightweight web service client.
|
||||
|
||||
@ivar wsdl: The WSDL object.
|
||||
@type wsdl:L{Definitions}
|
||||
@ivar service: The service proxy used to invoke operations.
|
||||
@type service: L{Service}
|
||||
@ivar factory: The factory used to create objects.
|
||||
@type factory: L{Factory}
|
||||
@ivar sd: The service definition
|
||||
@type sd: L{ServiceDefinition}
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def items(cls, sobject):
|
||||
"""
|
||||
Extract I{items} from a suds object.
|
||||
|
||||
Much like the items() method works on I{dict}.
|
||||
|
||||
@param sobject: A suds object
|
||||
@type sobject: L{Object}
|
||||
@return: A list of items contained in I{sobject}.
|
||||
@rtype: [(key, value),...]
|
||||
|
||||
"""
|
||||
return sudsobject.items(sobject)
|
||||
|
||||
@classmethod
|
||||
def dict(cls, sobject):
|
||||
"""
|
||||
Convert a sudsobject into a dictionary.
|
||||
|
||||
@param sobject: A suds object
|
||||
@type sobject: L{Object}
|
||||
@return: A dictionary of items contained in I{sobject}.
|
||||
@rtype: dict
|
||||
|
||||
"""
|
||||
return sudsobject.asdict(sobject)
|
||||
|
||||
@classmethod
|
||||
def metadata(cls, sobject):
|
||||
"""
|
||||
Extract the metadata from a suds object.
|
||||
|
||||
@param sobject: A suds object
|
||||
@type sobject: L{Object}
|
||||
@return: The object's metadata
|
||||
@rtype: L{sudsobject.Metadata}
|
||||
|
||||
"""
|
||||
return sobject.__metadata__
|
||||
|
||||
def __init__(self, url, **kwargs):
|
||||
"""
|
||||
@param url: The URL for the WSDL.
|
||||
@type url: str
|
||||
@param kwargs: keyword arguments.
|
||||
@see: L{Options}
|
||||
|
||||
"""
|
||||
options = Options()
|
||||
options.transport = suds.transport.https.HttpAuthenticated()
|
||||
self.options = options
|
||||
if "cache" not in kwargs:
|
||||
kwargs["cache"] = suds.cache.ObjectCache(days=1)
|
||||
self.set_options(**kwargs)
|
||||
reader = DefinitionsReader(options, Definitions)
|
||||
self.wsdl = reader.open(url)
|
||||
plugins = PluginContainer(options.plugins)
|
||||
plugins.init.initialized(wsdl=self.wsdl)
|
||||
self.factory = Factory(self.wsdl)
|
||||
self.service = ServiceSelector(self, self.wsdl.services)
|
||||
self.sd = []
|
||||
for s in self.wsdl.services:
|
||||
sd = ServiceDefinition(self.wsdl, s)
|
||||
self.sd.append(sd)
|
||||
|
||||
def set_options(self, **kwargs):
|
||||
"""
|
||||
Set options.
|
||||
|
||||
@param kwargs: keyword arguments.
|
||||
@see: L{Options}
|
||||
|
||||
"""
|
||||
p = Unskin(self.options)
|
||||
p.update(kwargs)
|
||||
|
||||
def add_prefix(self, prefix, uri):
|
||||
"""
|
||||
Add I{static} mapping of an XML namespace prefix to a namespace.
|
||||
|
||||
Useful for cases when a WSDL and referenced XSD schemas make heavy use
|
||||
of namespaces and those namespaces are subject to change.
|
||||
|
||||
@param prefix: An XML namespace prefix.
|
||||
@type prefix: str
|
||||
@param uri: An XML namespace URI.
|
||||
@type uri: str
|
||||
@raise Exception: prefix already mapped.
|
||||
|
||||
"""
|
||||
root = self.wsdl.root
|
||||
mapped = root.resolvePrefix(prefix, None)
|
||||
if mapped is None:
|
||||
root.addPrefix(prefix, uri)
|
||||
return
|
||||
if mapped[1] != uri:
|
||||
raise Exception('"%s" already mapped as "%s"' % (prefix, mapped))
|
||||
|
||||
def clone(self):
|
||||
"""
|
||||
Get a shallow clone of this object.
|
||||
|
||||
The clone only shares the WSDL. All other attributes are unique to the
|
||||
cloned object including options.
|
||||
|
||||
@return: A shallow clone.
|
||||
@rtype: L{Client}
|
||||
|
||||
"""
|
||||
class Uninitialized(Client):
|
||||
def __init__(self):
|
||||
pass
|
||||
clone = Uninitialized()
|
||||
clone.options = Options()
|
||||
cp = Unskin(clone.options)
|
||||
mp = Unskin(self.options)
|
||||
cp.update(deepcopy(mp))
|
||||
clone.wsdl = self.wsdl
|
||||
clone.factory = self.factory
|
||||
clone.service = ServiceSelector(clone, self.wsdl.services)
|
||||
clone.sd = self.sd
|
||||
return clone
|
||||
|
||||
def __unicode__(self):
|
||||
s = ["\n"]
|
||||
s.append("Suds ( https://fedorahosted.org/suds/ )")
|
||||
s.append(" version: %s" % (suds.__version__,))
|
||||
if suds.__build__:
|
||||
s.append(" build: %s" % (suds.__build__,))
|
||||
for sd in self.sd:
|
||||
s.append("\n\n%s" % (unicode(sd),))
|
||||
return "".join(s)
|
||||
|
||||
|
||||
class Factory:
|
||||
"""
|
||||
A factory for instantiating types defined in the WSDL.
|
||||
|
||||
@ivar resolver: A schema type resolver.
|
||||
@type resolver: L{PathResolver}
|
||||
@ivar builder: A schema object builder.
|
||||
@type builder: L{Builder}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, wsdl):
|
||||
"""
|
||||
@param wsdl: A schema object.
|
||||
@type wsdl: L{wsdl.Definitions}
|
||||
|
||||
"""
|
||||
self.wsdl = wsdl
|
||||
self.resolver = PathResolver(wsdl)
|
||||
self.builder = Builder(self.resolver)
|
||||
|
||||
def create(self, name):
|
||||
"""
|
||||
Create a WSDL type by name.
|
||||
|
||||
@param name: The name of a type defined in the WSDL.
|
||||
@type name: str
|
||||
@return: The requested object.
|
||||
@rtype: L{Object}
|
||||
|
||||
"""
|
||||
timer = metrics.Timer()
|
||||
timer.start()
|
||||
type = self.resolver.find(name)
|
||||
if type is None:
|
||||
raise TypeNotFound(name)
|
||||
if type.enum():
|
||||
result = sudsobject.Factory.object(name)
|
||||
for e, a in type.children():
|
||||
setattr(result, e.name, e.name)
|
||||
else:
|
||||
try:
|
||||
result = self.builder.build(type)
|
||||
except Exception, e:
|
||||
log.error("create '%s' failed", name, exc_info=True)
|
||||
raise BuildError(name, e)
|
||||
timer.stop()
|
||||
metrics.log.debug("%s created: %s", name, timer)
|
||||
return result
|
||||
|
||||
def separator(self, ps):
|
||||
"""
|
||||
Set the path separator.
|
||||
|
||||
@param ps: The new path separator.
|
||||
@type ps: char
|
||||
|
||||
"""
|
||||
self.resolver = PathResolver(self.wsdl, ps)
|
||||
|
||||
|
||||
class ServiceSelector:
|
||||
"""
|
||||
The B{service} selector is used to select a web service.
|
||||
|
||||
Most WSDLs only define a single service in which case access by subscript
|
||||
is passed through to a L{PortSelector}. This is also the behavior when a
|
||||
I{default} service has been specified. In cases where multiple services
|
||||
have been defined and no default has been specified, the service is found
|
||||
by name (or index) and a L{PortSelector} for the service is returned. In
|
||||
all cases, attribute access is forwarded to the L{PortSelector} for either
|
||||
the I{first} service or the I{default} service (when specified).
|
||||
|
||||
@ivar __client: A suds client.
|
||||
@type __client: L{Client}
|
||||
@ivar __services: A list of I{WSDL} services.
|
||||
@type __services: list
|
||||
|
||||
"""
|
||||
def __init__(self, client, services):
|
||||
"""
|
||||
@param client: A suds client.
|
||||
@type client: L{Client}
|
||||
@param services: A list of I{WSDL} services.
|
||||
@type services: list
|
||||
|
||||
"""
|
||||
self.__client = client
|
||||
self.__services = services
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Attribute access is forwarded to the L{PortSelector}.
|
||||
|
||||
Uses the I{default} service if specified or the I{first} service
|
||||
otherwise.
|
||||
|
||||
@param name: Method name.
|
||||
@type name: str
|
||||
@return: A L{PortSelector}.
|
||||
@rtype: L{PortSelector}.
|
||||
|
||||
"""
|
||||
default = self.__ds()
|
||||
if default is None:
|
||||
port = self.__find(0)
|
||||
else:
|
||||
port = default
|
||||
return getattr(port, name)
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""
|
||||
Provides I{service} selection by name (string) or index (integer).
|
||||
|
||||
In cases where only a single service is defined or a I{default} has
|
||||
been specified, the request is forwarded to the L{PortSelector}.
|
||||
|
||||
@param name: The name (or index) of a service.
|
||||
@type name: int|str
|
||||
@return: A L{PortSelector} for the specified service.
|
||||
@rtype: L{PortSelector}.
|
||||
|
||||
"""
|
||||
if len(self.__services) == 1:
|
||||
port = self.__find(0)
|
||||
return port[name]
|
||||
default = self.__ds()
|
||||
if default is not None:
|
||||
port = default
|
||||
return port[name]
|
||||
return self.__find(name)
|
||||
|
||||
def __find(self, name):
|
||||
"""
|
||||
Find a I{service} by name (string) or index (integer).
|
||||
|
||||
@param name: The name (or index) of a service.
|
||||
@type name: int|str
|
||||
@return: A L{PortSelector} for the found service.
|
||||
@rtype: L{PortSelector}.
|
||||
|
||||
"""
|
||||
service = None
|
||||
if not self.__services:
|
||||
raise Exception, "No services defined"
|
||||
if isinstance(name, int):
|
||||
try:
|
||||
service = self.__services[name]
|
||||
name = service.name
|
||||
except IndexError:
|
||||
raise ServiceNotFound, "at [%d]" % (name,)
|
||||
else:
|
||||
for s in self.__services:
|
||||
if name == s.name:
|
||||
service = s
|
||||
break
|
||||
if service is None:
|
||||
raise ServiceNotFound, name
|
||||
return PortSelector(self.__client, service.ports, name)
|
||||
|
||||
def __ds(self):
|
||||
"""
|
||||
Get the I{default} service if defined in the I{options}.
|
||||
|
||||
@return: A L{PortSelector} for the I{default} service.
|
||||
@rtype: L{PortSelector}.
|
||||
|
||||
"""
|
||||
ds = self.__client.options.service
|
||||
if ds is not None:
|
||||
return self.__find(ds)
|
||||
|
||||
|
||||
class PortSelector:
|
||||
"""
|
||||
The B{port} selector is used to select a I{web service} B{port}.
|
||||
|
||||
In cases where multiple ports have been defined and no default has been
|
||||
specified, the port is found by name (or index) and a L{MethodSelector} for
|
||||
the port is returned. In all cases, attribute access is forwarded to the
|
||||
L{MethodSelector} for either the I{first} port or the I{default} port (when
|
||||
specified).
|
||||
|
||||
@ivar __client: A suds client.
|
||||
@type __client: L{Client}
|
||||
@ivar __ports: A list of I{service} ports.
|
||||
@type __ports: list
|
||||
@ivar __qn: The I{qualified} name of the port (used for logging).
|
||||
@type __qn: str
|
||||
|
||||
"""
|
||||
def __init__(self, client, ports, qn):
|
||||
"""
|
||||
@param client: A suds client.
|
||||
@type client: L{Client}
|
||||
@param ports: A list of I{service} ports.
|
||||
@type ports: list
|
||||
@param qn: The name of the service.
|
||||
@type qn: str
|
||||
|
||||
"""
|
||||
self.__client = client
|
||||
self.__ports = ports
|
||||
self.__qn = qn
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Attribute access is forwarded to the L{MethodSelector}.
|
||||
|
||||
Uses the I{default} port when specified or the I{first} port otherwise.
|
||||
|
||||
@param name: The name of a method.
|
||||
@type name: str
|
||||
@return: A L{MethodSelector}.
|
||||
@rtype: L{MethodSelector}.
|
||||
|
||||
"""
|
||||
default = self.__dp()
|
||||
if default is None:
|
||||
m = self.__find(0)
|
||||
else:
|
||||
m = default
|
||||
return getattr(m, name)
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""
|
||||
Provides I{port} selection by name (string) or index (integer).
|
||||
|
||||
In cases where only a single port is defined or a I{default} has been
|
||||
specified, the request is forwarded to the L{MethodSelector}.
|
||||
|
||||
@param name: The name (or index) of a port.
|
||||
@type name: int|str
|
||||
@return: A L{MethodSelector} for the specified port.
|
||||
@rtype: L{MethodSelector}.
|
||||
|
||||
"""
|
||||
default = self.__dp()
|
||||
if default is None:
|
||||
return self.__find(name)
|
||||
return default
|
||||
|
||||
def __find(self, name):
|
||||
"""
|
||||
Find a I{port} by name (string) or index (integer).
|
||||
|
||||
@param name: The name (or index) of a port.
|
||||
@type name: int|str
|
||||
@return: A L{MethodSelector} for the found port.
|
||||
@rtype: L{MethodSelector}.
|
||||
|
||||
"""
|
||||
port = None
|
||||
if not self.__ports:
|
||||
raise Exception, "No ports defined: %s" % (self.__qn,)
|
||||
if isinstance(name, int):
|
||||
qn = "%s[%d]" % (self.__qn, name)
|
||||
try:
|
||||
port = self.__ports[name]
|
||||
except IndexError:
|
||||
raise PortNotFound, qn
|
||||
else:
|
||||
qn = ".".join((self.__qn, name))
|
||||
for p in self.__ports:
|
||||
if name == p.name:
|
||||
port = p
|
||||
break
|
||||
if port is None:
|
||||
raise PortNotFound, qn
|
||||
qn = ".".join((self.__qn, port.name))
|
||||
return MethodSelector(self.__client, port.methods, qn)
|
||||
|
||||
def __dp(self):
|
||||
"""
|
||||
Get the I{default} port if defined in the I{options}.
|
||||
|
||||
@return: A L{MethodSelector} for the I{default} port.
|
||||
@rtype: L{MethodSelector}.
|
||||
|
||||
"""
|
||||
dp = self.__client.options.port
|
||||
if dp is not None:
|
||||
return self.__find(dp)
|
||||
|
||||
|
||||
class MethodSelector:
|
||||
"""
|
||||
The B{method} selector is used to select a B{method} by name.
|
||||
|
||||
@ivar __client: A suds client.
|
||||
@type __client: L{Client}
|
||||
@ivar __methods: A dictionary of methods.
|
||||
@type __methods: dict
|
||||
@ivar __qn: The I{qualified} name of the method (used for logging).
|
||||
@type __qn: str
|
||||
|
||||
"""
|
||||
def __init__(self, client, methods, qn):
|
||||
"""
|
||||
@param client: A suds client.
|
||||
@type client: L{Client}
|
||||
@param methods: A dictionary of methods.
|
||||
@type methods: dict
|
||||
@param qn: The I{qualified} name of the port.
|
||||
@type qn: str
|
||||
|
||||
"""
|
||||
self.__client = client
|
||||
self.__methods = methods
|
||||
self.__qn = qn
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Get a method by name and return it in an I{execution wrapper}.
|
||||
|
||||
@param name: The name of a method.
|
||||
@type name: str
|
||||
@return: An I{execution wrapper} for the specified method name.
|
||||
@rtype: L{Method}
|
||||
|
||||
"""
|
||||
return self[name]
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""
|
||||
Get a method by name and return it in an I{execution wrapper}.
|
||||
|
||||
@param name: The name of a method.
|
||||
@type name: str
|
||||
@return: An I{execution wrapper} for the specified method name.
|
||||
@rtype: L{Method}
|
||||
|
||||
"""
|
||||
m = self.__methods.get(name)
|
||||
if m is None:
|
||||
qn = ".".join((self.__qn, name))
|
||||
raise MethodNotFound, qn
|
||||
return Method(self.__client, m)
|
||||
|
||||
|
||||
class Method:
|
||||
"""
|
||||
The I{method} (namespace) object.
|
||||
|
||||
@ivar client: A client object.
|
||||
@type client: L{Client}
|
||||
@ivar method: A I{WSDL} method.
|
||||
@type I{raw} Method.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, client, method):
|
||||
"""
|
||||
@param client: A client object.
|
||||
@type client: L{Client}
|
||||
@param method: A I{raw} method.
|
||||
@type I{raw} Method.
|
||||
|
||||
"""
|
||||
self.client = client
|
||||
self.method = method
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Invoke the method."""
|
||||
clientclass = self.clientclass(kwargs)
|
||||
client = clientclass(self.client, self.method)
|
||||
try:
|
||||
return client.invoke(args, kwargs)
|
||||
except WebFault, e:
|
||||
if self.faults():
|
||||
raise
|
||||
return httplib.INTERNAL_SERVER_ERROR, e
|
||||
|
||||
def faults(self):
|
||||
"""Get faults option."""
|
||||
return self.client.options.faults
|
||||
|
||||
def clientclass(self, kwargs):
|
||||
"""Get SOAP client class."""
|
||||
if _SimClient.simulation(kwargs):
|
||||
return _SimClient
|
||||
return _SoapClient
|
||||
|
||||
|
||||
class RequestContext:
|
||||
"""
|
||||
A request context.
|
||||
|
||||
Returned by a suds Client when invoking a web service operation with the
|
||||
``nosend`` enabled. Allows the caller to take care of sending the request
|
||||
himself and return back the reply data for further processing.
|
||||
|
||||
@ivar envelope: The SOAP request envelope.
|
||||
@type envelope: I{bytes}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, process_reply, envelope):
|
||||
"""
|
||||
@param process_reply: A callback for processing a user defined reply.
|
||||
@type process_reply: I{callable}
|
||||
@param envelope: The SOAP request envelope.
|
||||
@type envelope: I{bytes}
|
||||
|
||||
"""
|
||||
self.__process_reply = process_reply
|
||||
self.envelope = envelope
|
||||
|
||||
def process_reply(self, reply, status=None, description=None):
|
||||
"""
|
||||
Re-entry for processing a successful reply.
|
||||
|
||||
Depending on how the ``retxml`` option is set, may return the SOAP
|
||||
reply XML or process it and return the Python object representing the
|
||||
returned value.
|
||||
|
||||
@param reply: The SOAP reply envelope.
|
||||
@type reply: I{bytes}
|
||||
@param status: The HTTP status code.
|
||||
@type status: int
|
||||
@param description: Additional status description.
|
||||
@type description: I{bytes}
|
||||
@return: The invoked web service operation return value.
|
||||
@rtype: I{builtin}|I{subclass of} L{Object}|I{bytes}|I{None}
|
||||
|
||||
"""
|
||||
return self.__process_reply(reply, status, description)
|
||||
|
||||
|
||||
class _SoapClient:
|
||||
"""
|
||||
An internal lightweight SOAP based web service operation client.
|
||||
|
||||
Each instance is constructed for specific web service operation and knows
|
||||
how to:
|
||||
- Construct a SOAP request for it.
|
||||
- Transport a SOAP request for it using a configured transport.
|
||||
- Receive a SOAP reply using a configured transport.
|
||||
- Process the received SOAP reply.
|
||||
|
||||
Depending on the given suds options, may do all the tasks listed above or
|
||||
may stop the process at an earlier point and return some intermediate
|
||||
result, e.g. the constructed SOAP request or the raw received SOAP reply.
|
||||
See the invoke() method for more detailed information.
|
||||
|
||||
@ivar service: The target method.
|
||||
@type service: L{Service}
|
||||
@ivar method: A target method.
|
||||
@type method: L{Method}
|
||||
@ivar options: A dictonary of options.
|
||||
@type options: dict
|
||||
@ivar cookiejar: A cookie jar.
|
||||
@type cookiejar: libcookie.CookieJar
|
||||
|
||||
"""
|
||||
|
||||
TIMEOUT_ARGUMENT = "__timeout"
|
||||
|
||||
def __init__(self, client, method):
|
||||
"""
|
||||
@param client: A suds client.
|
||||
@type client: L{Client}
|
||||
@param method: A target method.
|
||||
@type method: L{Method}
|
||||
|
||||
"""
|
||||
self.client = client
|
||||
self.method = method
|
||||
self.options = client.options
|
||||
self.cookiejar = CookieJar()
|
||||
|
||||
def invoke(self, args, kwargs):
|
||||
"""
|
||||
Invoke a specified web service method.
|
||||
|
||||
Depending on how the ``nosend`` & ``retxml`` options are set, may do
|
||||
one of the following:
|
||||
* Return a constructed web service operation SOAP request without
|
||||
sending it to the web service.
|
||||
* Invoke the web service operation and return its SOAP reply XML.
|
||||
* Invoke the web service operation, process its results and return
|
||||
the Python object representing the returned value.
|
||||
|
||||
When returning a SOAP request, the request is wrapped inside a
|
||||
RequestContext object allowing the user to acquire a corresponding SOAP
|
||||
reply himself and then pass it back to suds for further processing.
|
||||
|
||||
Constructed request data is automatically processed using registered
|
||||
plugins and serialized into a byte-string. Exact request XML formatting
|
||||
may be affected by the ``prettyxml`` suds option.
|
||||
|
||||
@param args: A list of args for the method invoked.
|
||||
@type args: list|tuple
|
||||
@param kwargs: Named (keyword) args for the method invoked.
|
||||
@type kwargs: dict
|
||||
@return: SOAP request, SOAP reply or a web service return value.
|
||||
@rtype: L{RequestContext}|I{builtin}|I{subclass of} L{Object}|I{bytes}|
|
||||
I{None}
|
||||
|
||||
"""
|
||||
timer = metrics.Timer()
|
||||
timer.start()
|
||||
binding = self.method.binding.input
|
||||
timeout = kwargs.pop(_SoapClient.TIMEOUT_ARGUMENT, None)
|
||||
soapenv = binding.get_message(self.method, args, kwargs)
|
||||
timer.stop()
|
||||
method_name = self.method.name
|
||||
metrics.log.debug("message for '%s' created: %s", method_name, timer)
|
||||
timer.start()
|
||||
result = self.send(soapenv, timeout=timeout)
|
||||
timer.stop()
|
||||
metrics.log.debug("method '%s' invoked: %s", method_name, timer)
|
||||
return result
|
||||
|
||||
def send(self, soapenv, timeout=None):
|
||||
"""
|
||||
Send SOAP message.
|
||||
|
||||
Depending on how the ``nosend`` & ``retxml`` options are set, may do
|
||||
one of the following:
|
||||
* Return a constructed web service operation request without sending
|
||||
it to the web service.
|
||||
* Invoke the web service operation and return its SOAP reply XML.
|
||||
* Invoke the web service operation, process its results and return
|
||||
the Python object representing the returned value.
|
||||
|
||||
@param soapenv: A SOAP envelope to send.
|
||||
@type soapenv: L{Document}
|
||||
@return: SOAP request, SOAP reply or a web service return value.
|
||||
@rtype: L{RequestContext}|I{builtin}|I{subclass of} L{Object}|I{bytes}|
|
||||
I{None}
|
||||
|
||||
"""
|
||||
location = self.__location()
|
||||
log.debug("sending to (%s)\nmessage:\n%s", location, soapenv)
|
||||
plugins = PluginContainer(self.options.plugins)
|
||||
plugins.message.marshalled(envelope=soapenv.root())
|
||||
if self.options.prettyxml:
|
||||
soapenv = soapenv.str()
|
||||
else:
|
||||
soapenv = soapenv.plain()
|
||||
soapenv = soapenv.encode("utf-8")
|
||||
ctx = plugins.message.sending(envelope=soapenv)
|
||||
soapenv = ctx.envelope
|
||||
if self.options.nosend:
|
||||
return RequestContext(self.process_reply, soapenv)
|
||||
request = suds.transport.Request(location, soapenv, timeout)
|
||||
request.headers = self.__headers()
|
||||
try:
|
||||
timer = metrics.Timer()
|
||||
timer.start()
|
||||
reply = self.options.transport.send(request)
|
||||
timer.stop()
|
||||
metrics.log.debug("waited %s on server reply", timer)
|
||||
except suds.transport.TransportError, e:
|
||||
content = e.fp and e.fp.read() or ""
|
||||
return self.process_reply(content, e.httpcode, tostr(e))
|
||||
return self.process_reply(reply.message, None, None)
|
||||
|
||||
def process_reply(self, reply, status, description):
|
||||
"""
|
||||
Process a web service operation SOAP reply.
|
||||
|
||||
Depending on how the ``retxml`` option is set, may return the SOAP
|
||||
reply XML or process it and return the Python object representing the
|
||||
returned value.
|
||||
|
||||
@param reply: The SOAP reply envelope.
|
||||
@type reply: I{bytes}
|
||||
@param status: The HTTP status code (None indicates httplib.OK).
|
||||
@type status: int|I{None}
|
||||
@param description: Additional status description.
|
||||
@type description: str
|
||||
@return: The invoked web service operation return value.
|
||||
@rtype: I{builtin}|I{subclass of} L{Object}|I{bytes}|I{None}
|
||||
|
||||
"""
|
||||
if status is None:
|
||||
status = httplib.OK
|
||||
debug_message = "Reply HTTP status - %d" % (status,)
|
||||
if status in (httplib.ACCEPTED, httplib.NO_CONTENT):
|
||||
log.debug(debug_message)
|
||||
return
|
||||
#TODO: Consider whether and how to allow plugins to handle error,
|
||||
# httplib.ACCEPTED & httplib.NO_CONTENT replies as well as successful
|
||||
# ones.
|
||||
if status == httplib.OK:
|
||||
log.debug("%s\n%s", debug_message, reply)
|
||||
else:
|
||||
log.debug("%s - %s\n%s", debug_message, description, reply)
|
||||
|
||||
plugins = PluginContainer(self.options.plugins)
|
||||
ctx = plugins.message.received(reply=reply)
|
||||
reply = ctx.reply
|
||||
|
||||
# SOAP standard states that SOAP errors must be accompanied by HTTP
|
||||
# status code 500 - internal server error:
|
||||
#
|
||||
# From SOAP 1.1 specification:
|
||||
# In case of a SOAP error while processing the request, the SOAP HTTP
|
||||
# server MUST issue an HTTP 500 "Internal Server Error" response and
|
||||
# include a SOAP message in the response containing a SOAP Fault
|
||||
# element (see section 4.4) indicating the SOAP processing error.
|
||||
#
|
||||
# From WS-I Basic profile:
|
||||
# An INSTANCE MUST use a "500 Internal Server Error" HTTP status code
|
||||
# if the response message is a SOAP Fault.
|
||||
replyroot = None
|
||||
if status in (httplib.OK, httplib.INTERNAL_SERVER_ERROR):
|
||||
replyroot = _parse(reply)
|
||||
plugins.message.parsed(reply=replyroot)
|
||||
fault = self.__get_fault(replyroot)
|
||||
if fault:
|
||||
if status != httplib.INTERNAL_SERVER_ERROR:
|
||||
log.warning("Web service reported a SOAP processing fault "
|
||||
"using an unexpected HTTP status code %d. Reporting "
|
||||
"as an internal server error.", status)
|
||||
if self.options.faults:
|
||||
raise WebFault(fault, replyroot)
|
||||
return httplib.INTERNAL_SERVER_ERROR, fault
|
||||
if status != httplib.OK:
|
||||
if self.options.faults:
|
||||
#TODO: Use a more specific exception class here.
|
||||
raise Exception((status, description))
|
||||
return status, description
|
||||
|
||||
if self.options.retxml:
|
||||
return reply
|
||||
|
||||
result = replyroot and self.method.binding.output.get_reply(
|
||||
self.method, replyroot)
|
||||
ctx = plugins.message.unmarshalled(reply=result)
|
||||
result = ctx.reply
|
||||
if self.options.faults:
|
||||
return result
|
||||
return httplib.OK, result
|
||||
|
||||
def __get_fault(self, replyroot):
|
||||
"""
|
||||
Extract fault information from a SOAP reply.
|
||||
|
||||
Returns an I{unmarshalled} fault L{Object} or None in case the given
|
||||
XML document does not contain a SOAP <Fault> element.
|
||||
|
||||
@param replyroot: A SOAP reply message root XML element or None.
|
||||
@type replyroot: L{Element}|I{None}
|
||||
@return: A fault object.
|
||||
@rtype: L{Object}
|
||||
|
||||
"""
|
||||
envns = suds.bindings.binding.envns
|
||||
soapenv = replyroot and replyroot.getChild("Envelope", envns)
|
||||
soapbody = soapenv and soapenv.getChild("Body", envns)
|
||||
fault = soapbody and soapbody.getChild("Fault", envns)
|
||||
return fault is not None and UmxBasic().process(fault)
|
||||
|
||||
def __headers(self):
|
||||
"""
|
||||
Get HTTP headers for a HTTP/HTTPS SOAP request.
|
||||
|
||||
@return: A dictionary of header/values.
|
||||
@rtype: dict
|
||||
|
||||
"""
|
||||
action = self.method.soap.action
|
||||
if isinstance(action, unicode):
|
||||
action = action.encode("utf-8")
|
||||
result = {
|
||||
"Content-Type": "text/xml; charset=utf-8",
|
||||
"SOAPAction": action}
|
||||
result.update(**self.options.headers)
|
||||
log.debug("headers = %s", result)
|
||||
return result
|
||||
|
||||
def __location(self):
|
||||
"""Returns the SOAP request's target location URL."""
|
||||
return Unskin(self.options).get("location", self.method.location)
|
||||
|
||||
|
||||
class _SimClient(_SoapClient):
|
||||
"""
|
||||
Loopback _SoapClient used for SOAP request/reply simulation.
|
||||
|
||||
Used when a web service operation is invoked with injected SOAP request or
|
||||
reply data.
|
||||
|
||||
"""
|
||||
|
||||
__injkey = "__inject"
|
||||
|
||||
@classmethod
|
||||
def simulation(cls, kwargs):
|
||||
"""Get whether injected data has been specified in I{kwargs}."""
|
||||
return kwargs.has_key(_SimClient.__injkey)
|
||||
|
||||
def invoke(self, args, kwargs):
|
||||
"""
|
||||
Invoke a specified web service method.
|
||||
|
||||
Uses an injected SOAP request/response instead of a regularly
|
||||
constructed/received one.
|
||||
|
||||
Depending on how the ``nosend`` & ``retxml`` options are set, may do
|
||||
one of the following:
|
||||
* Return a constructed web service operation request without sending
|
||||
it to the web service.
|
||||
* Invoke the web service operation and return its SOAP reply XML.
|
||||
* Invoke the web service operation, process its results and return
|
||||
the Python object representing the returned value.
|
||||
|
||||
@param args: Positional arguments for the method invoked.
|
||||
@type args: list|tuple
|
||||
@param kwargs: Keyword arguments for the method invoked.
|
||||
@type kwargs: dict
|
||||
@return: SOAP request, SOAP reply or a web service return value.
|
||||
@rtype: L{RequestContext}|I{builtin}|I{subclass of} L{Object}|I{bytes}|
|
||||
I{None}
|
||||
|
||||
"""
|
||||
simulation = kwargs.pop(self.__injkey)
|
||||
msg = simulation.get("msg")
|
||||
if msg is not None:
|
||||
assert msg.__class__ is suds.byte_str_class
|
||||
return self.send(_parse(msg))
|
||||
msg = self.method.binding.input.get_message(self.method, args, kwargs)
|
||||
log.debug("inject (simulated) send message:\n%s", msg)
|
||||
reply = simulation.get("reply")
|
||||
if reply is not None:
|
||||
assert reply.__class__ is suds.byte_str_class
|
||||
status = simulation.get("status")
|
||||
description = simulation.get("description")
|
||||
if description is None:
|
||||
description = "injected reply"
|
||||
return self.process_reply(reply, status, description)
|
||||
raise Exception("reply or msg injection parameter expected")
|
||||
|
||||
|
||||
def _parse(string):
|
||||
"""
|
||||
Parses given XML document content.
|
||||
|
||||
Returns the resulting root XML element node or None if the given XML
|
||||
content is empty.
|
||||
|
||||
@param string: XML document content to parse.
|
||||
@type string: I{bytes}
|
||||
@return: Resulting root XML element node or None.
|
||||
@rtype: L{Element}|I{None}
|
||||
|
||||
"""
|
||||
if string:
|
||||
return suds.sax.parser.Parser().parse(string=string)
|
@ -1,63 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The I{metrics} module defines classes and other resources
|
||||
designed for collecting and reporting performance metrics.
|
||||
"""
|
||||
|
||||
import time
|
||||
from suds import *
|
||||
from math import modf
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Timer:
|
||||
|
||||
def __init__(self):
|
||||
self.started = 0
|
||||
self.stopped = 0
|
||||
|
||||
def start(self):
|
||||
self.started = time.time()
|
||||
self.stopped = 0
|
||||
return self
|
||||
|
||||
def stop(self):
|
||||
if self.started > 0:
|
||||
self.stopped = time.time()
|
||||
return self
|
||||
|
||||
def duration(self):
|
||||
return ( self.stopped - self.started )
|
||||
|
||||
def __str__(self):
|
||||
if self.started == 0:
|
||||
return 'not-running'
|
||||
if self.started > 0 and self.stopped == 0:
|
||||
return 'started: %d (running)' % self.started
|
||||
duration = self.duration()
|
||||
jmod = ( lambda m : (m[1], m[0]*1000) )
|
||||
if duration < 1:
|
||||
ms = (duration*1000)
|
||||
return '%d (ms)' % ms
|
||||
if duration < 60:
|
||||
m = modf(duration)
|
||||
return '%d.%.3d (seconds)' % jmod(m)
|
||||
m = modf(duration/60)
|
||||
return '%d.%.3d (minutes)' % jmod(m)
|
@ -1,60 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides modules containing classes to support marshalling to XML.
|
||||
|
||||
"""
|
||||
|
||||
from suds.sudsobject import Object
|
||||
|
||||
|
||||
class Content(Object):
|
||||
"""
|
||||
Marshaller content.
|
||||
|
||||
@ivar tag: The content tag.
|
||||
@type tag: str
|
||||
@ivar value: The content's value.
|
||||
@type value: I{any}
|
||||
|
||||
"""
|
||||
|
||||
extensions = []
|
||||
|
||||
def __init__(self, tag=None, value=None, **kwargs):
|
||||
"""
|
||||
@param tag: The content tag.
|
||||
@type tag: str
|
||||
@param value: The content's value.
|
||||
@type value: I{any}
|
||||
|
||||
"""
|
||||
Object.__init__(self)
|
||||
self.tag = tag
|
||||
self.value = value
|
||||
for k, v in kwargs.iteritems():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self.__dict__[name]
|
||||
except KeyError:
|
||||
pass
|
||||
if name in self.extensions:
|
||||
value = None
|
||||
setattr(self, name, value)
|
||||
return value
|
||||
raise AttributeError("Content has no attribute %s" % (name,))
|
@ -1,282 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides appender classes for I{marshalling}.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.mx import *
|
||||
from suds.sudsobject import Object, Property
|
||||
from suds.sax.element import Element
|
||||
from suds.sax.text import Text
|
||||
|
||||
|
||||
class Matcher:
|
||||
"""
|
||||
Appender matcher.
|
||||
@ivar cls: A class object.
|
||||
@type cls: I{classobj}
|
||||
"""
|
||||
|
||||
def __init__(self, cls):
|
||||
"""
|
||||
@param cls: A class object.
|
||||
@type cls: I{classobj}
|
||||
"""
|
||||
self.cls = cls
|
||||
|
||||
def __eq__(self, x):
|
||||
if self.cls is None:
|
||||
return x is None
|
||||
return isinstance(x, self.cls)
|
||||
|
||||
|
||||
class ContentAppender:
|
||||
"""
|
||||
Appender used to add content to marshalled objects.
|
||||
@ivar default: The default appender.
|
||||
@type default: L{Appender}
|
||||
@ivar appenders: A I{table} of appenders mapped by class.
|
||||
@type appenders: I{table}
|
||||
"""
|
||||
|
||||
def __init__(self, marshaller):
|
||||
"""
|
||||
@param marshaller: A marshaller.
|
||||
@type marshaller: L{suds.mx.core.Core}
|
||||
"""
|
||||
self.default = PrimitiveAppender(marshaller)
|
||||
self.appenders = (
|
||||
(Matcher(None), NoneAppender(marshaller)),
|
||||
(Matcher(null), NoneAppender(marshaller)),
|
||||
(Matcher(Property), PropertyAppender(marshaller)),
|
||||
(Matcher(Object), ObjectAppender(marshaller)),
|
||||
(Matcher(Element), ElementAppender(marshaller)),
|
||||
(Matcher(Text), TextAppender(marshaller)),
|
||||
(Matcher(list), ListAppender(marshaller)),
|
||||
(Matcher(tuple), ListAppender(marshaller)))
|
||||
|
||||
def append(self, parent, content):
|
||||
"""
|
||||
Select an appender and append the content to parent.
|
||||
@param parent: A parent node.
|
||||
@type parent: L{Element}
|
||||
@param content: The content to append.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
appender = self.default
|
||||
for matcher, candidate_appender in self.appenders:
|
||||
if matcher == content.value:
|
||||
appender = candidate_appender
|
||||
break
|
||||
appender.append(parent, content)
|
||||
|
||||
|
||||
class Appender:
|
||||
"""
|
||||
An appender used by the marshaller to append content.
|
||||
@ivar marshaller: A marshaller.
|
||||
@type marshaller: L{suds.mx.core.Core}
|
||||
"""
|
||||
|
||||
def __init__(self, marshaller):
|
||||
"""
|
||||
@param marshaller: A marshaller.
|
||||
@type marshaller: L{suds.mx.core.Core}
|
||||
"""
|
||||
self.marshaller = marshaller
|
||||
|
||||
def node(self, content):
|
||||
"""
|
||||
Create and return an XML node that is qualified
|
||||
using the I{type}. Also, make sure all referenced namespace
|
||||
prefixes are declared.
|
||||
@param content: The content for which processing has ended.
|
||||
@type content: L{Object}
|
||||
@return: A new node.
|
||||
@rtype: L{Element}
|
||||
"""
|
||||
return self.marshaller.node(content)
|
||||
|
||||
def setnil(self, node, content):
|
||||
"""
|
||||
Set the value of the I{node} to nill.
|
||||
@param node: A I{nil} node.
|
||||
@type node: L{Element}
|
||||
@param content: The content for which processing has ended.
|
||||
@type content: L{Object}
|
||||
"""
|
||||
self.marshaller.setnil(node, content)
|
||||
|
||||
def setdefault(self, node, content):
|
||||
"""
|
||||
Set the value of the I{node} to a default value.
|
||||
@param node: A I{nil} node.
|
||||
@type node: L{Element}
|
||||
@param content: The content for which processing has ended.
|
||||
@type content: L{Object}
|
||||
@return: The default.
|
||||
"""
|
||||
return self.marshaller.setdefault(node, content)
|
||||
|
||||
def optional(self, content):
|
||||
"""
|
||||
Get whether the specified content is optional.
|
||||
@param content: The content which to check.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
return self.marshaller.optional(content)
|
||||
|
||||
def suspend(self, content):
|
||||
"""
|
||||
Notify I{marshaller} that appending this content has suspended.
|
||||
@param content: The content for which processing has been suspended.
|
||||
@type content: L{Object}
|
||||
"""
|
||||
self.marshaller.suspend(content)
|
||||
|
||||
def resume(self, content):
|
||||
"""
|
||||
Notify I{marshaller} that appending this content has resumed.
|
||||
@param content: The content for which processing has been resumed.
|
||||
@type content: L{Object}
|
||||
"""
|
||||
self.marshaller.resume(content)
|
||||
|
||||
def append(self, parent, content):
|
||||
"""
|
||||
Append the specified L{content} to the I{parent}.
|
||||
@param content: The content to append.
|
||||
@type content: L{Object}
|
||||
"""
|
||||
self.marshaller.append(parent, content)
|
||||
|
||||
|
||||
class PrimitiveAppender(Appender):
|
||||
"""
|
||||
An appender for python I{primitive} types.
|
||||
"""
|
||||
|
||||
def append(self, parent, content):
|
||||
if content.tag.startswith('_'):
|
||||
attr = content.tag[1:]
|
||||
value = tostr(content.value)
|
||||
if value:
|
||||
parent.set(attr, value)
|
||||
else:
|
||||
child = self.node(content)
|
||||
child.setText(tostr(content.value))
|
||||
parent.append(child)
|
||||
|
||||
|
||||
class NoneAppender(Appender):
|
||||
"""
|
||||
An appender for I{None} values.
|
||||
"""
|
||||
|
||||
def append(self, parent, content):
|
||||
child = self.node(content)
|
||||
default = self.setdefault(child, content)
|
||||
if default is None:
|
||||
self.setnil(child, content)
|
||||
parent.append(child)
|
||||
|
||||
|
||||
class PropertyAppender(Appender):
|
||||
"""
|
||||
A L{Property} appender.
|
||||
"""
|
||||
|
||||
def append(self, parent, content):
|
||||
p = content.value
|
||||
child = self.node(content)
|
||||
child.setText(p.get())
|
||||
parent.append(child)
|
||||
for item in p.items():
|
||||
cont = Content(tag=item[0], value=item[1])
|
||||
Appender.append(self, child, cont)
|
||||
|
||||
|
||||
class ObjectAppender(Appender):
|
||||
"""
|
||||
An L{Object} appender.
|
||||
"""
|
||||
|
||||
def append(self, parent, content):
|
||||
object = content.value
|
||||
child = self.node(content)
|
||||
parent.append(child)
|
||||
for item in object:
|
||||
cont = Content(tag=item[0], value=item[1])
|
||||
Appender.append(self, child, cont)
|
||||
|
||||
|
||||
class ElementWrapper(Element):
|
||||
"""
|
||||
Element wrapper.
|
||||
"""
|
||||
|
||||
def __init__(self, content):
|
||||
Element.__init__(self, content.name, content.parent)
|
||||
self.__content = content
|
||||
|
||||
def str(self, indent=0):
|
||||
return self.__content.str(indent)
|
||||
|
||||
|
||||
class ElementAppender(Appender):
|
||||
"""
|
||||
An appender for I{Element} types.
|
||||
"""
|
||||
|
||||
def append(self, parent, content):
|
||||
if content.tag.startswith('_'):
|
||||
raise Exception('raw XML not valid as attribute value')
|
||||
child = ElementWrapper(content.value)
|
||||
parent.append(child)
|
||||
|
||||
|
||||
class ListAppender(Appender):
|
||||
"""
|
||||
A list/tuple appender.
|
||||
"""
|
||||
|
||||
def append(self, parent, content):
|
||||
collection = content.value
|
||||
if len(collection):
|
||||
self.suspend(content)
|
||||
for item in collection:
|
||||
cont = Content(tag=content.tag, value=item)
|
||||
Appender.append(self, parent, cont)
|
||||
self.resume(content)
|
||||
|
||||
|
||||
class TextAppender(Appender):
|
||||
"""
|
||||
An appender for I{Text} values.
|
||||
"""
|
||||
|
||||
def append(self, parent, content):
|
||||
if content.tag.startswith('_'):
|
||||
attr = content.tag[1:]
|
||||
value = tostr(content.value)
|
||||
if value:
|
||||
parent.set(attr, value)
|
||||
else:
|
||||
child = self.node(content)
|
||||
child.setText(content.value)
|
||||
parent.append(child)
|
@ -1,45 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides basic I{marshaller} classes.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.mx import *
|
||||
from suds.mx.core import Core
|
||||
|
||||
|
||||
class Basic(Core):
|
||||
"""
|
||||
A I{basic} (untyped) marshaller.
|
||||
"""
|
||||
|
||||
def process(self, value, tag=None):
|
||||
"""
|
||||
Process (marshal) the tag with the specified value using the
|
||||
optional type information.
|
||||
@param value: The value (content) of the XML node.
|
||||
@type value: (L{Object}|any)
|
||||
@param tag: The (optional) tag name for the value. The default is
|
||||
value.__class__.__name__
|
||||
@type tag: str
|
||||
@return: An xml node.
|
||||
@rtype: L{Element}
|
||||
"""
|
||||
content = Content(tag=tag, value=value)
|
||||
result = Core.process(self, content)
|
||||
return result
|
@ -1,150 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides I{marshaller} core classes.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.mx import *
|
||||
from suds.mx.appender import ContentAppender
|
||||
from suds.sax.document import Document
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Core:
|
||||
"""
|
||||
An I{abstract} marshaller. This class implement the core
|
||||
functionality of the marshaller.
|
||||
@ivar appender: A content appender.
|
||||
@type appender: L{ContentAppender}
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
"""
|
||||
self.appender = ContentAppender(self)
|
||||
|
||||
def process(self, content):
|
||||
"""
|
||||
Process (marshal) the tag with the specified value using the
|
||||
optional type information.
|
||||
@param content: The content to process.
|
||||
@type content: L{Object}
|
||||
"""
|
||||
log.debug('processing:\n%s', content)
|
||||
self.reset()
|
||||
if content.tag is None:
|
||||
content.tag = content.value.__class__.__name__
|
||||
document = Document()
|
||||
self.append(document, content)
|
||||
return document.root()
|
||||
|
||||
def append(self, parent, content):
|
||||
"""
|
||||
Append the specified L{content} to the I{parent}.
|
||||
@param parent: The parent node to append to.
|
||||
@type parent: L{Element}
|
||||
@param content: The content to append.
|
||||
@type content: L{Object}
|
||||
"""
|
||||
log.debug('appending parent:\n%s\ncontent:\n%s', parent, content)
|
||||
if self.start(content):
|
||||
self.appender.append(parent, content)
|
||||
self.end(parent, content)
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Reset the marshaller.
|
||||
"""
|
||||
pass
|
||||
|
||||
def node(self, content):
|
||||
"""
|
||||
Create and return an XML node.
|
||||
@param content: Content information for the new node.
|
||||
@type content: L{Content}
|
||||
@return: An element.
|
||||
@rtype: L{Element}
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def start(self, content):
|
||||
"""
|
||||
Appending this content has started.
|
||||
@param content: The content for which processing has started.
|
||||
@type content: L{Content}
|
||||
@return: True to continue appending
|
||||
@rtype: boolean
|
||||
"""
|
||||
return True
|
||||
|
||||
def suspend(self, content):
|
||||
"""
|
||||
Appending this content has suspended.
|
||||
@param content: The content for which processing has been suspended.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
pass
|
||||
|
||||
def resume(self, content):
|
||||
"""
|
||||
Appending this content has resumed.
|
||||
@param content: The content for which processing has been resumed.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
pass
|
||||
|
||||
def end(self, parent, content):
|
||||
"""
|
||||
Appending this content has ended.
|
||||
@param parent: The parent node ending.
|
||||
@type parent: L{Element}
|
||||
@param content: The content for which processing has ended.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
pass
|
||||
|
||||
def setnil(self, node, content):
|
||||
"""
|
||||
Set the value of the I{node} to nill.
|
||||
@param node: A I{nil} node.
|
||||
@type node: L{Element}
|
||||
@param content: The content to set nil.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
pass
|
||||
|
||||
def setdefault(self, node, content):
|
||||
"""
|
||||
Set the value of the I{node} to a default value.
|
||||
@param node: A I{nil} node.
|
||||
@type node: L{Element}
|
||||
@param content: The content to set the default value.
|
||||
@type content: L{Content}
|
||||
@return: The default.
|
||||
"""
|
||||
pass
|
||||
|
||||
def optional(self, content):
|
||||
"""
|
||||
Get whether the specified content is optional.
|
||||
@param content: The content which to check.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
return False
|
@ -1,131 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides encoded I{marshaller} classes.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.mx import *
|
||||
from suds.mx.literal import Literal
|
||||
from suds.mx.typer import Typer
|
||||
from suds.sudsobject import Factory, Object
|
||||
from suds.xsd.query import TypeQuery
|
||||
|
||||
|
||||
#
|
||||
# Add encoded extensions
|
||||
# aty = The soap (section 5) encoded array type.
|
||||
#
|
||||
Content.extensions.append('aty')
|
||||
|
||||
|
||||
class Encoded(Literal):
|
||||
"""
|
||||
A SOAP section (5) encoding marshaller.
|
||||
This marshaller supports rpc/encoded soap styles.
|
||||
"""
|
||||
|
||||
def start(self, content):
|
||||
#
|
||||
# For soap encoded arrays, the 'aty' (array type) information
|
||||
# is extracted and added to the 'content'. Then, the content.value
|
||||
# is replaced with an object containing an 'item=[]' attribute
|
||||
# containing values that are 'typed' suds objects.
|
||||
#
|
||||
start = Literal.start(self, content)
|
||||
if start and isinstance(content.value, (list,tuple)):
|
||||
resolved = content.type.resolve()
|
||||
for c in resolved:
|
||||
if hasattr(c[0], 'aty'):
|
||||
content.aty = (content.tag, c[0].aty)
|
||||
self.cast(content)
|
||||
break
|
||||
return start
|
||||
|
||||
def end(self, parent, content):
|
||||
#
|
||||
# For soap encoded arrays, the soapenc:arrayType attribute is
|
||||
# added with proper type and size information.
|
||||
# Eg: soapenc:arrayType="xs:int[3]"
|
||||
#
|
||||
Literal.end(self, parent, content)
|
||||
if content.aty is None:
|
||||
return
|
||||
tag, aty = content.aty
|
||||
ns0 = ('at0', aty[1])
|
||||
ns1 = ('at1', 'http://schemas.xmlsoap.org/soap/encoding/')
|
||||
array = content.value.item
|
||||
child = parent.getChild(tag)
|
||||
child.addPrefix(ns0[0], ns0[1])
|
||||
child.addPrefix(ns1[0], ns1[1])
|
||||
name = '%s:arrayType' % ns1[0]
|
||||
value = '%s:%s[%d]' % (ns0[0], aty[0], len(array))
|
||||
child.set(name, value)
|
||||
|
||||
def encode(self, node, content):
|
||||
if content.type.any():
|
||||
Typer.auto(node, content.value)
|
||||
return
|
||||
if content.real.any():
|
||||
Typer.auto(node, content.value)
|
||||
return
|
||||
ns = None
|
||||
name = content.real.name
|
||||
if self.xstq:
|
||||
ns = content.real.namespace()
|
||||
Typer.manual(node, name, ns)
|
||||
|
||||
def cast(self, content):
|
||||
"""
|
||||
Cast the I{untyped} list items found in content I{value}.
|
||||
Each items contained in the list is checked for XSD type information.
|
||||
Items (values) that are I{untyped}, are replaced with suds objects and
|
||||
type I{metadata} is added.
|
||||
@param content: The content holding the collection.
|
||||
@type content: L{Content}
|
||||
@return: self
|
||||
@rtype: L{Encoded}
|
||||
"""
|
||||
aty = content.aty[1]
|
||||
resolved = content.type.resolve()
|
||||
array = Factory.object(resolved.name)
|
||||
array.item = []
|
||||
query = TypeQuery(aty)
|
||||
ref = query.execute(self.schema)
|
||||
if ref is None:
|
||||
raise TypeNotFound(qref)
|
||||
for x in content.value:
|
||||
if isinstance(x, (list, tuple)):
|
||||
array.item.append(x)
|
||||
continue
|
||||
if isinstance(x, Object):
|
||||
md = x.__metadata__
|
||||
md.sxtype = ref
|
||||
array.item.append(x)
|
||||
continue
|
||||
if isinstance(x, dict):
|
||||
x = Factory.object(ref.name, x)
|
||||
md = x.__metadata__
|
||||
md.sxtype = ref
|
||||
array.item.append(x)
|
||||
continue
|
||||
x = Factory.property(ref.name, x)
|
||||
md = x.__metadata__
|
||||
md.sxtype = ref
|
||||
array.item.append(x)
|
||||
content.value = array
|
||||
return self
|
@ -1,311 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides literal I{marshaller} classes.
|
||||
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.mx import *
|
||||
from suds.mx.core import Core
|
||||
from suds.mx.typer import Typer
|
||||
from suds.resolver import Frame, GraphResolver
|
||||
from suds.sax.element import Element
|
||||
from suds.sudsobject import Factory
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
# add typed extensions
|
||||
Content.extensions.append("type") # The expected xsd type
|
||||
Content.extensions.append("real") # The 'true' XSD type
|
||||
Content.extensions.append("ancestry") # The 'type' ancestry
|
||||
|
||||
|
||||
class Typed(Core):
|
||||
"""
|
||||
A I{typed} marshaller.
|
||||
|
||||
This marshaller is semi-typed as needed to support both I{document/literal}
|
||||
and I{rpc/literal} SOAP message styles.
|
||||
|
||||
@ivar schema: An XSD schema.
|
||||
@type schema: L{xsd.schema.Schema}
|
||||
@ivar resolver: A schema type resolver.
|
||||
@type resolver: L{GraphResolver}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, schema, xstq=True):
|
||||
"""
|
||||
@param schema: A schema object
|
||||
@type schema: L{xsd.schema.Schema}
|
||||
@param xstq: The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates
|
||||
that the I{xsi:type} attribute values should be qualified by
|
||||
namespace.
|
||||
@type xstq: bool
|
||||
|
||||
"""
|
||||
Core.__init__(self)
|
||||
self.schema = schema
|
||||
self.xstq = xstq
|
||||
self.resolver = GraphResolver(self.schema)
|
||||
|
||||
def reset(self):
|
||||
self.resolver.reset()
|
||||
|
||||
def start(self, content):
|
||||
"""
|
||||
Start marshalling the 'content' by ensuring that both the 'content'
|
||||
_and_ the resolver are primed with the XSD type information. The
|
||||
'content' value is both translated and sorted based on the XSD type.
|
||||
Only values that are objects have their attributes sorted.
|
||||
|
||||
"""
|
||||
log.debug("starting content:\n%s", content)
|
||||
if content.type is None:
|
||||
name = content.tag
|
||||
if name.startswith("_"):
|
||||
name = "@" + name[1:]
|
||||
content.type = self.resolver.find(name, content.value)
|
||||
if content.type is None:
|
||||
raise TypeNotFound(content.tag)
|
||||
else:
|
||||
known = None
|
||||
if isinstance(content.value, Object):
|
||||
known = self.resolver.known(content.value)
|
||||
if known is None:
|
||||
log.debug("object %s has no type information",
|
||||
content.value)
|
||||
known = content.type
|
||||
frame = Frame(content.type, resolved=known)
|
||||
self.resolver.push(frame)
|
||||
frame = self.resolver.top()
|
||||
content.real = frame.resolved
|
||||
content.ancestry = frame.ancestry
|
||||
self.translate(content)
|
||||
self.sort(content)
|
||||
if self.skip(content):
|
||||
log.debug("skipping (optional) content:\n%s", content)
|
||||
self.resolver.pop()
|
||||
return False
|
||||
return True
|
||||
|
||||
def suspend(self, content):
|
||||
"""
|
||||
Suspend to process list content.
|
||||
|
||||
Primarily, this involves popping the 'list' content off the resolver's
|
||||
stack so its list items can be marshalled.
|
||||
|
||||
"""
|
||||
self.resolver.pop()
|
||||
|
||||
def resume(self, content):
|
||||
"""
|
||||
Resume processing list content.
|
||||
|
||||
To do this, we really need to simply push the 'list' content back onto
|
||||
the resolver stack.
|
||||
|
||||
"""
|
||||
self.resolver.push(Frame(content.type))
|
||||
|
||||
def end(self, parent, content):
|
||||
"""
|
||||
End processing the content.
|
||||
|
||||
Make sure the content ending matches the top of the resolver stack
|
||||
since for list processing we play games with the resolver stack.
|
||||
|
||||
"""
|
||||
log.debug("ending content:\n%s", content)
|
||||
current = self.resolver.top().type
|
||||
if current != content.type:
|
||||
raise Exception("content (end) mismatch: top=(%s) cont=(%s)" % (
|
||||
current, content))
|
||||
self.resolver.pop()
|
||||
|
||||
def node(self, content):
|
||||
"""
|
||||
Create an XML node.
|
||||
|
||||
The XML node is namespace qualified as defined by the corresponding
|
||||
schema element.
|
||||
|
||||
"""
|
||||
ns = content.type.namespace()
|
||||
if content.type.form_qualified:
|
||||
node = Element(content.tag, ns=ns)
|
||||
if ns[0]:
|
||||
node.addPrefix(ns[0], ns[1])
|
||||
else:
|
||||
node = Element(content.tag)
|
||||
self.encode(node, content)
|
||||
log.debug("created - node:\n%s", node)
|
||||
return node
|
||||
|
||||
def setnil(self, node, content):
|
||||
"""
|
||||
Set the 'node' nil only if the XSD type specifies that it is permitted.
|
||||
|
||||
"""
|
||||
if content.type.nillable:
|
||||
node.setnil()
|
||||
|
||||
def setdefault(self, node, content):
|
||||
"""Set the node to the default value specified by the XSD type."""
|
||||
default = content.type.default
|
||||
if default is not None:
|
||||
node.setText(default)
|
||||
return default
|
||||
|
||||
def optional(self, content):
|
||||
if content.type.optional():
|
||||
return True
|
||||
for a in content.ancestry:
|
||||
if a.optional():
|
||||
return True
|
||||
return False
|
||||
|
||||
def encode(self, node, content):
|
||||
"""
|
||||
Add (SOAP) encoding information if needed.
|
||||
|
||||
The encoding information is added only if the resolved type is derived
|
||||
by extension. Furthermore, the xsi:type value is qualified by namespace
|
||||
only if the content (tag) and referenced type are in different
|
||||
namespaces.
|
||||
|
||||
"""
|
||||
if content.type.any():
|
||||
return
|
||||
if not content.real.extension():
|
||||
return
|
||||
if content.type.resolve() == content.real:
|
||||
return
|
||||
ns = None
|
||||
name = content.real.name
|
||||
if self.xstq:
|
||||
ns = content.real.namespace("ns1")
|
||||
Typer.manual(node, name, ns)
|
||||
|
||||
def skip(self, content):
|
||||
"""
|
||||
Get whether to skip this I{content}.
|
||||
|
||||
Should be skipped when the content is optional and value is either None
|
||||
or an empty list.
|
||||
|
||||
@param content: Content to skip.
|
||||
@type content: L{Object}
|
||||
@return: True if content is to be skipped.
|
||||
@rtype: bool
|
||||
|
||||
"""
|
||||
if self.optional(content):
|
||||
v = content.value
|
||||
if v is None:
|
||||
return True
|
||||
if isinstance(v, (list, tuple)) and not v:
|
||||
return True
|
||||
return False
|
||||
|
||||
def optional(self, content):
|
||||
if content.type.optional():
|
||||
return True
|
||||
for a in content.ancestry:
|
||||
if a.optional():
|
||||
return True
|
||||
return False
|
||||
|
||||
def translate(self, content):
|
||||
"""
|
||||
Translate using the XSD type information.
|
||||
|
||||
Python I{dict} is translated to a suds object. Most importantly,
|
||||
primitive values are translated from python to XML types using the XSD
|
||||
type.
|
||||
|
||||
@param content: Content to translate.
|
||||
@type content: L{Object}
|
||||
@return: self
|
||||
@rtype: L{Typed}
|
||||
|
||||
"""
|
||||
v = content.value
|
||||
if v is None:
|
||||
return
|
||||
if isinstance(v, dict):
|
||||
cls = content.real.name
|
||||
content.value = Factory.object(cls, v)
|
||||
md = content.value.__metadata__
|
||||
md.sxtype = content.type
|
||||
return
|
||||
v = content.real.translate(v, False)
|
||||
content.value = v
|
||||
return self
|
||||
|
||||
def sort(self, content):
|
||||
"""
|
||||
Sort suds object attributes.
|
||||
|
||||
The attributes are sorted based on the ordering defined in the XSD type
|
||||
information.
|
||||
|
||||
@param content: Content to sort.
|
||||
@type content: L{Object}
|
||||
@return: self
|
||||
@rtype: L{Typed}
|
||||
|
||||
"""
|
||||
v = content.value
|
||||
if isinstance(v, Object):
|
||||
md = v.__metadata__
|
||||
md.ordering = self.ordering(content.real)
|
||||
return self
|
||||
|
||||
def ordering(self, type):
|
||||
"""
|
||||
Attribute ordering defined in the specified XSD type information.
|
||||
|
||||
@param type: XSD type object.
|
||||
@type type: L{SchemaObject}
|
||||
@return: An ordered list of attribute names.
|
||||
@rtype: list
|
||||
|
||||
"""
|
||||
result = []
|
||||
for child, ancestry in type.resolve():
|
||||
name = child.name
|
||||
if child.name is None:
|
||||
continue
|
||||
if child.isattr():
|
||||
name = "_%s" % (child.name,)
|
||||
result.append(name)
|
||||
return result
|
||||
|
||||
|
||||
class Literal(Typed):
|
||||
"""
|
||||
A I{literal} marshaller.
|
||||
|
||||
This marshaller is semi-typed as needed to support both I{document/literal}
|
||||
and I{rpc/literal} soap message styles.
|
||||
|
||||
"""
|
||||
pass
|
@ -1,126 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides XSD typing classes.
|
||||
|
||||
"""
|
||||
|
||||
from suds.sax import Namespace
|
||||
from suds.sax.text import Text
|
||||
from suds.sudsobject import Object
|
||||
|
||||
|
||||
class Typer:
|
||||
"""
|
||||
Provides XML node typing as either automatic or manual.
|
||||
|
||||
@cvar types: Class to XSD type mapping.
|
||||
@type types: dict
|
||||
|
||||
"""
|
||||
|
||||
types = {
|
||||
bool: ("boolean", Namespace.xsdns),
|
||||
float: ("float", Namespace.xsdns),
|
||||
int: ("int", Namespace.xsdns),
|
||||
long: ("long", Namespace.xsdns),
|
||||
str: ("string", Namespace.xsdns),
|
||||
Text: ("string", Namespace.xsdns),
|
||||
unicode: ("string", Namespace.xsdns)}
|
||||
|
||||
@classmethod
|
||||
def auto(cls, node, value=None):
|
||||
"""
|
||||
Automatically set the node's xsi:type attribute based on either
|
||||
I{value}'s or the node text's class. When I{value} is an unmapped
|
||||
class, the default type (xs:any) is set.
|
||||
|
||||
@param node: XML node.
|
||||
@type node: L{sax.element.Element}
|
||||
@param value: Object that is or would be the node's text.
|
||||
@type value: I{any}
|
||||
@return: Specified node.
|
||||
@rtype: L{sax.element.Element}
|
||||
|
||||
"""
|
||||
if value is None:
|
||||
value = node.getText()
|
||||
if isinstance(value, Object):
|
||||
known = cls.known(value)
|
||||
if known.name is None:
|
||||
return node
|
||||
tm = known.name, known.namespace()
|
||||
else:
|
||||
tm = cls.types.get(value.__class__, cls.types.get(str))
|
||||
cls.manual(node, *tm)
|
||||
return node
|
||||
|
||||
@classmethod
|
||||
def manual(cls, node, tval, ns=None):
|
||||
"""
|
||||
Set the node's xsi:type attribute based on either I{value}'s or the
|
||||
node text's class. Then adds the referenced prefix(s) to the node's
|
||||
prefix mapping.
|
||||
|
||||
@param node: XML node.
|
||||
@type node: L{sax.element.Element}
|
||||
@param tval: XSD schema type name.
|
||||
@type tval: str
|
||||
@param ns: I{tval} XML namespace.
|
||||
@type ns: (prefix, URI)
|
||||
@return: Specified node.
|
||||
@rtype: L{sax.element.Element}
|
||||
|
||||
"""
|
||||
xta = ":".join((Namespace.xsins[0], "type"))
|
||||
node.addPrefix(Namespace.xsins[0], Namespace.xsins[1])
|
||||
if ns is None:
|
||||
node.set(xta, tval)
|
||||
else:
|
||||
ns = cls.genprefix(node, ns)
|
||||
qname = ":".join((ns[0], tval))
|
||||
node.set(xta, qname)
|
||||
node.addPrefix(ns[0], ns[1])
|
||||
return node
|
||||
|
||||
@classmethod
|
||||
def genprefix(cls, node, ns):
|
||||
"""
|
||||
Generate a prefix.
|
||||
|
||||
@param node: XML node on which the prefix will be used.
|
||||
@type node: L{sax.element.Element}
|
||||
@param ns: Namespace needing a unique prefix.
|
||||
@type ns: (prefix, URI)
|
||||
@return: I{ns} with a new prefix.
|
||||
@rtype: (prefix, URI)
|
||||
|
||||
"""
|
||||
for i in range(1, 1024):
|
||||
prefix = "ns%d" % (i,)
|
||||
uri = node.resolvePrefix(prefix, default=None)
|
||||
if uri in (None, ns[1]):
|
||||
return prefix, ns[1]
|
||||
raise Exception("auto prefix, exhausted")
|
||||
|
||||
@classmethod
|
||||
def known(cls, object):
|
||||
try:
|
||||
md = object.__metadata__
|
||||
known = md.sxtype
|
||||
return known
|
||||
except Exception:
|
||||
pass
|
@ -1,162 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Suds basic options classes.
|
||||
"""
|
||||
|
||||
from suds.cache import Cache, NoCache
|
||||
from suds.properties import *
|
||||
from suds.store import DocumentStore, defaultDocumentStore
|
||||
from suds.transport import Transport
|
||||
from suds.wsse import Security
|
||||
from suds.xsd.doctor import Doctor
|
||||
|
||||
|
||||
class TpLinker(AutoLinker):
|
||||
"""
|
||||
Transport (auto) linker used to manage linkage between
|
||||
transport objects Properties and those Properties that contain them.
|
||||
"""
|
||||
|
||||
def updated(self, properties, prev, next):
|
||||
if isinstance(prev, Transport):
|
||||
tp = Unskin(prev.options)
|
||||
properties.unlink(tp)
|
||||
if isinstance(next, Transport):
|
||||
tp = Unskin(next.options)
|
||||
properties.link(tp)
|
||||
|
||||
|
||||
class Options(Skin):
|
||||
"""
|
||||
Options:
|
||||
- B{cache} - The XML document cache. May be set to None for no caching.
|
||||
- type: L{Cache}
|
||||
- default: L{NoCache()}
|
||||
- B{documentStore} - The XML document store used to access locally
|
||||
stored documents without having to download them from an external
|
||||
location. May be set to None for no internal suds library document
|
||||
store.
|
||||
- type: L{DocumentStore}
|
||||
- default: L{defaultDocumentStore}
|
||||
- B{extraArgumentErrors} - Raise exceptions when unknown message parts
|
||||
are detected when receiving a web service reply, compared to the
|
||||
operation's WSDL schema definition.
|
||||
- type: I{bool}
|
||||
- default: True
|
||||
- B{allowUnknownMessageParts} - Raise exceptions when extra arguments are
|
||||
detected when invoking a web service operation, compared to the
|
||||
operation's WSDL schema definition.
|
||||
- type: I{bool}
|
||||
- default: False
|
||||
- B{faults} - Raise faults raised by server, else return tuple from
|
||||
service method invocation as (httpcode, object).
|
||||
- type: I{bool}
|
||||
- default: True
|
||||
- B{service} - The default service name.
|
||||
- type: I{str}
|
||||
- default: None
|
||||
- B{port} - The default service port name, not tcp port.
|
||||
- type: I{str}
|
||||
- default: None
|
||||
- B{location} - This overrides the service port address I{URL} defined
|
||||
in the WSDL.
|
||||
- type: I{str}
|
||||
- default: None
|
||||
- B{transport} - The message transport.
|
||||
- type: L{Transport}
|
||||
- default: None
|
||||
- B{soapheaders} - The soap headers to be included in the soap message.
|
||||
- type: I{any}
|
||||
- default: None
|
||||
- B{wsse} - The web services I{security} provider object.
|
||||
- type: L{Security}
|
||||
- default: None
|
||||
- B{doctor} - A schema I{doctor} object.
|
||||
- type: L{Doctor}
|
||||
- default: None
|
||||
- B{xstq} - The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates
|
||||
that the I{xsi:type} attribute values should be qualified by
|
||||
namespace.
|
||||
- type: I{bool}
|
||||
- default: True
|
||||
- B{prefixes} - Elements of the soap message should be qualified (when
|
||||
needed) using XML prefixes as opposed to xmlns="" syntax.
|
||||
- type: I{bool}
|
||||
- default: True
|
||||
- B{retxml} - Flag that causes the I{raw} soap envelope to be returned
|
||||
instead of the python object graph.
|
||||
- type: I{bool}
|
||||
- default: False
|
||||
- B{prettyxml} - Flag that causes I{pretty} xml to be rendered when
|
||||
generating the outbound soap envelope.
|
||||
- type: I{bool}
|
||||
- default: False
|
||||
- B{autoblend} - Flag that ensures that the schema(s) defined within
|
||||
the WSDL import each other.
|
||||
- type: I{bool}
|
||||
- default: False
|
||||
- B{cachingpolicy} - The caching policy.
|
||||
- type: I{int}
|
||||
- 0 = Cache XML documents.
|
||||
- 1 = Cache WSDL (pickled) object.
|
||||
- default: 0
|
||||
- B{plugins} - A plugin container.
|
||||
- type: I{list}
|
||||
- default: I{list()}
|
||||
- B{nosend} - Create the soap envelope but do not send.
|
||||
When specified, method invocation returns a I{RequestContext}
|
||||
instead of sending it.
|
||||
- type: I{bool}
|
||||
- default: False
|
||||
- B{unwrap} - Enable automatic parameter unwrapping when possible.
|
||||
Enabled by default. If disabled, no input or output parameters are
|
||||
ever automatically unwrapped.
|
||||
- type: I{bool}
|
||||
- default: True
|
||||
- B{sortNamespaces} - Namespaces are sorted alphabetically. If disabled,
|
||||
namespaces are left in the order they are received from the source.
|
||||
Enabled by default for historical purposes.
|
||||
- type: I{bool}
|
||||
- default: True
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
domain = __name__
|
||||
definitions = [
|
||||
Definition('cache', Cache, NoCache()),
|
||||
Definition('documentStore', DocumentStore, defaultDocumentStore),
|
||||
Definition('extraArgumentErrors', bool, True),
|
||||
Definition('allowUnknownMessageParts', bool, False),
|
||||
Definition('faults', bool, True),
|
||||
Definition('transport', Transport, None, TpLinker()),
|
||||
Definition('service', (int, basestring), None),
|
||||
Definition('port', (int, basestring), None),
|
||||
Definition('location', basestring, None),
|
||||
Definition('soapheaders', (), ()),
|
||||
Definition('wsse', Security, None),
|
||||
Definition('doctor', Doctor, None),
|
||||
Definition('xstq', bool, True),
|
||||
Definition('prefixes', bool, True),
|
||||
Definition('retxml', bool, False),
|
||||
Definition('prettyxml', bool, False),
|
||||
Definition('autoblend', bool, False),
|
||||
Definition('cachingpolicy', int, 0),
|
||||
Definition('plugins', (list, tuple), []),
|
||||
Definition('nosend', bool, False),
|
||||
Definition('unwrap', bool, True),
|
||||
Definition('sortNamespaces', bool, True)]
|
||||
Skin.__init__(self, domain, definitions, kwargs)
|
@ -1,277 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The plugin module provides suds plugin implementation classes.
|
||||
|
||||
"""
|
||||
|
||||
from future.utils import raise_
|
||||
from suds import *
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Context(object):
|
||||
"""Plugin context."""
|
||||
pass
|
||||
|
||||
|
||||
class InitContext(Context):
|
||||
"""
|
||||
Init Context.
|
||||
|
||||
@ivar wsdl: The WSDL.
|
||||
@type wsdl: L{wsdl.Definitions}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DocumentContext(Context):
|
||||
"""
|
||||
The XML document load context.
|
||||
|
||||
@ivar url: The URL.
|
||||
@type url: str
|
||||
@ivar document: Either the XML text or the B{parsed} document root.
|
||||
@type document: (str|L{sax.element.Element})
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class MessageContext(Context):
|
||||
"""
|
||||
The context for sending the SOAP envelope.
|
||||
|
||||
@ivar envelope: The SOAP envelope to be sent.
|
||||
@type envelope: (str|L{sax.element.Element})
|
||||
@ivar reply: The reply.
|
||||
@type reply: (str|L{sax.element.Element}|object)
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Plugin:
|
||||
"""Plugin base."""
|
||||
pass
|
||||
|
||||
|
||||
class InitPlugin(Plugin):
|
||||
"""Base class for all suds I{init} plugins."""
|
||||
|
||||
def initialized(self, context):
|
||||
"""
|
||||
Suds client initialization.
|
||||
|
||||
Called after WSDL the has been loaded. Provides the plugin with the
|
||||
opportunity to inspect/modify the WSDL.
|
||||
|
||||
@param context: The init context.
|
||||
@type context: L{InitContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DocumentPlugin(Plugin):
|
||||
"""Base class for suds I{document} plugins."""
|
||||
|
||||
def loaded(self, context):
|
||||
"""
|
||||
Suds has loaded a WSDL/XSD document.
|
||||
|
||||
Provides the plugin with an opportunity to inspect/modify the unparsed
|
||||
document. Called after each WSDL/XSD document is loaded.
|
||||
|
||||
@param context: The document context.
|
||||
@type context: L{DocumentContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def parsed(self, context):
|
||||
"""
|
||||
Suds has parsed a WSDL/XSD document.
|
||||
|
||||
Provides the plugin with an opportunity to inspect/modify the parsed
|
||||
document. Called after each WSDL/XSD document is parsed.
|
||||
|
||||
@param context: The document context.
|
||||
@type context: L{DocumentContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class MessagePlugin(Plugin):
|
||||
"""Base class for suds I{SOAP message} plugins."""
|
||||
|
||||
def marshalled(self, context):
|
||||
"""
|
||||
Suds is about to send the specified SOAP envelope.
|
||||
|
||||
Provides the plugin with the opportunity to inspect/modify the envelope
|
||||
Document before it is sent.
|
||||
|
||||
@param context: The send context.
|
||||
The I{envelope} is the envelope document.
|
||||
@type context: L{MessageContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def sending(self, context):
|
||||
"""
|
||||
Suds is about to send the specified SOAP envelope.
|
||||
|
||||
Provides the plugin with the opportunity to inspect/modify the message
|
||||
text before it is sent.
|
||||
|
||||
@param context: The send context.
|
||||
The I{envelope} is the envelope text.
|
||||
@type context: L{MessageContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def received(self, context):
|
||||
"""
|
||||
Suds has received the specified reply.
|
||||
|
||||
Provides the plugin with the opportunity to inspect/modify the received
|
||||
XML text before it is SAX parsed.
|
||||
|
||||
@param context: The reply context.
|
||||
The I{reply} is the raw text.
|
||||
@type context: L{MessageContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def parsed(self, context):
|
||||
"""
|
||||
Suds has SAX parsed the received reply.
|
||||
|
||||
Provides the plugin with the opportunity to inspect/modify the SAX
|
||||
parsed DOM tree for the reply before it is unmarshalled.
|
||||
|
||||
@param context: The reply context.
|
||||
The I{reply} is DOM tree.
|
||||
@type context: L{MessageContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def unmarshalled(self, context):
|
||||
"""
|
||||
Suds has unmarshalled the received reply.
|
||||
|
||||
Provides the plugin with the opportunity to inspect/modify the
|
||||
unmarshalled reply object before it is returned.
|
||||
|
||||
@param context: The reply context.
|
||||
The I{reply} is unmarshalled suds object.
|
||||
@type context: L{MessageContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PluginContainer:
|
||||
"""
|
||||
Plugin container provides easy method invocation.
|
||||
|
||||
@ivar plugins: A list of plugin objects.
|
||||
@type plugins: [L{Plugin},]
|
||||
@cvar ctxclass: A dict of plugin method / context classes.
|
||||
@type ctxclass: dict
|
||||
|
||||
"""
|
||||
|
||||
domains = {
|
||||
'init': (InitContext, InitPlugin),
|
||||
'document': (DocumentContext, DocumentPlugin),
|
||||
'message': (MessageContext, MessagePlugin)}
|
||||
|
||||
def __init__(self, plugins):
|
||||
"""
|
||||
@param plugins: A list of plugin objects.
|
||||
@type plugins: [L{Plugin},]
|
||||
|
||||
"""
|
||||
self.plugins = plugins
|
||||
|
||||
def __getattr__(self, name):
|
||||
domain = self.domains.get(name)
|
||||
if not domain:
|
||||
raise_(Exception, 'plugin domain (%s), invalid' % (name,))
|
||||
ctx, pclass = domain
|
||||
plugins = [p for p in self.plugins if isinstance(p, pclass)]
|
||||
return PluginDomain(ctx, plugins)
|
||||
|
||||
|
||||
class PluginDomain:
|
||||
"""
|
||||
The plugin domain.
|
||||
|
||||
@ivar ctx: A context.
|
||||
@type ctx: L{Context}
|
||||
@ivar plugins: A list of plugins (targets).
|
||||
@type plugins: list
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, ctx, plugins):
|
||||
self.ctx = ctx
|
||||
self.plugins = plugins
|
||||
|
||||
def __getattr__(self, name):
|
||||
return Method(name, self)
|
||||
|
||||
|
||||
class Method:
|
||||
"""
|
||||
Plugin method.
|
||||
|
||||
@ivar name: The method name.
|
||||
@type name: str
|
||||
@ivar domain: The plugin domain.
|
||||
@type domain: L{PluginDomain}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name, domain):
|
||||
"""
|
||||
@param name: The method name.
|
||||
@type name: str
|
||||
@param domain: A plugin domain.
|
||||
@type domain: L{PluginDomain}
|
||||
|
||||
"""
|
||||
self.name = name
|
||||
self.domain = domain
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
ctx = self.domain.ctx()
|
||||
ctx.__dict__.update(kwargs)
|
||||
for plugin in self.domain.plugins:
|
||||
method = getattr(plugin, self.name, None)
|
||||
if method and callable(method):
|
||||
method(ctx)
|
||||
return ctx
|
@ -1,276 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The plugin module provides suds plugin implementation classes.
|
||||
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Context(object):
|
||||
"""Plugin context."""
|
||||
pass
|
||||
|
||||
|
||||
class InitContext(Context):
|
||||
"""
|
||||
Init Context.
|
||||
|
||||
@ivar wsdl: The WSDL.
|
||||
@type wsdl: L{wsdl.Definitions}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DocumentContext(Context):
|
||||
"""
|
||||
The XML document load context.
|
||||
|
||||
@ivar url: The URL.
|
||||
@type url: str
|
||||
@ivar document: Either the XML text or the B{parsed} document root.
|
||||
@type document: (str|L{sax.element.Element})
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class MessageContext(Context):
|
||||
"""
|
||||
The context for sending the SOAP envelope.
|
||||
|
||||
@ivar envelope: The SOAP envelope to be sent.
|
||||
@type envelope: (str|L{sax.element.Element})
|
||||
@ivar reply: The reply.
|
||||
@type reply: (str|L{sax.element.Element}|object)
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Plugin:
|
||||
"""Plugin base."""
|
||||
pass
|
||||
|
||||
|
||||
class InitPlugin(Plugin):
|
||||
"""Base class for all suds I{init} plugins."""
|
||||
|
||||
def initialized(self, context):
|
||||
"""
|
||||
Suds client initialization.
|
||||
|
||||
Called after WSDL the has been loaded. Provides the plugin with the
|
||||
opportunity to inspect/modify the WSDL.
|
||||
|
||||
@param context: The init context.
|
||||
@type context: L{InitContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DocumentPlugin(Plugin):
|
||||
"""Base class for suds I{document} plugins."""
|
||||
|
||||
def loaded(self, context):
|
||||
"""
|
||||
Suds has loaded a WSDL/XSD document.
|
||||
|
||||
Provides the plugin with an opportunity to inspect/modify the unparsed
|
||||
document. Called after each WSDL/XSD document is loaded.
|
||||
|
||||
@param context: The document context.
|
||||
@type context: L{DocumentContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def parsed(self, context):
|
||||
"""
|
||||
Suds has parsed a WSDL/XSD document.
|
||||
|
||||
Provides the plugin with an opportunity to inspect/modify the parsed
|
||||
document. Called after each WSDL/XSD document is parsed.
|
||||
|
||||
@param context: The document context.
|
||||
@type context: L{DocumentContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class MessagePlugin(Plugin):
|
||||
"""Base class for suds I{SOAP message} plugins."""
|
||||
|
||||
def marshalled(self, context):
|
||||
"""
|
||||
Suds is about to send the specified SOAP envelope.
|
||||
|
||||
Provides the plugin with the opportunity to inspect/modify the envelope
|
||||
Document before it is sent.
|
||||
|
||||
@param context: The send context.
|
||||
The I{envelope} is the envelope document.
|
||||
@type context: L{MessageContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def sending(self, context):
|
||||
"""
|
||||
Suds is about to send the specified SOAP envelope.
|
||||
|
||||
Provides the plugin with the opportunity to inspect/modify the message
|
||||
text before it is sent.
|
||||
|
||||
@param context: The send context.
|
||||
The I{envelope} is the envelope text.
|
||||
@type context: L{MessageContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def received(self, context):
|
||||
"""
|
||||
Suds has received the specified reply.
|
||||
|
||||
Provides the plugin with the opportunity to inspect/modify the received
|
||||
XML text before it is SAX parsed.
|
||||
|
||||
@param context: The reply context.
|
||||
The I{reply} is the raw text.
|
||||
@type context: L{MessageContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def parsed(self, context):
|
||||
"""
|
||||
Suds has SAX parsed the received reply.
|
||||
|
||||
Provides the plugin with the opportunity to inspect/modify the SAX
|
||||
parsed DOM tree for the reply before it is unmarshalled.
|
||||
|
||||
@param context: The reply context.
|
||||
The I{reply} is DOM tree.
|
||||
@type context: L{MessageContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def unmarshalled(self, context):
|
||||
"""
|
||||
Suds has unmarshalled the received reply.
|
||||
|
||||
Provides the plugin with the opportunity to inspect/modify the
|
||||
unmarshalled reply object before it is returned.
|
||||
|
||||
@param context: The reply context.
|
||||
The I{reply} is unmarshalled suds object.
|
||||
@type context: L{MessageContext}
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PluginContainer:
|
||||
"""
|
||||
Plugin container provides easy method invocation.
|
||||
|
||||
@ivar plugins: A list of plugin objects.
|
||||
@type plugins: [L{Plugin},]
|
||||
@cvar ctxclass: A dict of plugin method / context classes.
|
||||
@type ctxclass: dict
|
||||
|
||||
"""
|
||||
|
||||
domains = {
|
||||
'init': (InitContext, InitPlugin),
|
||||
'document': (DocumentContext, DocumentPlugin),
|
||||
'message': (MessageContext, MessagePlugin)}
|
||||
|
||||
def __init__(self, plugins):
|
||||
"""
|
||||
@param plugins: A list of plugin objects.
|
||||
@type plugins: [L{Plugin},]
|
||||
|
||||
"""
|
||||
self.plugins = plugins
|
||||
|
||||
def __getattr__(self, name):
|
||||
domain = self.domains.get(name)
|
||||
if not domain:
|
||||
raise Exception, 'plugin domain (%s), invalid' % (name,)
|
||||
ctx, pclass = domain
|
||||
plugins = [p for p in self.plugins if isinstance(p, pclass)]
|
||||
return PluginDomain(ctx, plugins)
|
||||
|
||||
|
||||
class PluginDomain:
|
||||
"""
|
||||
The plugin domain.
|
||||
|
||||
@ivar ctx: A context.
|
||||
@type ctx: L{Context}
|
||||
@ivar plugins: A list of plugins (targets).
|
||||
@type plugins: list
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, ctx, plugins):
|
||||
self.ctx = ctx
|
||||
self.plugins = plugins
|
||||
|
||||
def __getattr__(self, name):
|
||||
return Method(name, self)
|
||||
|
||||
|
||||
class Method:
|
||||
"""
|
||||
Plugin method.
|
||||
|
||||
@ivar name: The method name.
|
||||
@type name: str
|
||||
@ivar domain: The plugin domain.
|
||||
@type domain: L{PluginDomain}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name, domain):
|
||||
"""
|
||||
@param name: The method name.
|
||||
@type name: str
|
||||
@param domain: A plugin domain.
|
||||
@type domain: L{PluginDomain}
|
||||
|
||||
"""
|
||||
self.name = name
|
||||
self.domain = domain
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
ctx = self.domain.ctx()
|
||||
ctx.__dict__.update(kwargs)
|
||||
for plugin in self.domain.plugins:
|
||||
method = getattr(plugin, self.name, None)
|
||||
if method and callable(method):
|
||||
method(ctx)
|
||||
return ctx
|
@ -1,540 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Properties classes.
|
||||
"""
|
||||
|
||||
|
||||
from future.utils import raise_
|
||||
class AutoLinker(object):
|
||||
"""
|
||||
Base class, provides interface for I{automatic} link
|
||||
management between a L{Properties} object and the L{Properties}
|
||||
contained within I{values}.
|
||||
"""
|
||||
def updated(self, properties, prev, next):
|
||||
"""
|
||||
Notification that a values was updated and the linkage
|
||||
between the I{properties} contained with I{prev} need to
|
||||
be relinked to the L{Properties} contained within the
|
||||
I{next} value.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Link(object):
|
||||
"""
|
||||
Property link object.
|
||||
@ivar endpoints: A tuple of the (2) endpoints of the link.
|
||||
@type endpoints: tuple(2)
|
||||
"""
|
||||
def __init__(self, a, b):
|
||||
"""
|
||||
@param a: Property (A) to link.
|
||||
@type a: L{Property}
|
||||
@param b: Property (B) to link.
|
||||
@type b: L{Property}
|
||||
"""
|
||||
pA = Endpoint(self, a)
|
||||
pB = Endpoint(self, b)
|
||||
self.endpoints = (pA, pB)
|
||||
self.validate(a, b)
|
||||
a.links.append(pB)
|
||||
b.links.append(pA)
|
||||
|
||||
def validate(self, pA, pB):
|
||||
"""
|
||||
Validate that the two properties may be linked.
|
||||
@param pA: Endpoint (A) to link.
|
||||
@type pA: L{Endpoint}
|
||||
@param pB: Endpoint (B) to link.
|
||||
@type pB: L{Endpoint}
|
||||
@return: self
|
||||
@rtype: L{Link}
|
||||
"""
|
||||
if pA in pB.links or \
|
||||
pB in pA.links:
|
||||
raise Exception('Already linked')
|
||||
dA = pA.domains()
|
||||
dB = pB.domains()
|
||||
for d in dA:
|
||||
if d in dB:
|
||||
raise_(Exception, 'Duplicate domain "%s" found' % d)
|
||||
for d in dB:
|
||||
if d in dA:
|
||||
raise_(Exception, 'Duplicate domain "%s" found' % d)
|
||||
kA = pA.keys()
|
||||
kB = pB.keys()
|
||||
for k in kA:
|
||||
if k in kB:
|
||||
raise_(Exception, 'Duplicate key %s found' % k)
|
||||
for k in kB:
|
||||
if k in kA:
|
||||
raise_(Exception, 'Duplicate key %s found' % k)
|
||||
return self
|
||||
|
||||
def teardown(self):
|
||||
"""
|
||||
Teardown the link.
|
||||
Removes endpoints from properties I{links} collection.
|
||||
@return: self
|
||||
@rtype: L{Link}
|
||||
"""
|
||||
pA, pB = self.endpoints
|
||||
if pA in pB.links:
|
||||
pB.links.remove(pA)
|
||||
if pB in pA.links:
|
||||
pA.links.remove(pB)
|
||||
return self
|
||||
|
||||
|
||||
class Endpoint(object):
|
||||
"""
|
||||
Link endpoint (wrapper).
|
||||
@ivar link: The associated link.
|
||||
@type link: L{Link}
|
||||
@ivar target: The properties object.
|
||||
@type target: L{Property}
|
||||
"""
|
||||
def __init__(self, link, target):
|
||||
self.link = link
|
||||
self.target = target
|
||||
|
||||
def teardown(self):
|
||||
return self.link.teardown()
|
||||
|
||||
def __eq__(self, rhs):
|
||||
return ( self.target == rhs )
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.target)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.target, name)
|
||||
|
||||
|
||||
class Definition:
|
||||
"""
|
||||
Property definition.
|
||||
@ivar name: The property name.
|
||||
@type name: str
|
||||
@ivar classes: The (class) list of permitted values
|
||||
@type classes: tuple
|
||||
@ivar default: The default value.
|
||||
@ivar type: any
|
||||
"""
|
||||
def __init__(self, name, classes, default, linker=AutoLinker()):
|
||||
"""
|
||||
@param name: The property name.
|
||||
@type name: str
|
||||
@param classes: The (class) list of permitted values
|
||||
@type classes: tuple
|
||||
@param default: The default value.
|
||||
@type default: any
|
||||
"""
|
||||
if not isinstance(classes, (list, tuple)):
|
||||
classes = (classes,)
|
||||
self.name = name
|
||||
self.classes = classes
|
||||
self.default = default
|
||||
self.linker = linker
|
||||
|
||||
def nvl(self, value=None):
|
||||
"""
|
||||
Convert the I{value} into the default when I{None}.
|
||||
@param value: The proposed value.
|
||||
@type value: any
|
||||
@return: The I{default} when I{value} is I{None}, else I{value}.
|
||||
@rtype: any
|
||||
"""
|
||||
if value is None:
|
||||
return self.default
|
||||
else:
|
||||
return value
|
||||
|
||||
def validate(self, value):
|
||||
"""
|
||||
Validate the I{value} is of the correct class.
|
||||
@param value: The value to validate.
|
||||
@type value: any
|
||||
@raise AttributeError: When I{value} is invalid.
|
||||
"""
|
||||
if value is None:
|
||||
return
|
||||
if len(self.classes) and \
|
||||
not isinstance(value, self.classes):
|
||||
msg = '"%s" must be: %s' % (self.name, self.classes)
|
||||
raise_(AttributeError,msg)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '%s: %s' % (self.name, str(self))
|
||||
|
||||
def __str__(self):
|
||||
s = []
|
||||
if len(self.classes):
|
||||
s.append('classes=%s' % str(self.classes))
|
||||
else:
|
||||
s.append('classes=*')
|
||||
s.append("default=%s" % str(self.default))
|
||||
return ', '.join(s)
|
||||
|
||||
|
||||
class Properties:
|
||||
"""
|
||||
Represents basic application properties.
|
||||
Provides basic type validation, default values and
|
||||
link/synchronization behavior.
|
||||
@ivar domain: The domain name.
|
||||
@type domain: str
|
||||
@ivar definitions: A table of property definitions.
|
||||
@type definitions: {name: L{Definition}}
|
||||
@ivar links: A list of linked property objects used to create
|
||||
a network of properties.
|
||||
@type links: [L{Property},..]
|
||||
@ivar defined: A dict of property values.
|
||||
@type defined: dict
|
||||
"""
|
||||
def __init__(self, domain, definitions, kwargs):
|
||||
"""
|
||||
@param domain: The property domain name.
|
||||
@type domain: str
|
||||
@param definitions: A table of property definitions.
|
||||
@type definitions: {name: L{Definition}}
|
||||
@param kwargs: A list of property name/values to set.
|
||||
@type kwargs: dict
|
||||
"""
|
||||
self.definitions = {}
|
||||
for d in definitions:
|
||||
self.definitions[d.name] = d
|
||||
self.domain = domain
|
||||
self.links = []
|
||||
self.defined = {}
|
||||
self.modified = set()
|
||||
self.prime()
|
||||
self.update(kwargs)
|
||||
|
||||
def definition(self, name):
|
||||
"""
|
||||
Get the definition for the property I{name}.
|
||||
@param name: The property I{name} to find the definition for.
|
||||
@type name: str
|
||||
@return: The property definition
|
||||
@rtype: L{Definition}
|
||||
@raise AttributeError: On not found.
|
||||
"""
|
||||
d = self.definitions.get(name)
|
||||
if d is None:
|
||||
raise AttributeError(name)
|
||||
return d
|
||||
|
||||
def update(self, other):
|
||||
"""
|
||||
Update the property values as specified by keyword/value.
|
||||
@param other: An object to update from.
|
||||
@type other: (dict|L{Properties})
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
if isinstance(other, Properties):
|
||||
other = other.defined
|
||||
for n,v in other.items():
|
||||
self.set(n, v)
|
||||
return self
|
||||
|
||||
def notset(self, name):
|
||||
"""
|
||||
Get whether a property has never been set by I{name}.
|
||||
@param name: A property name.
|
||||
@type name: str
|
||||
@return: True if never been set.
|
||||
@rtype: bool
|
||||
"""
|
||||
self.provider(name).__notset(name)
|
||||
|
||||
def set(self, name, value):
|
||||
"""
|
||||
Set the I{value} of a property by I{name}.
|
||||
The value is validated against the definition and set
|
||||
to the default when I{value} is None.
|
||||
@param name: The property name.
|
||||
@type name: str
|
||||
@param value: The new property value.
|
||||
@type value: any
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
self.provider(name).__set(name, value)
|
||||
return self
|
||||
|
||||
def unset(self, name):
|
||||
"""
|
||||
Unset a property by I{name}.
|
||||
@param name: A property name.
|
||||
@type name: str
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
self.provider(name).__set(name, None)
|
||||
return self
|
||||
|
||||
def get(self, name, *df):
|
||||
"""
|
||||
Get the value of a property by I{name}.
|
||||
@param name: The property name.
|
||||
@type name: str
|
||||
@param df: An optional value to be returned when the value
|
||||
is not set
|
||||
@type df: [1].
|
||||
@return: The stored value, or I{df[0]} if not set.
|
||||
@rtype: any
|
||||
"""
|
||||
return self.provider(name).__get(name, *df)
|
||||
|
||||
def link(self, other):
|
||||
"""
|
||||
Link (associate) this object with anI{other} properties object
|
||||
to create a network of properties. Links are bidirectional.
|
||||
@param other: The object to link.
|
||||
@type other: L{Properties}
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
Link(self, other)
|
||||
return self
|
||||
|
||||
def unlink(self, *others):
|
||||
"""
|
||||
Unlink (disassociate) the specified properties object.
|
||||
@param others: The list object to unlink. Unspecified means unlink all.
|
||||
@type others: [L{Properties},..]
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
if not len(others):
|
||||
others = self.links[:]
|
||||
for p in self.links[:]:
|
||||
if p in others:
|
||||
p.teardown()
|
||||
return self
|
||||
|
||||
def provider(self, name, history=None):
|
||||
"""
|
||||
Find the provider of the property by I{name}.
|
||||
@param name: The property name.
|
||||
@type name: str
|
||||
@param history: A history of nodes checked to prevent
|
||||
circular hunting.
|
||||
@type history: [L{Properties},..]
|
||||
@return: The provider when found. Otherwise, None (when nested)
|
||||
and I{self} when not nested.
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
if history is None:
|
||||
history = []
|
||||
history.append(self)
|
||||
if name in self.definitions:
|
||||
return self
|
||||
for x in self.links:
|
||||
if x in history:
|
||||
continue
|
||||
provider = x.provider(name, history)
|
||||
if provider is not None:
|
||||
return provider
|
||||
history.remove(self)
|
||||
if len(history):
|
||||
return None
|
||||
return self
|
||||
|
||||
def keys(self, history=None):
|
||||
"""
|
||||
Get the set of I{all} property names.
|
||||
@param history: A history of nodes checked to prevent
|
||||
circular hunting.
|
||||
@type history: [L{Properties},..]
|
||||
@return: A set of property names.
|
||||
@rtype: list
|
||||
"""
|
||||
if history is None:
|
||||
history = []
|
||||
history.append(self)
|
||||
keys = set()
|
||||
keys.update(self.definitions.keys())
|
||||
for x in self.links:
|
||||
if x in history:
|
||||
continue
|
||||
keys.update(x.keys(history))
|
||||
history.remove(self)
|
||||
return keys
|
||||
|
||||
def domains(self, history=None):
|
||||
"""
|
||||
Get the set of I{all} domain names.
|
||||
@param history: A history of nodes checked to prevent
|
||||
circular hunting.
|
||||
@type history: [L{Properties},..]
|
||||
@return: A set of domain names.
|
||||
@rtype: list
|
||||
"""
|
||||
if history is None:
|
||||
history = []
|
||||
history.append(self)
|
||||
domains = set()
|
||||
domains.add(self.domain)
|
||||
for x in self.links:
|
||||
if x in history:
|
||||
continue
|
||||
domains.update(x.domains(history))
|
||||
history.remove(self)
|
||||
return domains
|
||||
|
||||
def prime(self):
|
||||
"""
|
||||
Prime the stored values based on default values
|
||||
found in property definitions.
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
for d in self.definitions.values():
|
||||
self.defined[d.name] = d.default
|
||||
return self
|
||||
|
||||
def __notset(self, name):
|
||||
return not (name in self.modified)
|
||||
|
||||
def __set(self, name, value):
|
||||
d = self.definition(name)
|
||||
d.validate(value)
|
||||
value = d.nvl(value)
|
||||
prev = self.defined[name]
|
||||
self.defined[name] = value
|
||||
self.modified.add(name)
|
||||
d.linker.updated(self, prev, value)
|
||||
|
||||
def __get(self, name, *df):
|
||||
d = self.definition(name)
|
||||
value = self.defined.get(name)
|
||||
if value == d.default and len(df):
|
||||
value = df[0]
|
||||
return value
|
||||
|
||||
def str(self, history):
|
||||
s = []
|
||||
s.append('Definitions:')
|
||||
for d in self.definitions.values():
|
||||
s.append('\t%s' % repr(d))
|
||||
s.append('Content:')
|
||||
for d in self.defined.items():
|
||||
s.append('\t%s' % str(d))
|
||||
if self not in history:
|
||||
history.append(self)
|
||||
s.append('Linked:')
|
||||
for x in self.links:
|
||||
s.append(x.str(history))
|
||||
history.remove(self)
|
||||
return '\n'.join(s)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def __str__(self):
|
||||
return self.str([])
|
||||
|
||||
|
||||
class Skin(object):
|
||||
"""
|
||||
The meta-programming I{skin} around the L{Properties} object.
|
||||
@ivar __pts__: The wrapped object.
|
||||
@type __pts__: L{Properties}.
|
||||
"""
|
||||
def __init__(self, domain, definitions, kwargs):
|
||||
self.__pts__ = Properties(domain, definitions, kwargs)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
builtin = name.startswith('__') and name.endswith('__')
|
||||
if builtin:
|
||||
self.__dict__[name] = value
|
||||
return
|
||||
self.__pts__.set(name, value)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.__pts__.get(name)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__pts__)
|
||||
|
||||
|
||||
class Unskin(object):
|
||||
def __new__(self, *args, **kwargs):
|
||||
return args[0].__pts__
|
||||
|
||||
|
||||
class Inspector:
|
||||
"""
|
||||
Wrapper inspector.
|
||||
"""
|
||||
def __init__(self, options):
|
||||
self.properties = options.__pts__
|
||||
|
||||
def get(self, name, *df):
|
||||
"""
|
||||
Get the value of a property by I{name}.
|
||||
@param name: The property name.
|
||||
@type name: str
|
||||
@param df: An optional value to be returned when the value
|
||||
is not set
|
||||
@type df: [1].
|
||||
@return: The stored value, or I{df[0]} if not set.
|
||||
@rtype: any
|
||||
"""
|
||||
return self.properties.get(name, *df)
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""
|
||||
Update the property values as specified by keyword/value.
|
||||
@param kwargs: A list of property name/values to set.
|
||||
@type kwargs: dict
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
return self.properties.update(**kwargs)
|
||||
|
||||
def link(self, other):
|
||||
"""
|
||||
Link (associate) this object with anI{other} properties object
|
||||
to create a network of properties. Links are bidirectional.
|
||||
@param other: The object to link.
|
||||
@type other: L{Properties}
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
p = other.__pts__
|
||||
return self.properties.link(p)
|
||||
|
||||
def unlink(self, other):
|
||||
"""
|
||||
Unlink (disassociate) the specified properties object.
|
||||
@param other: The object to unlink.
|
||||
@type other: L{Properties}
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
p = other.__pts__
|
||||
return self.properties.unlink(p)
|
@ -1,539 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Properties classes.
|
||||
"""
|
||||
|
||||
|
||||
class AutoLinker(object):
|
||||
"""
|
||||
Base class, provides interface for I{automatic} link
|
||||
management between a L{Properties} object and the L{Properties}
|
||||
contained within I{values}.
|
||||
"""
|
||||
def updated(self, properties, prev, next):
|
||||
"""
|
||||
Notification that a values was updated and the linkage
|
||||
between the I{properties} contained with I{prev} need to
|
||||
be relinked to the L{Properties} contained within the
|
||||
I{next} value.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Link(object):
|
||||
"""
|
||||
Property link object.
|
||||
@ivar endpoints: A tuple of the (2) endpoints of the link.
|
||||
@type endpoints: tuple(2)
|
||||
"""
|
||||
def __init__(self, a, b):
|
||||
"""
|
||||
@param a: Property (A) to link.
|
||||
@type a: L{Property}
|
||||
@param b: Property (B) to link.
|
||||
@type b: L{Property}
|
||||
"""
|
||||
pA = Endpoint(self, a)
|
||||
pB = Endpoint(self, b)
|
||||
self.endpoints = (pA, pB)
|
||||
self.validate(a, b)
|
||||
a.links.append(pB)
|
||||
b.links.append(pA)
|
||||
|
||||
def validate(self, pA, pB):
|
||||
"""
|
||||
Validate that the two properties may be linked.
|
||||
@param pA: Endpoint (A) to link.
|
||||
@type pA: L{Endpoint}
|
||||
@param pB: Endpoint (B) to link.
|
||||
@type pB: L{Endpoint}
|
||||
@return: self
|
||||
@rtype: L{Link}
|
||||
"""
|
||||
if pA in pB.links or \
|
||||
pB in pA.links:
|
||||
raise Exception, 'Already linked'
|
||||
dA = pA.domains()
|
||||
dB = pB.domains()
|
||||
for d in dA:
|
||||
if d in dB:
|
||||
raise Exception, 'Duplicate domain "%s" found' % d
|
||||
for d in dB:
|
||||
if d in dA:
|
||||
raise Exception, 'Duplicate domain "%s" found' % d
|
||||
kA = pA.keys()
|
||||
kB = pB.keys()
|
||||
for k in kA:
|
||||
if k in kB:
|
||||
raise Exception, 'Duplicate key %s found' % k
|
||||
for k in kB:
|
||||
if k in kA:
|
||||
raise Exception, 'Duplicate key %s found' % k
|
||||
return self
|
||||
|
||||
def teardown(self):
|
||||
"""
|
||||
Teardown the link.
|
||||
Removes endpoints from properties I{links} collection.
|
||||
@return: self
|
||||
@rtype: L{Link}
|
||||
"""
|
||||
pA, pB = self.endpoints
|
||||
if pA in pB.links:
|
||||
pB.links.remove(pA)
|
||||
if pB in pA.links:
|
||||
pA.links.remove(pB)
|
||||
return self
|
||||
|
||||
|
||||
class Endpoint(object):
|
||||
"""
|
||||
Link endpoint (wrapper).
|
||||
@ivar link: The associated link.
|
||||
@type link: L{Link}
|
||||
@ivar target: The properties object.
|
||||
@type target: L{Property}
|
||||
"""
|
||||
def __init__(self, link, target):
|
||||
self.link = link
|
||||
self.target = target
|
||||
|
||||
def teardown(self):
|
||||
return self.link.teardown()
|
||||
|
||||
def __eq__(self, rhs):
|
||||
return ( self.target == rhs )
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.target)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.target, name)
|
||||
|
||||
|
||||
class Definition:
|
||||
"""
|
||||
Property definition.
|
||||
@ivar name: The property name.
|
||||
@type name: str
|
||||
@ivar classes: The (class) list of permitted values
|
||||
@type classes: tuple
|
||||
@ivar default: The default value.
|
||||
@ivar type: any
|
||||
"""
|
||||
def __init__(self, name, classes, default, linker=AutoLinker()):
|
||||
"""
|
||||
@param name: The property name.
|
||||
@type name: str
|
||||
@param classes: The (class) list of permitted values
|
||||
@type classes: tuple
|
||||
@param default: The default value.
|
||||
@type default: any
|
||||
"""
|
||||
if not isinstance(classes, (list, tuple)):
|
||||
classes = (classes,)
|
||||
self.name = name
|
||||
self.classes = classes
|
||||
self.default = default
|
||||
self.linker = linker
|
||||
|
||||
def nvl(self, value=None):
|
||||
"""
|
||||
Convert the I{value} into the default when I{None}.
|
||||
@param value: The proposed value.
|
||||
@type value: any
|
||||
@return: The I{default} when I{value} is I{None}, else I{value}.
|
||||
@rtype: any
|
||||
"""
|
||||
if value is None:
|
||||
return self.default
|
||||
else:
|
||||
return value
|
||||
|
||||
def validate(self, value):
|
||||
"""
|
||||
Validate the I{value} is of the correct class.
|
||||
@param value: The value to validate.
|
||||
@type value: any
|
||||
@raise AttributeError: When I{value} is invalid.
|
||||
"""
|
||||
if value is None:
|
||||
return
|
||||
if len(self.classes) and \
|
||||
not isinstance(value, self.classes):
|
||||
msg = '"%s" must be: %s' % (self.name, self.classes)
|
||||
raise AttributeError,msg
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '%s: %s' % (self.name, str(self))
|
||||
|
||||
def __str__(self):
|
||||
s = []
|
||||
if len(self.classes):
|
||||
s.append('classes=%s' % str(self.classes))
|
||||
else:
|
||||
s.append('classes=*')
|
||||
s.append("default=%s" % str(self.default))
|
||||
return ', '.join(s)
|
||||
|
||||
|
||||
class Properties:
|
||||
"""
|
||||
Represents basic application properties.
|
||||
Provides basic type validation, default values and
|
||||
link/synchronization behavior.
|
||||
@ivar domain: The domain name.
|
||||
@type domain: str
|
||||
@ivar definitions: A table of property definitions.
|
||||
@type definitions: {name: L{Definition}}
|
||||
@ivar links: A list of linked property objects used to create
|
||||
a network of properties.
|
||||
@type links: [L{Property},..]
|
||||
@ivar defined: A dict of property values.
|
||||
@type defined: dict
|
||||
"""
|
||||
def __init__(self, domain, definitions, kwargs):
|
||||
"""
|
||||
@param domain: The property domain name.
|
||||
@type domain: str
|
||||
@param definitions: A table of property definitions.
|
||||
@type definitions: {name: L{Definition}}
|
||||
@param kwargs: A list of property name/values to set.
|
||||
@type kwargs: dict
|
||||
"""
|
||||
self.definitions = {}
|
||||
for d in definitions:
|
||||
self.definitions[d.name] = d
|
||||
self.domain = domain
|
||||
self.links = []
|
||||
self.defined = {}
|
||||
self.modified = set()
|
||||
self.prime()
|
||||
self.update(kwargs)
|
||||
|
||||
def definition(self, name):
|
||||
"""
|
||||
Get the definition for the property I{name}.
|
||||
@param name: The property I{name} to find the definition for.
|
||||
@type name: str
|
||||
@return: The property definition
|
||||
@rtype: L{Definition}
|
||||
@raise AttributeError: On not found.
|
||||
"""
|
||||
d = self.definitions.get(name)
|
||||
if d is None:
|
||||
raise AttributeError(name)
|
||||
return d
|
||||
|
||||
def update(self, other):
|
||||
"""
|
||||
Update the property values as specified by keyword/value.
|
||||
@param other: An object to update from.
|
||||
@type other: (dict|L{Properties})
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
if isinstance(other, Properties):
|
||||
other = other.defined
|
||||
for n,v in other.items():
|
||||
self.set(n, v)
|
||||
return self
|
||||
|
||||
def notset(self, name):
|
||||
"""
|
||||
Get whether a property has never been set by I{name}.
|
||||
@param name: A property name.
|
||||
@type name: str
|
||||
@return: True if never been set.
|
||||
@rtype: bool
|
||||
"""
|
||||
self.provider(name).__notset(name)
|
||||
|
||||
def set(self, name, value):
|
||||
"""
|
||||
Set the I{value} of a property by I{name}.
|
||||
The value is validated against the definition and set
|
||||
to the default when I{value} is None.
|
||||
@param name: The property name.
|
||||
@type name: str
|
||||
@param value: The new property value.
|
||||
@type value: any
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
self.provider(name).__set(name, value)
|
||||
return self
|
||||
|
||||
def unset(self, name):
|
||||
"""
|
||||
Unset a property by I{name}.
|
||||
@param name: A property name.
|
||||
@type name: str
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
self.provider(name).__set(name, None)
|
||||
return self
|
||||
|
||||
def get(self, name, *df):
|
||||
"""
|
||||
Get the value of a property by I{name}.
|
||||
@param name: The property name.
|
||||
@type name: str
|
||||
@param df: An optional value to be returned when the value
|
||||
is not set
|
||||
@type df: [1].
|
||||
@return: The stored value, or I{df[0]} if not set.
|
||||
@rtype: any
|
||||
"""
|
||||
return self.provider(name).__get(name, *df)
|
||||
|
||||
def link(self, other):
|
||||
"""
|
||||
Link (associate) this object with anI{other} properties object
|
||||
to create a network of properties. Links are bidirectional.
|
||||
@param other: The object to link.
|
||||
@type other: L{Properties}
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
Link(self, other)
|
||||
return self
|
||||
|
||||
def unlink(self, *others):
|
||||
"""
|
||||
Unlink (disassociate) the specified properties object.
|
||||
@param others: The list object to unlink. Unspecified means unlink all.
|
||||
@type others: [L{Properties},..]
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
if not len(others):
|
||||
others = self.links[:]
|
||||
for p in self.links[:]:
|
||||
if p in others:
|
||||
p.teardown()
|
||||
return self
|
||||
|
||||
def provider(self, name, history=None):
|
||||
"""
|
||||
Find the provider of the property by I{name}.
|
||||
@param name: The property name.
|
||||
@type name: str
|
||||
@param history: A history of nodes checked to prevent
|
||||
circular hunting.
|
||||
@type history: [L{Properties},..]
|
||||
@return: The provider when found. Otherwise, None (when nested)
|
||||
and I{self} when not nested.
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
if history is None:
|
||||
history = []
|
||||
history.append(self)
|
||||
if name in self.definitions:
|
||||
return self
|
||||
for x in self.links:
|
||||
if x in history:
|
||||
continue
|
||||
provider = x.provider(name, history)
|
||||
if provider is not None:
|
||||
return provider
|
||||
history.remove(self)
|
||||
if len(history):
|
||||
return None
|
||||
return self
|
||||
|
||||
def keys(self, history=None):
|
||||
"""
|
||||
Get the set of I{all} property names.
|
||||
@param history: A history of nodes checked to prevent
|
||||
circular hunting.
|
||||
@type history: [L{Properties},..]
|
||||
@return: A set of property names.
|
||||
@rtype: list
|
||||
"""
|
||||
if history is None:
|
||||
history = []
|
||||
history.append(self)
|
||||
keys = set()
|
||||
keys.update(self.definitions.keys())
|
||||
for x in self.links:
|
||||
if x in history:
|
||||
continue
|
||||
keys.update(x.keys(history))
|
||||
history.remove(self)
|
||||
return keys
|
||||
|
||||
def domains(self, history=None):
|
||||
"""
|
||||
Get the set of I{all} domain names.
|
||||
@param history: A history of nodes checked to prevent
|
||||
circular hunting.
|
||||
@type history: [L{Properties},..]
|
||||
@return: A set of domain names.
|
||||
@rtype: list
|
||||
"""
|
||||
if history is None:
|
||||
history = []
|
||||
history.append(self)
|
||||
domains = set()
|
||||
domains.add(self.domain)
|
||||
for x in self.links:
|
||||
if x in history:
|
||||
continue
|
||||
domains.update(x.domains(history))
|
||||
history.remove(self)
|
||||
return domains
|
||||
|
||||
def prime(self):
|
||||
"""
|
||||
Prime the stored values based on default values
|
||||
found in property definitions.
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
for d in self.definitions.values():
|
||||
self.defined[d.name] = d.default
|
||||
return self
|
||||
|
||||
def __notset(self, name):
|
||||
return not (name in self.modified)
|
||||
|
||||
def __set(self, name, value):
|
||||
d = self.definition(name)
|
||||
d.validate(value)
|
||||
value = d.nvl(value)
|
||||
prev = self.defined[name]
|
||||
self.defined[name] = value
|
||||
self.modified.add(name)
|
||||
d.linker.updated(self, prev, value)
|
||||
|
||||
def __get(self, name, *df):
|
||||
d = self.definition(name)
|
||||
value = self.defined.get(name)
|
||||
if value == d.default and len(df):
|
||||
value = df[0]
|
||||
return value
|
||||
|
||||
def str(self, history):
|
||||
s = []
|
||||
s.append('Definitions:')
|
||||
for d in self.definitions.values():
|
||||
s.append('\t%s' % repr(d))
|
||||
s.append('Content:')
|
||||
for d in self.defined.items():
|
||||
s.append('\t%s' % str(d))
|
||||
if self not in history:
|
||||
history.append(self)
|
||||
s.append('Linked:')
|
||||
for x in self.links:
|
||||
s.append(x.str(history))
|
||||
history.remove(self)
|
||||
return '\n'.join(s)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def __str__(self):
|
||||
return self.str([])
|
||||
|
||||
|
||||
class Skin(object):
|
||||
"""
|
||||
The meta-programming I{skin} around the L{Properties} object.
|
||||
@ivar __pts__: The wrapped object.
|
||||
@type __pts__: L{Properties}.
|
||||
"""
|
||||
def __init__(self, domain, definitions, kwargs):
|
||||
self.__pts__ = Properties(domain, definitions, kwargs)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
builtin = name.startswith('__') and name.endswith('__')
|
||||
if builtin:
|
||||
self.__dict__[name] = value
|
||||
return
|
||||
self.__pts__.set(name, value)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.__pts__.get(name)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__pts__)
|
||||
|
||||
|
||||
class Unskin(object):
|
||||
def __new__(self, *args, **kwargs):
|
||||
return args[0].__pts__
|
||||
|
||||
|
||||
class Inspector:
|
||||
"""
|
||||
Wrapper inspector.
|
||||
"""
|
||||
def __init__(self, options):
|
||||
self.properties = options.__pts__
|
||||
|
||||
def get(self, name, *df):
|
||||
"""
|
||||
Get the value of a property by I{name}.
|
||||
@param name: The property name.
|
||||
@type name: str
|
||||
@param df: An optional value to be returned when the value
|
||||
is not set
|
||||
@type df: [1].
|
||||
@return: The stored value, or I{df[0]} if not set.
|
||||
@rtype: any
|
||||
"""
|
||||
return self.properties.get(name, *df)
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""
|
||||
Update the property values as specified by keyword/value.
|
||||
@param kwargs: A list of property name/values to set.
|
||||
@type kwargs: dict
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
return self.properties.update(**kwargs)
|
||||
|
||||
def link(self, other):
|
||||
"""
|
||||
Link (associate) this object with anI{other} properties object
|
||||
to create a network of properties. Links are bidirectional.
|
||||
@param other: The object to link.
|
||||
@type other: L{Properties}
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
p = other.__pts__
|
||||
return self.properties.link(p)
|
||||
|
||||
def unlink(self, other):
|
||||
"""
|
||||
Unlink (disassociate) the specified properties object.
|
||||
@param other: The object to unlink.
|
||||
@type other: L{Properties}
|
||||
@return: self
|
||||
@rtype: L{Properties}
|
||||
"""
|
||||
p = other.__pts__
|
||||
return self.properties.unlink(p)
|
@ -1,197 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
XML document reader classes providing integration with the suds library's
|
||||
caching system.
|
||||
|
||||
"""
|
||||
|
||||
import suds.cache
|
||||
import suds.plugin
|
||||
import suds.sax.parser
|
||||
import suds.transport
|
||||
|
||||
try:
|
||||
from hashlib import md5
|
||||
except ImportError:
|
||||
# 'hashlib' package added in Python 2.5 so use the now deprecated/removed
|
||||
# 'md5' package in older Python versions.
|
||||
from md5 import md5
|
||||
|
||||
|
||||
class Reader(object):
|
||||
"""
|
||||
Provides integration with the cache.
|
||||
|
||||
@ivar options: An options object.
|
||||
@type options: I{Options}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, options):
|
||||
"""
|
||||
@param options: An options object.
|
||||
@type options: I{Options}
|
||||
|
||||
"""
|
||||
self.options = options
|
||||
self.plugins = suds.plugin.PluginContainer(options.plugins)
|
||||
|
||||
def mangle(self, name, x):
|
||||
"""
|
||||
Mangle the name by hashing the I{name} and appending I{x}.
|
||||
|
||||
@return: The mangled name.
|
||||
@rtype: str
|
||||
|
||||
"""
|
||||
h = md5(name.encode()).hexdigest()
|
||||
return '%s-%s' % (h, x)
|
||||
|
||||
|
||||
class DefinitionsReader(Reader):
|
||||
"""
|
||||
Integrates between the WSDL Definitions object and the object cache.
|
||||
|
||||
@ivar fn: A factory function used to create objects not found in the cache.
|
||||
@type fn: I{Constructor}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, options, fn):
|
||||
"""
|
||||
@param options: An options object.
|
||||
@type options: I{Options}
|
||||
@param fn: A factory function used to create objects not found in the
|
||||
cache.
|
||||
@type fn: I{Constructor}
|
||||
|
||||
"""
|
||||
super(DefinitionsReader, self).__init__(options)
|
||||
self.fn = fn
|
||||
|
||||
def open(self, url):
|
||||
"""
|
||||
Open a WSDL schema at the specified I{URL}.
|
||||
|
||||
First, the WSDL schema is looked up in the I{object cache}. If not
|
||||
found, a new one constructed using the I{fn} factory function and the
|
||||
result is cached for the next open().
|
||||
|
||||
@param url: A WSDL URL.
|
||||
@type url: str.
|
||||
@return: The WSDL object.
|
||||
@rtype: I{Definitions}
|
||||
|
||||
"""
|
||||
cache = self.__cache()
|
||||
id = self.mangle(url, "wsdl")
|
||||
wsdl = cache.get(id)
|
||||
if wsdl is None:
|
||||
wsdl = self.fn(url, self.options)
|
||||
cache.put(id, wsdl)
|
||||
else:
|
||||
# Cached WSDL Definitions objects may have been created with
|
||||
# different options so we update them here with our current ones.
|
||||
wsdl.options = self.options
|
||||
for imp in wsdl.imports:
|
||||
imp.imported.options = self.options
|
||||
return wsdl
|
||||
|
||||
def __cache(self):
|
||||
"""
|
||||
Get the I{object cache}.
|
||||
|
||||
@return: The I{cache} when I{cachingpolicy} = B{1}.
|
||||
@rtype: L{Cache}
|
||||
|
||||
"""
|
||||
if self.options.cachingpolicy == 1:
|
||||
return self.options.cache
|
||||
return suds.cache.NoCache()
|
||||
|
||||
|
||||
class DocumentReader(Reader):
|
||||
"""Integrates between the SAX L{Parser} and the document cache."""
|
||||
|
||||
def open(self, url):
|
||||
"""
|
||||
Open an XML document at the specified I{URL}.
|
||||
|
||||
First, a preparsed document is looked up in the I{object cache}. If not
|
||||
found, its content is fetched from an external source and parsed using
|
||||
the SAX parser. The result is cached for the next open().
|
||||
|
||||
@param url: A document URL.
|
||||
@type url: str.
|
||||
@return: The specified XML document.
|
||||
@rtype: I{Document}
|
||||
|
||||
"""
|
||||
cache = self.__cache()
|
||||
id = self.mangle(url, "document")
|
||||
xml = cache.get(id)
|
||||
if xml is None:
|
||||
xml = self.__fetch(url)
|
||||
cache.put(id, xml)
|
||||
self.plugins.document.parsed(url=url, document=xml.root())
|
||||
return xml
|
||||
|
||||
def __cache(self):
|
||||
"""
|
||||
Get the I{object cache}.
|
||||
|
||||
@return: The I{cache} when I{cachingpolicy} = B{0}.
|
||||
@rtype: L{Cache}
|
||||
|
||||
"""
|
||||
if self.options.cachingpolicy == 0:
|
||||
return self.options.cache
|
||||
return suds.cache.NoCache()
|
||||
|
||||
def __fetch(self, url):
|
||||
"""
|
||||
Fetch document content from an external source.
|
||||
|
||||
The document content will first be looked up in the registered document
|
||||
store, and if not found there, downloaded using the registered
|
||||
transport system.
|
||||
|
||||
Before being returned, the fetched document content first gets
|
||||
processed by all the registered 'loaded' plugins.
|
||||
|
||||
@param url: A document URL.
|
||||
@type url: str.
|
||||
@return: A file pointer to the fetched document content.
|
||||
@rtype: file-like
|
||||
|
||||
"""
|
||||
content = None
|
||||
store = self.options.documentStore
|
||||
if store is not None:
|
||||
content = store.open(url)
|
||||
if content is None:
|
||||
request = suds.transport.Request(url)
|
||||
request.headers = self.options.headers
|
||||
fp = self.options.transport.open(request)
|
||||
try:
|
||||
content = fp.read()
|
||||
finally:
|
||||
fp.close()
|
||||
ctx = self.plugins.document.loaded(url=url, document=content)
|
||||
content = ctx.document
|
||||
sax = suds.sax.parser.Parser()
|
||||
return sax.parse(string=content)
|
@ -1,493 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The I{resolver} module provides a collection of classes that
|
||||
provide wsdl/xsd named type resolution.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.sax import splitPrefix, Namespace
|
||||
from suds.sudsobject import Object
|
||||
from suds.xsd.query import BlindQuery, TypeQuery, qualify
|
||||
|
||||
import re
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Resolver:
|
||||
"""
|
||||
An I{abstract} schema-type resolver.
|
||||
@ivar schema: A schema object.
|
||||
@type schema: L{xsd.schema.Schema}
|
||||
"""
|
||||
|
||||
def __init__(self, schema):
|
||||
"""
|
||||
@param schema: A schema object.
|
||||
@type schema: L{xsd.schema.Schema}
|
||||
"""
|
||||
self.schema = schema
|
||||
|
||||
def find(self, name, resolved=True):
|
||||
"""
|
||||
Get the definition object for the schema object by name.
|
||||
@param name: The name of a schema object.
|
||||
@type name: basestring
|
||||
@param resolved: A flag indicating that the fully resolved type
|
||||
should be returned.
|
||||
@type resolved: boolean
|
||||
@return: The found schema I{type}
|
||||
@rtype: L{xsd.sxbase.SchemaObject}
|
||||
"""
|
||||
log.debug('searching schema for (%s)', name)
|
||||
qref = qualify(name, self.schema.root, self.schema.tns)
|
||||
query = BlindQuery(qref)
|
||||
result = query.execute(self.schema)
|
||||
if result is None:
|
||||
log.error('(%s) not-found', name)
|
||||
return None
|
||||
log.debug('found (%s) as (%s)', name, Repr(result))
|
||||
if resolved:
|
||||
result = result.resolve()
|
||||
return result
|
||||
|
||||
|
||||
class PathResolver(Resolver):
|
||||
"""
|
||||
Resolves the definition object for the schema type located at a given path.
|
||||
The path may contain (.) dot notation to specify nested types.
|
||||
@ivar wsdl: A wsdl object.
|
||||
@type wsdl: L{wsdl.Definitions}
|
||||
"""
|
||||
|
||||
def __init__(self, wsdl, ps='.'):
|
||||
"""
|
||||
@param wsdl: A schema object.
|
||||
@type wsdl: L{wsdl.Definitions}
|
||||
@param ps: The path separator character
|
||||
@type ps: char
|
||||
"""
|
||||
Resolver.__init__(self, wsdl.schema)
|
||||
self.wsdl = wsdl
|
||||
self.altp = re.compile('({)(.+)(})(.+)')
|
||||
self.splitp = re.compile('({.+})*[^\\%s]+' % ps[0])
|
||||
|
||||
def find(self, path, resolved=True):
|
||||
"""
|
||||
Get the definition object for the schema type located at the specified path.
|
||||
The path may contain (.) dot notation to specify nested types.
|
||||
Actually, the path separator is usually a (.) but can be redefined
|
||||
during contruction.
|
||||
@param path: A (.) separated path to a schema type.
|
||||
@type path: basestring
|
||||
@param resolved: A flag indicating that the fully resolved type
|
||||
should be returned.
|
||||
@type resolved: boolean
|
||||
@return: The found schema I{type}
|
||||
@rtype: L{xsd.sxbase.SchemaObject}
|
||||
"""
|
||||
result = None
|
||||
parts = self.split(path)
|
||||
try:
|
||||
result = self.root(parts)
|
||||
if len(parts) > 1:
|
||||
result = result.resolve(nobuiltin=True)
|
||||
result = self.branch(result, parts)
|
||||
result = self.leaf(result, parts)
|
||||
if resolved:
|
||||
result = result.resolve(nobuiltin=True)
|
||||
except PathResolver.BadPath:
|
||||
log.error('path: "%s", not-found' % path)
|
||||
return result
|
||||
|
||||
def root(self, parts):
|
||||
"""
|
||||
Find the path root.
|
||||
@param parts: A list of path parts.
|
||||
@type parts: [str,..]
|
||||
@return: The root.
|
||||
@rtype: L{xsd.sxbase.SchemaObject}
|
||||
"""
|
||||
result = None
|
||||
name = parts[0]
|
||||
log.debug('searching schema for (%s)', name)
|
||||
qref = self.qualify(parts[0])
|
||||
query = BlindQuery(qref)
|
||||
result = query.execute(self.schema)
|
||||
if result is None:
|
||||
log.error('(%s) not-found', name)
|
||||
raise PathResolver.BadPath(name)
|
||||
log.debug('found (%s) as (%s)', name, Repr(result))
|
||||
return result
|
||||
|
||||
def branch(self, root, parts):
|
||||
"""
|
||||
Traverse the path until a leaf is reached.
|
||||
@param parts: A list of path parts.
|
||||
@type parts: [str,..]
|
||||
@param root: The root.
|
||||
@type root: L{xsd.sxbase.SchemaObject}
|
||||
@return: The end of the branch.
|
||||
@rtype: L{xsd.sxbase.SchemaObject}
|
||||
"""
|
||||
result = root
|
||||
for part in parts[1:-1]:
|
||||
name = splitPrefix(part)[1]
|
||||
log.debug('searching parent (%s) for (%s)', Repr(result), name)
|
||||
result, ancestry = result.get_child(name)
|
||||
if result is None:
|
||||
log.error('(%s) not-found', name)
|
||||
raise PathResolver.BadPath(name)
|
||||
result = result.resolve(nobuiltin=True)
|
||||
log.debug('found (%s) as (%s)', name, Repr(result))
|
||||
return result
|
||||
|
||||
def leaf(self, parent, parts):
|
||||
"""
|
||||
Find the leaf.
|
||||
@param parts: A list of path parts.
|
||||
@type parts: [str,..]
|
||||
@param parent: The leaf's parent.
|
||||
@type parent: L{xsd.sxbase.SchemaObject}
|
||||
@return: The leaf.
|
||||
@rtype: L{xsd.sxbase.SchemaObject}
|
||||
"""
|
||||
name = splitPrefix(parts[-1])[1]
|
||||
if name.startswith('@'):
|
||||
result, path = parent.get_attribute(name[1:])
|
||||
else:
|
||||
result, ancestry = parent.get_child(name)
|
||||
if result is None:
|
||||
raise PathResolver.BadPath(name)
|
||||
return result
|
||||
|
||||
def qualify(self, name):
|
||||
"""
|
||||
Qualify the name as either:
|
||||
- plain name
|
||||
- ns prefixed name (eg: ns0:Person)
|
||||
- fully ns qualified name (eg: {http://myns-uri}Person)
|
||||
@param name: The name of an object in the schema.
|
||||
@type name: str
|
||||
@return: A qualified name.
|
||||
@rtype: qname
|
||||
"""
|
||||
m = self.altp.match(name)
|
||||
if m is None:
|
||||
return qualify(name, self.wsdl.root, self.wsdl.tns)
|
||||
else:
|
||||
return (m.group(4), m.group(2))
|
||||
|
||||
def split(self, s):
|
||||
"""
|
||||
Split the string on (.) while preserving any (.) inside the
|
||||
'{}' alternalte syntax for full ns qualification.
|
||||
@param s: A plain or qualified name.
|
||||
@type s: str
|
||||
@return: A list of the name's parts.
|
||||
@rtype: [str,..]
|
||||
"""
|
||||
parts = []
|
||||
b = 0
|
||||
while 1:
|
||||
m = self.splitp.match(s, b)
|
||||
if m is None:
|
||||
break
|
||||
b,e = m.span()
|
||||
parts.append(s[b:e])
|
||||
b = e+1
|
||||
return parts
|
||||
|
||||
class BadPath(Exception): pass
|
||||
|
||||
|
||||
class TreeResolver(Resolver):
|
||||
"""
|
||||
The tree resolver is a I{stateful} tree resolver
|
||||
used to resolve each node in a tree. As such, it mirrors
|
||||
the tree structure to ensure that nodes are resolved in
|
||||
context.
|
||||
@ivar stack: The context stack.
|
||||
@type stack: list
|
||||
"""
|
||||
|
||||
def __init__(self, schema):
|
||||
"""
|
||||
@param schema: A schema object.
|
||||
@type schema: L{xsd.schema.Schema}
|
||||
"""
|
||||
Resolver.__init__(self, schema)
|
||||
self.stack = Stack()
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Reset the resolver's state.
|
||||
"""
|
||||
self.stack = Stack()
|
||||
|
||||
def push(self, x):
|
||||
"""
|
||||
Push an I{object} onto the stack.
|
||||
@param x: An object to push.
|
||||
@type x: L{Frame}
|
||||
@return: The pushed frame.
|
||||
@rtype: L{Frame}
|
||||
"""
|
||||
if isinstance(x, Frame):
|
||||
frame = x
|
||||
else:
|
||||
frame = Frame(x)
|
||||
self.stack.append(frame)
|
||||
log.debug('push: (%s)\n%s', Repr(frame), Repr(self.stack))
|
||||
return frame
|
||||
|
||||
def top(self):
|
||||
"""
|
||||
Get the I{frame} at the top of the stack.
|
||||
@return: The top I{frame}, else None.
|
||||
@rtype: L{Frame}
|
||||
"""
|
||||
if len(self.stack):
|
||||
return self.stack[-1]
|
||||
else:
|
||||
return Frame.Empty()
|
||||
|
||||
def pop(self):
|
||||
"""
|
||||
Pop the frame at the top of the stack.
|
||||
@return: The popped frame, else None.
|
||||
@rtype: L{Frame}
|
||||
"""
|
||||
if len(self.stack):
|
||||
popped = self.stack.pop()
|
||||
log.debug('pop: (%s)\n%s', Repr(popped), Repr(self.stack))
|
||||
return popped
|
||||
log.debug('stack empty, not-popped')
|
||||
return None
|
||||
|
||||
def depth(self):
|
||||
"""
|
||||
Get the current stack depth.
|
||||
@return: The current stack depth.
|
||||
@rtype: int
|
||||
"""
|
||||
return len(self.stack)
|
||||
|
||||
def getchild(self, name, parent):
|
||||
"""Get a child by name."""
|
||||
log.debug('searching parent (%s) for (%s)', Repr(parent), name)
|
||||
if name.startswith('@'):
|
||||
return parent.get_attribute(name[1:])
|
||||
return parent.get_child(name)
|
||||
|
||||
|
||||
class NodeResolver(TreeResolver):
|
||||
"""
|
||||
The node resolver is a I{stateful} XML document resolver
|
||||
used to resolve each node in a tree. As such, it mirrors
|
||||
the tree structure to ensure that nodes are resolved in
|
||||
context.
|
||||
"""
|
||||
|
||||
def __init__(self, schema):
|
||||
"""
|
||||
@param schema: A schema object.
|
||||
@type schema: L{xsd.schema.Schema}
|
||||
"""
|
||||
TreeResolver.__init__(self, schema)
|
||||
|
||||
def find(self, node, resolved=False, push=True):
|
||||
"""
|
||||
@param node: An xml node to be resolved.
|
||||
@type node: L{sax.element.Element}
|
||||
@param resolved: A flag indicating that the fully resolved type should be
|
||||
returned.
|
||||
@type resolved: boolean
|
||||
@param push: Indicates that the resolved type should be
|
||||
pushed onto the stack.
|
||||
@type push: boolean
|
||||
@return: The found schema I{type}
|
||||
@rtype: L{xsd.sxbase.SchemaObject}
|
||||
"""
|
||||
name = node.name
|
||||
parent = self.top().resolved
|
||||
if parent is None:
|
||||
result, ancestry = self.query(name, node)
|
||||
else:
|
||||
result, ancestry = self.getchild(name, parent)
|
||||
known = self.known(node)
|
||||
if result is None:
|
||||
return result
|
||||
if push:
|
||||
frame = Frame(result, resolved=known, ancestry=ancestry)
|
||||
pushed = self.push(frame)
|
||||
if resolved:
|
||||
result = result.resolve()
|
||||
return result
|
||||
|
||||
def findattr(self, name, resolved=True):
|
||||
"""
|
||||
Find an attribute type definition.
|
||||
@param name: An attribute name.
|
||||
@type name: basestring
|
||||
@param resolved: A flag indicating that the fully resolved type should be
|
||||
returned.
|
||||
@type resolved: boolean
|
||||
@return: The found schema I{type}
|
||||
@rtype: L{xsd.sxbase.SchemaObject}
|
||||
"""
|
||||
name = '@%s'%name
|
||||
parent = self.top().resolved
|
||||
if parent is None:
|
||||
result, ancestry = self.query(name, node)
|
||||
else:
|
||||
result, ancestry = self.getchild(name, parent)
|
||||
if result is None:
|
||||
return result
|
||||
if resolved:
|
||||
result = result.resolve()
|
||||
return result
|
||||
|
||||
def query(self, name, node):
|
||||
"""Blindly query the schema by name."""
|
||||
log.debug('searching schema for (%s)', name)
|
||||
qref = qualify(name, node, node.namespace())
|
||||
query = BlindQuery(qref)
|
||||
result = query.execute(self.schema)
|
||||
return (result, [])
|
||||
|
||||
def known(self, node):
|
||||
"""Resolve type referenced by @xsi:type."""
|
||||
ref = node.get('type', Namespace.xsins)
|
||||
if ref is None:
|
||||
return None
|
||||
qref = qualify(ref, node, node.namespace())
|
||||
query = BlindQuery(qref)
|
||||
return query.execute(self.schema)
|
||||
|
||||
|
||||
class GraphResolver(TreeResolver):
|
||||
"""
|
||||
The graph resolver is a I{stateful} L{Object} graph resolver
|
||||
used to resolve each node in a tree. As such, it mirrors
|
||||
the tree structure to ensure that nodes are resolved in
|
||||
context.
|
||||
"""
|
||||
|
||||
def __init__(self, schema):
|
||||
"""
|
||||
@param schema: A schema object.
|
||||
@type schema: L{xsd.schema.Schema}
|
||||
"""
|
||||
TreeResolver.__init__(self, schema)
|
||||
|
||||
def find(self, name, object, resolved=False, push=True):
|
||||
"""
|
||||
@param name: The name of the object to be resolved.
|
||||
@type name: basestring
|
||||
@param object: The name's value.
|
||||
@type object: (any|L{Object})
|
||||
@param resolved: A flag indicating that the fully resolved type
|
||||
should be returned.
|
||||
@type resolved: boolean
|
||||
@param push: Indicates that the resolved type should be
|
||||
pushed onto the stack.
|
||||
@type push: boolean
|
||||
@return: The found schema I{type}
|
||||
@rtype: L{xsd.sxbase.SchemaObject}
|
||||
"""
|
||||
known = None
|
||||
parent = self.top().resolved
|
||||
if parent is None:
|
||||
result, ancestry = self.query(name)
|
||||
else:
|
||||
result, ancestry = self.getchild(name, parent)
|
||||
if result is None:
|
||||
return None
|
||||
if isinstance(object, Object):
|
||||
known = self.known(object)
|
||||
if push:
|
||||
frame = Frame(result, resolved=known, ancestry=ancestry)
|
||||
pushed = self.push(frame)
|
||||
if resolved:
|
||||
if known is None:
|
||||
result = result.resolve()
|
||||
else:
|
||||
result = known
|
||||
return result
|
||||
|
||||
def query(self, name):
|
||||
"""Blindly query the schema by name."""
|
||||
log.debug('searching schema for (%s)', name)
|
||||
schema = self.schema
|
||||
wsdl = self.wsdl()
|
||||
if wsdl is None:
|
||||
qref = qualify(name, schema.root, schema.tns)
|
||||
else:
|
||||
qref = qualify(name, wsdl.root, wsdl.tns)
|
||||
query = BlindQuery(qref)
|
||||
result = query.execute(schema)
|
||||
return (result, [])
|
||||
|
||||
def wsdl(self):
|
||||
"""Get the wsdl."""
|
||||
container = self.schema.container
|
||||
if container is None:
|
||||
return None
|
||||
else:
|
||||
return container.wsdl
|
||||
|
||||
def known(self, object):
|
||||
"""Get the type specified in the object's metadata."""
|
||||
try:
|
||||
md = object.__metadata__
|
||||
known = md.sxtype
|
||||
return known
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class Frame:
|
||||
def __init__(self, type, resolved=None, ancestry=()):
|
||||
self.type = type
|
||||
if resolved is None:
|
||||
resolved = type.resolve()
|
||||
self.resolved = resolved.resolve()
|
||||
self.ancestry = ancestry
|
||||
|
||||
def __str__(self):
|
||||
return '%s\n%s\n%s' % \
|
||||
(Repr(self.type),
|
||||
Repr(self.resolved),
|
||||
[Repr(t) for t in self.ancestry])
|
||||
|
||||
class Empty:
|
||||
def __getattr__(self, name):
|
||||
if name == 'ancestry':
|
||||
return ()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class Stack(list):
|
||||
def __repr__(self):
|
||||
result = []
|
||||
for item in self:
|
||||
result.append(repr(item))
|
||||
return '\n'.join(result)
|
@ -1,104 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The sax module contains a collection of classes that provide a (D)ocument
|
||||
(O)bject (M)odel representation of an XML document. The goal is to provide an
|
||||
easy, intuitive interface for managing XML documents. Although the term DOM is
|
||||
used here, this model is B{far} better.
|
||||
|
||||
XML namespaces in suds are represented using a (2) element tuple containing the
|
||||
prefix and the URI, e.g. I{('tns', 'http://myns')}
|
||||
|
||||
@var encoder: A I{pluggable} XML special character processor used to encode/
|
||||
decode strings.
|
||||
@type encoder: L{Encoder}
|
||||
|
||||
"""
|
||||
|
||||
from suds.sax.enc import Encoder
|
||||
|
||||
# pluggable XML special character encoder.
|
||||
encoder = Encoder()
|
||||
|
||||
|
||||
def splitPrefix(name):
|
||||
"""
|
||||
Split the name into a tuple (I{prefix}, I{name}). The first element in the
|
||||
tuple is I{None} when the name does not have a prefix.
|
||||
|
||||
@param name: A node name containing an optional prefix.
|
||||
@type name: basestring
|
||||
@return: A tuple containing the (2) parts of I{name}.
|
||||
@rtype: (I{prefix}, I{name})
|
||||
|
||||
"""
|
||||
if isinstance(name, basestring) and ":" in name:
|
||||
return tuple(name.split(":", 1))
|
||||
return None, name
|
||||
|
||||
|
||||
class Namespace:
|
||||
"""XML namespace."""
|
||||
|
||||
default = (None, None)
|
||||
xmlns = ("xml", "http://www.w3.org/XML/1998/namespace")
|
||||
xsdns = ("xs", "http://www.w3.org/2001/XMLSchema")
|
||||
xsins = ("xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
all = (xsdns, xsins)
|
||||
|
||||
@classmethod
|
||||
def create(cls, p=None, u=None):
|
||||
return p, u
|
||||
|
||||
@classmethod
|
||||
def none(cls, ns):
|
||||
return ns == cls.default
|
||||
|
||||
@classmethod
|
||||
def xsd(cls, ns):
|
||||
try:
|
||||
return cls.w3(ns) and ns[1].endswith("XMLSchema")
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def xsi(cls, ns):
|
||||
try:
|
||||
return cls.w3(ns) and ns[1].endswith("XMLSchema-instance")
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def xs(cls, ns):
|
||||
return cls.xsd(ns) or cls.xsi(ns)
|
||||
|
||||
@classmethod
|
||||
def w3(cls, ns):
|
||||
try:
|
||||
return ns[1].startswith("http://www.w3.org")
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def isns(cls, ns):
|
||||
try:
|
||||
return isinstance(ns, tuple) and len(ns) == len(cls.default)
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
@ -1,173 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides XML I{attribute} classes.
|
||||
|
||||
"""
|
||||
|
||||
from suds import UnicodeMixin
|
||||
from suds.sax import splitPrefix, Namespace
|
||||
from suds.sax.text import Text
|
||||
|
||||
|
||||
class Attribute(UnicodeMixin):
|
||||
"""
|
||||
An XML attribute object.
|
||||
|
||||
@ivar parent: The node containing this attribute.
|
||||
@type parent: L{element.Element}
|
||||
@ivar prefix: The I{optional} namespace prefix.
|
||||
@type prefix: basestring
|
||||
@ivar name: The I{unqualified} attribute name.
|
||||
@type name: basestring
|
||||
@ivar value: The attribute's value.
|
||||
@type value: basestring
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name, value=None):
|
||||
"""
|
||||
@param name: The attribute's name with I{optional} namespace prefix.
|
||||
@type name: basestring
|
||||
@param value: The attribute's value.
|
||||
@type value: basestring
|
||||
|
||||
"""
|
||||
self.parent = None
|
||||
self.prefix, self.name = splitPrefix(name)
|
||||
self.setValue(value)
|
||||
|
||||
def clone(self, parent=None):
|
||||
"""
|
||||
Clone this object.
|
||||
|
||||
@param parent: The parent for the clone.
|
||||
@type parent: L{element.Element}
|
||||
@return: A copy of this object assigned to the new parent.
|
||||
@rtype: L{Attribute}
|
||||
|
||||
"""
|
||||
a = Attribute(self.qname(), self.value)
|
||||
a.parent = parent
|
||||
return a
|
||||
|
||||
def qname(self):
|
||||
"""
|
||||
Get this attribute's B{fully} qualified name.
|
||||
|
||||
@return: The fully qualified name.
|
||||
@rtype: basestring
|
||||
|
||||
"""
|
||||
if self.prefix is None:
|
||||
return self.name
|
||||
return ":".join((self.prefix, self.name))
|
||||
|
||||
def setValue(self, value):
|
||||
"""
|
||||
Set the attribute's value.
|
||||
|
||||
@param value: The new value (may be None).
|
||||
@type value: basestring
|
||||
@return: self
|
||||
@rtype: L{Attribute}
|
||||
|
||||
"""
|
||||
if isinstance(value, Text):
|
||||
self.value = value
|
||||
else:
|
||||
self.value = Text(value)
|
||||
return self
|
||||
|
||||
def getValue(self, default=Text("")):
|
||||
"""
|
||||
Get the attributes value with optional default.
|
||||
|
||||
@param default: An optional value to return when the attribute's value
|
||||
has not been set.
|
||||
@type default: basestring
|
||||
@return: The attribute's value, or I{default}.
|
||||
@rtype: L{Text}
|
||||
|
||||
"""
|
||||
return self.value or default
|
||||
|
||||
def hasText(self):
|
||||
"""
|
||||
Get whether the attribute has a non-empty I{text} string value.
|
||||
|
||||
@return: True when has I{text}.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return bool(self.value)
|
||||
|
||||
def namespace(self):
|
||||
"""
|
||||
Get the attribute's namespace. This may either be the namespace defined
|
||||
by an optional prefix, or the default namespace.
|
||||
|
||||
@return: The attribute's namespace.
|
||||
@rtype: (I{prefix}, I{name})
|
||||
|
||||
"""
|
||||
if self.prefix is None:
|
||||
return Namespace.default
|
||||
return self.resolvePrefix(self.prefix)
|
||||
|
||||
def resolvePrefix(self, prefix):
|
||||
"""
|
||||
Resolve the specified prefix to a known namespace.
|
||||
|
||||
@param prefix: A declared prefix.
|
||||
@type prefix: basestring
|
||||
@return: The namespace mapped to I{prefix}.
|
||||
@rtype: (I{prefix}, I{name})
|
||||
|
||||
"""
|
||||
if self.parent is None:
|
||||
return Namespace.default
|
||||
return self.parent.resolvePrefix(prefix)
|
||||
|
||||
def match(self, name=None, ns=None):
|
||||
"""
|
||||
Match by (optional) name and/or (optional) namespace.
|
||||
|
||||
@param name: The optional attribute tag name.
|
||||
@type name: str
|
||||
@param ns: An optional namespace.
|
||||
@type ns: (I{prefix}, I{name})
|
||||
@return: True if matched.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
byname = name is None or (self.name == name)
|
||||
byns = ns is None or (self.namespace()[1] == ns[1])
|
||||
return byname and byns
|
||||
|
||||
def __eq__(self, rhs):
|
||||
"""Equals operator."""
|
||||
return (isinstance(rhs, Attribute) and self.prefix == rhs.name and
|
||||
self.name == rhs.name)
|
||||
|
||||
def __repr__(self):
|
||||
"""Programmer friendly string representation."""
|
||||
return "attr (prefix=%s, name=%s, value=(%s))" % (self.prefix,
|
||||
self.name, self.value)
|
||||
|
||||
def __unicode__(self):
|
||||
"""XML string representation."""
|
||||
return u'%s="%s"' % (self.qname(), self.value and self.value.escape())
|
@ -1,460 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr )
|
||||
# based on code by: Glen Walker
|
||||
# based on code by: Nathan Van Gheem ( vangheem@gmail.com )
|
||||
|
||||
"""Classes for conversion between XML dates and Python objects."""
|
||||
|
||||
from suds import UnicodeMixin
|
||||
|
||||
import datetime
|
||||
import re
|
||||
import time
|
||||
|
||||
|
||||
_SNIPPET_DATE = \
|
||||
r"(?P<year>\d{1,})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
|
||||
_SNIPPET_TIME = \
|
||||
r"(?P<hour>\d{1,2}):(?P<minute>[0-5]?[0-9]):(?P<second>[0-5]?[0-9])" \
|
||||
r"(?:\.(?P<subsecond>\d+))?"
|
||||
_SNIPPET_ZONE = \
|
||||
r"(?:(?P<tz_sign>[-+])(?P<tz_hour>\d{1,2})" \
|
||||
r"(?::(?P<tz_minute>[0-5]?[0-9]))?)" \
|
||||
r"|(?P<tz_utc>[Zz])"
|
||||
|
||||
_PATTERN_DATE = r"^%s(?:%s)?$" % (_SNIPPET_DATE, _SNIPPET_ZONE)
|
||||
_PATTERN_TIME = r"^%s(?:%s)?$" % (_SNIPPET_TIME, _SNIPPET_ZONE)
|
||||
_PATTERN_DATETIME = r"^%s[T ]%s(?:%s)?$" % (_SNIPPET_DATE, _SNIPPET_TIME,
|
||||
_SNIPPET_ZONE)
|
||||
|
||||
_RE_DATE = re.compile(_PATTERN_DATE)
|
||||
_RE_TIME = re.compile(_PATTERN_TIME)
|
||||
_RE_DATETIME = re.compile(_PATTERN_DATETIME)
|
||||
|
||||
|
||||
class Date(UnicodeMixin):
|
||||
"""
|
||||
An XML date object supporting the xsd:date datatype.
|
||||
|
||||
@ivar value: The object value.
|
||||
@type value: B{datetime}.I{date}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, value):
|
||||
"""
|
||||
@param value: The date value of the object.
|
||||
@type value: (datetime.date|str)
|
||||
@raise ValueError: When I{value} is invalid.
|
||||
|
||||
"""
|
||||
if isinstance(value, datetime.datetime):
|
||||
self.value = value.date()
|
||||
elif isinstance(value, datetime.date):
|
||||
self.value = value
|
||||
elif isinstance(value, basestring):
|
||||
self.value = self.__parse(value)
|
||||
else:
|
||||
raise ValueError("invalid type for Date(): %s" % type(value))
|
||||
|
||||
@staticmethod
|
||||
def __parse(value):
|
||||
"""
|
||||
Parse the string date.
|
||||
|
||||
Supports the subset of ISO8601 used by xsd:date, but is lenient with
|
||||
what is accepted, handling most reasonable syntax.
|
||||
|
||||
Any timezone is parsed but ignored because a) it is meaningless without
|
||||
a time and b) B{datetime}.I{date} does not support timezone
|
||||
information.
|
||||
|
||||
@param value: A date string.
|
||||
@type value: str
|
||||
@return: A date object.
|
||||
@rtype: B{datetime}.I{date}
|
||||
|
||||
"""
|
||||
match_result = _RE_DATE.match(value)
|
||||
if match_result is None:
|
||||
raise ValueError("date data has invalid format '%s'" % (value,))
|
||||
return _date_from_match(match_result)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.value.isoformat()
|
||||
|
||||
|
||||
class DateTime(UnicodeMixin):
|
||||
"""
|
||||
An XML datetime object supporting the xsd:dateTime datatype.
|
||||
|
||||
@ivar value: The object value.
|
||||
@type value: B{datetime}.I{datetime}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, value):
|
||||
"""
|
||||
@param value: The datetime value of the object.
|
||||
@type value: (datetime.datetime|str)
|
||||
@raise ValueError: When I{value} is invalid.
|
||||
|
||||
"""
|
||||
if isinstance(value, datetime.datetime):
|
||||
self.value = value
|
||||
elif isinstance(value, basestring):
|
||||
self.value = self.__parse(value)
|
||||
else:
|
||||
raise ValueError("invalid type for DateTime(): %s" % type(value))
|
||||
|
||||
@staticmethod
|
||||
def __parse(value):
|
||||
"""
|
||||
Parse the string datetime.
|
||||
|
||||
Supports the subset of ISO8601 used by xsd:dateTime, but is lenient
|
||||
with what is accepted, handling most reasonable syntax.
|
||||
|
||||
Subsecond information is rounded to microseconds due to a restriction
|
||||
in the python datetime.datetime/time implementation.
|
||||
|
||||
@param value: A datetime string.
|
||||
@type value: str
|
||||
@return: A datetime object.
|
||||
@rtype: B{datetime}.I{datetime}
|
||||
|
||||
"""
|
||||
match_result = _RE_DATETIME.match(value)
|
||||
if match_result is None:
|
||||
raise ValueError("date data has invalid format '%s'" % (value,))
|
||||
|
||||
date = _date_from_match(match_result)
|
||||
time, round_up = _time_from_match(match_result)
|
||||
tzinfo = _tzinfo_from_match(match_result)
|
||||
|
||||
value = datetime.datetime.combine(date, time)
|
||||
value = value.replace(tzinfo=tzinfo)
|
||||
if round_up:
|
||||
value += datetime.timedelta(microseconds=1)
|
||||
return value
|
||||
|
||||
def __unicode__(self):
|
||||
return self.value.isoformat()
|
||||
|
||||
|
||||
class Time(UnicodeMixin):
|
||||
"""
|
||||
An XML time object supporting the xsd:time datatype.
|
||||
|
||||
@ivar value: The object value.
|
||||
@type value: B{datetime}.I{time}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, value):
|
||||
"""
|
||||
@param value: The time value of the object.
|
||||
@type value: (datetime.time|str)
|
||||
@raise ValueError: When I{value} is invalid.
|
||||
|
||||
"""
|
||||
if isinstance(value, datetime.time):
|
||||
self.value = value
|
||||
elif isinstance(value, basestring):
|
||||
self.value = self.__parse(value)
|
||||
else:
|
||||
raise ValueError("invalid type for Time(): %s" % type(value))
|
||||
|
||||
@staticmethod
|
||||
def __parse(value):
|
||||
"""
|
||||
Parse the string date.
|
||||
|
||||
Supports the subset of ISO8601 used by xsd:time, but is lenient with
|
||||
what is accepted, handling most reasonable syntax.
|
||||
|
||||
Subsecond information is rounded to microseconds due to a restriction
|
||||
in the python datetime.time implementation.
|
||||
|
||||
@param value: A time string.
|
||||
@type value: str
|
||||
@return: A time object.
|
||||
@rtype: B{datetime}.I{time}
|
||||
|
||||
"""
|
||||
match_result = _RE_TIME.match(value)
|
||||
if match_result is None:
|
||||
raise ValueError("date data has invalid format '%s'" % (value,))
|
||||
|
||||
time, round_up = _time_from_match(match_result)
|
||||
tzinfo = _tzinfo_from_match(match_result)
|
||||
if round_up:
|
||||
time = _bump_up_time_by_microsecond(time)
|
||||
return time.replace(tzinfo=tzinfo)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.value.isoformat()
|
||||
|
||||
|
||||
class FixedOffsetTimezone(datetime.tzinfo, UnicodeMixin):
|
||||
"""
|
||||
A timezone with a fixed offset and no daylight savings adjustment.
|
||||
|
||||
http://docs.python.org/library/datetime.html#datetime.tzinfo
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, offset):
|
||||
"""
|
||||
@param offset: The fixed offset of the timezone.
|
||||
@type offset: I{int} or B{datetime}.I{timedelta}
|
||||
|
||||
"""
|
||||
if type(offset) == int:
|
||||
offset = datetime.timedelta(hours=offset)
|
||||
elif type(offset) != datetime.timedelta:
|
||||
raise TypeError("timezone offset must be an int or "
|
||||
"datetime.timedelta")
|
||||
if offset.microseconds or (offset.seconds % 60 != 0):
|
||||
raise ValueError("timezone offset must have minute precision")
|
||||
self.__offset = offset
|
||||
|
||||
def dst(self, dt):
|
||||
"""
|
||||
http://docs.python.org/library/datetime.html#datetime.tzinfo.dst
|
||||
|
||||
"""
|
||||
return datetime.timedelta(0)
|
||||
|
||||
def utcoffset(self, dt):
|
||||
"""
|
||||
http://docs.python.org/library/datetime.html#datetime.tzinfo.utcoffset
|
||||
|
||||
"""
|
||||
return self.__offset
|
||||
|
||||
def tzname(self, dt):
|
||||
"""
|
||||
http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname
|
||||
|
||||
"""
|
||||
# total_seconds was introduced in Python 2.7
|
||||
if hasattr(self.__offset, "total_seconds"):
|
||||
total_seconds = self.__offset.total_seconds()
|
||||
else:
|
||||
total_seconds = (self.__offset.days * 24 * 60 * 60) + \
|
||||
(self.__offset.seconds)
|
||||
|
||||
hours = total_seconds // (60 * 60)
|
||||
total_seconds -= hours * 60 * 60
|
||||
|
||||
minutes = total_seconds // 60
|
||||
total_seconds -= minutes * 60
|
||||
|
||||
seconds = total_seconds // 1
|
||||
total_seconds -= seconds
|
||||
|
||||
if seconds:
|
||||
return "%+03d:%02d:%02d" % (hours, minutes, seconds)
|
||||
return "%+03d:%02d" % (hours, minutes)
|
||||
|
||||
def __unicode__(self):
|
||||
return "FixedOffsetTimezone %s" % (self.tzname(None),)
|
||||
|
||||
|
||||
class UtcTimezone(FixedOffsetTimezone):
|
||||
"""
|
||||
The UTC timezone.
|
||||
|
||||
http://docs.python.org/library/datetime.html#datetime.tzinfo
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
FixedOffsetTimezone.__init__(self, datetime.timedelta(0))
|
||||
|
||||
def tzname(self, dt):
|
||||
"""
|
||||
http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname
|
||||
|
||||
"""
|
||||
return "UTC"
|
||||
|
||||
def __unicode__(self):
|
||||
return "UtcTimezone"
|
||||
|
||||
|
||||
class LocalTimezone(datetime.tzinfo):
|
||||
"""
|
||||
The local timezone of the operating system.
|
||||
|
||||
http://docs.python.org/library/datetime.html#datetime.tzinfo
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.__offset = datetime.timedelta(seconds=-time.timezone)
|
||||
self.__dst_offset = None
|
||||
if time.daylight:
|
||||
self.__dst_offset = datetime.timedelta(seconds=-time.altzone)
|
||||
|
||||
def dst(self, dt):
|
||||
"""
|
||||
http://docs.python.org/library/datetime.html#datetime.tzinfo.dst
|
||||
|
||||
"""
|
||||
if self.__is_daylight_time(dt):
|
||||
return self.__dst_offset - self.__offset
|
||||
return datetime.timedelta(0)
|
||||
|
||||
def tzname(self, dt):
|
||||
"""
|
||||
http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname
|
||||
|
||||
"""
|
||||
if self.__is_daylight_time(dt):
|
||||
return time.tzname[1]
|
||||
return time.tzname[0]
|
||||
|
||||
def utcoffset(self, dt):
|
||||
"""
|
||||
http://docs.python.org/library/datetime.html#datetime.tzinfo.utcoffset
|
||||
|
||||
"""
|
||||
if self.__is_daylight_time(dt):
|
||||
return self.__dst_offset
|
||||
return self.__offset
|
||||
|
||||
def __is_daylight_time(self, dt):
|
||||
if not time.daylight:
|
||||
return False
|
||||
time_tuple = dt.replace(tzinfo=None).timetuple()
|
||||
time_tuple = time.localtime(time.mktime(time_tuple))
|
||||
return time_tuple.tm_isdst > 0
|
||||
|
||||
def __unicode__(self):
|
||||
dt = datetime.datetime.now()
|
||||
return "LocalTimezone %s offset: %s dst: %s" % (self.tzname(dt),
|
||||
self.utcoffset(dt), self.dst(dt))
|
||||
|
||||
|
||||
def _bump_up_time_by_microsecond(time):
|
||||
"""
|
||||
Helper function bumping up the given datetime.time by a microsecond,
|
||||
cycling around silently to 00:00:00.0 in case of an overflow.
|
||||
|
||||
@param time: Time object.
|
||||
@type time: B{datetime}.I{time}
|
||||
@return: Time object.
|
||||
@rtype: B{datetime}.I{time}
|
||||
|
||||
"""
|
||||
dt = datetime.datetime(2000, 1, 1, time.hour, time.minute,
|
||||
time.second, time.microsecond)
|
||||
dt += datetime.timedelta(microseconds=1)
|
||||
return dt.time()
|
||||
|
||||
|
||||
def _date_from_match(match_object):
|
||||
"""
|
||||
Create a date object from a regular expression match.
|
||||
|
||||
The regular expression match is expected to be from _RE_DATE or
|
||||
_RE_DATETIME.
|
||||
|
||||
@param match_object: The regular expression match.
|
||||
@type match_object: B{re}.I{MatchObject}
|
||||
@return: A date object.
|
||||
@rtype: B{datetime}.I{date}
|
||||
|
||||
"""
|
||||
year = int(match_object.group("year"))
|
||||
month = int(match_object.group("month"))
|
||||
day = int(match_object.group("day"))
|
||||
return datetime.date(year, month, day)
|
||||
|
||||
|
||||
def _time_from_match(match_object):
|
||||
"""
|
||||
Create a time object from a regular expression match.
|
||||
|
||||
Returns the time object and information whether the resulting time should
|
||||
be bumped up by one microsecond due to microsecond rounding.
|
||||
|
||||
Subsecond information is rounded to microseconds due to a restriction in
|
||||
the python datetime.datetime/time implementation.
|
||||
|
||||
The regular expression match is expected to be from _RE_DATETIME or
|
||||
_RE_TIME.
|
||||
|
||||
@param match_object: The regular expression match.
|
||||
@type match_object: B{re}.I{MatchObject}
|
||||
@return: Time object + rounding flag.
|
||||
@rtype: tuple of B{datetime}.I{time} and bool
|
||||
|
||||
"""
|
||||
hour = int(match_object.group('hour'))
|
||||
minute = int(match_object.group('minute'))
|
||||
second = int(match_object.group('second'))
|
||||
subsecond = match_object.group('subsecond')
|
||||
|
||||
round_up = False
|
||||
microsecond = 0
|
||||
if subsecond:
|
||||
round_up = len(subsecond) > 6 and int(subsecond[6]) >= 5
|
||||
subsecond = subsecond[:6]
|
||||
microsecond = int(subsecond + "0" * (6 - len(subsecond)))
|
||||
return datetime.time(hour, minute, second, microsecond), round_up
|
||||
|
||||
|
||||
def _tzinfo_from_match(match_object):
|
||||
"""
|
||||
Create a timezone information object from a regular expression match.
|
||||
|
||||
The regular expression match is expected to be from _RE_DATE, _RE_DATETIME
|
||||
or _RE_TIME.
|
||||
|
||||
@param match_object: The regular expression match.
|
||||
@type match_object: B{re}.I{MatchObject}
|
||||
@return: A timezone information object.
|
||||
@rtype: B{datetime}.I{tzinfo}
|
||||
|
||||
"""
|
||||
tz_utc = match_object.group("tz_utc")
|
||||
if tz_utc:
|
||||
return UtcTimezone()
|
||||
|
||||
tz_sign = match_object.group("tz_sign")
|
||||
if not tz_sign:
|
||||
return
|
||||
|
||||
h = int(match_object.group("tz_hour") or 0)
|
||||
m = int(match_object.group("tz_minute") or 0)
|
||||
if h == 0 and m == 0:
|
||||
return UtcTimezone()
|
||||
|
||||
# Python limitation - timezone offsets larger than one day (in absolute)
|
||||
# will cause operations depending on tzinfo.utcoffset() to fail, e.g.
|
||||
# comparing two timezone aware datetime.datetime/time objects.
|
||||
if h >= 24:
|
||||
raise ValueError("timezone indicator too large")
|
||||
|
||||
tz_delta = datetime.timedelta(hours=h, minutes=m)
|
||||
if tz_sign == "-":
|
||||
tz_delta *= -1
|
||||
return FixedOffsetTimezone(tz_delta)
|
@ -1,176 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides XML I{document} classes.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.sax import *
|
||||
from suds.sax.element import Element
|
||||
|
||||
|
||||
class Document(UnicodeMixin):
|
||||
""" An XML Document """
|
||||
|
||||
DECL = '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
|
||||
def __init__(self, root=None):
|
||||
"""
|
||||
@param root: A root L{Element} or name used to build
|
||||
the document root element.
|
||||
@type root: (L{Element}|str|None)
|
||||
"""
|
||||
self.__root = None
|
||||
self.append(root)
|
||||
|
||||
def root(self):
|
||||
"""
|
||||
Get the document root element (can be None)
|
||||
@return: The document root.
|
||||
@rtype: L{Element}
|
||||
"""
|
||||
return self.__root
|
||||
|
||||
def append(self, node):
|
||||
"""
|
||||
Append (set) the document root.
|
||||
@param node: A root L{Element} or name used to build
|
||||
the document root element.
|
||||
@type node: (L{Element}|str|None)
|
||||
"""
|
||||
if isinstance(node, basestring):
|
||||
self.__root = Element(node)
|
||||
return
|
||||
if isinstance(node, Element):
|
||||
self.__root = node
|
||||
return
|
||||
|
||||
def getChild(self, name, ns=None, default=None):
|
||||
"""
|
||||
Get a child by (optional) name and/or (optional) namespace.
|
||||
@param name: The name of a child element (may contain prefix).
|
||||
@type name: basestring
|
||||
@param ns: An optional namespace used to match the child.
|
||||
@type ns: (I{prefix}, I{name})
|
||||
@param default: Returned when child not-found.
|
||||
@type default: L{Element}
|
||||
@return: The requested child, or I{default} when not-found.
|
||||
@rtype: L{Element}
|
||||
"""
|
||||
if self.__root is None:
|
||||
return default
|
||||
if ns is None:
|
||||
prefix, name = splitPrefix(name)
|
||||
if prefix is None:
|
||||
ns = None
|
||||
else:
|
||||
ns = self.__root.resolvePrefix(prefix)
|
||||
if self.__root.match(name, ns):
|
||||
return self.__root
|
||||
else:
|
||||
return default
|
||||
|
||||
def childAtPath(self, path):
|
||||
"""
|
||||
Get a child at I{path} where I{path} is a (/) separated
|
||||
list of element names that are expected to be children.
|
||||
@param path: A (/) separated list of element names.
|
||||
@type path: basestring
|
||||
@return: The leaf node at the end of I{path}
|
||||
@rtype: L{Element}
|
||||
"""
|
||||
if self.__root is None:
|
||||
return None
|
||||
if path[0] == '/':
|
||||
path = path[1:]
|
||||
path = path.split('/',1)
|
||||
if self.getChild(path[0]) is None:
|
||||
return None
|
||||
if len(path) > 1:
|
||||
return self.__root.childAtPath(path[1])
|
||||
else:
|
||||
return self.__root
|
||||
|
||||
def childrenAtPath(self, path):
|
||||
"""
|
||||
Get a list of children at I{path} where I{path} is a (/) separated
|
||||
list of element names that are expected to be children.
|
||||
@param path: A (/) separated list of element names.
|
||||
@type path: basestring
|
||||
@return: The collection leaf nodes at the end of I{path}
|
||||
@rtype: [L{Element},...]
|
||||
"""
|
||||
if self.__root is None:
|
||||
return []
|
||||
if path[0] == '/':
|
||||
path = path[1:]
|
||||
path = path.split('/',1)
|
||||
if self.getChild(path[0]) is None:
|
||||
return []
|
||||
if len(path) > 1:
|
||||
return self.__root.childrenAtPath(path[1])
|
||||
else:
|
||||
return [self.__root,]
|
||||
|
||||
def getChildren(self, name=None, ns=None):
|
||||
"""
|
||||
Get a list of children by (optional) name and/or (optional) namespace.
|
||||
@param name: The name of a child element (may contain prefix).
|
||||
@type name: basestring
|
||||
@param ns: An optional namespace used to match the child.
|
||||
@type ns: (I{prefix}, I{name})
|
||||
@return: The list of matching children.
|
||||
@rtype: [L{Element},...]
|
||||
"""
|
||||
if name is None:
|
||||
matched = self.__root
|
||||
else:
|
||||
matched = self.getChild(name, ns)
|
||||
if matched is None:
|
||||
return []
|
||||
else:
|
||||
return [matched,]
|
||||
|
||||
def str(self):
|
||||
"""
|
||||
Get a string representation of this XML document.
|
||||
@return: A I{pretty} string.
|
||||
@rtype: basestring
|
||||
"""
|
||||
s = []
|
||||
s.append(self.DECL)
|
||||
root = self.root()
|
||||
if root is not None:
|
||||
s.append('\n')
|
||||
s.append(root.str())
|
||||
return ''.join(s)
|
||||
|
||||
def plain(self):
|
||||
"""
|
||||
Get a string representation of this XML document.
|
||||
@return: A I{plain} string.
|
||||
@rtype: basestring
|
||||
"""
|
||||
s = []
|
||||
s.append(self.DECL)
|
||||
root = self.root()
|
||||
if root is not None:
|
||||
s.append(root.plain())
|
||||
return ''.join(s)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.str()
|
File diff suppressed because it is too large
Load Diff
@ -1,94 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides XML I{special character} encoder classes.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class Encoder:
|
||||
"""
|
||||
An XML special character encoder/decoder.
|
||||
|
||||
@cvar encodings: A mapping of special characters encoding.
|
||||
@type encodings: [(str, str),...]
|
||||
@cvar decodings: A mapping of special characters decoding.
|
||||
@type decodings: [(str, str),...]
|
||||
@cvar special: A list of special characters.
|
||||
@type special: [char,...]
|
||||
|
||||
"""
|
||||
|
||||
encodings = (
|
||||
("&(?!(amp|lt|gt|quot|apos);)", "&"),
|
||||
("<", "<"),
|
||||
(">", ">"),
|
||||
('"', """),
|
||||
("'", "'"))
|
||||
decodings = (
|
||||
("<", "<"),
|
||||
(">", ">"),
|
||||
(""", '"'),
|
||||
("'", "'"),
|
||||
("&", "&"))
|
||||
special = ("&", "<", ">", '"', "'")
|
||||
|
||||
def encode(self, s):
|
||||
"""
|
||||
Encode special characters found in string I{s}.
|
||||
|
||||
@param s: A string to encode.
|
||||
@type s: str
|
||||
@return: The encoded string.
|
||||
@rtype: str
|
||||
|
||||
"""
|
||||
if isinstance(s, basestring) and self.__needs_encoding(s):
|
||||
for x in self.encodings:
|
||||
s = re.sub(x[0], x[1], s)
|
||||
return s
|
||||
|
||||
def decode(self, s):
|
||||
"""
|
||||
Decode special characters encodings found in string I{s}.
|
||||
|
||||
@param s: A string to decode.
|
||||
@type s: str
|
||||
@return: The decoded string.
|
||||
@rtype: str
|
||||
|
||||
"""
|
||||
if isinstance(s, basestring) and "&" in s:
|
||||
for x in self.decodings:
|
||||
s = s.replace(x[0], x[1])
|
||||
return s
|
||||
|
||||
def __needs_encoding(self, s):
|
||||
"""
|
||||
Get whether string I{s} contains special characters.
|
||||
|
||||
@param s: A string to check.
|
||||
@type s: str
|
||||
@return: True if needs encoding.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
if isinstance(s, basestring):
|
||||
for c in self.special:
|
||||
if c in s:
|
||||
return True
|
@ -1,137 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Classes providing a (D)ocument (O)bject (M)odel representation of an XML
|
||||
document.
|
||||
|
||||
The goal is to provide an easy, intuitive interface for managing XML documents.
|
||||
Although the term DOM is used above, this model is B{far} better.
|
||||
|
||||
XML namespaces in suds are represented using a (2) element tuple containing the
|
||||
prefix and the URI, e.g. I{('tns', 'http://myns')}.
|
||||
|
||||
"""
|
||||
|
||||
import suds
|
||||
from suds import *
|
||||
from suds.sax import *
|
||||
from suds.sax.attribute import Attribute
|
||||
from suds.sax.document import Document
|
||||
from suds.sax.element import Element
|
||||
from suds.sax.text import Text
|
||||
|
||||
import sys
|
||||
from xml.sax import make_parser, InputSource, ContentHandler
|
||||
from xml.sax.handler import feature_external_ges
|
||||
|
||||
|
||||
class Handler(ContentHandler):
|
||||
"""SAX handler."""
|
||||
|
||||
def __init__(self):
|
||||
self.nodes = [Document()]
|
||||
|
||||
def startElement(self, name, attrs):
|
||||
top = self.top()
|
||||
node = Element(unicode(name))
|
||||
for a in attrs.getNames():
|
||||
n = unicode(a)
|
||||
v = unicode(attrs.getValue(a))
|
||||
attribute = Attribute(n, v)
|
||||
if self.mapPrefix(node, attribute):
|
||||
continue
|
||||
node.append(attribute)
|
||||
node.charbuffer = []
|
||||
top.append(node)
|
||||
self.push(node)
|
||||
|
||||
def mapPrefix(self, node, attribute):
|
||||
if attribute.name == "xmlns":
|
||||
if len(attribute.value):
|
||||
node.expns = unicode(attribute.value)
|
||||
return True
|
||||
if attribute.prefix == "xmlns":
|
||||
prefix = attribute.name
|
||||
node.nsprefixes[prefix] = unicode(attribute.value)
|
||||
return True
|
||||
return False
|
||||
|
||||
def endElement(self, name):
|
||||
name = unicode(name)
|
||||
current = self.pop()
|
||||
if name != current.qname():
|
||||
raise Exception("malformed document")
|
||||
if current.charbuffer:
|
||||
current.text = Text(u"".join(current.charbuffer))
|
||||
del current.charbuffer
|
||||
if current:
|
||||
current.trim()
|
||||
|
||||
def characters(self, content):
|
||||
text = unicode(content)
|
||||
node = self.top()
|
||||
node.charbuffer.append(text)
|
||||
|
||||
def push(self, node):
|
||||
self.nodes.append(node)
|
||||
return node
|
||||
|
||||
def pop(self):
|
||||
return self.nodes.pop()
|
||||
|
||||
def top(self):
|
||||
return self.nodes[-1]
|
||||
|
||||
|
||||
class Parser:
|
||||
"""SAX parser."""
|
||||
|
||||
@classmethod
|
||||
def saxparser(cls):
|
||||
p = make_parser()
|
||||
p.setFeature(feature_external_ges, 0)
|
||||
h = Handler()
|
||||
p.setContentHandler(h)
|
||||
return p, h
|
||||
|
||||
def parse(self, file=None, string=None):
|
||||
"""
|
||||
SAX parse XML text.
|
||||
|
||||
@param file: Parse a python I{file-like} object.
|
||||
@type file: I{file-like} object
|
||||
@param string: Parse string XML.
|
||||
@type string: str
|
||||
@return: Parsed XML document.
|
||||
@rtype: L{Document}
|
||||
|
||||
"""
|
||||
if file is None and string is None:
|
||||
return
|
||||
timer = suds.metrics.Timer()
|
||||
timer.start()
|
||||
source = file
|
||||
if file is None:
|
||||
source = InputSource(None)
|
||||
source.setByteStream(suds.BytesIO(string))
|
||||
sax, handler = self.saxparser()
|
||||
sax.parse(source)
|
||||
timer.stop()
|
||||
if file is None:
|
||||
suds.metrics.log.debug("%s\nsax duration: %s", string, timer)
|
||||
else:
|
||||
suds.metrics.log.debug("sax (%s) duration: %s", file, timer)
|
||||
return handler.nodes[0]
|
@ -1,116 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Contains XML text classes.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.sax import *
|
||||
|
||||
|
||||
class Text(unicode):
|
||||
"""
|
||||
An XML text object used to represent text content.
|
||||
@ivar lang: The (optional) language flag.
|
||||
@type lang: bool
|
||||
@ivar escaped: The (optional) XML special character escaped flag.
|
||||
@type escaped: bool
|
||||
"""
|
||||
__slots__ = ('lang', 'escaped')
|
||||
|
||||
@classmethod
|
||||
def __valid(cls, *args):
|
||||
return len(args) and args[0] is not None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls.__valid(*args):
|
||||
lang = kwargs.pop('lang', None)
|
||||
escaped = kwargs.pop('escaped', False)
|
||||
result = super(Text, cls).__new__(cls, *args, **kwargs)
|
||||
result.lang = lang
|
||||
result.escaped = escaped
|
||||
else:
|
||||
result = None
|
||||
return result
|
||||
|
||||
def escape(self):
|
||||
"""
|
||||
Encode (escape) special XML characters.
|
||||
@return: The text with XML special characters escaped.
|
||||
@rtype: L{Text}
|
||||
"""
|
||||
if not self.escaped:
|
||||
post = sax.encoder.encode(self)
|
||||
escaped = ( post != self )
|
||||
return Text(post, lang=self.lang, escaped=escaped)
|
||||
return self
|
||||
|
||||
def unescape(self):
|
||||
"""
|
||||
Decode (unescape) special XML characters.
|
||||
@return: The text with escaped XML special characters decoded.
|
||||
@rtype: L{Text}
|
||||
"""
|
||||
if self.escaped:
|
||||
post = sax.encoder.decode(self)
|
||||
return Text(post, lang=self.lang)
|
||||
return self
|
||||
|
||||
def trim(self):
|
||||
post = self.strip()
|
||||
return Text(post, lang=self.lang, escaped=self.escaped)
|
||||
|
||||
def __add__(self, other):
|
||||
joined = u''.join((self, other))
|
||||
result = Text(joined, lang=self.lang, escaped=self.escaped)
|
||||
if isinstance(other, Text):
|
||||
result.escaped = self.escaped or other.escaped
|
||||
return result
|
||||
|
||||
def __repr__(self):
|
||||
s = [self]
|
||||
if self.lang is not None:
|
||||
s.append(' [%s]' % self.lang)
|
||||
if self.escaped:
|
||||
s.append(' <escaped>')
|
||||
return ''.join(s)
|
||||
|
||||
def __getstate__(self):
|
||||
state = {}
|
||||
for k in self.__slots__:
|
||||
state[k] = getattr(self, k)
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
for k in self.__slots__:
|
||||
setattr(self, k, state[k])
|
||||
|
||||
|
||||
class Raw(Text):
|
||||
"""
|
||||
Raw text which is not XML escaped.
|
||||
This may include I{string} XML.
|
||||
"""
|
||||
def escape(self):
|
||||
return self
|
||||
|
||||
def unescape(self):
|
||||
return self
|
||||
|
||||
def __add__(self, other):
|
||||
joined = u''.join((self, other))
|
||||
return Raw(joined, lang=self.lang)
|
@ -1,246 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The I{service definition} provides a textual representation of a service.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
import suds.metrics as metrics
|
||||
from suds.sax import Namespace
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class ServiceDefinition(UnicodeMixin):
|
||||
"""
|
||||
A service definition provides an object used to generate a textual description
|
||||
of a service.
|
||||
@ivar wsdl: A wsdl.
|
||||
@type wsdl: L{wsdl.Definitions}
|
||||
@ivar sort_namespaces: Whether to sort namespaces on storing them.
|
||||
@ivar service: The service object.
|
||||
@type service: L{suds.wsdl.Service}
|
||||
@ivar ports: A list of port-tuple: (port, [(method-name, pdef)])
|
||||
@type ports: [port-tuple,..]
|
||||
@ivar prefixes: A list of remapped prefixes.
|
||||
@type prefixes: [(prefix,uri),..]
|
||||
@ivar types: A list of type definitions
|
||||
@type types: [I{Type},..]
|
||||
"""
|
||||
|
||||
def __init__(self, wsdl, service):
|
||||
"""
|
||||
@param wsdl: A WSDL object
|
||||
@type wsdl: L{Definitions}
|
||||
@param service: A service B{name}.
|
||||
@type service: str
|
||||
@param service: A service B{name}.
|
||||
@param sort_namespaces: Whether to sort namespaces on storing them.
|
||||
"""
|
||||
self.wsdl = wsdl
|
||||
self.service = service
|
||||
self.ports = []
|
||||
self.params = []
|
||||
self.types = []
|
||||
self.prefixes = []
|
||||
self.addports()
|
||||
self.paramtypes()
|
||||
self.publictypes()
|
||||
self.getprefixes()
|
||||
self.pushprefixes()
|
||||
|
||||
def pushprefixes(self):
|
||||
"""
|
||||
Add our prefixes to the WSDL so that when users invoke methods
|
||||
and reference the prefixes, they will resolve properly.
|
||||
"""
|
||||
for ns in self.prefixes:
|
||||
self.wsdl.root.addPrefix(ns[0], ns[1])
|
||||
|
||||
def addports(self):
|
||||
"""
|
||||
Look through the list of service ports and construct a list of tuples
|
||||
where each tuple is used to describe a port and its list of methods as:
|
||||
(port, [method]). Each method is a tuple: (name, [pdef,..]) where each
|
||||
pdef is a tuple: (param-name, type).
|
||||
"""
|
||||
timer = metrics.Timer()
|
||||
timer.start()
|
||||
for port in self.service.ports:
|
||||
p = self.findport(port)
|
||||
for op in port.binding.operations.values():
|
||||
m = p[0].method(op.name)
|
||||
binding = m.binding.input
|
||||
method = (m.name, binding.param_defs(m))
|
||||
p[1].append(method)
|
||||
metrics.log.debug("method '%s' created: %s", m.name, timer)
|
||||
p[1].sort()
|
||||
timer.stop()
|
||||
|
||||
def findport(self, port):
|
||||
"""
|
||||
Find and return a port tuple for the specified port.
|
||||
Created and added when not found.
|
||||
@param port: A port.
|
||||
@type port: I{service.Port}
|
||||
@return: A port tuple.
|
||||
@rtype: (port, [method])
|
||||
"""
|
||||
for p in self.ports:
|
||||
if p[0] == p: return p
|
||||
p = (port, [])
|
||||
self.ports.append(p)
|
||||
return p
|
||||
|
||||
def getprefixes(self):
|
||||
"""Add prefixes for each namespace referenced by parameter types."""
|
||||
namespaces = []
|
||||
for l in (self.params, self.types):
|
||||
for t,r in l:
|
||||
ns = r.namespace()
|
||||
if ns[1] is None: continue
|
||||
if ns[1] in namespaces: continue
|
||||
if Namespace.xs(ns) or Namespace.xsd(ns):
|
||||
continue
|
||||
namespaces.append(ns[1])
|
||||
if t == r: continue
|
||||
ns = t.namespace()
|
||||
if ns[1] is None: continue
|
||||
if ns[1] in namespaces: continue
|
||||
namespaces.append(ns[1])
|
||||
i = 0
|
||||
|
||||
if self.wsdl.options.sortNamespaces:
|
||||
namespaces.sort()
|
||||
|
||||
for u in namespaces:
|
||||
p = self.nextprefix()
|
||||
ns = (p, u)
|
||||
self.prefixes.append(ns)
|
||||
|
||||
def paramtypes(self):
|
||||
"""Get all parameter types."""
|
||||
for m in [p[1] for p in self.ports]:
|
||||
for p in [p[1] for p in m]:
|
||||
for pd in p:
|
||||
if pd[1] in self.params: continue
|
||||
item = (pd[1], pd[1].resolve())
|
||||
self.params.append(item)
|
||||
|
||||
def publictypes(self):
|
||||
"""Get all public types."""
|
||||
for t in self.wsdl.schema.types.values():
|
||||
if t in self.params: continue
|
||||
if t in self.types: continue
|
||||
item = (t, t)
|
||||
self.types.append(item)
|
||||
self.types.sort(key=lambda x: x[0].name)
|
||||
|
||||
def nextprefix(self):
|
||||
"""
|
||||
Get the next available prefix. This means a prefix starting with 'ns' with
|
||||
a number appended as (ns0, ns1, ..) that is not already defined in the
|
||||
WSDL document.
|
||||
"""
|
||||
used = [ns[0] for ns in self.prefixes]
|
||||
used += [ns[0] for ns in self.wsdl.root.nsprefixes.items()]
|
||||
for n in range(0,1024):
|
||||
p = 'ns%d'%n
|
||||
if p not in used:
|
||||
return p
|
||||
raise Exception('prefixes exhausted')
|
||||
|
||||
def getprefix(self, u):
|
||||
"""
|
||||
Get the prefix for the specified namespace (URI)
|
||||
@param u: A namespace URI.
|
||||
@type u: str
|
||||
@return: The namspace.
|
||||
@rtype: (prefix, uri).
|
||||
"""
|
||||
for ns in Namespace.all:
|
||||
if u == ns[1]: return ns[0]
|
||||
for ns in self.prefixes:
|
||||
if u == ns[1]: return ns[0]
|
||||
raise Exception('ns (%s) not mapped' % u)
|
||||
|
||||
def xlate(self, type):
|
||||
"""
|
||||
Get a (namespace) translated I{qualified} name for specified type.
|
||||
@param type: A schema type.
|
||||
@type type: I{suds.xsd.sxbasic.SchemaObject}
|
||||
@return: A translated I{qualified} name.
|
||||
@rtype: str
|
||||
"""
|
||||
resolved = type.resolve()
|
||||
name = resolved.name
|
||||
if type.multi_occurrence():
|
||||
name += '[]'
|
||||
ns = resolved.namespace()
|
||||
if ns[1] == self.wsdl.tns[1]:
|
||||
return name
|
||||
prefix = self.getprefix(ns[1])
|
||||
return ':'.join((prefix, name))
|
||||
|
||||
def description(self):
|
||||
"""
|
||||
Get a textual description of the service for which this object represents.
|
||||
@return: A textual description.
|
||||
@rtype: str
|
||||
"""
|
||||
s = []
|
||||
indent = (lambda n : '\n%*s'%(n*3,' '))
|
||||
s.append('Service ( %s ) tns="%s"' % (self.service.name, self.wsdl.tns[1]))
|
||||
s.append(indent(1))
|
||||
s.append('Prefixes (%d)' % len(self.prefixes))
|
||||
for p in self.prefixes:
|
||||
s.append(indent(2))
|
||||
s.append('%s = "%s"' % p)
|
||||
s.append(indent(1))
|
||||
s.append('Ports (%d):' % len(self.ports))
|
||||
for p in self.ports:
|
||||
s.append(indent(2))
|
||||
s.append('(%s)' % p[0].name)
|
||||
s.append(indent(3))
|
||||
s.append('Methods (%d):' % len(p[1]))
|
||||
for m in p[1]:
|
||||
sig = []
|
||||
s.append(indent(4))
|
||||
sig.append(m[0])
|
||||
sig.append('(')
|
||||
sig.append(', '.join("%s %s" % (self.xlate(p[1]), p[0]) for p
|
||||
in m[1]))
|
||||
sig.append(')')
|
||||
try:
|
||||
s.append(''.join(sig))
|
||||
except Exception:
|
||||
pass
|
||||
s.append(indent(3))
|
||||
s.append('Types (%d):' % len(self.types))
|
||||
for t in self.types:
|
||||
s.append(indent(4))
|
||||
s.append(self.xlate(t[0]))
|
||||
s.append('\n\n')
|
||||
return ''.join(s)
|
||||
|
||||
def __unicode__(self):
|
||||
try:
|
||||
return self.description()
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
return tostr(e)
|
@ -1,80 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The service proxy provides access to web services.
|
||||
|
||||
Replaced by: L{client.Client}
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.client import Client
|
||||
|
||||
|
||||
class ServiceProxy(UnicodeMixin):
|
||||
|
||||
"""
|
||||
A lightweight soap based web service proxy.
|
||||
@ivar __client__: A client.
|
||||
Everything is delegated to the 2nd generation API.
|
||||
@type __client__: L{Client}
|
||||
@note: Deprecated, replaced by L{Client}.
|
||||
"""
|
||||
|
||||
def __init__(self, url, **kwargs):
|
||||
"""
|
||||
@param url: The URL for the WSDL.
|
||||
@type url: str
|
||||
@param kwargs: keyword arguments.
|
||||
@keyword faults: Raise faults raised by server (default:True),
|
||||
else return tuple from service method invocation as (http code, object).
|
||||
@type faults: boolean
|
||||
@keyword proxy: An http proxy to be specified on requests (default:{}).
|
||||
The proxy is defined as {protocol:proxy,}
|
||||
@type proxy: dict
|
||||
"""
|
||||
client = Client(url, **kwargs)
|
||||
self.__client__ = client
|
||||
|
||||
def get_instance(self, name):
|
||||
"""
|
||||
Get an instance of a WSDL type by name
|
||||
@param name: The name of a type defined in the WSDL.
|
||||
@type name: str
|
||||
@return: An instance on success, else None
|
||||
@rtype: L{sudsobject.Object}
|
||||
"""
|
||||
return self.__client__.factory.create(name)
|
||||
|
||||
def get_enum(self, name):
|
||||
"""
|
||||
Get an instance of an enumeration defined in the WSDL by name.
|
||||
@param name: The name of a enumeration defined in the WSDL.
|
||||
@type name: str
|
||||
@return: An instance on success, else None
|
||||
@rtype: L{sudsobject.Object}
|
||||
"""
|
||||
return self.__client__.factory.create(name)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.__client__)
|
||||
|
||||
def __getattr__(self, name):
|
||||
builtin = name.startswith('__') and name.endswith('__')
|
||||
if builtin:
|
||||
return self.__dict__[name]
|
||||
else:
|
||||
return getattr(self.__client__.service, name)
|
@ -1,71 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The I{soaparray} module provides XSD extensions for handling
|
||||
soap (section 5) encoded arrays.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from logging import getLogger
|
||||
from suds.xsd.sxbasic import Factory as SXFactory
|
||||
from suds.xsd.sxbasic import Attribute as SXAttribute
|
||||
|
||||
|
||||
class Attribute(SXAttribute):
|
||||
"""
|
||||
Represents an XSD <attribute/> that handles special
|
||||
attributes that are extensions for WSDLs.
|
||||
@ivar aty: Array type information.
|
||||
@type aty: The value of wsdl:arrayType.
|
||||
"""
|
||||
|
||||
def __init__(self, schema, root, aty):
|
||||
"""
|
||||
@param aty: Array type information.
|
||||
@type aty: The value of wsdl:arrayType.
|
||||
"""
|
||||
SXAttribute.__init__(self, schema, root)
|
||||
if aty.endswith('[]'):
|
||||
self.aty = aty[:-2]
|
||||
else:
|
||||
self.aty = aty
|
||||
|
||||
def autoqualified(self):
|
||||
aqs = SXAttribute.autoqualified(self)
|
||||
aqs.append('aty')
|
||||
return aqs
|
||||
|
||||
def description(self):
|
||||
d = SXAttribute.description(self)
|
||||
d = d+('aty',)
|
||||
return d
|
||||
|
||||
#
|
||||
# Builder function, only builds Attribute when arrayType
|
||||
# attribute is defined on root.
|
||||
#
|
||||
def __fn(x, y):
|
||||
ns = (None, "http://schemas.xmlsoap.org/wsdl/")
|
||||
aty = y.get('arrayType', ns=ns)
|
||||
if aty is None:
|
||||
return SXAttribute(x, y)
|
||||
return Attribute(x, y, aty)
|
||||
|
||||
#
|
||||
# Remap <xs:attribute/> tags to __fn() builder.
|
||||
#
|
||||
SXFactory.maptag('attribute', __fn)
|
@ -1,601 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Support for holding XML document content that may then be accessed internally
|
||||
by suds without having to download them from an external source. Also contains
|
||||
XML document content to be distributed alongside the suds library.
|
||||
|
||||
"""
|
||||
|
||||
from future.utils import raise_
|
||||
import suds
|
||||
|
||||
|
||||
soap5_encoding_schema = suds.byte_str("""\
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:tns="http://schemas.xmlsoap.org/soap/encoding/"
|
||||
targetNamespace="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
|
||||
<xs:attribute name="root">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
'root' can be used to distinguish serialization roots from other
|
||||
elements that are present in a serialization but are not roots of
|
||||
a serialized value graph
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:boolean">
|
||||
<xs:pattern value="0|1"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
|
||||
<xs:attributeGroup name="commonAttributes">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
Attributes common to all elements that function as accessors or
|
||||
represent independent (multi-ref) values. The href attribute is
|
||||
intended to be used in a manner like CONREF. That is, the element
|
||||
content should be empty iff the href attribute appears
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:attribute name="id" type="xs:ID"/>
|
||||
<xs:attribute name="href" type="xs:anyURI"/>
|
||||
<xs:anyAttribute namespace="##other" processContents="lax"/>
|
||||
</xs:attributeGroup>
|
||||
|
||||
<!-- Global Attributes. The following attributes are intended to be usable via qualified attribute names on any complex type referencing them. -->
|
||||
|
||||
<!-- Array attributes. Needed to give the type and dimensions of an array's contents, and the offset for partially-transmitted arrays. -->
|
||||
|
||||
<xs:simpleType name="arrayCoordinate">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:attribute name="arrayType" type="xs:string"/>
|
||||
<xs:attribute name="offset" type="tns:arrayCoordinate"/>
|
||||
|
||||
<xs:attributeGroup name="arrayAttributes">
|
||||
<xs:attribute ref="tns:arrayType"/>
|
||||
<xs:attribute ref="tns:offset"/>
|
||||
</xs:attributeGroup>
|
||||
|
||||
<xs:attribute name="position" type="tns:arrayCoordinate"/>
|
||||
|
||||
<xs:attributeGroup name="arrayMemberAttributes">
|
||||
<xs:attribute ref="tns:position"/>
|
||||
</xs:attributeGroup>
|
||||
|
||||
<xs:group name="Array">
|
||||
<xs:sequence>
|
||||
<xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
|
||||
</xs:sequence>
|
||||
</xs:group>
|
||||
|
||||
<xs:element name="Array" type="tns:Array"/>
|
||||
<xs:complexType name="Array">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
'Array' is a complex type for accessors identified by position
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:group ref="tns:Array" minOccurs="0"/>
|
||||
<xs:attributeGroup ref="tns:arrayAttributes"/>
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- 'Struct' is a complex type for accessors identified by name.
|
||||
Constraint: No element may be have the same name as any other,
|
||||
nor may any element have a maxOccurs > 1. -->
|
||||
|
||||
<xs:element name="Struct" type="tns:Struct"/>
|
||||
|
||||
<xs:group name="Struct">
|
||||
<xs:sequence>
|
||||
<xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
|
||||
</xs:sequence>
|
||||
</xs:group>
|
||||
|
||||
<xs:complexType name="Struct">
|
||||
<xs:group ref="tns:Struct" minOccurs="0"/>
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- 'Base64' can be used to serialize binary data using base64 encoding
|
||||
as defined in RFC2045 but without the MIME line length limitation. -->
|
||||
|
||||
<xs:simpleType name="base64">
|
||||
<xs:restriction base="xs:base64Binary"/>
|
||||
</xs:simpleType>
|
||||
|
||||
<!-- Element declarations corresponding to each of the simple types in the
|
||||
XML Schemas Specification. -->
|
||||
|
||||
<xs:element name="duration" type="tns:duration"/>
|
||||
<xs:complexType name="duration">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:duration">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="dateTime" type="tns:dateTime"/>
|
||||
<xs:complexType name="dateTime">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:dateTime">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="NOTATION" type="tns:NOTATION"/>
|
||||
<xs:complexType name="NOTATION">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:QName">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="time" type="tns:time"/>
|
||||
<xs:complexType name="time">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:time">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="date" type="tns:date"/>
|
||||
<xs:complexType name="date">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:date">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="gYearMonth" type="tns:gYearMonth"/>
|
||||
<xs:complexType name="gYearMonth">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:gYearMonth">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="gYear" type="tns:gYear"/>
|
||||
<xs:complexType name="gYear">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:gYear">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="gMonthDay" type="tns:gMonthDay"/>
|
||||
<xs:complexType name="gMonthDay">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:gMonthDay">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="gDay" type="tns:gDay"/>
|
||||
<xs:complexType name="gDay">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:gDay">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="gMonth" type="tns:gMonth"/>
|
||||
<xs:complexType name="gMonth">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:gMonth">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="boolean" type="tns:boolean"/>
|
||||
<xs:complexType name="boolean">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:boolean">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="base64Binary" type="tns:base64Binary"/>
|
||||
<xs:complexType name="base64Binary">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:base64Binary">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="hexBinary" type="tns:hexBinary"/>
|
||||
<xs:complexType name="hexBinary">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:hexBinary">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="float" type="tns:float"/>
|
||||
<xs:complexType name="float">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:float">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="double" type="tns:double"/>
|
||||
<xs:complexType name="double">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:double">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="anyURI" type="tns:anyURI"/>
|
||||
<xs:complexType name="anyURI">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:anyURI">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="QName" type="tns:QName"/>
|
||||
<xs:complexType name="QName">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:QName">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="string" type="tns:string"/>
|
||||
<xs:complexType name="string">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="normalizedString" type="tns:normalizedString"/>
|
||||
<xs:complexType name="normalizedString">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:normalizedString">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="token" type="tns:token"/>
|
||||
<xs:complexType name="token">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:token">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="language" type="tns:language"/>
|
||||
<xs:complexType name="language">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:language">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="Name" type="tns:Name"/>
|
||||
<xs:complexType name="Name">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:Name">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="NMTOKEN" type="tns:NMTOKEN"/>
|
||||
<xs:complexType name="NMTOKEN">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:NMTOKEN">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="NCName" type="tns:NCName"/>
|
||||
<xs:complexType name="NCName">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:NCName">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="NMTOKENS" type="tns:NMTOKENS"/>
|
||||
<xs:complexType name="NMTOKENS">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:NMTOKENS">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="ID" type="tns:ID"/>
|
||||
<xs:complexType name="ID">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:ID">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="IDREF" type="tns:IDREF"/>
|
||||
<xs:complexType name="IDREF">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:IDREF">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="ENTITY" type="tns:ENTITY"/>
|
||||
<xs:complexType name="ENTITY">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:ENTITY">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="IDREFS" type="tns:IDREFS"/>
|
||||
<xs:complexType name="IDREFS">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:IDREFS">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="ENTITIES" type="tns:ENTITIES"/>
|
||||
<xs:complexType name="ENTITIES">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:ENTITIES">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="decimal" type="tns:decimal"/>
|
||||
<xs:complexType name="decimal">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:decimal">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="integer" type="tns:integer"/>
|
||||
<xs:complexType name="integer">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:integer">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="nonPositiveInteger" type="tns:nonPositiveInteger"/>
|
||||
<xs:complexType name="nonPositiveInteger">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:nonPositiveInteger">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="negativeInteger" type="tns:negativeInteger"/>
|
||||
<xs:complexType name="negativeInteger">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:negativeInteger">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="long" type="tns:long"/>
|
||||
<xs:complexType name="long">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:long">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="int" type="tns:int"/>
|
||||
<xs:complexType name="int">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:int">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="short" type="tns:short"/>
|
||||
<xs:complexType name="short">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:short">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="byte" type="tns:byte"/>
|
||||
<xs:complexType name="byte">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:byte">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="nonNegativeInteger" type="tns:nonNegativeInteger"/>
|
||||
<xs:complexType name="nonNegativeInteger">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:nonNegativeInteger">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="unsignedLong" type="tns:unsignedLong"/>
|
||||
<xs:complexType name="unsignedLong">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:unsignedLong">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="unsignedInt" type="tns:unsignedInt"/>
|
||||
<xs:complexType name="unsignedInt">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:unsignedInt">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="unsignedShort" type="tns:unsignedShort"/>
|
||||
<xs:complexType name="unsignedShort">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:unsignedShort">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="unsignedByte" type="tns:unsignedByte"/>
|
||||
<xs:complexType name="unsignedByte">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:unsignedByte">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="positiveInteger" type="tns:positiveInteger"/>
|
||||
<xs:complexType name="positiveInteger">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:positiveInteger">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="anyType"/>
|
||||
</xs:schema>
|
||||
""")
|
||||
|
||||
|
||||
class DocumentStore(object):
|
||||
"""
|
||||
Local XML document content repository.
|
||||
|
||||
Each XML document is identified by its location, i.e. URL without any
|
||||
protocol identifier. Contained XML documents can be looked up using any URL
|
||||
referencing that same location.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.__store = {
|
||||
'schemas.xmlsoap.org/soap/encoding/': soap5_encoding_schema}
|
||||
self.update = self.__store.update
|
||||
self.update(*args, **kwargs)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__store)
|
||||
|
||||
def open(self, url):
|
||||
"""
|
||||
Open a document at the specified URL.
|
||||
|
||||
The document URL's needs not contain a protocol identifier, and if it
|
||||
does, that protocol identifier is ignored when looking up the store
|
||||
content.
|
||||
|
||||
Missing documents referenced using the internal 'suds' protocol are
|
||||
reported by raising an exception. For other protocols, None is returned
|
||||
instead.
|
||||
|
||||
@param url: A document URL.
|
||||
@type url: str
|
||||
@return: Document content or None if not found.
|
||||
@rtype: bytes
|
||||
|
||||
"""
|
||||
protocol, location = self.__split(url)
|
||||
content = self.__find(location)
|
||||
if protocol == 'suds' and content is None:
|
||||
raise_(Exception, 'location "%s" not in document store' % location)
|
||||
return content
|
||||
|
||||
def __find(self, location):
|
||||
"""
|
||||
Find the specified location in the store.
|
||||
|
||||
@param location: The I{location} part of a URL.
|
||||
@type location: str
|
||||
@return: Document content or None if not found.
|
||||
@rtype: bytes
|
||||
|
||||
"""
|
||||
return self.__store.get(location)
|
||||
|
||||
def __split(self, url):
|
||||
"""
|
||||
Split the given URL into its I{protocol} & I{location} components.
|
||||
|
||||
@param url: A URL.
|
||||
@param url: str
|
||||
@return: (I{protocol}, I{location})
|
||||
@rtype: (str, str)
|
||||
|
||||
"""
|
||||
parts = url.split('://', 1)
|
||||
if len(parts) == 2:
|
||||
return parts
|
||||
return None, url
|
||||
|
||||
|
||||
defaultDocumentStore = DocumentStore()
|
@ -1,600 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Support for holding XML document content that may then be accessed internally
|
||||
by suds without having to download them from an external source. Also contains
|
||||
XML document content to be distributed alongside the suds library.
|
||||
|
||||
"""
|
||||
|
||||
import suds
|
||||
|
||||
|
||||
soap5_encoding_schema = suds.byte_str("""\
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:tns="http://schemas.xmlsoap.org/soap/encoding/"
|
||||
targetNamespace="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
|
||||
<xs:attribute name="root">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
'root' can be used to distinguish serialization roots from other
|
||||
elements that are present in a serialization but are not roots of
|
||||
a serialized value graph
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:boolean">
|
||||
<xs:pattern value="0|1"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
|
||||
<xs:attributeGroup name="commonAttributes">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
Attributes common to all elements that function as accessors or
|
||||
represent independent (multi-ref) values. The href attribute is
|
||||
intended to be used in a manner like CONREF. That is, the element
|
||||
content should be empty iff the href attribute appears
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:attribute name="id" type="xs:ID"/>
|
||||
<xs:attribute name="href" type="xs:anyURI"/>
|
||||
<xs:anyAttribute namespace="##other" processContents="lax"/>
|
||||
</xs:attributeGroup>
|
||||
|
||||
<!-- Global Attributes. The following attributes are intended to be usable via qualified attribute names on any complex type referencing them. -->
|
||||
|
||||
<!-- Array attributes. Needed to give the type and dimensions of an array's contents, and the offset for partially-transmitted arrays. -->
|
||||
|
||||
<xs:simpleType name="arrayCoordinate">
|
||||
<xs:restriction base="xs:string"/>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:attribute name="arrayType" type="xs:string"/>
|
||||
<xs:attribute name="offset" type="tns:arrayCoordinate"/>
|
||||
|
||||
<xs:attributeGroup name="arrayAttributes">
|
||||
<xs:attribute ref="tns:arrayType"/>
|
||||
<xs:attribute ref="tns:offset"/>
|
||||
</xs:attributeGroup>
|
||||
|
||||
<xs:attribute name="position" type="tns:arrayCoordinate"/>
|
||||
|
||||
<xs:attributeGroup name="arrayMemberAttributes">
|
||||
<xs:attribute ref="tns:position"/>
|
||||
</xs:attributeGroup>
|
||||
|
||||
<xs:group name="Array">
|
||||
<xs:sequence>
|
||||
<xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
|
||||
</xs:sequence>
|
||||
</xs:group>
|
||||
|
||||
<xs:element name="Array" type="tns:Array"/>
|
||||
<xs:complexType name="Array">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
'Array' is a complex type for accessors identified by position
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:group ref="tns:Array" minOccurs="0"/>
|
||||
<xs:attributeGroup ref="tns:arrayAttributes"/>
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- 'Struct' is a complex type for accessors identified by name.
|
||||
Constraint: No element may be have the same name as any other,
|
||||
nor may any element have a maxOccurs > 1. -->
|
||||
|
||||
<xs:element name="Struct" type="tns:Struct"/>
|
||||
|
||||
<xs:group name="Struct">
|
||||
<xs:sequence>
|
||||
<xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
|
||||
</xs:sequence>
|
||||
</xs:group>
|
||||
|
||||
<xs:complexType name="Struct">
|
||||
<xs:group ref="tns:Struct" minOccurs="0"/>
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- 'Base64' can be used to serialize binary data using base64 encoding
|
||||
as defined in RFC2045 but without the MIME line length limitation. -->
|
||||
|
||||
<xs:simpleType name="base64">
|
||||
<xs:restriction base="xs:base64Binary"/>
|
||||
</xs:simpleType>
|
||||
|
||||
<!-- Element declarations corresponding to each of the simple types in the
|
||||
XML Schemas Specification. -->
|
||||
|
||||
<xs:element name="duration" type="tns:duration"/>
|
||||
<xs:complexType name="duration">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:duration">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="dateTime" type="tns:dateTime"/>
|
||||
<xs:complexType name="dateTime">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:dateTime">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="NOTATION" type="tns:NOTATION"/>
|
||||
<xs:complexType name="NOTATION">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:QName">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="time" type="tns:time"/>
|
||||
<xs:complexType name="time">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:time">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="date" type="tns:date"/>
|
||||
<xs:complexType name="date">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:date">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="gYearMonth" type="tns:gYearMonth"/>
|
||||
<xs:complexType name="gYearMonth">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:gYearMonth">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="gYear" type="tns:gYear"/>
|
||||
<xs:complexType name="gYear">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:gYear">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="gMonthDay" type="tns:gMonthDay"/>
|
||||
<xs:complexType name="gMonthDay">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:gMonthDay">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="gDay" type="tns:gDay"/>
|
||||
<xs:complexType name="gDay">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:gDay">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="gMonth" type="tns:gMonth"/>
|
||||
<xs:complexType name="gMonth">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:gMonth">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="boolean" type="tns:boolean"/>
|
||||
<xs:complexType name="boolean">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:boolean">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="base64Binary" type="tns:base64Binary"/>
|
||||
<xs:complexType name="base64Binary">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:base64Binary">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="hexBinary" type="tns:hexBinary"/>
|
||||
<xs:complexType name="hexBinary">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:hexBinary">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="float" type="tns:float"/>
|
||||
<xs:complexType name="float">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:float">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="double" type="tns:double"/>
|
||||
<xs:complexType name="double">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:double">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="anyURI" type="tns:anyURI"/>
|
||||
<xs:complexType name="anyURI">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:anyURI">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="QName" type="tns:QName"/>
|
||||
<xs:complexType name="QName">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:QName">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="string" type="tns:string"/>
|
||||
<xs:complexType name="string">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="normalizedString" type="tns:normalizedString"/>
|
||||
<xs:complexType name="normalizedString">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:normalizedString">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="token" type="tns:token"/>
|
||||
<xs:complexType name="token">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:token">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="language" type="tns:language"/>
|
||||
<xs:complexType name="language">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:language">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="Name" type="tns:Name"/>
|
||||
<xs:complexType name="Name">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:Name">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="NMTOKEN" type="tns:NMTOKEN"/>
|
||||
<xs:complexType name="NMTOKEN">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:NMTOKEN">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="NCName" type="tns:NCName"/>
|
||||
<xs:complexType name="NCName">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:NCName">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="NMTOKENS" type="tns:NMTOKENS"/>
|
||||
<xs:complexType name="NMTOKENS">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:NMTOKENS">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="ID" type="tns:ID"/>
|
||||
<xs:complexType name="ID">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:ID">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="IDREF" type="tns:IDREF"/>
|
||||
<xs:complexType name="IDREF">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:IDREF">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="ENTITY" type="tns:ENTITY"/>
|
||||
<xs:complexType name="ENTITY">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:ENTITY">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="IDREFS" type="tns:IDREFS"/>
|
||||
<xs:complexType name="IDREFS">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:IDREFS">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="ENTITIES" type="tns:ENTITIES"/>
|
||||
<xs:complexType name="ENTITIES">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:ENTITIES">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="decimal" type="tns:decimal"/>
|
||||
<xs:complexType name="decimal">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:decimal">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="integer" type="tns:integer"/>
|
||||
<xs:complexType name="integer">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:integer">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="nonPositiveInteger" type="tns:nonPositiveInteger"/>
|
||||
<xs:complexType name="nonPositiveInteger">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:nonPositiveInteger">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="negativeInteger" type="tns:negativeInteger"/>
|
||||
<xs:complexType name="negativeInteger">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:negativeInteger">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="long" type="tns:long"/>
|
||||
<xs:complexType name="long">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:long">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="int" type="tns:int"/>
|
||||
<xs:complexType name="int">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:int">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="short" type="tns:short"/>
|
||||
<xs:complexType name="short">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:short">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="byte" type="tns:byte"/>
|
||||
<xs:complexType name="byte">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:byte">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="nonNegativeInteger" type="tns:nonNegativeInteger"/>
|
||||
<xs:complexType name="nonNegativeInteger">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:nonNegativeInteger">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="unsignedLong" type="tns:unsignedLong"/>
|
||||
<xs:complexType name="unsignedLong">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:unsignedLong">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="unsignedInt" type="tns:unsignedInt"/>
|
||||
<xs:complexType name="unsignedInt">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:unsignedInt">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="unsignedShort" type="tns:unsignedShort"/>
|
||||
<xs:complexType name="unsignedShort">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:unsignedShort">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="unsignedByte" type="tns:unsignedByte"/>
|
||||
<xs:complexType name="unsignedByte">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:unsignedByte">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="positiveInteger" type="tns:positiveInteger"/>
|
||||
<xs:complexType name="positiveInteger">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:positiveInteger">
|
||||
<xs:attributeGroup ref="tns:commonAttributes"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="anyType"/>
|
||||
</xs:schema>
|
||||
""")
|
||||
|
||||
|
||||
class DocumentStore(object):
|
||||
"""
|
||||
Local XML document content repository.
|
||||
|
||||
Each XML document is identified by its location, i.e. URL without any
|
||||
protocol identifier. Contained XML documents can be looked up using any URL
|
||||
referencing that same location.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.__store = {
|
||||
'schemas.xmlsoap.org/soap/encoding/': soap5_encoding_schema}
|
||||
self.update = self.__store.update
|
||||
self.update(*args, **kwargs)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__store)
|
||||
|
||||
def open(self, url):
|
||||
"""
|
||||
Open a document at the specified URL.
|
||||
|
||||
The document URL's needs not contain a protocol identifier, and if it
|
||||
does, that protocol identifier is ignored when looking up the store
|
||||
content.
|
||||
|
||||
Missing documents referenced using the internal 'suds' protocol are
|
||||
reported by raising an exception. For other protocols, None is returned
|
||||
instead.
|
||||
|
||||
@param url: A document URL.
|
||||
@type url: str
|
||||
@return: Document content or None if not found.
|
||||
@rtype: bytes
|
||||
|
||||
"""
|
||||
protocol, location = self.__split(url)
|
||||
content = self.__find(location)
|
||||
if protocol == 'suds' and content is None:
|
||||
raise Exception, 'location "%s" not in document store' % location
|
||||
return content
|
||||
|
||||
def __find(self, location):
|
||||
"""
|
||||
Find the specified location in the store.
|
||||
|
||||
@param location: The I{location} part of a URL.
|
||||
@type location: str
|
||||
@return: Document content or None if not found.
|
||||
@rtype: bytes
|
||||
|
||||
"""
|
||||
return self.__store.get(location)
|
||||
|
||||
def __split(self, url):
|
||||
"""
|
||||
Split the given URL into its I{protocol} & I{location} components.
|
||||
|
||||
@param url: A URL.
|
||||
@param url: str
|
||||
@return: (I{protocol}, I{location})
|
||||
@rtype: (str, str)
|
||||
|
||||
"""
|
||||
parts = url.split('://', 1)
|
||||
if len(parts) == 2:
|
||||
return parts
|
||||
return None, url
|
||||
|
||||
|
||||
defaultDocumentStore = DocumentStore()
|
@ -1,392 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides a collection of suds objects primarily used for highly dynamic
|
||||
interactions with WSDL/XSD defined types.
|
||||
|
||||
"""
|
||||
|
||||
from future.utils import raise_
|
||||
from suds import *
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
def items(sobject):
|
||||
"""
|
||||
Extract the I{items} from a suds object.
|
||||
|
||||
Much like the items() method works on I{dict}.
|
||||
|
||||
@param sobject: A suds object
|
||||
@type sobject: L{Object}
|
||||
@return: A list of items contained in I{sobject}.
|
||||
@rtype: [(key, value),...]
|
||||
|
||||
"""
|
||||
for item in sobject:
|
||||
yield item
|
||||
|
||||
|
||||
def asdict(sobject):
|
||||
"""
|
||||
Convert a sudsobject into a dictionary.
|
||||
|
||||
@param sobject: A suds object
|
||||
@type sobject: L{Object}
|
||||
@return: A python dictionary containing the items contained in I{sobject}.
|
||||
@rtype: dict
|
||||
|
||||
"""
|
||||
return dict(items(sobject))
|
||||
|
||||
def merge(a, b):
|
||||
"""
|
||||
Merge all attributes and metadata from I{a} to I{b}.
|
||||
|
||||
@param a: A I{source} object
|
||||
@type a: L{Object}
|
||||
@param b: A I{destination} object
|
||||
@type b: L{Object}
|
||||
|
||||
"""
|
||||
for item in a:
|
||||
setattr(b, item[0], item[1])
|
||||
b.__metadata__ = b.__metadata__
|
||||
return b
|
||||
|
||||
def footprint(sobject):
|
||||
"""
|
||||
Get the I{virtual footprint} of the object.
|
||||
|
||||
This is really a count of all the significant value attributes in the
|
||||
branch.
|
||||
|
||||
@param sobject: A suds object.
|
||||
@type sobject: L{Object}
|
||||
@return: The branch footprint.
|
||||
@rtype: int
|
||||
|
||||
"""
|
||||
n = 0
|
||||
for a in sobject.__keylist__:
|
||||
v = getattr(sobject, a)
|
||||
if v is None:
|
||||
continue
|
||||
if isinstance(v, Object):
|
||||
n += footprint(v)
|
||||
continue
|
||||
if hasattr(v, "__len__"):
|
||||
if len(v):
|
||||
n += 1
|
||||
continue
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
class Factory:
|
||||
|
||||
cache = {}
|
||||
|
||||
@classmethod
|
||||
def subclass(cls, name, bases, dict={}):
|
||||
if not isinstance(bases, tuple):
|
||||
bases = (bases,)
|
||||
# name is of type unicode in python 2 -> not accepted by type()
|
||||
name = str(name)
|
||||
key = ".".join((name, str(bases)))
|
||||
subclass = cls.cache.get(key)
|
||||
if subclass is None:
|
||||
subclass = type(name, bases, dict)
|
||||
cls.cache[key] = subclass
|
||||
return subclass
|
||||
|
||||
@classmethod
|
||||
def object(cls, classname=None, dict={}):
|
||||
if classname is not None:
|
||||
subclass = cls.subclass(classname, Object)
|
||||
inst = subclass()
|
||||
else:
|
||||
inst = Object()
|
||||
for a in dict.items():
|
||||
setattr(inst, a[0], a[1])
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def metadata(cls):
|
||||
return Metadata()
|
||||
|
||||
@classmethod
|
||||
def property(cls, name, value=None):
|
||||
subclass = cls.subclass(name, Property)
|
||||
return subclass(value)
|
||||
|
||||
|
||||
class Object(UnicodeMixin):
|
||||
|
||||
def __init__(self):
|
||||
self.__keylist__ = []
|
||||
self.__printer__ = Printer()
|
||||
self.__metadata__ = Metadata()
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
builtin = name.startswith("__") and name.endswith("__")
|
||||
if not builtin and name not in self.__keylist__:
|
||||
self.__keylist__.append(name)
|
||||
self.__dict__[name] = value
|
||||
|
||||
def __delattr__(self, name):
|
||||
try:
|
||||
del self.__dict__[name]
|
||||
builtin = name.startswith("__") and name.endswith("__")
|
||||
if not builtin:
|
||||
self.__keylist__.remove(name)
|
||||
except Exception:
|
||||
cls = self.__class__.__name__
|
||||
raise_(AttributeError, "%s has no attribute '%s'" % (cls, name))
|
||||
|
||||
def __getitem__(self, name):
|
||||
if isinstance(name, int):
|
||||
name = self.__keylist__[int(name)]
|
||||
return getattr(self, name)
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
setattr(self, name, value)
|
||||
|
||||
def __iter__(self):
|
||||
return Iter(self)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__keylist__)
|
||||
|
||||
def __contains__(self, name):
|
||||
return name in self.__keylist__
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.__printer__.tostr(self)
|
||||
|
||||
|
||||
class Iter:
|
||||
|
||||
def __init__(self, sobject):
|
||||
self.sobject = sobject
|
||||
self.keylist = self.__keylist(sobject)
|
||||
self.index = 0
|
||||
|
||||
def next(self):
|
||||
keylist = self.keylist
|
||||
nkeys = len(self.keylist)
|
||||
while self.index < nkeys:
|
||||
k = keylist[self.index]
|
||||
self.index += 1
|
||||
if hasattr(self.sobject, k):
|
||||
v = getattr(self.sobject, k)
|
||||
return (k, v)
|
||||
raise StopIteration()
|
||||
|
||||
def __keylist(self, sobject):
|
||||
keylist = sobject.__keylist__
|
||||
try:
|
||||
keyset = set(keylist)
|
||||
ordering = sobject.__metadata__.ordering
|
||||
ordered = set(ordering)
|
||||
if not ordered.issuperset(keyset):
|
||||
log.debug("%s must be superset of %s, ordering ignored",
|
||||
keylist, ordering)
|
||||
raise KeyError()
|
||||
return ordering
|
||||
except Exception:
|
||||
return keylist
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
|
||||
class Metadata(Object):
|
||||
def __init__(self):
|
||||
self.__keylist__ = []
|
||||
self.__printer__ = Printer()
|
||||
|
||||
|
||||
class Facade(Object):
|
||||
def __init__(self, name):
|
||||
Object.__init__(self)
|
||||
md = self.__metadata__
|
||||
md.facade = name
|
||||
|
||||
|
||||
class Property(Object):
|
||||
|
||||
def __init__(self, value):
|
||||
Object.__init__(self)
|
||||
self.value = value
|
||||
|
||||
def items(self):
|
||||
for item in self:
|
||||
if item[0] != "value":
|
||||
yield item
|
||||
|
||||
def get(self):
|
||||
return self.value
|
||||
|
||||
def set(self, value):
|
||||
self.value = value
|
||||
return self
|
||||
|
||||
|
||||
class Printer:
|
||||
"""Pretty printing of a Object object."""
|
||||
|
||||
@classmethod
|
||||
def indent(cls, n):
|
||||
return "%*s" % (n * 3, " ")
|
||||
|
||||
def tostr(self, object, indent=-2):
|
||||
"""Get s string representation of object."""
|
||||
history = []
|
||||
return self.process(object, history, indent)
|
||||
|
||||
def process(self, object, h, n=0, nl=False):
|
||||
"""Print object using the specified indent (n) and newline (nl)."""
|
||||
if object is None:
|
||||
return "None"
|
||||
if isinstance(object, Object):
|
||||
if len(object) == 0:
|
||||
return "<empty>"
|
||||
return self.print_object(object, h, n + 2, nl)
|
||||
if isinstance(object, dict):
|
||||
if len(object) == 0:
|
||||
return "<empty>"
|
||||
return self.print_dictionary(object, h, n + 2, nl)
|
||||
if isinstance(object, (list, tuple)):
|
||||
if len(object) == 0:
|
||||
return "<empty>"
|
||||
return self.print_collection(object, h, n + 2)
|
||||
if isinstance(object, basestring):
|
||||
return '"%s"' % (tostr(object),)
|
||||
return "%s" % (tostr(object),)
|
||||
|
||||
def print_object(self, d, h, n, nl=False):
|
||||
"""Print complex using the specified indent (n) and newline (nl)."""
|
||||
s = []
|
||||
cls = d.__class__
|
||||
if d in h:
|
||||
s.append("(")
|
||||
s.append(cls.__name__)
|
||||
s.append(")")
|
||||
s.append("...")
|
||||
return "".join(s)
|
||||
h.append(d)
|
||||
if nl:
|
||||
s.append("\n")
|
||||
s.append(self.indent(n))
|
||||
if cls != Object:
|
||||
s.append("(")
|
||||
if isinstance(d, Facade):
|
||||
s.append(d.__metadata__.facade)
|
||||
else:
|
||||
s.append(cls.__name__)
|
||||
s.append(")")
|
||||
s.append("{")
|
||||
for item in d:
|
||||
if self.exclude(d, item):
|
||||
continue
|
||||
item = self.unwrap(d, item)
|
||||
s.append("\n")
|
||||
s.append(self.indent(n+1))
|
||||
if isinstance(item[1], (list,tuple)):
|
||||
s.append(item[0])
|
||||
s.append("[]")
|
||||
else:
|
||||
s.append(item[0])
|
||||
s.append(" = ")
|
||||
s.append(self.process(item[1], h, n, True))
|
||||
s.append("\n")
|
||||
s.append(self.indent(n))
|
||||
s.append("}")
|
||||
h.pop()
|
||||
return "".join(s)
|
||||
|
||||
def print_dictionary(self, d, h, n, nl=False):
|
||||
"""Print complex using the specified indent (n) and newline (nl)."""
|
||||
if d in h:
|
||||
return "{}..."
|
||||
h.append(d)
|
||||
s = []
|
||||
if nl:
|
||||
s.append("\n")
|
||||
s.append(self.indent(n))
|
||||
s.append("{")
|
||||
for item in d.items():
|
||||
s.append("\n")
|
||||
s.append(self.indent(n+1))
|
||||
if isinstance(item[1], (list,tuple)):
|
||||
s.append(tostr(item[0]))
|
||||
s.append("[]")
|
||||
else:
|
||||
s.append(tostr(item[0]))
|
||||
s.append(" = ")
|
||||
s.append(self.process(item[1], h, n, True))
|
||||
s.append("\n")
|
||||
s.append(self.indent(n))
|
||||
s.append("}")
|
||||
h.pop()
|
||||
return "".join(s)
|
||||
|
||||
def print_collection(self, c, h, n):
|
||||
"""Print collection using the specified indent (n) and newline (nl)."""
|
||||
if c in h:
|
||||
return "[]..."
|
||||
h.append(c)
|
||||
s = []
|
||||
for item in c:
|
||||
s.append("\n")
|
||||
s.append(self.indent(n))
|
||||
s.append(self.process(item, h, n - 2))
|
||||
s.append(",")
|
||||
h.pop()
|
||||
return "".join(s)
|
||||
|
||||
def unwrap(self, d, item):
|
||||
"""Translate (unwrap) using an optional wrapper function."""
|
||||
try:
|
||||
md = d.__metadata__
|
||||
pmd = getattr(md, "__print__", None)
|
||||
if pmd is None:
|
||||
return item
|
||||
wrappers = getattr(pmd, "wrappers", {})
|
||||
fn = wrappers.get(item[0], lambda x: x)
|
||||
return (item[0], fn(item[1]))
|
||||
except Exception:
|
||||
pass
|
||||
return item
|
||||
|
||||
def exclude(self, d, item):
|
||||
"""Check metadata for excluded items."""
|
||||
try:
|
||||
md = d.__metadata__
|
||||
pmd = getattr(md, "__print__", None)
|
||||
if pmd is None:
|
||||
return False
|
||||
excludes = getattr(pmd, "excludes", [])
|
||||
return item[0] in excludes
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
@ -1,391 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides a collection of suds objects primarily used for highly dynamic
|
||||
interactions with WSDL/XSD defined types.
|
||||
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
def items(sobject):
|
||||
"""
|
||||
Extract the I{items} from a suds object.
|
||||
|
||||
Much like the items() method works on I{dict}.
|
||||
|
||||
@param sobject: A suds object
|
||||
@type sobject: L{Object}
|
||||
@return: A list of items contained in I{sobject}.
|
||||
@rtype: [(key, value),...]
|
||||
|
||||
"""
|
||||
for item in sobject:
|
||||
yield item
|
||||
|
||||
|
||||
def asdict(sobject):
|
||||
"""
|
||||
Convert a sudsobject into a dictionary.
|
||||
|
||||
@param sobject: A suds object
|
||||
@type sobject: L{Object}
|
||||
@return: A python dictionary containing the items contained in I{sobject}.
|
||||
@rtype: dict
|
||||
|
||||
"""
|
||||
return dict(items(sobject))
|
||||
|
||||
def merge(a, b):
|
||||
"""
|
||||
Merge all attributes and metadata from I{a} to I{b}.
|
||||
|
||||
@param a: A I{source} object
|
||||
@type a: L{Object}
|
||||
@param b: A I{destination} object
|
||||
@type b: L{Object}
|
||||
|
||||
"""
|
||||
for item in a:
|
||||
setattr(b, item[0], item[1])
|
||||
b.__metadata__ = b.__metadata__
|
||||
return b
|
||||
|
||||
def footprint(sobject):
|
||||
"""
|
||||
Get the I{virtual footprint} of the object.
|
||||
|
||||
This is really a count of all the significant value attributes in the
|
||||
branch.
|
||||
|
||||
@param sobject: A suds object.
|
||||
@type sobject: L{Object}
|
||||
@return: The branch footprint.
|
||||
@rtype: int
|
||||
|
||||
"""
|
||||
n = 0
|
||||
for a in sobject.__keylist__:
|
||||
v = getattr(sobject, a)
|
||||
if v is None:
|
||||
continue
|
||||
if isinstance(v, Object):
|
||||
n += footprint(v)
|
||||
continue
|
||||
if hasattr(v, "__len__"):
|
||||
if len(v):
|
||||
n += 1
|
||||
continue
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
class Factory:
|
||||
|
||||
cache = {}
|
||||
|
||||
@classmethod
|
||||
def subclass(cls, name, bases, dict={}):
|
||||
if not isinstance(bases, tuple):
|
||||
bases = (bases,)
|
||||
# name is of type unicode in python 2 -> not accepted by type()
|
||||
name = str(name)
|
||||
key = ".".join((name, str(bases)))
|
||||
subclass = cls.cache.get(key)
|
||||
if subclass is None:
|
||||
subclass = type(name, bases, dict)
|
||||
cls.cache[key] = subclass
|
||||
return subclass
|
||||
|
||||
@classmethod
|
||||
def object(cls, classname=None, dict={}):
|
||||
if classname is not None:
|
||||
subclass = cls.subclass(classname, Object)
|
||||
inst = subclass()
|
||||
else:
|
||||
inst = Object()
|
||||
for a in dict.items():
|
||||
setattr(inst, a[0], a[1])
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def metadata(cls):
|
||||
return Metadata()
|
||||
|
||||
@classmethod
|
||||
def property(cls, name, value=None):
|
||||
subclass = cls.subclass(name, Property)
|
||||
return subclass(value)
|
||||
|
||||
|
||||
class Object(UnicodeMixin):
|
||||
|
||||
def __init__(self):
|
||||
self.__keylist__ = []
|
||||
self.__printer__ = Printer()
|
||||
self.__metadata__ = Metadata()
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
builtin = name.startswith("__") and name.endswith("__")
|
||||
if not builtin and name not in self.__keylist__:
|
||||
self.__keylist__.append(name)
|
||||
self.__dict__[name] = value
|
||||
|
||||
def __delattr__(self, name):
|
||||
try:
|
||||
del self.__dict__[name]
|
||||
builtin = name.startswith("__") and name.endswith("__")
|
||||
if not builtin:
|
||||
self.__keylist__.remove(name)
|
||||
except Exception:
|
||||
cls = self.__class__.__name__
|
||||
raise AttributeError, "%s has no attribute '%s'" % (cls, name)
|
||||
|
||||
def __getitem__(self, name):
|
||||
if isinstance(name, int):
|
||||
name = self.__keylist__[int(name)]
|
||||
return getattr(self, name)
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
setattr(self, name, value)
|
||||
|
||||
def __iter__(self):
|
||||
return Iter(self)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__keylist__)
|
||||
|
||||
def __contains__(self, name):
|
||||
return name in self.__keylist__
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.__printer__.tostr(self)
|
||||
|
||||
|
||||
class Iter:
|
||||
|
||||
def __init__(self, sobject):
|
||||
self.sobject = sobject
|
||||
self.keylist = self.__keylist(sobject)
|
||||
self.index = 0
|
||||
|
||||
def next(self):
|
||||
keylist = self.keylist
|
||||
nkeys = len(self.keylist)
|
||||
while self.index < nkeys:
|
||||
k = keylist[self.index]
|
||||
self.index += 1
|
||||
if hasattr(self.sobject, k):
|
||||
v = getattr(self.sobject, k)
|
||||
return (k, v)
|
||||
raise StopIteration()
|
||||
|
||||
def __keylist(self, sobject):
|
||||
keylist = sobject.__keylist__
|
||||
try:
|
||||
keyset = set(keylist)
|
||||
ordering = sobject.__metadata__.ordering
|
||||
ordered = set(ordering)
|
||||
if not ordered.issuperset(keyset):
|
||||
log.debug("%s must be superset of %s, ordering ignored",
|
||||
keylist, ordering)
|
||||
raise KeyError()
|
||||
return ordering
|
||||
except Exception:
|
||||
return keylist
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
|
||||
class Metadata(Object):
|
||||
def __init__(self):
|
||||
self.__keylist__ = []
|
||||
self.__printer__ = Printer()
|
||||
|
||||
|
||||
class Facade(Object):
|
||||
def __init__(self, name):
|
||||
Object.__init__(self)
|
||||
md = self.__metadata__
|
||||
md.facade = name
|
||||
|
||||
|
||||
class Property(Object):
|
||||
|
||||
def __init__(self, value):
|
||||
Object.__init__(self)
|
||||
self.value = value
|
||||
|
||||
def items(self):
|
||||
for item in self:
|
||||
if item[0] != "value":
|
||||
yield item
|
||||
|
||||
def get(self):
|
||||
return self.value
|
||||
|
||||
def set(self, value):
|
||||
self.value = value
|
||||
return self
|
||||
|
||||
|
||||
class Printer:
|
||||
"""Pretty printing of a Object object."""
|
||||
|
||||
@classmethod
|
||||
def indent(cls, n):
|
||||
return "%*s" % (n * 3, " ")
|
||||
|
||||
def tostr(self, object, indent=-2):
|
||||
"""Get s string representation of object."""
|
||||
history = []
|
||||
return self.process(object, history, indent)
|
||||
|
||||
def process(self, object, h, n=0, nl=False):
|
||||
"""Print object using the specified indent (n) and newline (nl)."""
|
||||
if object is None:
|
||||
return "None"
|
||||
if isinstance(object, Object):
|
||||
if len(object) == 0:
|
||||
return "<empty>"
|
||||
return self.print_object(object, h, n + 2, nl)
|
||||
if isinstance(object, dict):
|
||||
if len(object) == 0:
|
||||
return "<empty>"
|
||||
return self.print_dictionary(object, h, n + 2, nl)
|
||||
if isinstance(object, (list, tuple)):
|
||||
if len(object) == 0:
|
||||
return "<empty>"
|
||||
return self.print_collection(object, h, n + 2)
|
||||
if isinstance(object, basestring):
|
||||
return '"%s"' % (tostr(object),)
|
||||
return "%s" % (tostr(object),)
|
||||
|
||||
def print_object(self, d, h, n, nl=False):
|
||||
"""Print complex using the specified indent (n) and newline (nl)."""
|
||||
s = []
|
||||
cls = d.__class__
|
||||
if d in h:
|
||||
s.append("(")
|
||||
s.append(cls.__name__)
|
||||
s.append(")")
|
||||
s.append("...")
|
||||
return "".join(s)
|
||||
h.append(d)
|
||||
if nl:
|
||||
s.append("\n")
|
||||
s.append(self.indent(n))
|
||||
if cls != Object:
|
||||
s.append("(")
|
||||
if isinstance(d, Facade):
|
||||
s.append(d.__metadata__.facade)
|
||||
else:
|
||||
s.append(cls.__name__)
|
||||
s.append(")")
|
||||
s.append("{")
|
||||
for item in d:
|
||||
if self.exclude(d, item):
|
||||
continue
|
||||
item = self.unwrap(d, item)
|
||||
s.append("\n")
|
||||
s.append(self.indent(n+1))
|
||||
if isinstance(item[1], (list,tuple)):
|
||||
s.append(item[0])
|
||||
s.append("[]")
|
||||
else:
|
||||
s.append(item[0])
|
||||
s.append(" = ")
|
||||
s.append(self.process(item[1], h, n, True))
|
||||
s.append("\n")
|
||||
s.append(self.indent(n))
|
||||
s.append("}")
|
||||
h.pop()
|
||||
return "".join(s)
|
||||
|
||||
def print_dictionary(self, d, h, n, nl=False):
|
||||
"""Print complex using the specified indent (n) and newline (nl)."""
|
||||
if d in h:
|
||||
return "{}..."
|
||||
h.append(d)
|
||||
s = []
|
||||
if nl:
|
||||
s.append("\n")
|
||||
s.append(self.indent(n))
|
||||
s.append("{")
|
||||
for item in d.items():
|
||||
s.append("\n")
|
||||
s.append(self.indent(n+1))
|
||||
if isinstance(item[1], (list,tuple)):
|
||||
s.append(tostr(item[0]))
|
||||
s.append("[]")
|
||||
else:
|
||||
s.append(tostr(item[0]))
|
||||
s.append(" = ")
|
||||
s.append(self.process(item[1], h, n, True))
|
||||
s.append("\n")
|
||||
s.append(self.indent(n))
|
||||
s.append("}")
|
||||
h.pop()
|
||||
return "".join(s)
|
||||
|
||||
def print_collection(self, c, h, n):
|
||||
"""Print collection using the specified indent (n) and newline (nl)."""
|
||||
if c in h:
|
||||
return "[]..."
|
||||
h.append(c)
|
||||
s = []
|
||||
for item in c:
|
||||
s.append("\n")
|
||||
s.append(self.indent(n))
|
||||
s.append(self.process(item, h, n - 2))
|
||||
s.append(",")
|
||||
h.pop()
|
||||
return "".join(s)
|
||||
|
||||
def unwrap(self, d, item):
|
||||
"""Translate (unwrap) using an optional wrapper function."""
|
||||
try:
|
||||
md = d.__metadata__
|
||||
pmd = getattr(md, "__print__", None)
|
||||
if pmd is None:
|
||||
return item
|
||||
wrappers = getattr(pmd, "wrappers", {})
|
||||
fn = wrappers.get(item[0], lambda x: x)
|
||||
return (item[0], fn(item[1]))
|
||||
except Exception:
|
||||
pass
|
||||
return item
|
||||
|
||||
def exclude(self, d, item):
|
||||
"""Check metadata for excluded items."""
|
||||
try:
|
||||
md = d.__metadata__
|
||||
pmd = getattr(md, "__print__", None)
|
||||
if pmd is None:
|
||||
return False
|
||||
excludes = getattr(pmd, "excludes", [])
|
||||
return item[0] in excludes
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
@ -1,166 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Contains transport interface (classes).
|
||||
|
||||
"""
|
||||
|
||||
from suds import UnicodeMixin
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
class TransportError(Exception):
|
||||
def __init__(self, reason, httpcode, fp=None):
|
||||
Exception.__init__(self, reason)
|
||||
self.httpcode = httpcode
|
||||
self.fp = fp
|
||||
|
||||
|
||||
class Request(UnicodeMixin):
|
||||
"""
|
||||
A transport request.
|
||||
|
||||
Request URL input data may be given as either a byte or a unicode string,
|
||||
but it may not under any circumstances contain non-ASCII characters. The
|
||||
URL value is stored as a str value internally. With Python versions prior
|
||||
to 3.0, str is the byte string type, while with later Python versions it is
|
||||
the unicode string type.
|
||||
|
||||
@ivar url: The URL for the request.
|
||||
@type url: str
|
||||
@ivar message: The optional message to be sent in the request body.
|
||||
@type message: bytes|None
|
||||
@type timeout: int|None
|
||||
@ivar headers: The HTTP headers to be used for the request.
|
||||
@type headers: dict
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, url, message=None, timeout=None):
|
||||
"""
|
||||
Raised exception in case of detected non-ASCII URL characters may be
|
||||
either UnicodeEncodeError or UnicodeDecodeError, depending on the used
|
||||
Python version's str type and the exact value passed as URL input data.
|
||||
|
||||
@param url: The URL for the request.
|
||||
@type url: bytes|str|unicode
|
||||
@param message: The optional message to be sent in the request body.
|
||||
@type message: bytes|None
|
||||
|
||||
"""
|
||||
self.__set_URL(url)
|
||||
self.headers = {}
|
||||
self.message = message
|
||||
self.timeout = timeout
|
||||
|
||||
def __unicode__(self):
|
||||
result = [u"URL: %s\nHEADERS: %s" % (self.url, self.headers)]
|
||||
if self.message is not None:
|
||||
result.append(u"MESSAGE:")
|
||||
result.append(self.message.decode("raw_unicode_escape"))
|
||||
return u"\n".join(result)
|
||||
|
||||
def __set_URL(self, url):
|
||||
"""
|
||||
URL is stored as a str internally and must not contain ASCII chars.
|
||||
|
||||
Raised exception in case of detected non-ASCII URL characters may be
|
||||
either UnicodeEncodeError or UnicodeDecodeError, depending on the used
|
||||
Python version's str type and the exact value passed as URL input data.
|
||||
|
||||
"""
|
||||
if isinstance(url, str):
|
||||
url.encode("ascii") # Check for non-ASCII characters.
|
||||
self.url = url
|
||||
elif sys.version_info < (3, 0):
|
||||
self.url = url.encode("ascii")
|
||||
else:
|
||||
self.url = url.decode("ascii")
|
||||
|
||||
|
||||
class Reply(UnicodeMixin):
|
||||
"""
|
||||
A transport reply.
|
||||
|
||||
@ivar code: The HTTP code returned.
|
||||
@type code: int
|
||||
@ivar headers: The HTTP headers included in the received reply.
|
||||
@type headers: dict
|
||||
@ivar message: The message received as a reply.
|
||||
@type message: bytes
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, code, headers, message):
|
||||
"""
|
||||
@param code: The HTTP code returned.
|
||||
@type code: int
|
||||
@param headers: The HTTP headers included in the received reply.
|
||||
@type headers: dict
|
||||
@param message: The (optional) message received as a reply.
|
||||
@type message: bytes
|
||||
|
||||
"""
|
||||
self.code = code
|
||||
self.headers = headers
|
||||
self.message = message
|
||||
|
||||
def __unicode__(self):
|
||||
return u"""\
|
||||
CODE: %s
|
||||
HEADERS: %s
|
||||
MESSAGE:
|
||||
%s""" % (self.code, self.headers, self.message.decode("raw_unicode_escape"))
|
||||
|
||||
|
||||
class Transport(object):
|
||||
"""The transport I{interface}."""
|
||||
|
||||
def __init__(self):
|
||||
from suds.transport.options import Options
|
||||
self.options = Options()
|
||||
|
||||
def open(self, request):
|
||||
"""
|
||||
Open the URL in the specified request.
|
||||
|
||||
@param request: A transport request.
|
||||
@type request: L{Request}
|
||||
@return: An input stream.
|
||||
@rtype: stream
|
||||
@raise TransportError: On all transport errors.
|
||||
|
||||
"""
|
||||
raise Exception('not-implemented')
|
||||
|
||||
def send(self, request):
|
||||
"""
|
||||
Send SOAP message. Implementations are expected to handle:
|
||||
- proxies
|
||||
- I{HTTP} headers
|
||||
- cookies
|
||||
- sending message
|
||||
- brokering exceptions into L{TransportError}
|
||||
|
||||
@param request: A transport request.
|
||||
@type request: L{Request}
|
||||
@return: The reply
|
||||
@rtype: L{Reply}
|
||||
@raise TransportError: On all transport errors.
|
||||
|
||||
"""
|
||||
raise Exception('not-implemented')
|
@ -1,249 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Basic HTTP transport implementation classes.
|
||||
|
||||
"""
|
||||
|
||||
from suds.properties import Unskin
|
||||
from suds.transport import *
|
||||
|
||||
import base64
|
||||
from cookielib import CookieJar
|
||||
import httplib
|
||||
import socket
|
||||
import sys
|
||||
import urllib2
|
||||
import gzip
|
||||
import zlib
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class HttpTransport(Transport):
|
||||
"""
|
||||
Basic HTTP transport implemented using using urllib2, that provides for
|
||||
cookies & proxies but no authentication.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
@param kwargs: Keyword arguments.
|
||||
- B{proxy} - An HTTP proxy to be specified on requests.
|
||||
The proxy is defined as {protocol:proxy,}
|
||||
- type: I{dict}
|
||||
- default: {}
|
||||
- B{timeout} - Set the URL open timeout (seconds).
|
||||
- type: I{float}
|
||||
- default: 90
|
||||
|
||||
"""
|
||||
Transport.__init__(self)
|
||||
Unskin(self.options).update(kwargs)
|
||||
self.cookiejar = CookieJar()
|
||||
self.proxy = {}
|
||||
self.urlopener = None
|
||||
|
||||
def open(self, request):
|
||||
try:
|
||||
url = self.__get_request_url_for_urllib(request)
|
||||
headers = request.headers
|
||||
log.debug('opening (%s)', url)
|
||||
u2request = urllib2.Request(url, headers=headers)
|
||||
self.proxy = self.options.proxy
|
||||
return self.u2open(u2request)
|
||||
except urllib2.HTTPError as e:
|
||||
raise TransportError(str(e), e.code, e.fp)
|
||||
|
||||
def send(self, request):
|
||||
url = self.__get_request_url_for_urllib(request)
|
||||
msg = request.message
|
||||
headers = request.headers
|
||||
if 'Content-Encoding' in headers:
|
||||
encoding = headers['Content-Encoding']
|
||||
if encoding == 'gzip':
|
||||
msg = gzip.compress(msg)
|
||||
elif encoding == 'deflate':
|
||||
msg = zlib.compress(msg)
|
||||
try:
|
||||
u2request = urllib2.Request(url, msg, headers)
|
||||
self.addcookies(u2request)
|
||||
self.proxy = self.options.proxy
|
||||
request.headers.update(u2request.headers)
|
||||
log.debug('sending:\n%s', request)
|
||||
fp = self.u2open(u2request, timeout=request.timeout)
|
||||
self.getcookies(fp, u2request)
|
||||
headers = fp.headers
|
||||
if sys.version_info < (3, 0):
|
||||
headers = headers.dict
|
||||
message = fp.read()
|
||||
if 'Content-Encoding' in headers:
|
||||
encoding = headers['Content-Encoding']
|
||||
if encoding == 'gzip':
|
||||
message = gzip.decompress(message)
|
||||
elif encoding == 'deflate':
|
||||
message = zlib.decompress(message)
|
||||
reply = Reply(httplib.OK, headers, message)
|
||||
log.debug('received:\n%s', reply)
|
||||
return reply
|
||||
except urllib2.HTTPError as e:
|
||||
if e.code not in (httplib.ACCEPTED, httplib.NO_CONTENT):
|
||||
raise TransportError(e.msg, e.code, e.fp)
|
||||
|
||||
def addcookies(self, u2request):
|
||||
"""
|
||||
Add cookies in the cookiejar to the request.
|
||||
|
||||
@param u2request: A urllib2 request.
|
||||
@rtype: u2request: urllib2.Request.
|
||||
|
||||
"""
|
||||
self.cookiejar.add_cookie_header(u2request)
|
||||
|
||||
def getcookies(self, fp, u2request):
|
||||
"""
|
||||
Add cookies in the request to the cookiejar.
|
||||
|
||||
@param u2request: A urllib2 request.
|
||||
@rtype: u2request: urllib2.Request.
|
||||
|
||||
"""
|
||||
self.cookiejar.extract_cookies(fp, u2request)
|
||||
|
||||
def u2open(self, u2request, timeout=None):
|
||||
"""
|
||||
Open a connection.
|
||||
|
||||
@param u2request: A urllib2 request.
|
||||
@type u2request: urllib2.Request.
|
||||
@return: The opened file-like urllib2 object.
|
||||
@rtype: fp
|
||||
|
||||
"""
|
||||
tm = timeout or self.options.timeout
|
||||
url = self.u2opener()
|
||||
if (sys.version_info < (3, 0)) and (self.u2ver() < 2.6):
|
||||
socket.setdefaulttimeout(tm)
|
||||
return url.open(u2request)
|
||||
return url.open(u2request, timeout=tm)
|
||||
|
||||
def u2opener(self):
|
||||
"""
|
||||
Create a urllib opener.
|
||||
|
||||
@return: An opener.
|
||||
@rtype: I{OpenerDirector}
|
||||
|
||||
"""
|
||||
if self.urlopener is None:
|
||||
return urllib2.build_opener(*self.u2handlers())
|
||||
return self.urlopener
|
||||
|
||||
def u2handlers(self):
|
||||
"""
|
||||
Get a collection of urllib handlers.
|
||||
|
||||
@return: A list of handlers to be installed in the opener.
|
||||
@rtype: [Handler,...]
|
||||
|
||||
"""
|
||||
return [urllib2.ProxyHandler(self.proxy)]
|
||||
|
||||
def u2ver(self):
|
||||
"""
|
||||
Get the major/minor version of the urllib2 lib.
|
||||
|
||||
@return: The urllib2 version.
|
||||
@rtype: float
|
||||
|
||||
"""
|
||||
try:
|
||||
part = urllib2.__version__.split('.', 1)
|
||||
return float('.'.join(part))
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
return 0
|
||||
|
||||
def __deepcopy__(self, memo={}):
|
||||
clone = self.__class__()
|
||||
p = Unskin(self.options)
|
||||
cp = Unskin(clone.options)
|
||||
cp.update(p)
|
||||
return clone
|
||||
|
||||
@staticmethod
|
||||
def __get_request_url_for_urllib(request):
|
||||
"""
|
||||
Returns the given request's URL, properly encoded for use with urllib.
|
||||
|
||||
We expect that the given request object already verified that the URL
|
||||
contains ASCII characters only and stored it as a native str value.
|
||||
|
||||
urllib accepts URL information as a native str value and may break
|
||||
unexpectedly if given URL information in another format.
|
||||
|
||||
Python 3.x httplib.client implementation must be given a unicode string
|
||||
and not a bytes object and the given string is internally converted to
|
||||
a bytes object using an explicitly specified ASCII encoding.
|
||||
|
||||
Python 2.7 httplib implementation expects the URL passed to it to not
|
||||
be a unicode string. If it is, then passing it to the underlying
|
||||
httplib Request object will cause that object to forcefully convert all
|
||||
of its data to unicode, assuming that data contains ASCII data only and
|
||||
raising a UnicodeDecodeError exception if it does not (caused by simple
|
||||
unicode + string concatenation).
|
||||
|
||||
Python 2.4 httplib implementation does not really care about this as it
|
||||
does not use the internal optimization present in the Python 2.7
|
||||
implementation causing all the requested data to be converted to
|
||||
unicode.
|
||||
|
||||
"""
|
||||
assert isinstance(request.url, str)
|
||||
return request.url
|
||||
|
||||
|
||||
class HttpAuthenticated(HttpTransport):
|
||||
"""
|
||||
Provides basic HTTP authentication for servers that do not follow the
|
||||
specified challenge/response model. Appends the I{Authorization} HTTP
|
||||
header with base64 encoded credentials on every HTTP request.
|
||||
|
||||
"""
|
||||
|
||||
def open(self, request):
|
||||
self.addcredentials(request)
|
||||
return HttpTransport.open(self, request)
|
||||
|
||||
def send(self, request):
|
||||
self.addcredentials(request)
|
||||
return HttpTransport.send(self, request)
|
||||
|
||||
def addcredentials(self, request):
|
||||
credentials = self.credentials()
|
||||
if None not in credentials:
|
||||
credentials = ':'.join(credentials)
|
||||
if sys.version_info < (3, 0):
|
||||
encodedString = base64.b64encode(credentials)
|
||||
else:
|
||||
encodedBytes = base64.urlsafe_b64encode(credentials.encode())
|
||||
encodedString = encodedBytes.decode()
|
||||
request.headers['Authorization'] = 'Basic %s' % encodedString
|
||||
|
||||
def credentials(self):
|
||||
return self.options.username, self.options.password
|
@ -1,99 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Contains classes for authenticated HTTP transport implementations.
|
||||
|
||||
"""
|
||||
|
||||
from suds.transport import *
|
||||
from suds.transport.http import HttpTransport
|
||||
|
||||
import urllib2
|
||||
|
||||
|
||||
class HttpAuthenticated(HttpTransport):
|
||||
"""
|
||||
Provides basic HTTP authentication that follows the RFC-2617 specification.
|
||||
|
||||
As defined by specifications, credentials are provided to the server upon
|
||||
request (HTTP/1.0 401 Authorization Required) by the server only.
|
||||
|
||||
@ivar pm: The password manager.
|
||||
@ivar handler: The authentication handler.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
@param kwargs: Keyword arguments.
|
||||
- B{proxy} - An HTTP proxy to be specified on requests.
|
||||
The proxy is defined as {protocol:proxy,}
|
||||
- type: I{dict}
|
||||
- default: {}
|
||||
- B{timeout} - Set the URL open timeout (seconds).
|
||||
- type: I{float}
|
||||
- default: 90
|
||||
- B{username} - The username used for HTTP authentication.
|
||||
- type: I{str}
|
||||
- default: None
|
||||
- B{password} - The password used for HTTP authentication.
|
||||
- type: I{str}
|
||||
- default: None
|
||||
|
||||
"""
|
||||
HttpTransport.__init__(self, **kwargs)
|
||||
self.pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
||||
|
||||
def open(self, request):
|
||||
self.addcredentials(request)
|
||||
return HttpTransport.open(self, request)
|
||||
|
||||
def send(self, request):
|
||||
self.addcredentials(request)
|
||||
return HttpTransport.send(self, request)
|
||||
|
||||
def addcredentials(self, request):
|
||||
credentials = self.credentials()
|
||||
if None not in credentials:
|
||||
u = credentials[0]
|
||||
p = credentials[1]
|
||||
self.pm.add_password(None, request.url, u, p)
|
||||
|
||||
def credentials(self):
|
||||
return self.options.username, self.options.password
|
||||
|
||||
def u2handlers(self):
|
||||
handlers = HttpTransport.u2handlers(self)
|
||||
handlers.append(urllib2.HTTPBasicAuthHandler(self.pm))
|
||||
return handlers
|
||||
|
||||
|
||||
class WindowsHttpAuthenticated(HttpAuthenticated):
|
||||
"""
|
||||
Provides Windows (NTLM) based HTTP authentication.
|
||||
|
||||
@author: Christopher Bess
|
||||
|
||||
"""
|
||||
|
||||
def u2handlers(self):
|
||||
try:
|
||||
from ntlm import HTTPNtlmAuthHandler
|
||||
except ImportError:
|
||||
raise Exception("Cannot import python-ntlm module")
|
||||
handlers = HttpTransport.u2handlers(self)
|
||||
handlers.append(HTTPNtlmAuthHandler.HTTPNtlmAuthHandler(self.pm))
|
||||
return handlers
|
@ -1,58 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Classes modeling transport options.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from suds.transport import *
|
||||
from suds.properties import *
|
||||
|
||||
|
||||
class Options(Skin):
|
||||
"""
|
||||
Options:
|
||||
- B{proxy} - An HTTP proxy to be specified on requests, defined as
|
||||
{protocol:proxy, ...}.
|
||||
- type: I{dict}
|
||||
- default: {}
|
||||
- B{timeout} - Set the URL open timeout (seconds).
|
||||
- type: I{float}
|
||||
- default: 90
|
||||
- B{headers} - Extra HTTP headers.
|
||||
- type: I{dict}
|
||||
- I{str} B{http} - The I{HTTP} protocol proxy URL.
|
||||
- I{str} B{https} - The I{HTTPS} protocol proxy URL.
|
||||
- default: {}
|
||||
- B{username} - The username used for HTTP authentication.
|
||||
- type: I{str}
|
||||
- default: None
|
||||
- B{password} - The password used for HTTP authentication.
|
||||
- type: I{str}
|
||||
- default: None
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
domain = __name__
|
||||
definitions = [
|
||||
Definition('proxy', dict, {}),
|
||||
Definition('timeout', (int,float), 90),
|
||||
Definition('headers', dict, {}),
|
||||
Definition('username', basestring, None),
|
||||
Definition('password', basestring, None)]
|
||||
Skin.__init__(self, domain, definitions, kwargs)
|
@ -1,57 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides modules containing classes to support
|
||||
unmarshalling (XML).
|
||||
"""
|
||||
|
||||
from future.utils import raise_
|
||||
from suds.sudsobject import Object
|
||||
|
||||
|
||||
|
||||
class Content(Object):
|
||||
"""
|
||||
@ivar node: The content source node.
|
||||
@type node: L{sax.element.Element}
|
||||
@ivar data: The (optional) content data.
|
||||
@type data: L{Object}
|
||||
@ivar text: The (optional) content (xml) text.
|
||||
@type text: basestring
|
||||
"""
|
||||
|
||||
extensions = []
|
||||
|
||||
def __init__(self, node, **kwargs):
|
||||
Object.__init__(self)
|
||||
self.node = node
|
||||
self.data = None
|
||||
self.text = None
|
||||
for k,v in kwargs.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name not in self.__dict__:
|
||||
if name in self.extensions:
|
||||
v = None
|
||||
setattr(self, name, v)
|
||||
else:
|
||||
raise_(AttributeError, \
|
||||
'Content has no attribute %s' % name)
|
||||
else:
|
||||
v = self.__dict__[name]
|
||||
return v
|
@ -1,56 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides modules containing classes to support
|
||||
unmarshalling (XML).
|
||||
"""
|
||||
|
||||
from suds.sudsobject import Object
|
||||
|
||||
|
||||
|
||||
class Content(Object):
|
||||
"""
|
||||
@ivar node: The content source node.
|
||||
@type node: L{sax.element.Element}
|
||||
@ivar data: The (optional) content data.
|
||||
@type data: L{Object}
|
||||
@ivar text: The (optional) content (xml) text.
|
||||
@type text: basestring
|
||||
"""
|
||||
|
||||
extensions = []
|
||||
|
||||
def __init__(self, node, **kwargs):
|
||||
Object.__init__(self)
|
||||
self.node = node
|
||||
self.data = None
|
||||
self.text = None
|
||||
for k,v in kwargs.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name not in self.__dict__:
|
||||
if name in self.extensions:
|
||||
v = None
|
||||
setattr(self, name, v)
|
||||
else:
|
||||
raise AttributeError, \
|
||||
'Content has no attribute %s' % name
|
||||
else:
|
||||
v = self.__dict__[name]
|
||||
return v
|
@ -1,88 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides filtered attribute list classes.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.umx import *
|
||||
from suds.sax import Namespace
|
||||
|
||||
|
||||
class AttrList:
|
||||
"""
|
||||
A filtered attribute list.
|
||||
Items are included during iteration if they are in either the (xs) or
|
||||
(xml) namespaces.
|
||||
@ivar raw: The I{raw} attribute list.
|
||||
@type raw: list
|
||||
"""
|
||||
def __init__(self, attributes):
|
||||
"""
|
||||
@param attributes: A list of attributes
|
||||
@type attributes: list
|
||||
"""
|
||||
self.raw = attributes
|
||||
|
||||
def real(self):
|
||||
"""
|
||||
Get list of I{real} attributes which exclude xs and xml attributes.
|
||||
@return: A list of I{real} attributes.
|
||||
@rtype: I{generator}
|
||||
"""
|
||||
for a in self.raw:
|
||||
if self.skip(a): continue
|
||||
yield a
|
||||
|
||||
def rlen(self):
|
||||
"""
|
||||
Get the number of I{real} attributes which exclude xs and xml attributes.
|
||||
@return: A count of I{real} attributes.
|
||||
@rtype: L{int}
|
||||
"""
|
||||
n = 0
|
||||
for a in self.real():
|
||||
n += 1
|
||||
return n
|
||||
|
||||
def lang(self):
|
||||
"""
|
||||
Get list of I{filtered} attributes which exclude xs.
|
||||
@return: A list of I{filtered} attributes.
|
||||
@rtype: I{generator}
|
||||
"""
|
||||
for a in self.raw:
|
||||
if a.qname() == 'xml:lang':
|
||||
return a.value
|
||||
return None
|
||||
|
||||
def skip(self, attr):
|
||||
"""
|
||||
Get whether to skip (filter-out) the specified attribute.
|
||||
@param attr: An attribute.
|
||||
@type attr: I{Attribute}
|
||||
@return: True if should be skipped.
|
||||
@rtype: bool
|
||||
"""
|
||||
ns = attr.namespace()
|
||||
skip = (
|
||||
Namespace.xmlns[1],
|
||||
'http://schemas.xmlsoap.org/soap/encoding/',
|
||||
'http://schemas.xmlsoap.org/soap/envelope/',
|
||||
'http://www.w3.org/2003/05/soap-envelope',
|
||||
)
|
||||
return ( Namespace.xs(ns) or ns[1] in skip )
|
@ -1,41 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides basic unmarshaller classes.
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
from suds import *
|
||||
from suds.umx import *
|
||||
from suds.umx.core import Core
|
||||
|
||||
|
||||
class Basic(Core):
|
||||
"""
|
||||
A object builder (unmarshaller).
|
||||
"""
|
||||
|
||||
def process(self, node):
|
||||
"""
|
||||
Process an object graph representation of the xml I{node}.
|
||||
@param node: An XML tree.
|
||||
@type node: L{sax.element.Element}
|
||||
@return: A suds object.
|
||||
@rtype: L{Object}
|
||||
"""
|
||||
content = Content(node)
|
||||
return Core.process(self, content)
|
@ -1,214 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides base classes for XML->object I{unmarshalling}.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.umx import *
|
||||
from suds.umx.attrlist import AttrList
|
||||
from suds.sax.text import Text
|
||||
from suds.sudsobject import Factory, merge
|
||||
|
||||
|
||||
reserved = {'class':'cls', 'def':'dfn'}
|
||||
|
||||
|
||||
class Core:
|
||||
"""
|
||||
The abstract XML I{node} unmarshaller. This class provides the
|
||||
I{core} unmarshalling functionality.
|
||||
"""
|
||||
|
||||
def process(self, content):
|
||||
"""
|
||||
Process an object graph representation of the xml I{node}.
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
@return: A suds object.
|
||||
@rtype: L{Object}
|
||||
"""
|
||||
self.reset()
|
||||
return self.append(content)
|
||||
|
||||
def append(self, content):
|
||||
"""
|
||||
Process the specified node and convert the XML document into
|
||||
a I{suds} L{object}.
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
@return: A I{append-result} tuple as: (L{Object}, I{value})
|
||||
@rtype: I{append-result}
|
||||
@note: This is not the proper entry point.
|
||||
@see: L{process()}
|
||||
"""
|
||||
self.start(content)
|
||||
self.append_attributes(content)
|
||||
self.append_children(content)
|
||||
self.append_text(content)
|
||||
self.end(content)
|
||||
return self.postprocess(content)
|
||||
|
||||
def postprocess(self, content):
|
||||
"""
|
||||
Perform final processing of the resulting data structure as follows:
|
||||
- Mixed values (children and text) will have a result of the I{content.node}.
|
||||
- Simi-simple values (attributes, no-children and text) will have a result of a
|
||||
property object.
|
||||
- Simple values (no-attributes, no-children with text nodes) will have a string
|
||||
result equal to the value of the content.node.getText().
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
@return: The post-processed result.
|
||||
@rtype: I{any}
|
||||
"""
|
||||
node = content.node
|
||||
if len(node.children) and node.hasText():
|
||||
return node
|
||||
attributes = AttrList(node.attributes)
|
||||
if attributes.rlen() and \
|
||||
not len(node.children) and \
|
||||
node.hasText():
|
||||
p = Factory.property(node.name, node.getText())
|
||||
return merge(content.data, p)
|
||||
if len(content.data):
|
||||
return content.data
|
||||
lang = attributes.lang()
|
||||
if content.node.isnil():
|
||||
return None
|
||||
if not len(node.children) and content.text is None:
|
||||
if self.nillable(content):
|
||||
return None
|
||||
else:
|
||||
return Text('', lang=lang)
|
||||
if isinstance(content.text, basestring):
|
||||
return Text(content.text, lang=lang)
|
||||
else:
|
||||
return content.text
|
||||
|
||||
def append_attributes(self, content):
|
||||
"""
|
||||
Append attribute nodes into L{Content.data}.
|
||||
Attributes in the I{schema} or I{xml} namespaces are skipped.
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
attributes = AttrList(content.node.attributes)
|
||||
for attr in attributes.real():
|
||||
name = attr.name
|
||||
value = attr.value
|
||||
self.append_attribute(name, value, content)
|
||||
|
||||
def append_attribute(self, name, value, content):
|
||||
"""
|
||||
Append an attribute name/value into L{Content.data}.
|
||||
@param name: The attribute name
|
||||
@type name: basestring
|
||||
@param value: The attribute's value
|
||||
@type value: basestring
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
key = name
|
||||
key = '_%s' % reserved.get(key, key)
|
||||
setattr(content.data, key, value)
|
||||
|
||||
def append_children(self, content):
|
||||
"""
|
||||
Append child nodes into L{Content.data}
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
for child in content.node:
|
||||
cont = Content(child)
|
||||
cval = self.append(cont)
|
||||
key = reserved.get(child.name, child.name)
|
||||
if key in content.data:
|
||||
v = getattr(content.data, key)
|
||||
if isinstance(v, list):
|
||||
v.append(cval)
|
||||
else:
|
||||
setattr(content.data, key, [v, cval])
|
||||
continue
|
||||
if self.multi_occurrence(cont):
|
||||
if cval is None:
|
||||
setattr(content.data, key, [])
|
||||
else:
|
||||
setattr(content.data, key, [cval,])
|
||||
else:
|
||||
setattr(content.data, key, cval)
|
||||
|
||||
def append_text(self, content):
|
||||
"""
|
||||
Append text nodes into L{Content.data}
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
if content.node.hasText():
|
||||
content.text = content.node.getText()
|
||||
|
||||
def reset(self):
|
||||
pass
|
||||
|
||||
def start(self, content):
|
||||
"""
|
||||
Processing on I{node} has started. Build and return
|
||||
the proper object.
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
@return: A subclass of Object.
|
||||
@rtype: L{Object}
|
||||
"""
|
||||
content.data = Factory.object(content.node.name)
|
||||
|
||||
def end(self, content):
|
||||
"""
|
||||
Processing on I{node} has ended.
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
pass
|
||||
|
||||
def single_occurrence(self, content):
|
||||
"""
|
||||
Get whether the content has at most a single occurrence (not a list).
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
@return: True if content has at most a single occurrence, else False.
|
||||
@rtype: boolean
|
||||
'"""
|
||||
return not self.multi_occurrence(content)
|
||||
|
||||
def multi_occurrence(self, content):
|
||||
"""
|
||||
Get whether the content has more than one occurrence (a list).
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
@return: True if content has more than one occurrence, else False.
|
||||
@rtype: boolean
|
||||
'"""
|
||||
return False
|
||||
|
||||
def nillable(self, content):
|
||||
"""
|
||||
Get whether the object is nillable.
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
@return: True if nillable, else False
|
||||
@rtype: boolean
|
||||
'"""
|
||||
return False
|
@ -1,126 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides soap encoded unmarshaller classes.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.umx import *
|
||||
from suds.umx.typed import Typed
|
||||
from suds.sax import Namespace
|
||||
|
||||
|
||||
#
|
||||
# Add encoded extensions
|
||||
# aty = The soap (section 5) encoded array type.
|
||||
#
|
||||
Content.extensions.append('aty')
|
||||
|
||||
|
||||
class Encoded(Typed):
|
||||
"""
|
||||
A SOAP section (5) encoding unmarshaller.
|
||||
This marshaller supports rpc/encoded soap styles.
|
||||
"""
|
||||
|
||||
def start(self, content):
|
||||
#
|
||||
# Grab the array type and continue
|
||||
#
|
||||
self.setaty(content)
|
||||
Typed.start(self, content)
|
||||
|
||||
def end(self, content):
|
||||
#
|
||||
# Squash soap encoded arrays into python lists. This is
|
||||
# also where we insure that empty arrays are represented
|
||||
# as empty python lists.
|
||||
#
|
||||
aty = content.aty
|
||||
if aty is not None:
|
||||
self.promote(content)
|
||||
return Typed.end(self, content)
|
||||
|
||||
def postprocess(self, content):
|
||||
#
|
||||
# Ensure proper rendering of empty arrays.
|
||||
#
|
||||
if content.aty is None:
|
||||
return Typed.postprocess(self, content)
|
||||
else:
|
||||
return content.data
|
||||
|
||||
def setaty(self, content):
|
||||
"""
|
||||
Grab the (aty) soap-enc:arrayType and attach it to the
|
||||
content for proper array processing later in end().
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
@return: self
|
||||
@rtype: L{Encoded}
|
||||
"""
|
||||
name = 'arrayType'
|
||||
ns = (None, 'http://schemas.xmlsoap.org/soap/encoding/')
|
||||
aty = content.node.get(name, ns)
|
||||
if aty is not None:
|
||||
content.aty = aty
|
||||
parts = aty.split('[')
|
||||
ref = parts[0]
|
||||
if len(parts) == 2:
|
||||
self.applyaty(content, ref)
|
||||
else:
|
||||
pass # (2) dimensional array
|
||||
return self
|
||||
|
||||
def applyaty(self, content, xty):
|
||||
"""
|
||||
Apply the type referenced in the I{arrayType} to the content
|
||||
(child nodes) of the array. Each element (node) in the array
|
||||
that does not have an explicit xsi:type attribute is given one
|
||||
based on the I{arrayType}.
|
||||
@param content: An array content.
|
||||
@type content: L{Content}
|
||||
@param xty: The XSI type reference.
|
||||
@type xty: str
|
||||
@return: self
|
||||
@rtype: L{Encoded}
|
||||
"""
|
||||
name = 'type'
|
||||
ns = Namespace.xsins
|
||||
parent = content.node
|
||||
for child in parent.getChildren():
|
||||
ref = child.get(name, ns)
|
||||
if ref is None:
|
||||
parent.addPrefix(ns[0], ns[1])
|
||||
attr = ':'.join((ns[0], name))
|
||||
child.set(attr, xty)
|
||||
return self
|
||||
|
||||
def promote(self, content):
|
||||
"""
|
||||
Promote (replace) the content.data with the first attribute
|
||||
of the current content.data that is a I{list}. Note: the
|
||||
content.data may be empty or contain only _x attributes.
|
||||
In either case, the content.data is assigned an empty list.
|
||||
@param content: An array content.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
for n,v in content.data:
|
||||
if isinstance(v, list):
|
||||
content.data = v
|
||||
return
|
||||
content.data = []
|
@ -1,140 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Provides typed unmarshaller classes.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.umx import *
|
||||
from suds.umx.core import Core
|
||||
from suds.resolver import NodeResolver, Frame
|
||||
from suds.sudsobject import Factory
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
#
|
||||
# Add typed extensions
|
||||
# type = The expected xsd type
|
||||
# real = The 'true' XSD type
|
||||
#
|
||||
Content.extensions.append('type')
|
||||
Content.extensions.append('real')
|
||||
|
||||
|
||||
class Typed(Core):
|
||||
"""
|
||||
A I{typed} XML unmarshaller
|
||||
@ivar resolver: A schema type resolver.
|
||||
@type resolver: L{NodeResolver}
|
||||
"""
|
||||
|
||||
def __init__(self, schema):
|
||||
"""
|
||||
@param schema: A schema object.
|
||||
@type schema: L{xsd.schema.Schema}
|
||||
"""
|
||||
self.resolver = NodeResolver(schema)
|
||||
|
||||
def process(self, node, type):
|
||||
"""
|
||||
Process an object graph representation of the xml L{node}.
|
||||
@param node: An XML tree.
|
||||
@type node: L{sax.element.Element}
|
||||
@param type: The I{optional} schema type.
|
||||
@type type: L{xsd.sxbase.SchemaObject}
|
||||
@return: A suds object.
|
||||
@rtype: L{Object}
|
||||
"""
|
||||
content = Content(node)
|
||||
content.type = type
|
||||
return Core.process(self, content)
|
||||
|
||||
def reset(self):
|
||||
log.debug('reset')
|
||||
self.resolver.reset()
|
||||
|
||||
def start(self, content):
|
||||
#
|
||||
# Resolve to the schema type; build an object and setup metadata.
|
||||
#
|
||||
if content.type is None:
|
||||
found = self.resolver.find(content.node)
|
||||
if found is None:
|
||||
log.error(self.resolver.schema)
|
||||
raise TypeNotFound(content.node.qname())
|
||||
content.type = found
|
||||
else:
|
||||
known = self.resolver.known(content.node)
|
||||
frame = Frame(content.type, resolved=known)
|
||||
self.resolver.push(frame)
|
||||
real = self.resolver.top().resolved
|
||||
content.real = real
|
||||
cls_name = real.name
|
||||
if cls_name is None:
|
||||
cls_name = content.node.name
|
||||
content.data = Factory.object(cls_name)
|
||||
md = content.data.__metadata__
|
||||
md.sxtype = real
|
||||
|
||||
def end(self, content):
|
||||
self.resolver.pop()
|
||||
|
||||
def multi_occurrence(self, content):
|
||||
return content.type.multi_occurrence()
|
||||
|
||||
def nillable(self, content):
|
||||
resolved = content.type.resolve()
|
||||
return ( content.type.nillable or \
|
||||
(resolved.builtin() and resolved.nillable ) )
|
||||
|
||||
def append_attribute(self, name, value, content):
|
||||
"""
|
||||
Append an attribute name/value into L{Content.data}.
|
||||
@param name: The attribute name
|
||||
@type name: basestring
|
||||
@param value: The attribute's value
|
||||
@type value: basestring
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
type = self.resolver.findattr(name)
|
||||
if type is None:
|
||||
log.warning('attribute (%s) type, not-found', name)
|
||||
else:
|
||||
value = self.translated(value, type)
|
||||
Core.append_attribute(self, name, value, content)
|
||||
|
||||
def append_text(self, content):
|
||||
"""
|
||||
Append text nodes into L{Content.data}
|
||||
Here is where the I{true} type is used to translate the value
|
||||
into the proper python type.
|
||||
@param content: The current content being unmarshalled.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
Core.append_text(self, content)
|
||||
known = self.resolver.top().resolved
|
||||
content.text = self.translated(content.text, known)
|
||||
|
||||
def translated(self, value, type):
|
||||
""" translate using the schema type """
|
||||
if value is not None:
|
||||
resolved = type.resolve()
|
||||
return resolved.translate(value)
|
||||
return value
|
@ -1,26 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
"""
|
||||
Module containing the library's version information.
|
||||
|
||||
This version information has been extracted into a separate file so it can be
|
||||
read from the setup.py script without having to import the suds package itself.
|
||||
See the setup.py script for more detailed information.
|
||||
|
||||
"""
|
||||
|
||||
__version__ = "0.8.5"
|
||||
__build__ = ""
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,236 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The I{wsse} module provides WS-Security.
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
from suds import *
|
||||
from suds.sudsobject import Object
|
||||
from suds.sax.element import Element
|
||||
from suds.sax.date import DateTime, UtcTimezone
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
try:
|
||||
from hashlib import md5
|
||||
except ImportError:
|
||||
# Python 2.4 compatibility
|
||||
from md5 import md5
|
||||
|
||||
|
||||
dsns = \
|
||||
('ds',
|
||||
'http://www.w3.org/2000/09/xmldsig#')
|
||||
wssens = \
|
||||
('wsse',
|
||||
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd')
|
||||
wsuns = \
|
||||
('wsu',
|
||||
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd')
|
||||
wsencns = \
|
||||
('wsenc',
|
||||
'http://www.w3.org/2001/04/xmlenc#')
|
||||
|
||||
nonce_encoding_type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
|
||||
username_token_profile = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0"
|
||||
wsdigest = "%s#PasswordDigest" % username_token_profile
|
||||
wstext = "%s#PasswordText" % username_token_profile
|
||||
|
||||
|
||||
class Security(Object):
|
||||
"""
|
||||
WS-Security object.
|
||||
@ivar tokens: A list of security tokens
|
||||
@type tokens: [L{Token},...]
|
||||
@ivar signatures: A list of signatures.
|
||||
@type signatures: TBD
|
||||
@ivar references: A list of references.
|
||||
@type references: TBD
|
||||
@ivar keys: A list of encryption keys.
|
||||
@type keys: TBD
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
""" """
|
||||
Object.__init__(self)
|
||||
self.mustUnderstand = True
|
||||
self.tokens = []
|
||||
self.signatures = []
|
||||
self.references = []
|
||||
self.keys = []
|
||||
|
||||
def xml(self):
|
||||
"""
|
||||
Get xml representation of the object.
|
||||
@return: The root node.
|
||||
@rtype: L{Element}
|
||||
"""
|
||||
root = Element('Security', ns=wssens)
|
||||
root.set('mustUnderstand', str(self.mustUnderstand).lower())
|
||||
for t in self.tokens:
|
||||
root.append(t.xml())
|
||||
return root
|
||||
|
||||
|
||||
class Token(Object):
|
||||
""" I{Abstract} security token. """
|
||||
|
||||
@classmethod
|
||||
def now(cls):
|
||||
return datetime.now()
|
||||
|
||||
@classmethod
|
||||
def utc(cls):
|
||||
return datetime.utcnow().replace(tzinfo=UtcTimezone())
|
||||
|
||||
@classmethod
|
||||
def sysdate(cls):
|
||||
utc = DateTime(cls.utc())
|
||||
return str(utc)
|
||||
|
||||
def __init__(self):
|
||||
Object.__init__(self)
|
||||
|
||||
|
||||
class UsernameToken(Token):
|
||||
"""
|
||||
Represents a basic I{UsernameToken} WS-Secuirty token.
|
||||
@ivar username: A username.
|
||||
@type username: str
|
||||
@ivar password: A password.
|
||||
@type password: str
|
||||
@type password_digest: A password digest
|
||||
@ivar nonce: A set of bytes to prevent replay attacks.
|
||||
@type nonce: str
|
||||
@ivar created: The token created.
|
||||
@type created: L{datetime}
|
||||
"""
|
||||
|
||||
def __init__(self, username=None, password=None):
|
||||
"""
|
||||
@param username: A username.
|
||||
@type username: str
|
||||
@param password: A password.
|
||||
@type password: str
|
||||
"""
|
||||
Token.__init__(self)
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.nonce = None
|
||||
self.created = None
|
||||
self.password_digest = None
|
||||
self.nonce_has_encoding = False
|
||||
|
||||
def setnonceencoding(self, value=False):
|
||||
self.nonce_has_encoding = value
|
||||
|
||||
def setpassworddigest(self, passwd_digest):
|
||||
"""
|
||||
Set password digest which is a text returned by
|
||||
auth WS.
|
||||
"""
|
||||
self.password_digest = passwd_digest
|
||||
|
||||
def setnonce(self, text=None):
|
||||
"""
|
||||
Set I{nonce} which is an arbitrary set of bytes to prevent replay
|
||||
attacks.
|
||||
@param text: The nonce text value.
|
||||
Generated when I{None}.
|
||||
@type text: str
|
||||
"""
|
||||
if text is None:
|
||||
s = []
|
||||
s.append(self.username)
|
||||
s.append(self.password)
|
||||
s.append(Token.sysdate())
|
||||
m = md5()
|
||||
m.update(':'.join(s).encode('utf-8'))
|
||||
self.nonce = m.hexdigest()
|
||||
else:
|
||||
self.nonce = text
|
||||
|
||||
def setcreated(self, dt=None):
|
||||
"""
|
||||
Set I{created}.
|
||||
@param dt: The created date & time.
|
||||
Set as datetime.utc() when I{None}.
|
||||
@type dt: L{datetime}
|
||||
"""
|
||||
if dt is None:
|
||||
self.created = Token.utc()
|
||||
else:
|
||||
self.created = dt
|
||||
|
||||
def xml(self):
|
||||
"""
|
||||
Get xml representation of the object.
|
||||
@return: The root node.
|
||||
@rtype: L{Element}
|
||||
"""
|
||||
root = Element('UsernameToken', ns=wssens)
|
||||
u = Element('Username', ns=wssens)
|
||||
u.setText(self.username)
|
||||
root.append(u)
|
||||
p = Element('Password', ns=wssens)
|
||||
p.setText(self.password)
|
||||
if self.password_digest:
|
||||
p.set("Type", wsdigest)
|
||||
p.setText(self.password_digest)
|
||||
else:
|
||||
p.set("Type", wstext)
|
||||
root.append(p)
|
||||
if self.nonce is not None:
|
||||
n = Element('Nonce', ns=wssens)
|
||||
if self.nonce_has_encoding:
|
||||
n.set("EncodingType", nonce_encoding_type)
|
||||
n.setText(self.nonce)
|
||||
root.append(n)
|
||||
if self.created is not None:
|
||||
n = Element('Created', ns=wsuns)
|
||||
n.setText(str(DateTime(self.created)))
|
||||
root.append(n)
|
||||
return root
|
||||
|
||||
|
||||
class Timestamp(Token):
|
||||
"""
|
||||
Represents the I{Timestamp} WS-Secuirty token.
|
||||
@ivar created: The token created.
|
||||
@type created: L{datetime}
|
||||
@ivar expires: The token expires.
|
||||
@type expires: L{datetime}
|
||||
"""
|
||||
|
||||
def __init__(self, validity=90):
|
||||
"""
|
||||
@param validity: The time in seconds.
|
||||
@type validity: int
|
||||
"""
|
||||
Token.__init__(self)
|
||||
self.created = Token.utc()
|
||||
self.expires = self.created + timedelta(seconds=validity)
|
||||
|
||||
def xml(self):
|
||||
root = Element("Timestamp", ns=wsuns)
|
||||
created = Element('Created', ns=wsuns)
|
||||
created.setText(str(DateTime(self.created)))
|
||||
expires = Element('Expires', ns=wsuns)
|
||||
expires.setText(str(DateTime(self.expires)))
|
||||
root.append(created)
|
||||
root.append(expires)
|
||||
return root
|
@ -1,75 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
|
||||
from suds import *
|
||||
from suds.sax import Namespace, splitPrefix
|
||||
|
||||
|
||||
def qualify(ref, resolvers, defns=Namespace.default):
|
||||
"""
|
||||
Get a reference that is I{qualified} by namespace.
|
||||
@param ref: A referenced schema type name.
|
||||
@type ref: str
|
||||
@param resolvers: A list of objects to be used to resolve types.
|
||||
@type resolvers: [L{sax.element.Element},]
|
||||
@param defns: An optional target namespace used to qualify references
|
||||
when no prefix is specified.
|
||||
@type defns: A default namespace I{tuple: (prefix,uri)} used when ref not prefixed.
|
||||
@return: A qualified reference.
|
||||
@rtype: (name, namespace-uri)
|
||||
"""
|
||||
ns = None
|
||||
p, n = splitPrefix(ref)
|
||||
if p is not None:
|
||||
if not isinstance(resolvers, (list, tuple)):
|
||||
resolvers = (resolvers,)
|
||||
for r in resolvers:
|
||||
resolved = r.resolvePrefix(p)
|
||||
if resolved[1] is not None:
|
||||
ns = resolved
|
||||
break
|
||||
if ns is None:
|
||||
raise Exception('prefix (%s) not resolved' % p)
|
||||
else:
|
||||
ns = defns
|
||||
return (n, ns[1])
|
||||
|
||||
def isqref(object):
|
||||
"""
|
||||
Get whether the object is a I{qualified reference}.
|
||||
@param object: An object to be tested.
|
||||
@type object: I{any}
|
||||
@rtype: boolean
|
||||
@see: L{qualify}
|
||||
"""
|
||||
return (\
|
||||
isinstance(object, tuple) and \
|
||||
len(object) == 2 and \
|
||||
isinstance(object[0], basestring) and \
|
||||
isinstance(object[1], basestring))
|
||||
|
||||
|
||||
class Filter:
|
||||
def __init__(self, inclusive=False, *items):
|
||||
self.inclusive = inclusive
|
||||
self.items = items
|
||||
def __contains__(self, x):
|
||||
if self.inclusive:
|
||||
result = ( x in self.items )
|
||||
else:
|
||||
result = ( x not in self.items )
|
||||
return result
|
@ -1,71 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
Dependency/topological sort implementation.
|
||||
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
def dependency_sort(dependency_tree):
|
||||
"""
|
||||
Sorts items 'dependencies first' in a given dependency tree.
|
||||
|
||||
A dependency tree is a dictionary mapping an object to a collection its
|
||||
dependency objects.
|
||||
|
||||
Result is a properly sorted list of items, where each item is a 2-tuple
|
||||
containing an object and its dependency list, as given in the input
|
||||
dependency tree.
|
||||
|
||||
If B is directly or indirectly dependent on A and they are not both a part
|
||||
of the same dependency cycle (i.e. then A is neither directly nor
|
||||
indirectly dependent on B) then A needs to come before B.
|
||||
|
||||
If A and B are a part of the same dependency cycle, i.e. if they are both
|
||||
directly or indirectly dependent on each other, then it does not matter
|
||||
which comes first.
|
||||
|
||||
Any entries found listed as dependencies, but that do not have their own
|
||||
dependencies listed as well, are logged & ignored.
|
||||
|
||||
@return: The sorted items.
|
||||
@rtype: list
|
||||
|
||||
"""
|
||||
sorted = []
|
||||
processed = set()
|
||||
for key, deps in dependency_tree.iteritems():
|
||||
_sort_r(sorted, processed, key, deps, dependency_tree)
|
||||
return sorted
|
||||
|
||||
|
||||
def _sort_r(sorted, processed, key, deps, dependency_tree):
|
||||
"""Recursive topological sort implementation."""
|
||||
if key in processed:
|
||||
return
|
||||
processed.add(key)
|
||||
for dep_key in deps:
|
||||
dep_deps = dependency_tree.get(dep_key)
|
||||
if dep_deps is None:
|
||||
log.debug('"%s" not found, skipped', Repr(dep_key))
|
||||
continue
|
||||
_sort_r(sorted, processed, dep_key, dep_deps, dependency_tree)
|
||||
sorted.append((key, deps))
|
@ -1,223 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The I{doctor} module provides classes for fixing broken (sick)
|
||||
schema(s).
|
||||
"""
|
||||
|
||||
from suds.sax import Namespace
|
||||
from suds.sax.element import Element
|
||||
from suds.plugin import DocumentPlugin, DocumentContext
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Doctor:
|
||||
"""
|
||||
Schema Doctor.
|
||||
"""
|
||||
def examine(self, root):
|
||||
"""
|
||||
Examine and repair the schema (if necessary).
|
||||
@param root: A schema root element.
|
||||
@type root: L{Element}
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Practice(Doctor):
|
||||
"""
|
||||
A collection of doctors.
|
||||
@ivar doctors: A list of doctors.
|
||||
@type doctors: list
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.doctors = []
|
||||
|
||||
def add(self, doctor):
|
||||
"""
|
||||
Add a doctor to the practice
|
||||
@param doctor: A doctor to add.
|
||||
@type doctor: L{Doctor}
|
||||
"""
|
||||
self.doctors.append(doctor)
|
||||
|
||||
def examine(self, root):
|
||||
for d in self.doctors:
|
||||
d.examine(root)
|
||||
return root
|
||||
|
||||
|
||||
class TnsFilter:
|
||||
"""
|
||||
Target Namespace filter.
|
||||
@ivar tns: A list of target namespaces.
|
||||
@type tns: [str,...]
|
||||
"""
|
||||
|
||||
def __init__(self, *tns):
|
||||
"""
|
||||
@param tns: A list of target namespaces.
|
||||
@type tns: [str,...]
|
||||
"""
|
||||
self.tns = []
|
||||
self.add(*tns)
|
||||
|
||||
def add(self, *tns):
|
||||
"""
|
||||
Add I{targetNamespaces} to be added.
|
||||
@param tns: A list of target namespaces.
|
||||
@type tns: [str,...]
|
||||
"""
|
||||
self.tns += tns
|
||||
|
||||
def match(self, root, ns):
|
||||
"""
|
||||
Match by I{targetNamespace} excluding those that
|
||||
are equal to the specified namespace to prevent
|
||||
adding an import to itself.
|
||||
@param root: A schema root.
|
||||
@type root: L{Element}
|
||||
"""
|
||||
tns = root.get('targetNamespace')
|
||||
if len(self.tns):
|
||||
matched = ( tns in self.tns )
|
||||
else:
|
||||
matched = 1
|
||||
itself = ( ns == tns )
|
||||
return ( matched and not itself )
|
||||
|
||||
|
||||
class Import:
|
||||
"""
|
||||
An <xs:import/> to be applied.
|
||||
@cvar xsdns: The XSD namespace.
|
||||
@type xsdns: (p,u)
|
||||
@ivar ns: An import namespace.
|
||||
@type ns: str
|
||||
@ivar location: An optional I{schemaLocation}.
|
||||
@type location: str
|
||||
@ivar filter: A filter used to restrict application to
|
||||
a particular schema.
|
||||
@type filter: L{TnsFilter}
|
||||
"""
|
||||
|
||||
xsdns = Namespace.xsdns
|
||||
|
||||
def __init__(self, ns, location=None):
|
||||
"""
|
||||
@param ns: An import namespace.
|
||||
@type ns: str
|
||||
@param location: An optional I{schemaLocation}.
|
||||
@type location: str
|
||||
"""
|
||||
self.ns = ns
|
||||
self.location = location
|
||||
self.filter = TnsFilter()
|
||||
|
||||
def setfilter(self, filter):
|
||||
"""
|
||||
Set the filter.
|
||||
@param filter: A filter to set.
|
||||
@type filter: L{TnsFilter}
|
||||
"""
|
||||
self.filter = filter
|
||||
|
||||
def apply(self, root):
|
||||
"""
|
||||
Apply the import (rule) to the specified schema.
|
||||
If the schema does not already contain an import for the
|
||||
I{namespace} specified here, it is added.
|
||||
@param root: A schema root.
|
||||
@type root: L{Element}
|
||||
"""
|
||||
if not self.filter.match(root, self.ns):
|
||||
return
|
||||
if self.exists(root):
|
||||
return
|
||||
node = Element('import', ns=self.xsdns)
|
||||
node.set('namespace', self.ns)
|
||||
if self.location is not None:
|
||||
node.set('schemaLocation', self.location)
|
||||
log.debug('inserting: %s', node)
|
||||
root.insert(node)
|
||||
|
||||
def add(self, root):
|
||||
"""
|
||||
Add an <xs:import/> to the specified schema root.
|
||||
@param root: A schema root.
|
||||
@type root: L{Element}
|
||||
"""
|
||||
node = Element('import', ns=self.xsdns)
|
||||
node.set('namespace', self.ns)
|
||||
if self.location is not None:
|
||||
node.set('schemaLocation', self.location)
|
||||
log.debug('%s inserted', node)
|
||||
root.insert(node)
|
||||
|
||||
def exists(self, root):
|
||||
"""
|
||||
Check to see if the <xs:import/> already exists
|
||||
in the specified schema root by matching I{namespace}.
|
||||
@param root: A schema root.
|
||||
@type root: L{Element}
|
||||
"""
|
||||
for node in root.children:
|
||||
if node.name != 'import':
|
||||
continue
|
||||
ns = node.get('namespace')
|
||||
if self.ns == ns:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
class ImportDoctor(Doctor, DocumentPlugin):
|
||||
"""
|
||||
Doctor used to fix missing imports.
|
||||
@ivar imports: A list of imports to apply.
|
||||
@type imports: [L{Import},...]
|
||||
"""
|
||||
|
||||
def __init__(self, *imports):
|
||||
self.imports = []
|
||||
self.add(*imports)
|
||||
|
||||
def add(self, *imports):
|
||||
"""
|
||||
Add a namespace to be checked.
|
||||
@param imports: A list of L{Import} objects.
|
||||
@type imports: [L{Import},..]
|
||||
"""
|
||||
self.imports += imports
|
||||
|
||||
def examine(self, node):
|
||||
for imp in self.imports:
|
||||
imp.apply(node)
|
||||
|
||||
def parsed(self, context):
|
||||
node = context.document
|
||||
# xsd root
|
||||
if node.name == 'schema' and Namespace.xsd(node.namespace()):
|
||||
self.examine(node)
|
||||
return
|
||||
# look deeper
|
||||
context = DocumentContext()
|
||||
for child in node:
|
||||
context.document = child
|
||||
self.parsed(context)
|
@ -1,208 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The I{query} module defines a class for performing schema queries.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.sudsobject import *
|
||||
from suds.xsd import qualify, isqref
|
||||
from suds.xsd.sxbuiltin import Factory
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Query(Object):
|
||||
"""
|
||||
Schema query base class.
|
||||
|
||||
"""
|
||||
def __init__(self, ref=None):
|
||||
"""
|
||||
@param ref: The schema reference being queried.
|
||||
@type ref: qref
|
||||
"""
|
||||
Object.__init__(self)
|
||||
self.id = objid(self)
|
||||
self.ref = ref
|
||||
self.history = []
|
||||
self.resolved = False
|
||||
if not isqref(self.ref):
|
||||
raise Exception('%s, must be qref' % tostr(self.ref))
|
||||
|
||||
def execute(self, schema):
|
||||
"""
|
||||
Execute this query using the specified schema.
|
||||
@param schema: The schema associated with the query. The schema is used
|
||||
by the query to search for items.
|
||||
@type schema: L{schema.Schema}
|
||||
@return: The item matching the search criteria.
|
||||
@rtype: L{sxbase.SchemaObject}
|
||||
"""
|
||||
raise Exception('not-implemented by subclass')
|
||||
|
||||
def filter(self, result):
|
||||
"""
|
||||
Filter the specified result based on query criteria.
|
||||
@param result: A potential result.
|
||||
@type result: L{sxbase.SchemaObject}
|
||||
@return: True if result should be excluded.
|
||||
@rtype: boolean
|
||||
"""
|
||||
if result is None:
|
||||
return True
|
||||
reject = ( result in self.history )
|
||||
if reject:
|
||||
log.debug('result %s, rejected by\n%s', Repr(result), self)
|
||||
return reject
|
||||
|
||||
def result(self, result):
|
||||
"""
|
||||
Query result post processing.
|
||||
@param result: A query result.
|
||||
@type result: L{sxbase.SchemaObject}
|
||||
"""
|
||||
if result is None:
|
||||
log.debug('%s, not-found', self.ref)
|
||||
return
|
||||
if self.resolved:
|
||||
result = result.resolve()
|
||||
log.debug('%s, found as: %s', self.ref, Repr(result))
|
||||
self.history.append(result)
|
||||
return result
|
||||
|
||||
|
||||
class BlindQuery(Query):
|
||||
"""
|
||||
Schema query class that I{blindly} searches for a reference in the
|
||||
specified schema. It may be used to find Elements and Types but will match
|
||||
on an Element first. This query will also find builtins.
|
||||
|
||||
"""
|
||||
def execute(self, schema):
|
||||
if schema.builtin(self.ref):
|
||||
name = self.ref[0]
|
||||
b = Factory.create(schema, name)
|
||||
log.debug('%s, found builtin (%s)', self.id, name)
|
||||
return b
|
||||
result = None
|
||||
for d in (schema.elements, schema.types):
|
||||
result = d.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = None
|
||||
else:
|
||||
break
|
||||
if result is None:
|
||||
eq = ElementQuery(self.ref)
|
||||
eq.history = self.history
|
||||
result = eq.execute(schema)
|
||||
return self.result(result)
|
||||
|
||||
|
||||
class TypeQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for Type references in the specified
|
||||
schema. Matches on root types only.
|
||||
|
||||
"""
|
||||
def execute(self, schema):
|
||||
if schema.builtin(self.ref):
|
||||
name = self.ref[0]
|
||||
b = Factory.create(schema, name)
|
||||
log.debug('%s, found builtin (%s)', self.id, name)
|
||||
return b
|
||||
result = schema.types.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = None
|
||||
return self.result(result)
|
||||
|
||||
|
||||
class GroupQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for Group references in the specified
|
||||
schema.
|
||||
|
||||
"""
|
||||
def execute(self, schema):
|
||||
result = schema.groups.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = None
|
||||
return self.result(result)
|
||||
|
||||
|
||||
class AttrQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for Attribute references in the specified
|
||||
schema. Matches on root Attribute by qname first, then searches deeper into
|
||||
the document.
|
||||
|
||||
"""
|
||||
def execute(self, schema):
|
||||
result = schema.attributes.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = self.__deepsearch(schema)
|
||||
return self.result(result)
|
||||
|
||||
def __deepsearch(self, schema):
|
||||
from suds.xsd.sxbasic import Attribute
|
||||
result = None
|
||||
for e in schema.all:
|
||||
result = e.find(self.ref, (Attribute,))
|
||||
if self.filter(result):
|
||||
result = None
|
||||
else:
|
||||
break
|
||||
return result
|
||||
|
||||
|
||||
class AttrGroupQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for attributeGroup references in the
|
||||
specified schema.
|
||||
|
||||
"""
|
||||
def execute(self, schema):
|
||||
result = schema.agrps.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = None
|
||||
return self.result(result)
|
||||
|
||||
|
||||
class ElementQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for Element references in the specified
|
||||
schema. Matches on root Elements by qname first, then searches deeper into
|
||||
the document.
|
||||
|
||||
"""
|
||||
def execute(self, schema):
|
||||
result = schema.elements.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = self.__deepsearch(schema)
|
||||
return self.result(result)
|
||||
|
||||
def __deepsearch(self, schema):
|
||||
from suds.xsd.sxbasic import Element
|
||||
result = None
|
||||
for e in schema.all:
|
||||
result = e.find(self.ref, (Element,))
|
||||
if self.filter(result):
|
||||
result = None
|
||||
else:
|
||||
break
|
||||
return result
|
@ -1,208 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the (LGPL) GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Library Lesser General Public License for more details at
|
||||
# ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The I{query} module defines a class for performing schema queries.
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.sudsobject import *
|
||||
from suds.xsd import qualify, isqref
|
||||
from suds.xsd.sxbuiltin import Factory
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Query(Object):
|
||||
"""
|
||||
Schema query base class.
|
||||
|
||||
"""
|
||||
def __init__(self, ref=None):
|
||||
"""
|
||||
@param ref: The schema reference being queried.
|
||||
@type ref: qref
|
||||
"""
|
||||
Object.__init__(self)
|
||||
self.id = objid(self)
|
||||
self.ref = ref
|
||||
self.history = []
|
||||
self.resolved = False
|
||||
if not isqref(self.ref):
|
||||
raise Exception('%s, must be qref' % tostr(self.ref))
|
||||
|
||||
def execute(self, schema):
|
||||
"""
|
||||
Execute this query using the specified schema.
|
||||
@param schema: The schema associated with the query. The schema is used
|
||||
by the query to search for items.
|
||||
@type schema: L{schema.Schema}
|
||||
@return: The item matching the search criteria.
|
||||
@rtype: L{sxbase.SchemaObject}
|
||||
"""
|
||||
raise Exception, 'not-implemented by subclass'
|
||||
|
||||
def filter(self, result):
|
||||
"""
|
||||
Filter the specified result based on query criteria.
|
||||
@param result: A potential result.
|
||||
@type result: L{sxbase.SchemaObject}
|
||||
@return: True if result should be excluded.
|
||||
@rtype: boolean
|
||||
"""
|
||||
if result is None:
|
||||
return True
|
||||
reject = ( result in self.history )
|
||||
if reject:
|
||||
log.debug('result %s, rejected by\n%s', Repr(result), self)
|
||||
return reject
|
||||
|
||||
def result(self, result):
|
||||
"""
|
||||
Query result post processing.
|
||||
@param result: A query result.
|
||||
@type result: L{sxbase.SchemaObject}
|
||||
"""
|
||||
if result is None:
|
||||
log.debug('%s, not-found', self.ref)
|
||||
return
|
||||
if self.resolved:
|
||||
result = result.resolve()
|
||||
log.debug('%s, found as: %s', self.ref, Repr(result))
|
||||
self.history.append(result)
|
||||
return result
|
||||
|
||||
|
||||
class BlindQuery(Query):
|
||||
"""
|
||||
Schema query class that I{blindly} searches for a reference in the
|
||||
specified schema. It may be used to find Elements and Types but will match
|
||||
on an Element first. This query will also find builtins.
|
||||
|
||||
"""
|
||||
def execute(self, schema):
|
||||
if schema.builtin(self.ref):
|
||||
name = self.ref[0]
|
||||
b = Factory.create(schema, name)
|
||||
log.debug('%s, found builtin (%s)', self.id, name)
|
||||
return b
|
||||
result = None
|
||||
for d in (schema.elements, schema.types):
|
||||
result = d.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = None
|
||||
else:
|
||||
break
|
||||
if result is None:
|
||||
eq = ElementQuery(self.ref)
|
||||
eq.history = self.history
|
||||
result = eq.execute(schema)
|
||||
return self.result(result)
|
||||
|
||||
|
||||
class TypeQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for Type references in the specified
|
||||
schema. Matches on root types only.
|
||||
|
||||
"""
|
||||
def execute(self, schema):
|
||||
if schema.builtin(self.ref):
|
||||
name = self.ref[0]
|
||||
b = Factory.create(schema, name)
|
||||
log.debug('%s, found builtin (%s)', self.id, name)
|
||||
return b
|
||||
result = schema.types.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = None
|
||||
return self.result(result)
|
||||
|
||||
|
||||
class GroupQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for Group references in the specified
|
||||
schema.
|
||||
|
||||
"""
|
||||
def execute(self, schema):
|
||||
result = schema.groups.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = None
|
||||
return self.result(result)
|
||||
|
||||
|
||||
class AttrQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for Attribute references in the specified
|
||||
schema. Matches on root Attribute by qname first, then searches deeper into
|
||||
the document.
|
||||
|
||||
"""
|
||||
def execute(self, schema):
|
||||
result = schema.attributes.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = self.__deepsearch(schema)
|
||||
return self.result(result)
|
||||
|
||||
def __deepsearch(self, schema):
|
||||
from suds.xsd.sxbasic import Attribute
|
||||
result = None
|
||||
for e in schema.all:
|
||||
result = e.find(self.ref, (Attribute,))
|
||||
if self.filter(result):
|
||||
result = None
|
||||
else:
|
||||
break
|
||||
return result
|
||||
|
||||
|
||||
class AttrGroupQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for attributeGroup references in the
|
||||
specified schema.
|
||||
|
||||
"""
|
||||
def execute(self, schema):
|
||||
result = schema.agrps.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = None
|
||||
return self.result(result)
|
||||
|
||||
|
||||
class ElementQuery(Query):
|
||||
"""
|
||||
Schema query class that searches for Element references in the specified
|
||||
schema. Matches on root Elements by qname first, then searches deeper into
|
||||
the document.
|
||||
|
||||
"""
|
||||
def execute(self, schema):
|
||||
result = schema.elements.get(self.ref)
|
||||
if self.filter(result):
|
||||
result = self.__deepsearch(schema)
|
||||
return self.result(result)
|
||||
|
||||
def __deepsearch(self, schema):
|
||||
from suds.xsd.sxbasic import Element
|
||||
result = None
|
||||
for e in schema.all:
|
||||
result = e.find(self.ref, (Element,))
|
||||
if self.filter(result):
|
||||
result = None
|
||||
else:
|
||||
break
|
||||
return result
|
@ -1,464 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""
|
||||
The I{schema} module provides an intelligent representation of an XSD schema.
|
||||
The I{raw} model is the XML tree and the I{model} is a denormalized,
|
||||
objectified and intelligent view of the schema. Most of the I{value-add}
|
||||
provided by the model is centered around transparent referenced type resolution
|
||||
and targeted denormalization.
|
||||
|
||||
"""
|
||||
|
||||
from suds import *
|
||||
from suds.xsd import *
|
||||
from suds.xsd.depsort import dependency_sort
|
||||
from suds.xsd.sxbuiltin import *
|
||||
from suds.xsd.sxbase import SchemaObject
|
||||
from suds.xsd.sxbasic import Factory as BasicFactory
|
||||
from suds.xsd.sxbuiltin import Factory as BuiltinFactory
|
||||
from suds.sax import splitPrefix, Namespace
|
||||
from suds.sax.element import Element
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class SchemaCollection(UnicodeMixin):
|
||||
"""
|
||||
A collection of schema objects.
|
||||
|
||||
This class is needed because a WSDL may contain more then one <schema/>
|
||||
node.
|
||||
|
||||
@ivar wsdl: A WSDL object.
|
||||
@type wsdl: L{suds.wsdl.Definitions}
|
||||
@ivar children: A list contained schemas.
|
||||
@type children: [L{Schema},...]
|
||||
@ivar namespaces: A dictionary of contained schemas by namespace.
|
||||
@type namespaces: {str: L{Schema}}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, wsdl):
|
||||
"""
|
||||
@param wsdl: A WSDL object.
|
||||
@type wsdl: L{suds.wsdl.Definitions}
|
||||
|
||||
"""
|
||||
self.wsdl = wsdl
|
||||
self.children = []
|
||||
self.namespaces = {}
|
||||
|
||||
def add(self, schema):
|
||||
"""
|
||||
Add a schema node to the collection. Schema(s) within the same target
|
||||
namespace are consolidated.
|
||||
|
||||
@param schema: A schema object.
|
||||
@type schema: (L{Schema})
|
||||
|
||||
"""
|
||||
key = schema.tns[1]
|
||||
existing = self.namespaces.get(key)
|
||||
if existing is None:
|
||||
self.children.append(schema)
|
||||
self.namespaces[key] = schema
|
||||
else:
|
||||
existing.root.children += schema.root.children
|
||||
existing.root.nsprefixes.update(schema.root.nsprefixes)
|
||||
|
||||
def load(self, options, loaded_schemata):
|
||||
"""
|
||||
Load schema objects for the root nodes.
|
||||
- de-reference schemas
|
||||
- merge schemas
|
||||
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
@param loaded_schemata: Already loaded schemata cache (URL --> Schema).
|
||||
@type loaded_schemata: dict
|
||||
@return: The merged schema.
|
||||
@rtype: L{Schema}
|
||||
|
||||
"""
|
||||
if options.autoblend:
|
||||
self.autoblend()
|
||||
for child in self.children:
|
||||
child.build()
|
||||
for child in self.children:
|
||||
child.open_imports(options, loaded_schemata)
|
||||
for child in self.children:
|
||||
child.dereference()
|
||||
log.debug("loaded:\n%s", self)
|
||||
merged = self.merge()
|
||||
log.debug("MERGED:\n%s", merged)
|
||||
return merged
|
||||
|
||||
def autoblend(self):
|
||||
"""
|
||||
Ensure that all schemas within the collection import each other which
|
||||
has a blending effect.
|
||||
|
||||
@return: self
|
||||
@rtype: L{SchemaCollection}
|
||||
|
||||
"""
|
||||
namespaces = self.namespaces.keys()
|
||||
for s in self.children:
|
||||
for ns in namespaces:
|
||||
tns = s.root.get("targetNamespace")
|
||||
if tns == ns:
|
||||
continue
|
||||
for imp in s.root.getChildren("import"):
|
||||
if imp.get("namespace") == ns:
|
||||
continue
|
||||
imp = Element("import", ns=Namespace.xsdns)
|
||||
imp.set("namespace", ns)
|
||||
s.root.append(imp)
|
||||
return self
|
||||
|
||||
def locate(self, ns):
|
||||
"""
|
||||
Find a schema by namespace. Only the URI portion of the namespace is
|
||||
compared to each schema's I{targetNamespace}.
|
||||
|
||||
@param ns: A namespace.
|
||||
@type ns: (prefix, URI)
|
||||
@return: The schema matching the namespace, else None.
|
||||
@rtype: L{Schema}
|
||||
|
||||
"""
|
||||
return self.namespaces.get(ns[1])
|
||||
|
||||
def merge(self):
|
||||
"""
|
||||
Merge contained schemas into one.
|
||||
|
||||
@return: The merged schema.
|
||||
@rtype: L{Schema}
|
||||
|
||||
"""
|
||||
if self.children:
|
||||
schema = self.children[0]
|
||||
for s in self.children[1:]:
|
||||
schema.merge(s)
|
||||
return schema
|
||||
|
||||
def __len__(self):
|
||||
return len(self.children)
|
||||
|
||||
def __unicode__(self):
|
||||
result = ["\nschema collection"]
|
||||
for s in self.children:
|
||||
result.append(s.str(1))
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
class Schema(UnicodeMixin):
|
||||
"""
|
||||
The schema is an objectification of a <schema/> (XSD) definition. It
|
||||
provides inspection, lookup and type resolution.
|
||||
|
||||
@ivar root: The root node.
|
||||
@type root: L{sax.element.Element}
|
||||
@ivar baseurl: The I{base} URL for this schema.
|
||||
@type baseurl: str
|
||||
@ivar container: A schema collection containing this schema.
|
||||
@type container: L{SchemaCollection}
|
||||
@ivar children: A list of direct top level children.
|
||||
@type children: [L{SchemaObject},...]
|
||||
@ivar all: A list of all (includes imported) top level children.
|
||||
@type all: [L{SchemaObject},...]
|
||||
@ivar types: A schema types cache.
|
||||
@type types: {name:L{SchemaObject}}
|
||||
@ivar imports: A list of import objects.
|
||||
@type imports: [L{SchemaObject},...]
|
||||
@ivar elements: A list of <element/> objects.
|
||||
@type elements: [L{SchemaObject},...]
|
||||
@ivar attributes: A list of <attribute/> objects.
|
||||
@type attributes: [L{SchemaObject},...]
|
||||
@ivar groups: A list of group objects.
|
||||
@type groups: [L{SchemaObject},...]
|
||||
@ivar agrps: A list of attribute group objects.
|
||||
@type agrps: [L{SchemaObject},...]
|
||||
@ivar form_qualified: The flag indicating: (@elementFormDefault).
|
||||
@type form_qualified: bool
|
||||
|
||||
"""
|
||||
|
||||
Tag = "schema"
|
||||
|
||||
def __init__(self, root, baseurl, options, loaded_schemata=None,
|
||||
container=None):
|
||||
"""
|
||||
@param root: The XML root.
|
||||
@type root: L{sax.element.Element}
|
||||
@param baseurl: The base URL used for importing.
|
||||
@type baseurl: basestring
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
@param loaded_schemata: An optional already loaded schemata cache (URL
|
||||
--> Schema).
|
||||
@type loaded_schemata: dict
|
||||
@param container: An optional container.
|
||||
@type container: L{SchemaCollection}
|
||||
|
||||
"""
|
||||
self.root = root
|
||||
self.id = objid(self)
|
||||
self.tns = self.mktns()
|
||||
self.baseurl = baseurl
|
||||
self.container = container
|
||||
self.children = []
|
||||
self.all = []
|
||||
self.types = {}
|
||||
self.imports = []
|
||||
self.elements = {}
|
||||
self.attributes = {}
|
||||
self.groups = {}
|
||||
self.agrps = {}
|
||||
if options.doctor is not None:
|
||||
options.doctor.examine(root)
|
||||
form = self.root.get("elementFormDefault")
|
||||
self.form_qualified = form == "qualified"
|
||||
|
||||
# If we have a container, that container is going to take care of
|
||||
# finishing our build for us in parallel with building all the other
|
||||
# schemata in that container. That allows the different schema within
|
||||
# the same container to freely reference each other.
|
||||
#TODO: check whether this container content build parallelization is
|
||||
# really necessary or if we can simply build our top-level WSDL
|
||||
# contained schemata one by one as they are loaded
|
||||
if container is None:
|
||||
if loaded_schemata is None:
|
||||
loaded_schemata = {}
|
||||
loaded_schemata[baseurl] = self
|
||||
#TODO: It seems like this build() step can be done for each schema
|
||||
# on its own instead of letting the container do it. Building our
|
||||
# XSD schema objects should not require any external schema
|
||||
# information and even references between XSD schema objects within
|
||||
# the same schema can not be established until all the XSD schema
|
||||
# objects have been built. The only reason I can see right now why
|
||||
# this step has been placed under container control is so our
|
||||
# container (a SchemaCollection instance) can add some additional
|
||||
# XML elements to our schema before our XSD schema object entities
|
||||
# get built, but there is bound to be a cleaner way to do this,
|
||||
# similar to how we support such XML modifications in suds plugins.
|
||||
self.build()
|
||||
self.open_imports(options, loaded_schemata)
|
||||
log.debug("built:\n%s", self)
|
||||
self.dereference()
|
||||
log.debug("dereferenced:\n%s", self)
|
||||
|
||||
def mktns(self):
|
||||
"""
|
||||
Make the schema's target namespace.
|
||||
|
||||
@return: namespace representation of the schema's targetNamespace
|
||||
value.
|
||||
@rtype: (prefix, URI)
|
||||
|
||||
"""
|
||||
tns = self.root.get("targetNamespace")
|
||||
tns_prefix = None
|
||||
if tns is not None:
|
||||
tns_prefix = self.root.findPrefix(tns)
|
||||
return tns_prefix, tns
|
||||
|
||||
def build(self):
|
||||
"""
|
||||
Build the schema (object graph) using the root node using the factory.
|
||||
- Build the graph.
|
||||
- Collate the children.
|
||||
|
||||
"""
|
||||
self.children = BasicFactory.build(self.root, self)
|
||||
collated = BasicFactory.collate(self.children)
|
||||
self.children = collated[0]
|
||||
self.attributes = collated[2]
|
||||
self.imports = collated[1]
|
||||
self.elements = collated[3]
|
||||
self.types = collated[4]
|
||||
self.groups = collated[5]
|
||||
self.agrps = collated[6]
|
||||
|
||||
def merge(self, schema):
|
||||
"""
|
||||
Merge the schema contents.
|
||||
|
||||
Only objects not already contained in this schema's collections are
|
||||
merged. This provides support for bidirectional imports producing
|
||||
cyclic includes.
|
||||
|
||||
@returns: self
|
||||
@rtype: L{Schema}
|
||||
|
||||
"""
|
||||
for item in schema.attributes.items():
|
||||
if item[0] in self.attributes:
|
||||
continue
|
||||
self.all.append(item[1])
|
||||
self.attributes[item[0]] = item[1]
|
||||
for item in schema.elements.items():
|
||||
if item[0] in self.elements:
|
||||
continue
|
||||
self.all.append(item[1])
|
||||
self.elements[item[0]] = item[1]
|
||||
for item in schema.types.items():
|
||||
if item[0] in self.types:
|
||||
continue
|
||||
self.all.append(item[1])
|
||||
self.types[item[0]] = item[1]
|
||||
for item in schema.groups.items():
|
||||
if item[0] in self.groups:
|
||||
continue
|
||||
self.all.append(item[1])
|
||||
self.groups[item[0]] = item[1]
|
||||
for item in schema.agrps.items():
|
||||
if item[0] in self.agrps:
|
||||
continue
|
||||
self.all.append(item[1])
|
||||
self.agrps[item[0]] = item[1]
|
||||
schema.merged = True
|
||||
return self
|
||||
|
||||
def open_imports(self, options, loaded_schemata):
|
||||
"""
|
||||
Instruct all contained L{sxbasic.Import} children to import all of
|
||||
their referenced schemas. The imported schema contents are I{merged}
|
||||
in.
|
||||
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
@param loaded_schemata: Already loaded schemata cache (URL --> Schema).
|
||||
@type loaded_schemata: dict
|
||||
|
||||
"""
|
||||
for imp in self.imports:
|
||||
imported = imp.open(options, loaded_schemata)
|
||||
if imported is None:
|
||||
continue
|
||||
imported.open_imports(options, loaded_schemata)
|
||||
log.debug("imported:\n%s", imported)
|
||||
self.merge(imported)
|
||||
|
||||
def dereference(self):
|
||||
"""Instruct all children to perform dereferencing."""
|
||||
all = []
|
||||
indexes = {}
|
||||
for child in self.children:
|
||||
child.content(all)
|
||||
dependencies = {}
|
||||
for x in all:
|
||||
x.qualify()
|
||||
midx, deps = x.dependencies()
|
||||
dependencies[x] = deps
|
||||
indexes[x] = midx
|
||||
for x, deps in dependency_sort(dependencies):
|
||||
midx = indexes.get(x)
|
||||
if midx is None:
|
||||
continue
|
||||
d = deps[midx]
|
||||
log.debug("(%s) merging %s <== %s", self.tns[1], Repr(x), Repr(d))
|
||||
x.merge(d)
|
||||
|
||||
def locate(self, ns):
|
||||
"""
|
||||
Find a schema by namespace. Only the URI portion of the namespace is
|
||||
compared to each schema's I{targetNamespace}. The request is passed on
|
||||
to the container.
|
||||
|
||||
@param ns: A namespace.
|
||||
@type ns: (prefix, URI)
|
||||
@return: The schema matching the namespace, else None.
|
||||
@rtype: L{Schema}
|
||||
|
||||
"""
|
||||
if self.container is not None:
|
||||
return self.container.locate(ns)
|
||||
|
||||
def custom(self, ref, context=None):
|
||||
"""
|
||||
Get whether the specified reference is B{not} an (xs) builtin.
|
||||
|
||||
@param ref: A str or qref.
|
||||
@type ref: (str|qref)
|
||||
@return: True if B{not} a builtin, else False.
|
||||
@rtype: bool
|
||||
|
||||
"""
|
||||
return ref is None or not self.builtin(ref, context)
|
||||
|
||||
def builtin(self, ref, context=None):
|
||||
"""
|
||||
Get whether the specified reference is an (xs) builtin.
|
||||
|
||||
@param ref: A str or qref.
|
||||
@type ref: (str|qref)
|
||||
@return: True if builtin, else False.
|
||||
@rtype: bool
|
||||
|
||||
"""
|
||||
w3 = "http://www.w3.org"
|
||||
try:
|
||||
if isqref(ref):
|
||||
ns = ref[1]
|
||||
return ref[0] in Factory.tags and ns.startswith(w3)
|
||||
if context is None:
|
||||
context = self.root
|
||||
prefix = splitPrefix(ref)[0]
|
||||
prefixes = context.findPrefixes(w3, "startswith")
|
||||
return prefix in prefixes and ref[0] in Factory.tags
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def instance(self, root, baseurl, loaded_schemata, options):
|
||||
"""
|
||||
Create and return an new schema object using the specified I{root} and
|
||||
I{URL}.
|
||||
|
||||
@param root: A schema root node.
|
||||
@type root: L{sax.element.Element}
|
||||
@param baseurl: A base URL.
|
||||
@type baseurl: str
|
||||
@param loaded_schemata: Already loaded schemata cache (URL --> Schema).
|
||||
@type loaded_schemata: dict
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
@return: The newly created schema object.
|
||||
@rtype: L{Schema}
|
||||
@note: This is only used by Import children.
|
||||
|
||||
"""
|
||||
return Schema(root, baseurl, options, loaded_schemata)
|
||||
|
||||
def str(self, indent=0):
|
||||
tab = "%*s" % (indent * 3, "")
|
||||
result = []
|
||||
result.append("%s%s" % (tab, self.id))
|
||||
result.append("%s(raw)" % (tab,))
|
||||
result.append(self.root.str(indent + 1))
|
||||
result.append("%s(model)" % (tab,))
|
||||
for c in self.children:
|
||||
result.append(c.str(indent + 1))
|
||||
result.append("")
|
||||
return "\n".join(result)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s tns="%s"/>' % (self.id, self.tns[1])
|
||||
|
||||
def __unicode__(self):
|
||||
return self.str()
|
@ -1,748 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""I{Base} classes representing XSD schema objects."""
|
||||
|
||||
from suds import *
|
||||
from suds.xsd import *
|
||||
from suds.sax.element import Element
|
||||
from suds.sax import Namespace
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class SchemaObject(UnicodeMixin):
|
||||
"""
|
||||
A schema object is an extension to object with schema awareness.
|
||||
|
||||
@ivar root: The XML root element.
|
||||
@type root: L{Element}
|
||||
@ivar schema: The schema containing this object.
|
||||
@type schema: L{schema.Schema}
|
||||
@ivar form_qualified: A flag indicating that @elementFormDefault has a
|
||||
value of I{qualified}.
|
||||
@type form_qualified: boolean
|
||||
@ivar nillable: A flag indicating that @nillable has a value of I{true}.
|
||||
@type nillable: boolean
|
||||
@ivar default: The default value.
|
||||
@type default: object
|
||||
@ivar rawchildren: A list raw of all children.
|
||||
@type rawchildren: [L{SchemaObject},...]
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def prepend(cls, d, s, filter=Filter()):
|
||||
"""
|
||||
Prepend B{s}ource XSD schema objects to the B{d}estination list.
|
||||
|
||||
B{filter} is used to decide which objects to prepend and which to skip.
|
||||
|
||||
@param d: The destination list.
|
||||
@type d: list
|
||||
@param s: The source list.
|
||||
@type s: list
|
||||
@param filter: A filter allowing items to be prepended.
|
||||
@type filter: L{Filter}
|
||||
|
||||
"""
|
||||
i = 0
|
||||
for x in s:
|
||||
if x in filter:
|
||||
d.insert(i, x)
|
||||
i += 1
|
||||
|
||||
@classmethod
|
||||
def append(cls, d, s, filter=Filter()):
|
||||
"""
|
||||
Append B{s}ource XSD schema objects to the B{d}estination list.
|
||||
|
||||
B{filter} is used to decide which objects to append and which to skip.
|
||||
|
||||
@param d: The destination list.
|
||||
@type d: list
|
||||
@param s: The source list.
|
||||
@type s: list
|
||||
@param filter: A filter that allows items to be appended.
|
||||
@type filter: L{Filter}
|
||||
|
||||
"""
|
||||
for item in s:
|
||||
if item in filter:
|
||||
d.append(item)
|
||||
|
||||
def __init__(self, schema, root):
|
||||
"""
|
||||
@param schema: The containing schema.
|
||||
@type schema: L{schema.Schema}
|
||||
@param root: The XML root node.
|
||||
@type root: L{Element}
|
||||
|
||||
"""
|
||||
self.schema = schema
|
||||
self.root = root
|
||||
self.id = objid(self)
|
||||
self.name = root.get("name")
|
||||
self.qname = (self.name, schema.tns[1])
|
||||
self.min = root.get("minOccurs")
|
||||
self.max = root.get("maxOccurs")
|
||||
self.type = root.get("type")
|
||||
self.ref = root.get("ref")
|
||||
self.form_qualified = schema.form_qualified
|
||||
self.nillable = False
|
||||
self.default = root.get("default")
|
||||
self.rawchildren = []
|
||||
|
||||
def attributes(self, filter=Filter()):
|
||||
"""
|
||||
Get only the attribute content.
|
||||
|
||||
@param filter: A filter to constrain the result.
|
||||
@type filter: L{Filter}
|
||||
@return: A list of (attr, ancestry) tuples.
|
||||
@rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..]
|
||||
|
||||
"""
|
||||
result = []
|
||||
for child, ancestry in self:
|
||||
if child.isattr() and child in filter:
|
||||
result.append((child, ancestry))
|
||||
return result
|
||||
|
||||
def children(self, filter=Filter()):
|
||||
"""
|
||||
Get only the I{direct} or non-attribute content.
|
||||
|
||||
@param filter: A filter to constrain the result.
|
||||
@type filter: L{Filter}
|
||||
@return: A list tuples: (child, ancestry)
|
||||
@rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..]
|
||||
|
||||
"""
|
||||
result = []
|
||||
for child, ancestry in self:
|
||||
if not child.isattr() and child in filter:
|
||||
result.append((child, ancestry))
|
||||
return result
|
||||
|
||||
def get_attribute(self, name):
|
||||
"""
|
||||
Get (find) an attribute by name.
|
||||
|
||||
@param name: A attribute name.
|
||||
@type name: str
|
||||
@return: A tuple: the requested (attribute, ancestry).
|
||||
@rtype: (L{SchemaObject}, [L{SchemaObject},..])
|
||||
|
||||
"""
|
||||
for child, ancestry in self.attributes():
|
||||
if child.name == name:
|
||||
return child, ancestry
|
||||
return None, []
|
||||
|
||||
def get_child(self, name):
|
||||
"""
|
||||
Get (find) a I{non-attribute} child by name.
|
||||
|
||||
@param name: A child name.
|
||||
@type name: str
|
||||
@return: A tuple: the requested (child, ancestry).
|
||||
@rtype: (L{SchemaObject}, [L{SchemaObject},..])
|
||||
|
||||
"""
|
||||
for child, ancestry in self.children():
|
||||
if child.any() or child.name == name:
|
||||
return child, ancestry
|
||||
return None, []
|
||||
|
||||
def namespace(self, prefix=None):
|
||||
"""
|
||||
Get this property's namespace.
|
||||
|
||||
@param prefix: The default prefix.
|
||||
@type prefix: str
|
||||
@return: The schema's target namespace.
|
||||
@rtype: (I{prefix}, I{URI})
|
||||
|
||||
"""
|
||||
ns = self.schema.tns
|
||||
if ns[0] is None:
|
||||
ns = (prefix, ns[1])
|
||||
return ns
|
||||
|
||||
def default_namespace(self):
|
||||
return self.root.defaultNamespace()
|
||||
|
||||
def multi_occurrence(self):
|
||||
"""
|
||||
Get whether the node has multiple occurrences, i.e. is a I{collection}.
|
||||
|
||||
@return: True if it has, False if it has at most 1 occurrence.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
max = self.max
|
||||
if max is None:
|
||||
return False
|
||||
if max.isdigit():
|
||||
return int(max) > 1
|
||||
return max == "unbounded"
|
||||
|
||||
def optional(self):
|
||||
"""
|
||||
Get whether this type is optional.
|
||||
|
||||
@return: True if optional, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return self.min == "0"
|
||||
|
||||
def required(self):
|
||||
"""
|
||||
Get whether this type is required.
|
||||
|
||||
@return: True if required, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return not self.optional()
|
||||
|
||||
def resolve(self, nobuiltin=False):
|
||||
"""
|
||||
Resolve the node's type reference and return the referenced type node.
|
||||
|
||||
Only XSD schema objects that actually support 'having a type' custom
|
||||
implement this interface while others simply resolve as themselves.
|
||||
|
||||
@param nobuiltin: Flag indicating whether resolving to an external XSD
|
||||
built-in type should not be allowed.
|
||||
@return: The resolved (true) type.
|
||||
@rtype: L{SchemaObject}
|
||||
"""
|
||||
return self
|
||||
|
||||
def sequence(self):
|
||||
"""
|
||||
Get whether this is an <xsd:sequence/>.
|
||||
|
||||
@return: True if <xsd:sequence/>, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def xslist(self):
|
||||
"""
|
||||
Get whether this is an <xsd:list/>.
|
||||
|
||||
@return: True if <xsd:list/>, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def all(self):
|
||||
"""
|
||||
Get whether this is an <xsd:all/>.
|
||||
|
||||
@return: True if <xsd:all/>, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def choice(self):
|
||||
"""
|
||||
Get whether this is an <xsd:choice/>.
|
||||
|
||||
@return: True if <xsd:choice/>, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def any(self):
|
||||
"""
|
||||
Get whether this is an <xsd:any/>.
|
||||
|
||||
@return: True if <xsd:any/>, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def builtin(self):
|
||||
"""
|
||||
Get whether this is a built-in schema-instance XSD type.
|
||||
|
||||
@return: True if a built-in type, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def enum(self):
|
||||
"""
|
||||
Get whether this is a simple-type containing an enumeration.
|
||||
|
||||
@return: True if enumeration, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def isattr(self):
|
||||
"""
|
||||
Get whether the object is a schema I{attribute} definition.
|
||||
|
||||
@return: True if an attribute, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def extension(self):
|
||||
"""
|
||||
Get whether the object is an extension of another type.
|
||||
|
||||
@return: True if an extension, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
"""
|
||||
Get whether the object is an restriction of another type.
|
||||
|
||||
@return: True if a restriction, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def mixed(self):
|
||||
"""Get whether the object has I{mixed} content."""
|
||||
return False
|
||||
|
||||
def find(self, qref, classes=[], ignore=None):
|
||||
"""
|
||||
Find a referenced type in self or children. Return None if not found.
|
||||
|
||||
Qualified references for all schema objects checked in this search will
|
||||
be added to the set of ignored qualified references to avoid the find
|
||||
operation going into an infinite loop in case of recursively defined
|
||||
structures.
|
||||
|
||||
@param qref: A qualified reference.
|
||||
@type qref: qref
|
||||
@param classes: A collection of classes used to qualify the match.
|
||||
@type classes: Collection(I{class},...), e.g. [I(class),...]
|
||||
@param ignore: A set of qualified references to ignore in this search.
|
||||
@type ignore: {qref,...}
|
||||
@return: The referenced type.
|
||||
@rtype: L{SchemaObject}
|
||||
@see: L{qualify()}
|
||||
|
||||
"""
|
||||
if not len(classes):
|
||||
classes = (self.__class__,)
|
||||
if ignore is None:
|
||||
ignore = set()
|
||||
if self.qname in ignore:
|
||||
return
|
||||
ignore.add(self.qname)
|
||||
if self.qname == qref and self.__class__ in classes:
|
||||
return self
|
||||
for c in self.rawchildren:
|
||||
p = c.find(qref, classes, ignore=ignore)
|
||||
if p is not None:
|
||||
return p
|
||||
|
||||
def translate(self, value, topython=True):
|
||||
"""
|
||||
Translate between an XSD type values and Python objects.
|
||||
|
||||
When converting a Python object to an XSD type value the operation may
|
||||
return any Python object whose string representation matches the
|
||||
desired XSD type value.
|
||||
|
||||
@param value: A value to translate.
|
||||
@type value: str if topython is True; any Python object otherwise
|
||||
@param topython: Flag indicating the translation direction.
|
||||
@type topython: bool
|
||||
@return: The converted I{language} type.
|
||||
|
||||
"""
|
||||
return value
|
||||
|
||||
def childtags(self):
|
||||
"""
|
||||
Get a list of valid child tag names.
|
||||
|
||||
@return: A list of child tag names.
|
||||
@rtype: [str,...]
|
||||
|
||||
"""
|
||||
return ()
|
||||
|
||||
def dependencies(self):
|
||||
"""
|
||||
Get a list of dependencies for dereferencing.
|
||||
|
||||
@return: A merge dependency index and a list of dependencies.
|
||||
@rtype: (int, [L{SchemaObject},...])
|
||||
|
||||
"""
|
||||
return None, []
|
||||
|
||||
def autoqualified(self):
|
||||
"""
|
||||
The list of I{auto} qualified attribute values.
|
||||
|
||||
Qualification means to convert values into I{qref}.
|
||||
|
||||
@return: A list of attribute names.
|
||||
@rtype: list
|
||||
|
||||
"""
|
||||
return ["type", "ref"]
|
||||
|
||||
def qualify(self):
|
||||
"""
|
||||
Convert reference attribute values into a I{qref}.
|
||||
|
||||
Constructed I{qref} uses the default document namespace. Since many
|
||||
WSDL schemas are written improperly: when the document does not define
|
||||
its default namespace, the schema target namespace is used to qualify
|
||||
references.
|
||||
|
||||
"""
|
||||
defns = self.root.defaultNamespace()
|
||||
if Namespace.none(defns):
|
||||
defns = self.schema.tns
|
||||
for a in self.autoqualified():
|
||||
ref = getattr(self, a)
|
||||
if ref is None:
|
||||
continue
|
||||
if isqref(ref):
|
||||
continue
|
||||
qref = qualify(ref, self.root, defns)
|
||||
log.debug("%s, convert %s='%s' to %s", self.id, a, ref, qref)
|
||||
setattr(self, a, qref)
|
||||
|
||||
def merge(self, other):
|
||||
"""Merge another object as needed."""
|
||||
other.qualify()
|
||||
for n in ("default", "max", "min", "name", "nillable", "qname",
|
||||
"type"):
|
||||
if getattr(self, n) is not None:
|
||||
continue
|
||||
v = getattr(other, n)
|
||||
if v is None:
|
||||
continue
|
||||
setattr(self, n, v)
|
||||
|
||||
def content(self, collection=None, filter=Filter(), history=None):
|
||||
"""
|
||||
Get a I{flattened} list of this node's contents.
|
||||
|
||||
@param collection: A list to fill.
|
||||
@type collection: list
|
||||
@param filter: A filter used to constrain the result.
|
||||
@type filter: L{Filter}
|
||||
@param history: The history list used to prevent cyclic dependency.
|
||||
@type history: list
|
||||
@return: The filled list.
|
||||
@rtype: list
|
||||
|
||||
"""
|
||||
if collection is None:
|
||||
collection = []
|
||||
if history is None:
|
||||
history = []
|
||||
if self in history:
|
||||
return collection
|
||||
history.append(self)
|
||||
if self in filter:
|
||||
collection.append(self)
|
||||
for c in self.rawchildren:
|
||||
c.content(collection, filter, history)
|
||||
history.pop()
|
||||
return collection
|
||||
|
||||
def str(self, indent=0, history=None):
|
||||
"""
|
||||
Get a string representation of this object.
|
||||
|
||||
@param indent: The indent.
|
||||
@type indent: int
|
||||
@return: A string.
|
||||
@rtype: str
|
||||
|
||||
"""
|
||||
if history is None:
|
||||
history = []
|
||||
if self in history:
|
||||
return "%s ..." % Repr(self)
|
||||
history.append(self)
|
||||
tab = "%*s" % (indent * 3, "")
|
||||
result = ["%s<%s" % (tab, self.id)]
|
||||
for n in self.description():
|
||||
if not hasattr(self, n):
|
||||
continue
|
||||
v = getattr(self, n)
|
||||
if v is None:
|
||||
continue
|
||||
result.append(' %s="%s"' % (n, v))
|
||||
if len(self):
|
||||
result.append(">")
|
||||
for c in self.rawchildren:
|
||||
result.append("\n")
|
||||
result.append(c.str(indent+1, history[:]))
|
||||
if c.isattr():
|
||||
result.append("@")
|
||||
result.append("\n%s" % (tab,))
|
||||
result.append("</%s>" % (self.__class__.__name__,))
|
||||
else:
|
||||
result.append(" />")
|
||||
return "".join(result)
|
||||
|
||||
def description(self):
|
||||
"""
|
||||
Get the names used for repr() and str() description.
|
||||
|
||||
@return: A dictionary of relevant attributes.
|
||||
@rtype: [str,...]
|
||||
|
||||
"""
|
||||
return ()
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.str())
|
||||
|
||||
def __repr__(self):
|
||||
s = []
|
||||
s.append("<%s" % (self.id,))
|
||||
for n in self.description():
|
||||
if not hasattr(self, n):
|
||||
continue
|
||||
v = getattr(self, n)
|
||||
if v is None:
|
||||
continue
|
||||
s.append(' %s="%s"' % (n, v))
|
||||
s.append(" />")
|
||||
return "".join(s)
|
||||
|
||||
def __len__(self):
|
||||
n = 0
|
||||
for x in self:
|
||||
n += 1
|
||||
return n
|
||||
|
||||
def __iter__(self):
|
||||
return Iter(self)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
Returns a contained schema object referenced by its 0-based index.
|
||||
|
||||
Returns None if such an object does not exist.
|
||||
|
||||
"""
|
||||
i = 0
|
||||
for c in self:
|
||||
if i == index:
|
||||
return c
|
||||
i += 1
|
||||
|
||||
|
||||
class Iter:
|
||||
"""
|
||||
The content iterator - used to iterate the L{Content} children.
|
||||
|
||||
The iterator provides a I{view} of the children that is free of container
|
||||
elements such as <xsd::all/>, <xsd:choice/> or <xsd:sequence/>.
|
||||
|
||||
@ivar stack: A stack used to control nesting.
|
||||
@type stack: list
|
||||
|
||||
"""
|
||||
|
||||
class Frame:
|
||||
"""A content iterator frame."""
|
||||
|
||||
def __init__(self, sx):
|
||||
"""
|
||||
@param sx: A schema object.
|
||||
@type sx: L{SchemaObject}
|
||||
|
||||
"""
|
||||
self.sx = sx
|
||||
self.items = sx.rawchildren
|
||||
self.index = 0
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
Get the I{next} item in the frame's collection.
|
||||
|
||||
@return: The next item or None
|
||||
@rtype: L{SchemaObject}
|
||||
|
||||
"""
|
||||
if self.index < len(self.items):
|
||||
result = self.items[self.index]
|
||||
self.index += 1
|
||||
return result
|
||||
|
||||
def __init__(self, sx):
|
||||
"""
|
||||
@param sx: A schema object.
|
||||
@type sx: L{SchemaObject}
|
||||
|
||||
"""
|
||||
self.stack = []
|
||||
self.push(sx)
|
||||
|
||||
def push(self, sx):
|
||||
"""
|
||||
Create a frame and push the specified object.
|
||||
|
||||
@param sx: A schema object to push.
|
||||
@type sx: L{SchemaObject}
|
||||
|
||||
"""
|
||||
self.stack.append(Iter.Frame(sx))
|
||||
|
||||
def pop(self):
|
||||
"""
|
||||
Pop the I{top} frame.
|
||||
|
||||
@return: The popped frame.
|
||||
@rtype: L{Frame}
|
||||
@raise StopIteration: when stack is empty.
|
||||
|
||||
"""
|
||||
if self.stack:
|
||||
return self.stack.pop()
|
||||
raise StopIteration()
|
||||
|
||||
def top(self):
|
||||
"""
|
||||
Get the I{top} frame.
|
||||
|
||||
@return: The top frame.
|
||||
@rtype: L{Frame}
|
||||
@raise StopIteration: when stack is empty.
|
||||
|
||||
"""
|
||||
if self.stack:
|
||||
return self.stack[-1]
|
||||
raise StopIteration()
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
Get the next item.
|
||||
|
||||
@return: A tuple: the next (child, ancestry).
|
||||
@rtype: (L{SchemaObject}, [L{SchemaObject},..])
|
||||
@raise StopIteration: A the end.
|
||||
|
||||
"""
|
||||
frame = self.top()
|
||||
while True:
|
||||
result = next(frame)
|
||||
if result is None:
|
||||
self.pop()
|
||||
return next(self)
|
||||
if isinstance(result, Content):
|
||||
ancestry = [f.sx for f in self.stack]
|
||||
return result, ancestry
|
||||
self.push(result)
|
||||
return next(self)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
|
||||
class XBuiltin(SchemaObject):
|
||||
"""Represents a built-in XSD schema <xsd:*/> node."""
|
||||
|
||||
def __init__(self, schema, name):
|
||||
"""
|
||||
@param schema: The containing schema.
|
||||
@type schema: L{schema.Schema}
|
||||
|
||||
"""
|
||||
root = Element(name)
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.name = name
|
||||
self.nillable = True
|
||||
|
||||
def namespace(self, prefix=None):
|
||||
return Namespace.xsdns
|
||||
|
||||
def builtin(self):
|
||||
return True
|
||||
|
||||
|
||||
class Content(SchemaObject):
|
||||
"""XSD schema objects representing real XML document content."""
|
||||
pass
|
||||
|
||||
|
||||
class NodeFinder:
|
||||
"""
|
||||
Find nodes based on flexable criteria.
|
||||
|
||||
I{matcher} may be any object implementing a match(n) method.
|
||||
|
||||
@ivar matcher: An object used as criteria for match.
|
||||
@type matcher: I{any}.match(n)
|
||||
@ivar limit: Limit the number of matches. 0=unlimited.
|
||||
@type limit: int
|
||||
|
||||
"""
|
||||
def __init__(self, matcher, limit=0):
|
||||
"""
|
||||
@param matcher: An object used as criteria for match.
|
||||
@type matcher: I{any}.match(n)
|
||||
@param limit: Limit the number of matches. 0=unlimited.
|
||||
@type limit: int
|
||||
|
||||
"""
|
||||
self.matcher = matcher
|
||||
self.limit = limit
|
||||
|
||||
def find(self, node, list):
|
||||
"""
|
||||
Traverse the tree looking for matches.
|
||||
|
||||
@param node: A node to match on.
|
||||
@type node: L{SchemaObject}
|
||||
@param list: A list to fill.
|
||||
@type list: list
|
||||
|
||||
"""
|
||||
if self.matcher.match(node):
|
||||
list.append(node)
|
||||
self.limit -= 1
|
||||
if self.limit == 0:
|
||||
return
|
||||
for c in node.rawchildren:
|
||||
self.find(c, list)
|
||||
return self
|
@ -1,748 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""I{Base} classes representing XSD schema objects."""
|
||||
|
||||
from suds import *
|
||||
from suds.xsd import *
|
||||
from suds.sax.element import Element
|
||||
from suds.sax import Namespace
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class SchemaObject(UnicodeMixin):
|
||||
"""
|
||||
A schema object is an extension to object with schema awareness.
|
||||
|
||||
@ivar root: The XML root element.
|
||||
@type root: L{Element}
|
||||
@ivar schema: The schema containing this object.
|
||||
@type schema: L{schema.Schema}
|
||||
@ivar form_qualified: A flag indicating that @elementFormDefault has a
|
||||
value of I{qualified}.
|
||||
@type form_qualified: boolean
|
||||
@ivar nillable: A flag indicating that @nillable has a value of I{true}.
|
||||
@type nillable: boolean
|
||||
@ivar default: The default value.
|
||||
@type default: object
|
||||
@ivar rawchildren: A list raw of all children.
|
||||
@type rawchildren: [L{SchemaObject},...]
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def prepend(cls, d, s, filter=Filter()):
|
||||
"""
|
||||
Prepend B{s}ource XSD schema objects to the B{d}estination list.
|
||||
|
||||
B{filter} is used to decide which objects to prepend and which to skip.
|
||||
|
||||
@param d: The destination list.
|
||||
@type d: list
|
||||
@param s: The source list.
|
||||
@type s: list
|
||||
@param filter: A filter allowing items to be prepended.
|
||||
@type filter: L{Filter}
|
||||
|
||||
"""
|
||||
i = 0
|
||||
for x in s:
|
||||
if x in filter:
|
||||
d.insert(i, x)
|
||||
i += 1
|
||||
|
||||
@classmethod
|
||||
def append(cls, d, s, filter=Filter()):
|
||||
"""
|
||||
Append B{s}ource XSD schema objects to the B{d}estination list.
|
||||
|
||||
B{filter} is used to decide which objects to append and which to skip.
|
||||
|
||||
@param d: The destination list.
|
||||
@type d: list
|
||||
@param s: The source list.
|
||||
@type s: list
|
||||
@param filter: A filter that allows items to be appended.
|
||||
@type filter: L{Filter}
|
||||
|
||||
"""
|
||||
for item in s:
|
||||
if item in filter:
|
||||
d.append(item)
|
||||
|
||||
def __init__(self, schema, root):
|
||||
"""
|
||||
@param schema: The containing schema.
|
||||
@type schema: L{schema.Schema}
|
||||
@param root: The XML root node.
|
||||
@type root: L{Element}
|
||||
|
||||
"""
|
||||
self.schema = schema
|
||||
self.root = root
|
||||
self.id = objid(self)
|
||||
self.name = root.get("name")
|
||||
self.qname = (self.name, schema.tns[1])
|
||||
self.min = root.get("minOccurs")
|
||||
self.max = root.get("maxOccurs")
|
||||
self.type = root.get("type")
|
||||
self.ref = root.get("ref")
|
||||
self.form_qualified = schema.form_qualified
|
||||
self.nillable = False
|
||||
self.default = root.get("default")
|
||||
self.rawchildren = []
|
||||
|
||||
def attributes(self, filter=Filter()):
|
||||
"""
|
||||
Get only the attribute content.
|
||||
|
||||
@param filter: A filter to constrain the result.
|
||||
@type filter: L{Filter}
|
||||
@return: A list of (attr, ancestry) tuples.
|
||||
@rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..]
|
||||
|
||||
"""
|
||||
result = []
|
||||
for child, ancestry in self:
|
||||
if child.isattr() and child in filter:
|
||||
result.append((child, ancestry))
|
||||
return result
|
||||
|
||||
def children(self, filter=Filter()):
|
||||
"""
|
||||
Get only the I{direct} or non-attribute content.
|
||||
|
||||
@param filter: A filter to constrain the result.
|
||||
@type filter: L{Filter}
|
||||
@return: A list tuples: (child, ancestry)
|
||||
@rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..]
|
||||
|
||||
"""
|
||||
result = []
|
||||
for child, ancestry in self:
|
||||
if not child.isattr() and child in filter:
|
||||
result.append((child, ancestry))
|
||||
return result
|
||||
|
||||
def get_attribute(self, name):
|
||||
"""
|
||||
Get (find) an attribute by name.
|
||||
|
||||
@param name: A attribute name.
|
||||
@type name: str
|
||||
@return: A tuple: the requested (attribute, ancestry).
|
||||
@rtype: (L{SchemaObject}, [L{SchemaObject},..])
|
||||
|
||||
"""
|
||||
for child, ancestry in self.attributes():
|
||||
if child.name == name:
|
||||
return child, ancestry
|
||||
return None, []
|
||||
|
||||
def get_child(self, name):
|
||||
"""
|
||||
Get (find) a I{non-attribute} child by name.
|
||||
|
||||
@param name: A child name.
|
||||
@type name: str
|
||||
@return: A tuple: the requested (child, ancestry).
|
||||
@rtype: (L{SchemaObject}, [L{SchemaObject},..])
|
||||
|
||||
"""
|
||||
for child, ancestry in self.children():
|
||||
if child.any() or child.name == name:
|
||||
return child, ancestry
|
||||
return None, []
|
||||
|
||||
def namespace(self, prefix=None):
|
||||
"""
|
||||
Get this property's namespace.
|
||||
|
||||
@param prefix: The default prefix.
|
||||
@type prefix: str
|
||||
@return: The schema's target namespace.
|
||||
@rtype: (I{prefix}, I{URI})
|
||||
|
||||
"""
|
||||
ns = self.schema.tns
|
||||
if ns[0] is None:
|
||||
ns = (prefix, ns[1])
|
||||
return ns
|
||||
|
||||
def default_namespace(self):
|
||||
return self.root.defaultNamespace()
|
||||
|
||||
def multi_occurrence(self):
|
||||
"""
|
||||
Get whether the node has multiple occurrences, i.e. is a I{collection}.
|
||||
|
||||
@return: True if it has, False if it has at most 1 occurrence.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
max = self.max
|
||||
if max is None:
|
||||
return False
|
||||
if max.isdigit():
|
||||
return int(max) > 1
|
||||
return max == "unbounded"
|
||||
|
||||
def optional(self):
|
||||
"""
|
||||
Get whether this type is optional.
|
||||
|
||||
@return: True if optional, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return self.min == "0"
|
||||
|
||||
def required(self):
|
||||
"""
|
||||
Get whether this type is required.
|
||||
|
||||
@return: True if required, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return not self.optional()
|
||||
|
||||
def resolve(self, nobuiltin=False):
|
||||
"""
|
||||
Resolve the node's type reference and return the referenced type node.
|
||||
|
||||
Only XSD schema objects that actually support 'having a type' custom
|
||||
implement this interface while others simply resolve as themselves.
|
||||
|
||||
@param nobuiltin: Flag indicating whether resolving to an external XSD
|
||||
built-in type should not be allowed.
|
||||
@return: The resolved (true) type.
|
||||
@rtype: L{SchemaObject}
|
||||
"""
|
||||
return self
|
||||
|
||||
def sequence(self):
|
||||
"""
|
||||
Get whether this is an <xsd:sequence/>.
|
||||
|
||||
@return: True if <xsd:sequence/>, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def xslist(self):
|
||||
"""
|
||||
Get whether this is an <xsd:list/>.
|
||||
|
||||
@return: True if <xsd:list/>, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def all(self):
|
||||
"""
|
||||
Get whether this is an <xsd:all/>.
|
||||
|
||||
@return: True if <xsd:all/>, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def choice(self):
|
||||
"""
|
||||
Get whether this is an <xsd:choice/>.
|
||||
|
||||
@return: True if <xsd:choice/>, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def any(self):
|
||||
"""
|
||||
Get whether this is an <xsd:any/>.
|
||||
|
||||
@return: True if <xsd:any/>, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def builtin(self):
|
||||
"""
|
||||
Get whether this is a built-in schema-instance XSD type.
|
||||
|
||||
@return: True if a built-in type, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def enum(self):
|
||||
"""
|
||||
Get whether this is a simple-type containing an enumeration.
|
||||
|
||||
@return: True if enumeration, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def isattr(self):
|
||||
"""
|
||||
Get whether the object is a schema I{attribute} definition.
|
||||
|
||||
@return: True if an attribute, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def extension(self):
|
||||
"""
|
||||
Get whether the object is an extension of another type.
|
||||
|
||||
@return: True if an extension, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
"""
|
||||
Get whether the object is an restriction of another type.
|
||||
|
||||
@return: True if a restriction, else False.
|
||||
@rtype: boolean
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def mixed(self):
|
||||
"""Get whether the object has I{mixed} content."""
|
||||
return False
|
||||
|
||||
def find(self, qref, classes=[], ignore=None):
|
||||
"""
|
||||
Find a referenced type in self or children. Return None if not found.
|
||||
|
||||
Qualified references for all schema objects checked in this search will
|
||||
be added to the set of ignored qualified references to avoid the find
|
||||
operation going into an infinite loop in case of recursively defined
|
||||
structures.
|
||||
|
||||
@param qref: A qualified reference.
|
||||
@type qref: qref
|
||||
@param classes: A collection of classes used to qualify the match.
|
||||
@type classes: Collection(I{class},...), e.g. [I(class),...]
|
||||
@param ignore: A set of qualified references to ignore in this search.
|
||||
@type ignore: {qref,...}
|
||||
@return: The referenced type.
|
||||
@rtype: L{SchemaObject}
|
||||
@see: L{qualify()}
|
||||
|
||||
"""
|
||||
if not len(classes):
|
||||
classes = (self.__class__,)
|
||||
if ignore is None:
|
||||
ignore = set()
|
||||
if self.qname in ignore:
|
||||
return
|
||||
ignore.add(self.qname)
|
||||
if self.qname == qref and self.__class__ in classes:
|
||||
return self
|
||||
for c in self.rawchildren:
|
||||
p = c.find(qref, classes, ignore=ignore)
|
||||
if p is not None:
|
||||
return p
|
||||
|
||||
def translate(self, value, topython=True):
|
||||
"""
|
||||
Translate between an XSD type values and Python objects.
|
||||
|
||||
When converting a Python object to an XSD type value the operation may
|
||||
return any Python object whose string representation matches the
|
||||
desired XSD type value.
|
||||
|
||||
@param value: A value to translate.
|
||||
@type value: str if topython is True; any Python object otherwise
|
||||
@param topython: Flag indicating the translation direction.
|
||||
@type topython: bool
|
||||
@return: The converted I{language} type.
|
||||
|
||||
"""
|
||||
return value
|
||||
|
||||
def childtags(self):
|
||||
"""
|
||||
Get a list of valid child tag names.
|
||||
|
||||
@return: A list of child tag names.
|
||||
@rtype: [str,...]
|
||||
|
||||
"""
|
||||
return ()
|
||||
|
||||
def dependencies(self):
|
||||
"""
|
||||
Get a list of dependencies for dereferencing.
|
||||
|
||||
@return: A merge dependency index and a list of dependencies.
|
||||
@rtype: (int, [L{SchemaObject},...])
|
||||
|
||||
"""
|
||||
return None, []
|
||||
|
||||
def autoqualified(self):
|
||||
"""
|
||||
The list of I{auto} qualified attribute values.
|
||||
|
||||
Qualification means to convert values into I{qref}.
|
||||
|
||||
@return: A list of attribute names.
|
||||
@rtype: list
|
||||
|
||||
"""
|
||||
return ["type", "ref"]
|
||||
|
||||
def qualify(self):
|
||||
"""
|
||||
Convert reference attribute values into a I{qref}.
|
||||
|
||||
Constructed I{qref} uses the default document namespace. Since many
|
||||
WSDL schemas are written improperly: when the document does not define
|
||||
its default namespace, the schema target namespace is used to qualify
|
||||
references.
|
||||
|
||||
"""
|
||||
defns = self.root.defaultNamespace()
|
||||
if Namespace.none(defns):
|
||||
defns = self.schema.tns
|
||||
for a in self.autoqualified():
|
||||
ref = getattr(self, a)
|
||||
if ref is None:
|
||||
continue
|
||||
if isqref(ref):
|
||||
continue
|
||||
qref = qualify(ref, self.root, defns)
|
||||
log.debug("%s, convert %s='%s' to %s", self.id, a, ref, qref)
|
||||
setattr(self, a, qref)
|
||||
|
||||
def merge(self, other):
|
||||
"""Merge another object as needed."""
|
||||
other.qualify()
|
||||
for n in ("default", "max", "min", "name", "nillable", "qname",
|
||||
"type"):
|
||||
if getattr(self, n) is not None:
|
||||
continue
|
||||
v = getattr(other, n)
|
||||
if v is None:
|
||||
continue
|
||||
setattr(self, n, v)
|
||||
|
||||
def content(self, collection=None, filter=Filter(), history=None):
|
||||
"""
|
||||
Get a I{flattened} list of this node's contents.
|
||||
|
||||
@param collection: A list to fill.
|
||||
@type collection: list
|
||||
@param filter: A filter used to constrain the result.
|
||||
@type filter: L{Filter}
|
||||
@param history: The history list used to prevent cyclic dependency.
|
||||
@type history: list
|
||||
@return: The filled list.
|
||||
@rtype: list
|
||||
|
||||
"""
|
||||
if collection is None:
|
||||
collection = []
|
||||
if history is None:
|
||||
history = []
|
||||
if self in history:
|
||||
return collection
|
||||
history.append(self)
|
||||
if self in filter:
|
||||
collection.append(self)
|
||||
for c in self.rawchildren:
|
||||
c.content(collection, filter, history)
|
||||
history.pop()
|
||||
return collection
|
||||
|
||||
def str(self, indent=0, history=None):
|
||||
"""
|
||||
Get a string representation of this object.
|
||||
|
||||
@param indent: The indent.
|
||||
@type indent: int
|
||||
@return: A string.
|
||||
@rtype: str
|
||||
|
||||
"""
|
||||
if history is None:
|
||||
history = []
|
||||
if self in history:
|
||||
return "%s ..." % Repr(self)
|
||||
history.append(self)
|
||||
tab = "%*s" % (indent * 3, "")
|
||||
result = ["%s<%s" % (tab, self.id)]
|
||||
for n in self.description():
|
||||
if not hasattr(self, n):
|
||||
continue
|
||||
v = getattr(self, n)
|
||||
if v is None:
|
||||
continue
|
||||
result.append(' %s="%s"' % (n, v))
|
||||
if len(self):
|
||||
result.append(">")
|
||||
for c in self.rawchildren:
|
||||
result.append("\n")
|
||||
result.append(c.str(indent+1, history[:]))
|
||||
if c.isattr():
|
||||
result.append("@")
|
||||
result.append("\n%s" % (tab,))
|
||||
result.append("</%s>" % (self.__class__.__name__,))
|
||||
else:
|
||||
result.append(" />")
|
||||
return "".join(result)
|
||||
|
||||
def description(self):
|
||||
"""
|
||||
Get the names used for repr() and str() description.
|
||||
|
||||
@return: A dictionary of relevant attributes.
|
||||
@rtype: [str,...]
|
||||
|
||||
"""
|
||||
return ()
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.str())
|
||||
|
||||
def __repr__(self):
|
||||
s = []
|
||||
s.append("<%s" % (self.id,))
|
||||
for n in self.description():
|
||||
if not hasattr(self, n):
|
||||
continue
|
||||
v = getattr(self, n)
|
||||
if v is None:
|
||||
continue
|
||||
s.append(' %s="%s"' % (n, v))
|
||||
s.append(" />")
|
||||
return "".join(s)
|
||||
|
||||
def __len__(self):
|
||||
n = 0
|
||||
for x in self:
|
||||
n += 1
|
||||
return n
|
||||
|
||||
def __iter__(self):
|
||||
return Iter(self)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
Returns a contained schema object referenced by its 0-based index.
|
||||
|
||||
Returns None if such an object does not exist.
|
||||
|
||||
"""
|
||||
i = 0
|
||||
for c in self:
|
||||
if i == index:
|
||||
return c
|
||||
i += 1
|
||||
|
||||
|
||||
class Iter:
|
||||
"""
|
||||
The content iterator - used to iterate the L{Content} children.
|
||||
|
||||
The iterator provides a I{view} of the children that is free of container
|
||||
elements such as <xsd::all/>, <xsd:choice/> or <xsd:sequence/>.
|
||||
|
||||
@ivar stack: A stack used to control nesting.
|
||||
@type stack: list
|
||||
|
||||
"""
|
||||
|
||||
class Frame:
|
||||
"""A content iterator frame."""
|
||||
|
||||
def __init__(self, sx):
|
||||
"""
|
||||
@param sx: A schema object.
|
||||
@type sx: L{SchemaObject}
|
||||
|
||||
"""
|
||||
self.sx = sx
|
||||
self.items = sx.rawchildren
|
||||
self.index = 0
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
Get the I{next} item in the frame's collection.
|
||||
|
||||
@return: The next item or None
|
||||
@rtype: L{SchemaObject}
|
||||
|
||||
"""
|
||||
if self.index < len(self.items):
|
||||
result = self.items[self.index]
|
||||
self.index += 1
|
||||
return result
|
||||
|
||||
def __init__(self, sx):
|
||||
"""
|
||||
@param sx: A schema object.
|
||||
@type sx: L{SchemaObject}
|
||||
|
||||
"""
|
||||
self.stack = []
|
||||
self.push(sx)
|
||||
|
||||
def push(self, sx):
|
||||
"""
|
||||
Create a frame and push the specified object.
|
||||
|
||||
@param sx: A schema object to push.
|
||||
@type sx: L{SchemaObject}
|
||||
|
||||
"""
|
||||
self.stack.append(Iter.Frame(sx))
|
||||
|
||||
def pop(self):
|
||||
"""
|
||||
Pop the I{top} frame.
|
||||
|
||||
@return: The popped frame.
|
||||
@rtype: L{Frame}
|
||||
@raise StopIteration: when stack is empty.
|
||||
|
||||
"""
|
||||
if self.stack:
|
||||
return self.stack.pop()
|
||||
raise StopIteration()
|
||||
|
||||
def top(self):
|
||||
"""
|
||||
Get the I{top} frame.
|
||||
|
||||
@return: The top frame.
|
||||
@rtype: L{Frame}
|
||||
@raise StopIteration: when stack is empty.
|
||||
|
||||
"""
|
||||
if self.stack:
|
||||
return self.stack[-1]
|
||||
raise StopIteration()
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
Get the next item.
|
||||
|
||||
@return: A tuple: the next (child, ancestry).
|
||||
@rtype: (L{SchemaObject}, [L{SchemaObject},..])
|
||||
@raise StopIteration: A the end.
|
||||
|
||||
"""
|
||||
frame = self.top()
|
||||
while True:
|
||||
result = frame.next()
|
||||
if result is None:
|
||||
self.pop()
|
||||
return self.next()
|
||||
if isinstance(result, Content):
|
||||
ancestry = [f.sx for f in self.stack]
|
||||
return result, ancestry
|
||||
self.push(result)
|
||||
return self.next()
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
|
||||
class XBuiltin(SchemaObject):
|
||||
"""Represents a built-in XSD schema <xsd:*/> node."""
|
||||
|
||||
def __init__(self, schema, name):
|
||||
"""
|
||||
@param schema: The containing schema.
|
||||
@type schema: L{schema.Schema}
|
||||
|
||||
"""
|
||||
root = Element(name)
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.name = name
|
||||
self.nillable = True
|
||||
|
||||
def namespace(self, prefix=None):
|
||||
return Namespace.xsdns
|
||||
|
||||
def builtin(self):
|
||||
return True
|
||||
|
||||
|
||||
class Content(SchemaObject):
|
||||
"""XSD schema objects representing real XML document content."""
|
||||
pass
|
||||
|
||||
|
||||
class NodeFinder:
|
||||
"""
|
||||
Find nodes based on flexable criteria.
|
||||
|
||||
I{matcher} may be any object implementing a match(n) method.
|
||||
|
||||
@ivar matcher: An object used as criteria for match.
|
||||
@type matcher: I{any}.match(n)
|
||||
@ivar limit: Limit the number of matches. 0=unlimited.
|
||||
@type limit: int
|
||||
|
||||
"""
|
||||
def __init__(self, matcher, limit=0):
|
||||
"""
|
||||
@param matcher: An object used as criteria for match.
|
||||
@type matcher: I{any}.match(n)
|
||||
@param limit: Limit the number of matches. 0=unlimited.
|
||||
@type limit: int
|
||||
|
||||
"""
|
||||
self.matcher = matcher
|
||||
self.limit = limit
|
||||
|
||||
def find(self, node, list):
|
||||
"""
|
||||
Traverse the tree looking for matches.
|
||||
|
||||
@param node: A node to match on.
|
||||
@type node: L{SchemaObject}
|
||||
@param list: A list to fill.
|
||||
@type list: list
|
||||
|
||||
"""
|
||||
if self.matcher.match(node):
|
||||
list.append(node)
|
||||
self.limit -= 1
|
||||
if self.limit == 0:
|
||||
return
|
||||
for c in node.rawchildren:
|
||||
self.find(c, list)
|
||||
return self
|
@ -1,863 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""Classes representing I{basic} XSD schema objects."""
|
||||
|
||||
from future.utils import raise_
|
||||
from suds import *
|
||||
from suds.reader import DocumentReader
|
||||
from suds.sax import Namespace
|
||||
from suds.transport import TransportError
|
||||
from suds.xsd import *
|
||||
from suds.xsd.query import *
|
||||
from suds.xsd.sxbase import *
|
||||
|
||||
from urlparse import urljoin
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class RestrictionMatcher:
|
||||
"""For use with L{NodeFinder} to match restriction."""
|
||||
def match(self, n):
|
||||
return isinstance(n, Restriction)
|
||||
|
||||
|
||||
class TypedContent(Content):
|
||||
"""Represents any I{typed} content."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
Content.__init__(self, *args, **kwargs)
|
||||
self.resolved_cache = {}
|
||||
|
||||
def resolve(self, nobuiltin=False):
|
||||
"""
|
||||
Resolve the node's type reference and return the referenced type node.
|
||||
|
||||
Returns self if the type is defined locally, e.g. as a <complexType>
|
||||
subnode. Otherwise returns the referenced external node.
|
||||
|
||||
@param nobuiltin: Flag indicating whether resolving to XSD built-in
|
||||
types should not be allowed.
|
||||
@return: The resolved (true) type.
|
||||
@rtype: L{SchemaObject}
|
||||
|
||||
"""
|
||||
cached = self.resolved_cache.get(nobuiltin)
|
||||
if cached is not None:
|
||||
return cached
|
||||
resolved = self.__resolve_type(nobuiltin)
|
||||
self.resolved_cache[nobuiltin] = resolved
|
||||
return resolved
|
||||
|
||||
def __resolve_type(self, nobuiltin=False):
|
||||
"""
|
||||
Private resolve() worker without any result caching.
|
||||
|
||||
@param nobuiltin: Flag indicating whether resolving to XSD built-in
|
||||
types should not be allowed.
|
||||
@return: The resolved (true) type.
|
||||
@rtype: L{SchemaObject}
|
||||
|
||||
"""
|
||||
# There is no need for a recursive implementation here since a node can
|
||||
# reference an external type node but XSD specification explicitly
|
||||
# states that that external node must not be a reference to yet another
|
||||
# node.
|
||||
qref = self.qref()
|
||||
if qref is None:
|
||||
return self
|
||||
query = TypeQuery(qref)
|
||||
query.history = [self]
|
||||
log.debug("%s, resolving: %s\n using:%s", self.id, qref, query)
|
||||
resolved = query.execute(self.schema)
|
||||
if resolved is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(qref)
|
||||
if resolved.builtin() and nobuiltin:
|
||||
return self
|
||||
return resolved
|
||||
|
||||
def qref(self):
|
||||
"""
|
||||
Get the I{type} qualified reference to the referenced XSD type.
|
||||
|
||||
This method takes into account simple types defined through restriction
|
||||
which are detected by determining that self is simple (len == 0) and by
|
||||
finding a restriction child.
|
||||
|
||||
@return: The I{type} qualified reference.
|
||||
@rtype: qref
|
||||
|
||||
"""
|
||||
qref = self.type
|
||||
if qref is None and len(self) == 0:
|
||||
ls = []
|
||||
m = RestrictionMatcher()
|
||||
finder = NodeFinder(m, 1)
|
||||
finder.find(self, ls)
|
||||
if ls:
|
||||
return ls[0].ref
|
||||
return qref
|
||||
|
||||
|
||||
class Complex(SchemaObject):
|
||||
"""
|
||||
Represents an XSD schema <xsd:complexType/> node.
|
||||
|
||||
@cvar childtags: A list of valid child node names.
|
||||
@type childtags: (I{str},...)
|
||||
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return ("all", "any", "attribute", "attributeGroup", "choice",
|
||||
"complexContent", "group", "sequence", "simpleContent")
|
||||
|
||||
def description(self):
|
||||
return ("name",)
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def mixed(self):
|
||||
for c in self.rawchildren:
|
||||
if isinstance(c, SimpleContent) and c.mixed():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Group(SchemaObject):
|
||||
"""
|
||||
Represents an XSD schema <xsd:group/> node.
|
||||
|
||||
@cvar childtags: A list of valid child node names.
|
||||
@type childtags: (I{str},...)
|
||||
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return "all", "choice", "sequence"
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = GroupQuery(self.ref)
|
||||
g = query.execute(self.schema)
|
||||
if g is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
deps.append(g)
|
||||
midx = 0
|
||||
return midx, deps
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
self.rawchildren = other.rawchildren
|
||||
|
||||
def description(self):
|
||||
return "name", "ref"
|
||||
|
||||
|
||||
class AttributeGroup(SchemaObject):
|
||||
"""
|
||||
Represents an XSD schema <xsd:attributeGroup/> node.
|
||||
|
||||
@cvar childtags: A list of valid child node names.
|
||||
@type childtags: (I{str},...)
|
||||
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return "attribute", "attributeGroup"
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = AttrGroupQuery(self.ref)
|
||||
ag = query.execute(self.schema)
|
||||
if ag is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
deps.append(ag)
|
||||
midx = 0
|
||||
return midx, deps
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
self.rawchildren = other.rawchildren
|
||||
|
||||
def description(self):
|
||||
return "name", "ref"
|
||||
|
||||
|
||||
class Simple(SchemaObject):
|
||||
"""Represents an XSD schema <xsd:simpleType/> node."""
|
||||
|
||||
def childtags(self):
|
||||
return "any", "list", "restriction"
|
||||
|
||||
def enum(self):
|
||||
for child, ancestry in self.children():
|
||||
if isinstance(child, Enumeration):
|
||||
return True
|
||||
return False
|
||||
|
||||
def mixed(self):
|
||||
return len(self)
|
||||
|
||||
def description(self):
|
||||
return ("name",)
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
for c in self.rawchildren:
|
||||
if c.restriction():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class List(SchemaObject):
|
||||
"""Represents an XSD schema <xsd:list/> node."""
|
||||
|
||||
def childtags(self):
|
||||
return ()
|
||||
|
||||
def description(self):
|
||||
return ("name",)
|
||||
|
||||
def xslist(self):
|
||||
return True
|
||||
|
||||
|
||||
class Restriction(SchemaObject):
|
||||
"""Represents an XSD schema <xsd:restriction/> node."""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.ref = root.get("base")
|
||||
|
||||
def childtags(self):
|
||||
return "attribute", "attributeGroup", "enumeration"
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = TypeQuery(self.ref)
|
||||
super = query.execute(self.schema)
|
||||
if super is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
if not super.builtin():
|
||||
deps.append(super)
|
||||
midx = 0
|
||||
return midx, deps
|
||||
|
||||
def restriction(self):
|
||||
return True
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
filter = Filter(False, self.rawchildren)
|
||||
self.prepend(self.rawchildren, other.rawchildren, filter)
|
||||
|
||||
def description(self):
|
||||
return ("ref",)
|
||||
|
||||
|
||||
class Collection(SchemaObject):
|
||||
"""Represents an XSD schema collection (a.k.a. order indicator) node."""
|
||||
|
||||
def childtags(self):
|
||||
return "all", "any", "choice", "element", "group", "sequence"
|
||||
|
||||
|
||||
class All(Collection):
|
||||
"""Represents an XSD schema <xsd:all/> node."""
|
||||
def all(self):
|
||||
return True
|
||||
|
||||
|
||||
class Choice(Collection):
|
||||
"""Represents an XSD schema <xsd:choice/> node."""
|
||||
def choice(self):
|
||||
return True
|
||||
|
||||
|
||||
class Sequence(Collection):
|
||||
"""Represents an XSD schema <xsd:sequence/> node."""
|
||||
def sequence(self):
|
||||
return True
|
||||
|
||||
|
||||
class ComplexContent(SchemaObject):
|
||||
"""Represents an XSD schema <xsd:complexContent/> node."""
|
||||
|
||||
def childtags(self):
|
||||
return "attribute", "attributeGroup", "extension", "restriction"
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
for c in self.rawchildren:
|
||||
if c.restriction():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class SimpleContent(SchemaObject):
|
||||
"""Represents an XSD schema <xsd:simpleContent/> node."""
|
||||
|
||||
def childtags(self):
|
||||
return "extension", "restriction"
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
for c in self.rawchildren:
|
||||
if c.restriction():
|
||||
return True
|
||||
return False
|
||||
|
||||
def mixed(self):
|
||||
return len(self)
|
||||
|
||||
|
||||
class Enumeration(Content):
|
||||
"""Represents an XSD schema <xsd:enumeration/> node."""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
Content.__init__(self, schema, root)
|
||||
self.name = root.get("value")
|
||||
|
||||
def description(self):
|
||||
return ("name",)
|
||||
|
||||
def enum(self):
|
||||
return True
|
||||
|
||||
|
||||
class Element(TypedContent):
|
||||
"""Represents an XSD schema <xsd:element/> node."""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
TypedContent.__init__(self, schema, root)
|
||||
is_reference = self.ref is not None
|
||||
is_top_level = root.parent is schema.root
|
||||
if is_reference or is_top_level:
|
||||
self.form_qualified = True
|
||||
else:
|
||||
form = root.get("form")
|
||||
if form is not None:
|
||||
self.form_qualified = (form == "qualified")
|
||||
nillable = self.root.get("nillable")
|
||||
if nillable is not None:
|
||||
self.nillable = (nillable in ("1", "true"))
|
||||
self.implany()
|
||||
|
||||
def implany(self):
|
||||
"""
|
||||
Set the type to <xsd:any/> when implicit.
|
||||
|
||||
An element has an implicit <xsd:any/> type when it has no body and no
|
||||
explicitly defined type.
|
||||
|
||||
@return: self
|
||||
@rtype: L{Element}
|
||||
|
||||
"""
|
||||
if self.type is None and self.ref is None and self.root.isempty():
|
||||
self.type = self.anytype()
|
||||
|
||||
def childtags(self):
|
||||
return "any", "attribute", "complexType", "simpleType"
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
for c in self.rawchildren:
|
||||
if c.restriction():
|
||||
return True
|
||||
return False
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
e = self.__deref()
|
||||
if e is not None:
|
||||
deps.append(e)
|
||||
midx = 0
|
||||
return midx, deps
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
self.rawchildren = other.rawchildren
|
||||
|
||||
def description(self):
|
||||
return "name", "ref", "type"
|
||||
|
||||
def anytype(self):
|
||||
"""Create an xsd:anyType reference."""
|
||||
p, u = Namespace.xsdns
|
||||
mp = self.root.findPrefix(u)
|
||||
if mp is None:
|
||||
mp = p
|
||||
self.root.addPrefix(p, u)
|
||||
return ":".join((mp, "anyType"))
|
||||
|
||||
def namespace(self, prefix=None):
|
||||
"""
|
||||
Get this schema element's target namespace.
|
||||
|
||||
In case of reference elements, the target namespace is defined by the
|
||||
referenced and not the referencing element node.
|
||||
|
||||
@param prefix: The default prefix.
|
||||
@type prefix: str
|
||||
@return: The schema element's target namespace
|
||||
@rtype: (I{prefix},I{URI})
|
||||
|
||||
"""
|
||||
e = self.__deref()
|
||||
if e is not None:
|
||||
return e.namespace(prefix)
|
||||
return super(Element, self).namespace()
|
||||
|
||||
def __deref(self):
|
||||
if self.ref is None:
|
||||
return
|
||||
query = ElementQuery(self.ref)
|
||||
e = query.execute(self.schema)
|
||||
if e is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
return e
|
||||
|
||||
|
||||
class Extension(SchemaObject):
|
||||
"""Represents an XSD schema <xsd:extension/> node."""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.ref = root.get("base")
|
||||
|
||||
def childtags(self):
|
||||
return ("all", "attribute", "attributeGroup", "choice", "group",
|
||||
"sequence")
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = TypeQuery(self.ref)
|
||||
super = query.execute(self.schema)
|
||||
if super is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
if not super.builtin():
|
||||
deps.append(super)
|
||||
midx = 0
|
||||
return midx, deps
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
filter = Filter(False, self.rawchildren)
|
||||
self.prepend(self.rawchildren, other.rawchildren, filter)
|
||||
|
||||
def extension(self):
|
||||
return self.ref is not None
|
||||
|
||||
def description(self):
|
||||
return ("ref",)
|
||||
|
||||
|
||||
class Import(SchemaObject):
|
||||
"""
|
||||
Represents an XSD schema <xsd:import/> node.
|
||||
|
||||
@cvar locations: A dictionary of namespace locations.
|
||||
@type locations: dict
|
||||
@ivar ns: The imported namespace.
|
||||
@type ns: str
|
||||
@ivar location: The (optional) location.
|
||||
@type location: namespace-uri
|
||||
@ivar opened: Opened and I{imported} flag.
|
||||
@type opened: boolean
|
||||
|
||||
"""
|
||||
|
||||
locations = {}
|
||||
|
||||
@classmethod
|
||||
def bind(cls, ns, location=None):
|
||||
"""
|
||||
Bind a namespace to a schema location (URI).
|
||||
|
||||
This is used for imports that do not specify a schemaLocation.
|
||||
|
||||
@param ns: A namespace-uri.
|
||||
@type ns: str
|
||||
@param location: The (optional) schema location for the namespace.
|
||||
(default=ns)
|
||||
@type location: str
|
||||
|
||||
"""
|
||||
if location is None:
|
||||
location = ns
|
||||
cls.locations[ns] = location
|
||||
|
||||
def __init__(self, schema, root):
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.ns = (None, root.get("namespace"))
|
||||
self.location = root.get("schemaLocation")
|
||||
if self.location is None:
|
||||
self.location = self.locations.get(self.ns[1])
|
||||
self.opened = False
|
||||
|
||||
def open(self, options, loaded_schemata):
|
||||
"""
|
||||
Open and import the referenced schema.
|
||||
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
@param loaded_schemata: Already loaded schemata cache (URL --> Schema).
|
||||
@type loaded_schemata: dict
|
||||
@return: The referenced schema.
|
||||
@rtype: L{Schema}
|
||||
|
||||
"""
|
||||
if self.opened:
|
||||
return
|
||||
self.opened = True
|
||||
log.debug("%s, importing ns='%s', location='%s'", self.id, self.ns[1],
|
||||
self.location)
|
||||
result = self.__locate()
|
||||
if result is None:
|
||||
if self.location is None:
|
||||
log.debug("imported schema (%s) not-found", self.ns[1])
|
||||
else:
|
||||
url = self.location
|
||||
if "://" not in url:
|
||||
url = urljoin(self.schema.baseurl, url)
|
||||
result = (loaded_schemata.get(url) or
|
||||
self.__download(url, loaded_schemata, options))
|
||||
log.debug("imported:\n%s", result)
|
||||
return result
|
||||
|
||||
def __locate(self):
|
||||
"""Find the schema locally."""
|
||||
if self.ns[1] != self.schema.tns[1]:
|
||||
return self.schema.locate(self.ns)
|
||||
|
||||
def __download(self, url, loaded_schemata, options):
|
||||
"""Download the schema."""
|
||||
try:
|
||||
reader = DocumentReader(options)
|
||||
d = reader.open(url)
|
||||
root = d.root()
|
||||
root.set("url", url)
|
||||
return self.schema.instance(root, url, loaded_schemata, options)
|
||||
except TransportError:
|
||||
msg = "import schema (%s) at (%s), failed" % (self.ns[1], url)
|
||||
log.error("%s, %s", self.id, msg, exc_info=True)
|
||||
raise Exception(msg)
|
||||
|
||||
def description(self):
|
||||
return "ns", "location"
|
||||
|
||||
|
||||
class Include(SchemaObject):
|
||||
"""
|
||||
Represents an XSD schema <xsd:include/> node.
|
||||
|
||||
@ivar location: The (optional) location.
|
||||
@type location: namespace-uri
|
||||
@ivar opened: Opened and I{imported} flag.
|
||||
@type opened: boolean
|
||||
|
||||
"""
|
||||
|
||||
locations = {}
|
||||
|
||||
def __init__(self, schema, root):
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.location = root.get("schemaLocation")
|
||||
if self.location is None:
|
||||
self.location = self.locations.get(self.ns[1])
|
||||
self.opened = False
|
||||
|
||||
def open(self, options, loaded_schemata):
|
||||
"""
|
||||
Open and include the referenced schema.
|
||||
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
@param loaded_schemata: Already loaded schemata cache (URL --> Schema).
|
||||
@type loaded_schemata: dict
|
||||
@return: The referenced schema.
|
||||
@rtype: L{Schema}
|
||||
|
||||
"""
|
||||
if self.opened:
|
||||
return
|
||||
self.opened = True
|
||||
log.debug("%s, including location='%s'", self.id, self.location)
|
||||
url = self.location
|
||||
if "://" not in url:
|
||||
url = urljoin(self.schema.baseurl, url)
|
||||
result = (loaded_schemata.get(url) or
|
||||
self.__download(url, loaded_schemata, options))
|
||||
log.debug("included:\n%s", result)
|
||||
return result
|
||||
|
||||
def __download(self, url, loaded_schemata, options):
|
||||
"""Download the schema."""
|
||||
try:
|
||||
reader = DocumentReader(options)
|
||||
d = reader.open(url)
|
||||
root = d.root()
|
||||
root.set("url", url)
|
||||
self.__applytns(root)
|
||||
return self.schema.instance(root, url, loaded_schemata, options)
|
||||
except TransportError:
|
||||
msg = "include schema at (%s), failed" % url
|
||||
log.error("%s, %s", self.id, msg, exc_info=True)
|
||||
raise Exception(msg)
|
||||
|
||||
def __applytns(self, root):
|
||||
"""Make sure included schema has the same target namespace."""
|
||||
TNS = "targetNamespace"
|
||||
tns = root.get(TNS)
|
||||
if tns is None:
|
||||
tns = self.schema.tns[1]
|
||||
root.set(TNS, tns)
|
||||
else:
|
||||
if self.schema.tns[1] != tns:
|
||||
raise_(Exception, "%s mismatch" % TNS)
|
||||
|
||||
def description(self):
|
||||
return "location"
|
||||
|
||||
|
||||
class Attribute(TypedContent):
|
||||
"""Represents an XSD schema <attribute/> node."""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
TypedContent.__init__(self, schema, root)
|
||||
self.use = root.get("use", default="")
|
||||
|
||||
def childtags(self):
|
||||
return ("restriction",)
|
||||
|
||||
def isattr(self):
|
||||
return True
|
||||
|
||||
def get_default(self):
|
||||
"""
|
||||
Gets the <xsd:attribute default=""/> attribute value.
|
||||
|
||||
@return: The default value for the attribute
|
||||
@rtype: str
|
||||
|
||||
"""
|
||||
return self.root.get("default", default="")
|
||||
|
||||
def optional(self):
|
||||
return self.use != "required"
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = AttrQuery(self.ref)
|
||||
a = query.execute(self.schema)
|
||||
if a is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
deps.append(a)
|
||||
midx = 0
|
||||
return midx, deps
|
||||
|
||||
def description(self):
|
||||
return "name", "ref", "type"
|
||||
|
||||
|
||||
class Any(Content):
|
||||
"""Represents an XSD schema <any/> node."""
|
||||
|
||||
def get_child(self, name):
|
||||
root = self.root.clone()
|
||||
root.set("note", "synthesized (any) child")
|
||||
child = Any(self.schema, root)
|
||||
return child, []
|
||||
|
||||
def get_attribute(self, name):
|
||||
root = self.root.clone()
|
||||
root.set("note", "synthesized (any) attribute")
|
||||
attribute = Any(self.schema, root)
|
||||
return attribute, []
|
||||
|
||||
def any(self):
|
||||
return True
|
||||
|
||||
|
||||
class Factory:
|
||||
"""
|
||||
@cvar tags: A factory to create object objects based on tag.
|
||||
@type tags: {tag:fn,}
|
||||
|
||||
"""
|
||||
|
||||
tags = {
|
||||
"all": All,
|
||||
"any": Any,
|
||||
"attribute": Attribute,
|
||||
"attributeGroup": AttributeGroup,
|
||||
"choice": Choice,
|
||||
"complexContent": ComplexContent,
|
||||
"complexType": Complex,
|
||||
"element": Element,
|
||||
"enumeration": Enumeration,
|
||||
"extension": Extension,
|
||||
"group": Group,
|
||||
"import": Import,
|
||||
"include": Include,
|
||||
"list": List,
|
||||
"restriction": Restriction,
|
||||
"simpleContent": SimpleContent,
|
||||
"simpleType": Simple,
|
||||
"sequence": Sequence,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def maptag(cls, tag, fn):
|
||||
"""
|
||||
Map (override) tag => I{class} mapping.
|
||||
|
||||
@param tag: An XSD tag name.
|
||||
@type tag: str
|
||||
@param fn: A function or class.
|
||||
@type fn: fn|class.
|
||||
|
||||
"""
|
||||
cls.tags[tag] = fn
|
||||
|
||||
@classmethod
|
||||
def create(cls, root, schema):
|
||||
"""
|
||||
Create an object based on the root tag name.
|
||||
|
||||
@param root: An XML root element.
|
||||
@type root: L{Element}
|
||||
@param schema: A schema object.
|
||||
@type schema: L{schema.Schema}
|
||||
@return: The created object.
|
||||
@rtype: L{SchemaObject}
|
||||
|
||||
"""
|
||||
fn = cls.tags.get(root.name)
|
||||
if fn is not None:
|
||||
return fn(schema, root)
|
||||
|
||||
@classmethod
|
||||
def build(cls, root, schema, filter=("*",)):
|
||||
"""
|
||||
Build an xsobject representation.
|
||||
|
||||
@param root: An schema XML root.
|
||||
@type root: L{sax.element.Element}
|
||||
@param filter: A tag filter.
|
||||
@type filter: [str,...]
|
||||
@return: A schema object graph.
|
||||
@rtype: L{sxbase.SchemaObject}
|
||||
|
||||
"""
|
||||
children = []
|
||||
for node in root.getChildren(ns=Namespace.xsdns):
|
||||
if "*" in filter or node.name in filter:
|
||||
child = cls.create(node, schema)
|
||||
if child is None:
|
||||
continue
|
||||
children.append(child)
|
||||
c = cls.build(node, schema, child.childtags())
|
||||
child.rawchildren = c
|
||||
return children
|
||||
|
||||
@classmethod
|
||||
def collate(cls, children):
|
||||
imports = []
|
||||
elements = {}
|
||||
attributes = {}
|
||||
types = {}
|
||||
groups = {}
|
||||
agrps = {}
|
||||
for c in children:
|
||||
if isinstance(c, (Import, Include)):
|
||||
imports.append(c)
|
||||
continue
|
||||
if isinstance(c, Attribute):
|
||||
attributes[c.qname] = c
|
||||
continue
|
||||
if isinstance(c, Element):
|
||||
elements[c.qname] = c
|
||||
continue
|
||||
if isinstance(c, Group):
|
||||
groups[c.qname] = c
|
||||
continue
|
||||
if isinstance(c, AttributeGroup):
|
||||
agrps[c.qname] = c
|
||||
continue
|
||||
types[c.qname] = c
|
||||
for i in imports:
|
||||
children.remove(i)
|
||||
return children, imports, attributes, elements, types, groups, agrps
|
||||
|
||||
|
||||
#######################################################
|
||||
# Static Import Bindings :-(
|
||||
#######################################################
|
||||
Import.bind(
|
||||
"http://schemas.xmlsoap.org/soap/encoding/",
|
||||
"suds://schemas.xmlsoap.org/soap/encoding/")
|
||||
Import.bind(
|
||||
"http://www.w3.org/XML/1998/namespace",
|
||||
"http://www.w3.org/2001/xml.xsd")
|
||||
Import.bind(
|
||||
"http://www.w3.org/2001/XMLSchema",
|
||||
"http://www.w3.org/2001/XMLSchema.xsd")
|
@ -1,862 +0,0 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the (LGPL) GNU Lesser General Public License as published by the
|
||||
# Free Software Foundation; either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
|
||||
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# written by: Jeff Ortel ( jortel@redhat.com )
|
||||
|
||||
"""Classes representing I{basic} XSD schema objects."""
|
||||
|
||||
from suds import *
|
||||
from suds.reader import DocumentReader
|
||||
from suds.sax import Namespace
|
||||
from suds.transport import TransportError
|
||||
from suds.xsd import *
|
||||
from suds.xsd.query import *
|
||||
from suds.xsd.sxbase import *
|
||||
|
||||
from urlparse import urljoin
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class RestrictionMatcher:
|
||||
"""For use with L{NodeFinder} to match restriction."""
|
||||
def match(self, n):
|
||||
return isinstance(n, Restriction)
|
||||
|
||||
|
||||
class TypedContent(Content):
|
||||
"""Represents any I{typed} content."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
Content.__init__(self, *args, **kwargs)
|
||||
self.resolved_cache = {}
|
||||
|
||||
def resolve(self, nobuiltin=False):
|
||||
"""
|
||||
Resolve the node's type reference and return the referenced type node.
|
||||
|
||||
Returns self if the type is defined locally, e.g. as a <complexType>
|
||||
subnode. Otherwise returns the referenced external node.
|
||||
|
||||
@param nobuiltin: Flag indicating whether resolving to XSD built-in
|
||||
types should not be allowed.
|
||||
@return: The resolved (true) type.
|
||||
@rtype: L{SchemaObject}
|
||||
|
||||
"""
|
||||
cached = self.resolved_cache.get(nobuiltin)
|
||||
if cached is not None:
|
||||
return cached
|
||||
resolved = self.__resolve_type(nobuiltin)
|
||||
self.resolved_cache[nobuiltin] = resolved
|
||||
return resolved
|
||||
|
||||
def __resolve_type(self, nobuiltin=False):
|
||||
"""
|
||||
Private resolve() worker without any result caching.
|
||||
|
||||
@param nobuiltin: Flag indicating whether resolving to XSD built-in
|
||||
types should not be allowed.
|
||||
@return: The resolved (true) type.
|
||||
@rtype: L{SchemaObject}
|
||||
|
||||
"""
|
||||
# There is no need for a recursive implementation here since a node can
|
||||
# reference an external type node but XSD specification explicitly
|
||||
# states that that external node must not be a reference to yet another
|
||||
# node.
|
||||
qref = self.qref()
|
||||
if qref is None:
|
||||
return self
|
||||
query = TypeQuery(qref)
|
||||
query.history = [self]
|
||||
log.debug("%s, resolving: %s\n using:%s", self.id, qref, query)
|
||||
resolved = query.execute(self.schema)
|
||||
if resolved is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(qref)
|
||||
if resolved.builtin() and nobuiltin:
|
||||
return self
|
||||
return resolved
|
||||
|
||||
def qref(self):
|
||||
"""
|
||||
Get the I{type} qualified reference to the referenced XSD type.
|
||||
|
||||
This method takes into account simple types defined through restriction
|
||||
which are detected by determining that self is simple (len == 0) and by
|
||||
finding a restriction child.
|
||||
|
||||
@return: The I{type} qualified reference.
|
||||
@rtype: qref
|
||||
|
||||
"""
|
||||
qref = self.type
|
||||
if qref is None and len(self) == 0:
|
||||
ls = []
|
||||
m = RestrictionMatcher()
|
||||
finder = NodeFinder(m, 1)
|
||||
finder.find(self, ls)
|
||||
if ls:
|
||||
return ls[0].ref
|
||||
return qref
|
||||
|
||||
|
||||
class Complex(SchemaObject):
|
||||
"""
|
||||
Represents an XSD schema <xsd:complexType/> node.
|
||||
|
||||
@cvar childtags: A list of valid child node names.
|
||||
@type childtags: (I{str},...)
|
||||
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return ("all", "any", "attribute", "attributeGroup", "choice",
|
||||
"complexContent", "group", "sequence", "simpleContent")
|
||||
|
||||
def description(self):
|
||||
return ("name",)
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def mixed(self):
|
||||
for c in self.rawchildren:
|
||||
if isinstance(c, SimpleContent) and c.mixed():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Group(SchemaObject):
|
||||
"""
|
||||
Represents an XSD schema <xsd:group/> node.
|
||||
|
||||
@cvar childtags: A list of valid child node names.
|
||||
@type childtags: (I{str},...)
|
||||
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return "all", "choice", "sequence"
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = GroupQuery(self.ref)
|
||||
g = query.execute(self.schema)
|
||||
if g is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
deps.append(g)
|
||||
midx = 0
|
||||
return midx, deps
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
self.rawchildren = other.rawchildren
|
||||
|
||||
def description(self):
|
||||
return "name", "ref"
|
||||
|
||||
|
||||
class AttributeGroup(SchemaObject):
|
||||
"""
|
||||
Represents an XSD schema <xsd:attributeGroup/> node.
|
||||
|
||||
@cvar childtags: A list of valid child node names.
|
||||
@type childtags: (I{str},...)
|
||||
|
||||
"""
|
||||
|
||||
def childtags(self):
|
||||
return "attribute", "attributeGroup"
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = AttrGroupQuery(self.ref)
|
||||
ag = query.execute(self.schema)
|
||||
if ag is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
deps.append(ag)
|
||||
midx = 0
|
||||
return midx, deps
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
self.rawchildren = other.rawchildren
|
||||
|
||||
def description(self):
|
||||
return "name", "ref"
|
||||
|
||||
|
||||
class Simple(SchemaObject):
|
||||
"""Represents an XSD schema <xsd:simpleType/> node."""
|
||||
|
||||
def childtags(self):
|
||||
return "any", "list", "restriction"
|
||||
|
||||
def enum(self):
|
||||
for child, ancestry in self.children():
|
||||
if isinstance(child, Enumeration):
|
||||
return True
|
||||
return False
|
||||
|
||||
def mixed(self):
|
||||
return len(self)
|
||||
|
||||
def description(self):
|
||||
return ("name",)
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
for c in self.rawchildren:
|
||||
if c.restriction():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class List(SchemaObject):
|
||||
"""Represents an XSD schema <xsd:list/> node."""
|
||||
|
||||
def childtags(self):
|
||||
return ()
|
||||
|
||||
def description(self):
|
||||
return ("name",)
|
||||
|
||||
def xslist(self):
|
||||
return True
|
||||
|
||||
|
||||
class Restriction(SchemaObject):
|
||||
"""Represents an XSD schema <xsd:restriction/> node."""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.ref = root.get("base")
|
||||
|
||||
def childtags(self):
|
||||
return "attribute", "attributeGroup", "enumeration"
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = TypeQuery(self.ref)
|
||||
super = query.execute(self.schema)
|
||||
if super is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
if not super.builtin():
|
||||
deps.append(super)
|
||||
midx = 0
|
||||
return midx, deps
|
||||
|
||||
def restriction(self):
|
||||
return True
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
filter = Filter(False, self.rawchildren)
|
||||
self.prepend(self.rawchildren, other.rawchildren, filter)
|
||||
|
||||
def description(self):
|
||||
return ("ref",)
|
||||
|
||||
|
||||
class Collection(SchemaObject):
|
||||
"""Represents an XSD schema collection (a.k.a. order indicator) node."""
|
||||
|
||||
def childtags(self):
|
||||
return "all", "any", "choice", "element", "group", "sequence"
|
||||
|
||||
|
||||
class All(Collection):
|
||||
"""Represents an XSD schema <xsd:all/> node."""
|
||||
def all(self):
|
||||
return True
|
||||
|
||||
|
||||
class Choice(Collection):
|
||||
"""Represents an XSD schema <xsd:choice/> node."""
|
||||
def choice(self):
|
||||
return True
|
||||
|
||||
|
||||
class Sequence(Collection):
|
||||
"""Represents an XSD schema <xsd:sequence/> node."""
|
||||
def sequence(self):
|
||||
return True
|
||||
|
||||
|
||||
class ComplexContent(SchemaObject):
|
||||
"""Represents an XSD schema <xsd:complexContent/> node."""
|
||||
|
||||
def childtags(self):
|
||||
return "attribute", "attributeGroup", "extension", "restriction"
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
for c in self.rawchildren:
|
||||
if c.restriction():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class SimpleContent(SchemaObject):
|
||||
"""Represents an XSD schema <xsd:simpleContent/> node."""
|
||||
|
||||
def childtags(self):
|
||||
return "extension", "restriction"
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
for c in self.rawchildren:
|
||||
if c.restriction():
|
||||
return True
|
||||
return False
|
||||
|
||||
def mixed(self):
|
||||
return len(self)
|
||||
|
||||
|
||||
class Enumeration(Content):
|
||||
"""Represents an XSD schema <xsd:enumeration/> node."""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
Content.__init__(self, schema, root)
|
||||
self.name = root.get("value")
|
||||
|
||||
def description(self):
|
||||
return ("name",)
|
||||
|
||||
def enum(self):
|
||||
return True
|
||||
|
||||
|
||||
class Element(TypedContent):
|
||||
"""Represents an XSD schema <xsd:element/> node."""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
TypedContent.__init__(self, schema, root)
|
||||
is_reference = self.ref is not None
|
||||
is_top_level = root.parent is schema.root
|
||||
if is_reference or is_top_level:
|
||||
self.form_qualified = True
|
||||
else:
|
||||
form = root.get("form")
|
||||
if form is not None:
|
||||
self.form_qualified = (form == "qualified")
|
||||
nillable = self.root.get("nillable")
|
||||
if nillable is not None:
|
||||
self.nillable = (nillable in ("1", "true"))
|
||||
self.implany()
|
||||
|
||||
def implany(self):
|
||||
"""
|
||||
Set the type to <xsd:any/> when implicit.
|
||||
|
||||
An element has an implicit <xsd:any/> type when it has no body and no
|
||||
explicitly defined type.
|
||||
|
||||
@return: self
|
||||
@rtype: L{Element}
|
||||
|
||||
"""
|
||||
if self.type is None and self.ref is None and self.root.isempty():
|
||||
self.type = self.anytype()
|
||||
|
||||
def childtags(self):
|
||||
return "any", "attribute", "complexType", "simpleType"
|
||||
|
||||
def extension(self):
|
||||
for c in self.rawchildren:
|
||||
if c.extension():
|
||||
return True
|
||||
return False
|
||||
|
||||
def restriction(self):
|
||||
for c in self.rawchildren:
|
||||
if c.restriction():
|
||||
return True
|
||||
return False
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
e = self.__deref()
|
||||
if e is not None:
|
||||
deps.append(e)
|
||||
midx = 0
|
||||
return midx, deps
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
self.rawchildren = other.rawchildren
|
||||
|
||||
def description(self):
|
||||
return "name", "ref", "type"
|
||||
|
||||
def anytype(self):
|
||||
"""Create an xsd:anyType reference."""
|
||||
p, u = Namespace.xsdns
|
||||
mp = self.root.findPrefix(u)
|
||||
if mp is None:
|
||||
mp = p
|
||||
self.root.addPrefix(p, u)
|
||||
return ":".join((mp, "anyType"))
|
||||
|
||||
def namespace(self, prefix=None):
|
||||
"""
|
||||
Get this schema element's target namespace.
|
||||
|
||||
In case of reference elements, the target namespace is defined by the
|
||||
referenced and not the referencing element node.
|
||||
|
||||
@param prefix: The default prefix.
|
||||
@type prefix: str
|
||||
@return: The schema element's target namespace
|
||||
@rtype: (I{prefix},I{URI})
|
||||
|
||||
"""
|
||||
e = self.__deref()
|
||||
if e is not None:
|
||||
return e.namespace(prefix)
|
||||
return super(Element, self).namespace()
|
||||
|
||||
def __deref(self):
|
||||
if self.ref is None:
|
||||
return
|
||||
query = ElementQuery(self.ref)
|
||||
e = query.execute(self.schema)
|
||||
if e is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
return e
|
||||
|
||||
|
||||
class Extension(SchemaObject):
|
||||
"""Represents an XSD schema <xsd:extension/> node."""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.ref = root.get("base")
|
||||
|
||||
def childtags(self):
|
||||
return ("all", "attribute", "attributeGroup", "choice", "group",
|
||||
"sequence")
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = TypeQuery(self.ref)
|
||||
super = query.execute(self.schema)
|
||||
if super is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
if not super.builtin():
|
||||
deps.append(super)
|
||||
midx = 0
|
||||
return midx, deps
|
||||
|
||||
def merge(self, other):
|
||||
SchemaObject.merge(self, other)
|
||||
filter = Filter(False, self.rawchildren)
|
||||
self.prepend(self.rawchildren, other.rawchildren, filter)
|
||||
|
||||
def extension(self):
|
||||
return self.ref is not None
|
||||
|
||||
def description(self):
|
||||
return ("ref",)
|
||||
|
||||
|
||||
class Import(SchemaObject):
|
||||
"""
|
||||
Represents an XSD schema <xsd:import/> node.
|
||||
|
||||
@cvar locations: A dictionary of namespace locations.
|
||||
@type locations: dict
|
||||
@ivar ns: The imported namespace.
|
||||
@type ns: str
|
||||
@ivar location: The (optional) location.
|
||||
@type location: namespace-uri
|
||||
@ivar opened: Opened and I{imported} flag.
|
||||
@type opened: boolean
|
||||
|
||||
"""
|
||||
|
||||
locations = {}
|
||||
|
||||
@classmethod
|
||||
def bind(cls, ns, location=None):
|
||||
"""
|
||||
Bind a namespace to a schema location (URI).
|
||||
|
||||
This is used for imports that do not specify a schemaLocation.
|
||||
|
||||
@param ns: A namespace-uri.
|
||||
@type ns: str
|
||||
@param location: The (optional) schema location for the namespace.
|
||||
(default=ns)
|
||||
@type location: str
|
||||
|
||||
"""
|
||||
if location is None:
|
||||
location = ns
|
||||
cls.locations[ns] = location
|
||||
|
||||
def __init__(self, schema, root):
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.ns = (None, root.get("namespace"))
|
||||
self.location = root.get("schemaLocation")
|
||||
if self.location is None:
|
||||
self.location = self.locations.get(self.ns[1])
|
||||
self.opened = False
|
||||
|
||||
def open(self, options, loaded_schemata):
|
||||
"""
|
||||
Open and import the referenced schema.
|
||||
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
@param loaded_schemata: Already loaded schemata cache (URL --> Schema).
|
||||
@type loaded_schemata: dict
|
||||
@return: The referenced schema.
|
||||
@rtype: L{Schema}
|
||||
|
||||
"""
|
||||
if self.opened:
|
||||
return
|
||||
self.opened = True
|
||||
log.debug("%s, importing ns='%s', location='%s'", self.id, self.ns[1],
|
||||
self.location)
|
||||
result = self.__locate()
|
||||
if result is None:
|
||||
if self.location is None:
|
||||
log.debug("imported schema (%s) not-found", self.ns[1])
|
||||
else:
|
||||
url = self.location
|
||||
if "://" not in url:
|
||||
url = urljoin(self.schema.baseurl, url)
|
||||
result = (loaded_schemata.get(url) or
|
||||
self.__download(url, loaded_schemata, options))
|
||||
log.debug("imported:\n%s", result)
|
||||
return result
|
||||
|
||||
def __locate(self):
|
||||
"""Find the schema locally."""
|
||||
if self.ns[1] != self.schema.tns[1]:
|
||||
return self.schema.locate(self.ns)
|
||||
|
||||
def __download(self, url, loaded_schemata, options):
|
||||
"""Download the schema."""
|
||||
try:
|
||||
reader = DocumentReader(options)
|
||||
d = reader.open(url)
|
||||
root = d.root()
|
||||
root.set("url", url)
|
||||
return self.schema.instance(root, url, loaded_schemata, options)
|
||||
except TransportError:
|
||||
msg = "import schema (%s) at (%s), failed" % (self.ns[1], url)
|
||||
log.error("%s, %s", self.id, msg, exc_info=True)
|
||||
raise Exception(msg)
|
||||
|
||||
def description(self):
|
||||
return "ns", "location"
|
||||
|
||||
|
||||
class Include(SchemaObject):
|
||||
"""
|
||||
Represents an XSD schema <xsd:include/> node.
|
||||
|
||||
@ivar location: The (optional) location.
|
||||
@type location: namespace-uri
|
||||
@ivar opened: Opened and I{imported} flag.
|
||||
@type opened: boolean
|
||||
|
||||
"""
|
||||
|
||||
locations = {}
|
||||
|
||||
def __init__(self, schema, root):
|
||||
SchemaObject.__init__(self, schema, root)
|
||||
self.location = root.get("schemaLocation")
|
||||
if self.location is None:
|
||||
self.location = self.locations.get(self.ns[1])
|
||||
self.opened = False
|
||||
|
||||
def open(self, options, loaded_schemata):
|
||||
"""
|
||||
Open and include the referenced schema.
|
||||
|
||||
@param options: An options dictionary.
|
||||
@type options: L{options.Options}
|
||||
@param loaded_schemata: Already loaded schemata cache (URL --> Schema).
|
||||
@type loaded_schemata: dict
|
||||
@return: The referenced schema.
|
||||
@rtype: L{Schema}
|
||||
|
||||
"""
|
||||
if self.opened:
|
||||
return
|
||||
self.opened = True
|
||||
log.debug("%s, including location='%s'", self.id, self.location)
|
||||
url = self.location
|
||||
if "://" not in url:
|
||||
url = urljoin(self.schema.baseurl, url)
|
||||
result = (loaded_schemata.get(url) or
|
||||
self.__download(url, loaded_schemata, options))
|
||||
log.debug("included:\n%s", result)
|
||||
return result
|
||||
|
||||
def __download(self, url, loaded_schemata, options):
|
||||
"""Download the schema."""
|
||||
try:
|
||||
reader = DocumentReader(options)
|
||||
d = reader.open(url)
|
||||
root = d.root()
|
||||
root.set("url", url)
|
||||
self.__applytns(root)
|
||||
return self.schema.instance(root, url, loaded_schemata, options)
|
||||
except TransportError:
|
||||
msg = "include schema at (%s), failed" % url
|
||||
log.error("%s, %s", self.id, msg, exc_info=True)
|
||||
raise Exception(msg)
|
||||
|
||||
def __applytns(self, root):
|
||||
"""Make sure included schema has the same target namespace."""
|
||||
TNS = "targetNamespace"
|
||||
tns = root.get(TNS)
|
||||
if tns is None:
|
||||
tns = self.schema.tns[1]
|
||||
root.set(TNS, tns)
|
||||
else:
|
||||
if self.schema.tns[1] != tns:
|
||||
raise Exception, "%s mismatch" % TNS
|
||||
|
||||
def description(self):
|
||||
return "location"
|
||||
|
||||
|
||||
class Attribute(TypedContent):
|
||||
"""Represents an XSD schema <attribute/> node."""
|
||||
|
||||
def __init__(self, schema, root):
|
||||
TypedContent.__init__(self, schema, root)
|
||||
self.use = root.get("use", default="")
|
||||
|
||||
def childtags(self):
|
||||
return ("restriction",)
|
||||
|
||||
def isattr(self):
|
||||
return True
|
||||
|
||||
def get_default(self):
|
||||
"""
|
||||
Gets the <xsd:attribute default=""/> attribute value.
|
||||
|
||||
@return: The default value for the attribute
|
||||
@rtype: str
|
||||
|
||||
"""
|
||||
return self.root.get("default", default="")
|
||||
|
||||
def optional(self):
|
||||
return self.use != "required"
|
||||
|
||||
def dependencies(self):
|
||||
deps = []
|
||||
midx = None
|
||||
if self.ref is not None:
|
||||
query = AttrQuery(self.ref)
|
||||
a = query.execute(self.schema)
|
||||
if a is None:
|
||||
log.debug(self.schema)
|
||||
raise TypeNotFound(self.ref)
|
||||
deps.append(a)
|
||||
midx = 0
|
||||
return midx, deps
|
||||
|
||||
def description(self):
|
||||
return "name", "ref", "type"
|
||||
|
||||
|
||||
class Any(Content):
|
||||
"""Represents an XSD schema <any/> node."""
|
||||
|
||||
def get_child(self, name):
|
||||
root = self.root.clone()
|
||||
root.set("note", "synthesized (any) child")
|
||||
child = Any(self.schema, root)
|
||||
return child, []
|
||||
|
||||
def get_attribute(self, name):
|
||||
root = self.root.clone()
|
||||
root.set("note", "synthesized (any) attribute")
|
||||
attribute = Any(self.schema, root)
|
||||
return attribute, []
|
||||
|
||||
def any(self):
|
||||
return True
|
||||
|
||||
|
||||
class Factory:
|
||||
"""
|
||||
@cvar tags: A factory to create object objects based on tag.
|
||||
@type tags: {tag:fn,}
|
||||
|
||||
"""
|
||||
|
||||
tags = {
|
||||
"all": All,
|
||||
"any": Any,
|
||||
"attribute": Attribute,
|
||||
"attributeGroup": AttributeGroup,
|
||||
"choice": Choice,
|
||||
"complexContent": ComplexContent,
|
||||
"complexType": Complex,
|
||||
"element": Element,
|
||||
"enumeration": Enumeration,
|
||||
"extension": Extension,
|
||||
"group": Group,
|
||||
"import": Import,
|
||||
"include": Include,
|
||||
"list": List,
|
||||
"restriction": Restriction,
|
||||
"simpleContent": SimpleContent,
|
||||
"simpleType": Simple,
|
||||
"sequence": Sequence,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def maptag(cls, tag, fn):
|
||||
"""
|
||||
Map (override) tag => I{class} mapping.
|
||||
|
||||
@param tag: An XSD tag name.
|
||||
@type tag: str
|
||||
@param fn: A function or class.
|
||||
@type fn: fn|class.
|
||||
|
||||
"""
|
||||
cls.tags[tag] = fn
|
||||
|
||||
@classmethod
|
||||
def create(cls, root, schema):
|
||||
"""
|
||||
Create an object based on the root tag name.
|
||||
|
||||
@param root: An XML root element.
|
||||
@type root: L{Element}
|
||||
@param schema: A schema object.
|
||||
@type schema: L{schema.Schema}
|
||||
@return: The created object.
|
||||
@rtype: L{SchemaObject}
|
||||
|
||||
"""
|
||||
fn = cls.tags.get(root.name)
|
||||
if fn is not None:
|
||||
return fn(schema, root)
|
||||
|
||||
@classmethod
|
||||
def build(cls, root, schema, filter=("*",)):
|
||||
"""
|
||||
Build an xsobject representation.
|
||||
|
||||
@param root: An schema XML root.
|
||||
@type root: L{sax.element.Element}
|
||||
@param filter: A tag filter.
|
||||
@type filter: [str,...]
|
||||
@return: A schema object graph.
|
||||
@rtype: L{sxbase.SchemaObject}
|
||||
|
||||
"""
|
||||
children = []
|
||||
for node in root.getChildren(ns=Namespace.xsdns):
|
||||
if "*" in filter or node.name in filter:
|
||||
child = cls.create(node, schema)
|
||||
if child is None:
|
||||
continue
|
||||
children.append(child)
|
||||
c = cls.build(node, schema, child.childtags())
|
||||
child.rawchildren = c
|
||||
return children
|
||||
|
||||
@classmethod
|
||||
def collate(cls, children):
|
||||
imports = []
|
||||
elements = {}
|
||||
attributes = {}
|
||||
types = {}
|
||||
groups = {}
|
||||
agrps = {}
|
||||
for c in children:
|
||||
if isinstance(c, (Import, Include)):
|
||||
imports.append(c)
|
||||
continue
|
||||
if isinstance(c, Attribute):
|
||||
attributes[c.qname] = c
|
||||
continue
|
||||
if isinstance(c, Element):
|
||||
elements[c.qname] = c
|
||||
continue
|
||||
if isinstance(c, Group):
|
||||
groups[c.qname] = c
|
||||
continue
|
||||
if isinstance(c, AttributeGroup):
|
||||
agrps[c.qname] = c
|
||||
continue
|
||||
types[c.qname] = c
|
||||
for i in imports:
|
||||
children.remove(i)
|
||||
return children, imports, attributes, elements, types, groups, agrps
|
||||
|
||||
|
||||
#######################################################
|
||||
# Static Import Bindings :-(
|
||||
#######################################################
|
||||
Import.bind(
|
||||
"http://schemas.xmlsoap.org/soap/encoding/",
|
||||
"suds://schemas.xmlsoap.org/soap/encoding/")
|
||||
Import.bind(
|
||||
"http://www.w3.org/XML/1998/namespace",
|
||||
"http://www.w3.org/2001/xml.xsd")
|
||||
Import.bind(
|
||||
"http://www.w3.org/2001/XMLSchema",
|
||||
"http://www.w3.org/2001/XMLSchema.xsd")
|
Loading…
Reference in new issue