Remove suds_bak

master
Mike Hiretsky 3 years ago
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);)", "&amp;"),
("<", "&lt;"),
(">", "&gt;"),
('"', "&quot;"),
("'", "&apos;"))
decodings = (
("&lt;", "<"),
("&gt;", ">"),
("&quot;", '"'),
("&apos;", "'"),
("&amp;", "&"))
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")

@ -1,347 +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: Jeff Ortel ( jortel@redhat.com )
"""Classes representing I{built-in} XSD schema objects."""
from suds import *
from suds.xsd import *
from suds.sax.date import *
from suds.xsd.sxbase import XBuiltin
import datetime
import decimal
import sys
class XAny(XBuiltin):
"""Represents an XSD <xsd:any/> node."""
def __init__(self, schema, name):
XBuiltin.__init__(self, schema, name)
self.nillable = False
def get_child(self, name):
child = XAny(self.schema, name)
return child, []
def any(self):
return True
class XBoolean(XBuiltin):
"""Represents an XSD boolean built-in type."""
_xml_to_python = {"1": True, "true": True, "0": False, "false": False}
_python_to_xml = {True: "true", 1: "true", False: "false", 0: "false"}
@staticmethod
def translate(value, topython=True):
if topython:
if isinstance(value, basestring):
return XBoolean._xml_to_python.get(value)
else:
if isinstance(value, (bool, int)):
return XBoolean._python_to_xml.get(value)
return value
class XDate(XBuiltin):
"""Represents an XSD <xsd:date/> built-in type."""
@staticmethod
def translate(value, topython=True):
if topython:
if isinstance(value, basestring) and value:
return Date(value).value
else:
if isinstance(value, datetime.date):
return Date(value)
return value
class XDateTime(XBuiltin):
"""Represents an XSD <xsd:datetime/> built-in type."""
@staticmethod
def translate(value, topython=True):
if topython:
if isinstance(value, basestring) and value:
return DateTime(value).value
else:
if isinstance(value, datetime.datetime):
return DateTime(value)
return value
class XDecimal(XBuiltin):
"""
Represents an XSD <xsd:decimal/> built-in type.
Excerpt from the XSD datatype specification
(http://www.w3.org/TR/2004/REC-xmlschema-2-20041028):
> 3.2.3 decimal
>
> [Definition:] decimal represents a subset of the real numbers, which can
> be represented by decimal numerals. The ·value space· of decimal is the
> set of numbers that can be obtained by multiplying an integer by a
> non-positive power of ten, i.e., expressible as i × 10^-n where i and n
> are integers and n >= 0. Precision is not reflected in this value space;
> the number 2.0 is not distinct from the number 2.00. The ·order-relation·
> on decimal is the order relation on real numbers, restricted to this
> subset.
>
> 3.2.3.1 Lexical representation
>
> decimal has a lexical representation consisting of a finite-length
> sequence of decimal digits (#x30-#x39) separated by a period as a decimal
> indicator. An optional leading sign is allowed. If the sign is omitted,
> "+" is assumed. Leading and trailing zeroes are optional. If the
> fractional part is zero, the period and following zero(es) can be
> omitted. For example: -1.23, 12678967.543233, +100000.00, 210.
"""
# Python versions before 2.7 do not support the decimal.Decimal.canonical()
# method but they maintain their decimal.Decimal encoded in canonical
# format internally so we can easily emulate that function by simply
# returning the same decimal instance.
if sys.version_info < (2, 7):
_decimal_canonical = staticmethod(lambda decimal: decimal)
else:
_decimal_canonical = decimal.Decimal.canonical
@staticmethod
def _decimal_to_xsd_format(value):
"""
Converts a decimal.Decimal value to its XSD decimal type value.
Result is a string containing the XSD decimal type's lexical value
representation. The conversion is done without any precision loss.
Note that Python's native decimal.Decimal string representation will
not do here as the lexical representation desired here does not allow
representing decimal values using float-like `<mantissa>E<exponent>'
format, e.g. 12E+30 or 0.10006E-12.
"""
value = XDecimal._decimal_canonical(value)
negative, digits, exponent = value.as_tuple()
# The following implementation assumes the following tuple decimal
# encoding (part of the canonical decimal value encoding):
# - digits must contain at least one element
# - no leading integral 0 digits except a single one in 0 (if a non-0
# decimal value has leading integral 0 digits they must be encoded
# in its 'exponent' value and not included explicitly in its
# 'digits' tuple)
assert digits
assert digits[0] != 0 or len(digits) == 1
result = []
if negative:
result.append("-")
# No fractional digits.
if exponent >= 0:
result.extend(str(x) for x in digits)
result.extend("0" * exponent)
return "".join(result)
digit_count = len(digits)
# Decimal point offset from the given digit start.
point_offset = digit_count + exponent
# Trim trailing fractional 0 digits.
fractional_digit_count = min(digit_count, -exponent)
while fractional_digit_count and digits[digit_count - 1] == 0:
digit_count -= 1
fractional_digit_count -= 1
# No trailing fractional 0 digits and a decimal point coming not after
# the given digits, meaning there is no need to add additional trailing
# integral 0 digits.
if point_offset <= 0:
# No integral digits.
result.append("0")
if digit_count > 0:
result.append(".")
result.append("0" * -point_offset)
result.extend(str(x) for x in digits[:digit_count])
else:
# Have integral and possibly some fractional digits.
result.extend(str(x) for x in digits[:point_offset])
if point_offset < digit_count:
result.append(".")
result.extend(str(x) for x in digits[point_offset:digit_count])
return "".join(result)
@classmethod
def translate(cls, value, topython=True):
if topython:
if isinstance(value, basestring) and value:
return decimal.Decimal(value)
else:
if isinstance(value, decimal.Decimal):
return cls._decimal_to_xsd_format(value)
return value
class XFloat(XBuiltin):
"""Represents an XSD <xsd:float/> built-in type."""
@staticmethod
def translate(value, topython=True):
if topython:
if isinstance(value, basestring) and value:
return float(value)
else:
return value
class XInteger(XBuiltin):
"""Represents an XSD <xsd:int/> built-in type."""
@staticmethod
def translate(value, topython=True):
if topython:
if isinstance(value, basestring) and value:
return int(value)
else:
return value
class XLong(XBuiltin):
"""Represents an XSD <xsd:long/> built-in type."""
@staticmethod
def translate(value, topython=True):
if topython:
if isinstance(value, basestring) and value:
return long(value)
else:
return value
class XString(XBuiltin):
"""Represents an XSD <xsd:string/> node."""
pass
class XTime(XBuiltin):
"""Represents an XSD <xsd:time/> built-in type."""
@staticmethod
def translate(value, topython=True):
if topython:
if isinstance(value, basestring) and value:
return Time(value).value
else:
if isinstance(value, datetime.time):
return Time(value)
return value
class Factory:
tags = {
# any
"anyType": XAny,
# strings
"string": XString,
"normalizedString": XString,
"ID": XString,
"Name": XString,
"QName": XString,
"NCName": XString,
"anySimpleType": XString,
"anyURI": XString,
"NOTATION": XString,
"token": XString,
"language": XString,
"IDREFS": XString,
"ENTITIES": XString,
"IDREF": XString,
"ENTITY": XString,
"NMTOKEN": XString,
"NMTOKENS": XString,
# binary
"hexBinary": XString,
"base64Binary": XString,
# integers
"int": XInteger,
"integer": XInteger,
"unsignedInt": XInteger,
"positiveInteger": XInteger,
"negativeInteger": XInteger,
"nonPositiveInteger": XInteger,
"nonNegativeInteger": XInteger,
# longs
"long": XLong,
"unsignedLong": XLong,
# shorts
"short": XInteger,
"unsignedShort": XInteger,
"byte": XInteger,
"unsignedByte": XInteger,
# floats
"float": XFloat,
"double": XFloat,
"decimal": XDecimal,
# dates & times
"date": XDate,
"time": XTime,
"dateTime": XDateTime,
"duration": XString,
"gYearMonth": XString,
"gYear": XString,
"gMonthDay": XString,
"gDay": XString,
"gMonth": XString,
# boolean
"boolean": XBoolean,
}
@classmethod
def maptag(cls, tag, fn):
"""
Map (override) tag => I{class} mapping.
@param tag: An XSD tag name.
@type tag: str
@param fn: A function or class.
@type fn: fn|class.
"""
cls.tags[tag] = fn
@classmethod
def create(cls, schema, name):
"""
Create an object based on the root tag name.
@param schema: A schema object.
@type schema: L{schema.Schema}
@param name: The name.
@type name: str
@return: The created object.
@rtype: L{XBuiltin}
"""
fn = cls.tags.get(name, XBuiltin)
return fn(schema, name)
Loading…
Cancel
Save