Libraries for Subliminal
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
# 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 (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.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
|
||||
@@ -0,0 +1,316 @@
|
||||
# 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 logging import getLogger
|
||||
from suds import *
|
||||
from suds.mx import *
|
||||
from suds.sudsobject import footprint
|
||||
from suds.sudsobject import Object, Property
|
||||
from suds.sax.element import Element
|
||||
from suds.sax.text import Text
|
||||
from copy import deepcopy
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
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 )
|
||||
else:
|
||||
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 = PrimativeAppender(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)),
|
||||
(Matcher(dict),
|
||||
DictAppender(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 a in self.appenders:
|
||||
if a[0] == content.value:
|
||||
appender = a[1]
|
||||
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 proccessing 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 proccessing 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 proccessing 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 proccessing 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 proccessing 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 PrimativeAppender(Appender):
|
||||
"""
|
||||
An appender for python I{primative} 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
|
||||
if self.optional(content) and footprint(object) == 0:
|
||||
return
|
||||
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 DictAppender(Appender):
|
||||
"""
|
||||
An python I{dict} appender.
|
||||
"""
|
||||
|
||||
def append(self, parent, content):
|
||||
d = content.value
|
||||
if self.optional(content) and len(d) == 0:
|
||||
return
|
||||
child = self.node(content)
|
||||
parent.append(child)
|
||||
for item in d.items():
|
||||
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)
|
||||
@@ -0,0 +1,48 @@
|
||||
# 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 logging import getLogger
|
||||
from suds import *
|
||||
from suds.mx import *
|
||||
from suds.mx.core import Core
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
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
|
||||
@@ -0,0 +1,158 @@
|
||||
# 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 logging import getLogger
|
||||
from suds import *
|
||||
from suds.mx import *
|
||||
from suds.mx.appender import ContentAppender
|
||||
from suds.sax.element import Element
|
||||
from suds.sax.document import Document
|
||||
from suds.sudsobject import Property
|
||||
|
||||
|
||||
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()
|
||||
if isinstance(content.value, Property):
|
||||
root = self.node(content)
|
||||
self.append(document, content)
|
||||
else:
|
||||
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: The content for which proccessing has been suspended.
|
||||
@type content: L{Object}
|
||||
@return: An element.
|
||||
@rtype: L{Element}
|
||||
"""
|
||||
return Element(content.tag)
|
||||
|
||||
def start(self, content):
|
||||
"""
|
||||
Appending this content has started.
|
||||
@param content: The content for which proccessing 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 proccessing has been suspended.
|
||||
@type content: L{Content}
|
||||
"""
|
||||
pass
|
||||
|
||||
def resume(self, content):
|
||||
"""
|
||||
Appending this content has resumed.
|
||||
@param content: The content for which proccessing 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 proccessing 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
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# 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 logging import getLogger
|
||||
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
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
#
|
||||
# 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
|
||||
@@ -0,0 +1,291 @@
|
||||
# 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 logging import getLogger
|
||||
from suds import *
|
||||
from suds.mx import *
|
||||
from suds.mx.core import Core
|
||||
from suds.mx.typer import Typer
|
||||
from suds.resolver import GraphResolver, Frame
|
||||
from suds.sax.element import Element
|
||||
from suds.sudsobject import Factory
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
#
|
||||
# Add typed extensions
|
||||
# type = The expected xsd type
|
||||
# real = The 'true' XSD type
|
||||
# ancestry = The 'type' ancestry
|
||||
#
|
||||
Content.extensions.append('type')
|
||||
Content.extensions.append('real')
|
||||
Content.extensions.append('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 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
|
||||
else:
|
||||
return True
|
||||
|
||||
def suspend(self, content):
|
||||
#
|
||||
# Suspend to process a list content. Primarily, this
|
||||
# involves popping the 'list' content off the resolver's
|
||||
# stack so the list items can be marshalled.
|
||||
#
|
||||
self.resolver.pop()
|
||||
|
||||
def resume(self, content):
|
||||
#
|
||||
# Resume processing a 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:
|
||||
self.resolver.pop()
|
||||
else:
|
||||
raise Exception, \
|
||||
'content (end) mismatch: top=(%s) cont=(%s)' % \
|
||||
(current, content)
|
||||
|
||||
def node(self, content):
|
||||
#
|
||||
# Create an XML node and namespace qualify as defined
|
||||
# by the schema (elementFormDefault).
|
||||
#
|
||||
ns = content.type.namespace()
|
||||
if content.type.form_qualified:
|
||||
node = Element(content.tag, ns=ns)
|
||||
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 None:
|
||||
pass
|
||||
else:
|
||||
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 only if the resolved
|
||||
# type is derived by extension. Further, the xsi:type values
|
||||
# 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 either the value=None or the value is an empty list.
|
||||
@param content: The 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 len(v) == 0:
|
||||
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, primative values are translated from python
|
||||
types to XML types using the XSD type.
|
||||
@param content: The 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 based on ordering defined
|
||||
in the XSD type information.
|
||||
@param content: The 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):
|
||||
"""
|
||||
Get the attribute ordering defined in the specified
|
||||
XSD type information.
|
||||
@param type: An XSD type object.
|
||||
@type type: 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
|
||||
@@ -0,0 +1,123 @@
|
||||
# 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 sx typing classes.
|
||||
"""
|
||||
|
||||
from logging import getLogger
|
||||
from suds import *
|
||||
from suds.mx import *
|
||||
from suds.sax import Namespace as NS
|
||||
from suds.sax.text import Text
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class Typer:
|
||||
"""
|
||||
Provides XML node typing as either automatic or manual.
|
||||
@cvar types: A dict of class to xs type mapping.
|
||||
@type types: dict
|
||||
"""
|
||||
|
||||
types = {
|
||||
int : ('int', NS.xsdns),
|
||||
long : ('long', NS.xsdns),
|
||||
float : ('float', NS.xsdns),
|
||||
str : ('string', NS.xsdns),
|
||||
unicode : ('string', NS.xsdns),
|
||||
Text : ('string', NS.xsdns),
|
||||
bool : ('boolean', NS.xsdns),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def auto(cls, node, value=None):
|
||||
"""
|
||||
Automatically set the node's xsi:type attribute based on either I{value}'s
|
||||
class or the class of the node's text. When I{value} is an unmapped class,
|
||||
the default type (xs:any) is set.
|
||||
@param node: An XML node
|
||||
@type node: L{sax.element.Element}
|
||||
@param value: An object that is or would be the node's text.
|
||||
@type value: I{any}
|
||||
@return: The 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
|
||||
class or the class of the node's text. Then adds the referenced
|
||||
prefix(s) to the node's prefix mapping.
|
||||
@param node: An XML node
|
||||
@type node: L{sax.element.Element}
|
||||
@param tval: The name of the schema type.
|
||||
@type tval: str
|
||||
@param ns: The XML namespace of I{tval}.
|
||||
@type ns: (prefix, uri)
|
||||
@return: The specified node.
|
||||
@rtype: L{sax.element.Element}
|
||||
"""
|
||||
xta = ':'.join((NS.xsins[0], 'type'))
|
||||
node.addPrefix(NS.xsins[0], NS.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: An XML node on which the prefix will be used.
|
||||
@type node: L{sax.element.Element}
|
||||
@param ns: A namespace needing an unique prefix.
|
||||
@type ns: (prefix, uri)
|
||||
@return: The I{ns} with a new prefix.
|
||||
"""
|
||||
for n in range(1, 1024):
|
||||
p = 'ns%d' % n
|
||||
u = node.resolvePrefix(p, default=None)
|
||||
if u is None or u == ns[1]:
|
||||
return (p, ns[1])
|
||||
raise Exception('auto prefix, exhausted')
|
||||
|
||||
@classmethod
|
||||
def known(cls, object):
|
||||
try:
|
||||
md = object.__metadata__
|
||||
known = md.sxtype
|
||||
return known
|
||||
except:
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user