# HG changeset patch # User Paul Boddie # Date 1221352258 -7200 # Node ID 788b7bf56c1eba795085c622564f499e3dfc9cfc # Parent 0a2899d30aa933172a52bfa73ca1966996e72801 Made numerous changes to the XML-RPC support in order to support conversion of incoming and outgoing Python values. Introduced prettyprinting support using a libxml2mod global setting. Updated release notes. Added more tests of XML-RPC processing, renaming one test program. Reintroduced value property usage where appropriate, since the standard value property exists only on attributes. diff -r 0a2899d30aa9 -r 788b7bf56c1e README.txt --- a/README.txt Fri Sep 12 01:03:56 2008 +0200 +++ b/README.txt Sun Sep 14 02:30:58 2008 +0200 @@ -71,9 +71,10 @@ ------------------------------------------------------ * Changed some XML-RPC node properties in order to retain underlying DOM - properties such as value and data. + properties such as data. * Added convenience methods to the XML-RPC implementation, with combined node creation and insertion if requested. + * Enabled prettyprinting support, finally. New in libxml2dom 0.4.7 (Changes since libxml2dom 0.4.6) -------------------------------------------------------- diff -r 0a2899d30aa9 -r 788b7bf56c1e libxml2dom/macrolib/macrolib.py --- a/libxml2dom/macrolib/macrolib.py Fri Sep 12 01:03:56 2008 +0200 +++ b/libxml2dom/macrolib/macrolib.py Sun Sep 14 02:30:58 2008 +0200 @@ -29,6 +29,10 @@ except ImportError: from libxmlmods import libxml2mod +# Global reconfiguration. This enables prettyprinting. + +libxml2mod.xmlKeepBlanksDefault(0) + # NOTE: libxml2 seems to use UTF-8 throughout. # NOTE: Implement: http://www.w3.org/TR/2006/REC-xml-20060816/#AVNormalize diff -r 0a2899d30aa9 -r 788b7bf56c1e libxml2dom/xmlrpc.py --- a/libxml2dom/xmlrpc.py Fri Sep 12 01:03:56 2008 +0200 +++ b/libxml2dom/xmlrpc.py Sun Sep 14 02:30:58 2008 +0200 @@ -25,7 +25,13 @@ The sending and receiving of XML-RPC messages can be done using traditional HTTP libraries. -See tests/xmlrpc_test.py for more details. +See tests/test_xmlrpc_parse.py for more details. + +Useful properties: + + * container - returns the actual value element containing transmitted data + * contents - returns the converted data from within a container or a + container object """ import libxml2dom @@ -33,6 +39,7 @@ from libxml2dom.macrolib import \ createDocument as Node_createDocument import datetime +import xml.dom # Node classes. @@ -41,12 +48,78 @@ "Convenience modifications to nodes specific to libxml2dom.xmlrpc." def add_or_replace_element(self, new_element): + + """ + Add or replace the given 'new_element', using its localName to find any + element to be replaced. + """ + elements = self.xpath(new_element.localName) if elements: self.replaceChild(new_element, elements[0]) else: self.appendChild(new_element) + def serialise_value(self, parent, value): + + "Serialise, under the 'parent', the given 'value' object." + + if isinstance(value, (str, int, float, bool)): + valueElement = parent.createValue(typenames[value.__class__.__name__], 1) + valueElement.container.value = str(value) + + elif isinstance(value, datetime.datetime): + valueElement = parent.createValue("dateTime.iso8601", 1) + valueElement.container.value = value.strftime("%Y%m%dT%H:%M:%S") + + elif isinstance(value, (tuple, list)): + array = parent.createValue("array", 1).container + dataElement = array.createData(1) + for v in value: + self.serialise_value(dataElement, v) + + elif isinstance(value, dict): + struct = parent.createValue("struct", 1).container + for k, v in value.items(): + member = struct.createMember(1) + member.memberName = str(k) + self.serialise_value(member, v) + + else: + raise ValueError, "Value %r cannot be serialised." % value + +class ContentEquality: + + "Equality testing." + + def __eq__(self, other): + if hasattr(other, "contents"): + return self.contents == other.contents + else: + return self.contents == other + + def __ne__(self, other): + return not self.__eq__(other) + +class SequenceEquality: + + "Sequence equality testing." + + def __eq__(self, other): + for i, j in map(None, self, other): + if i != j: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def __len__(self): + return len(self.values()) + + def __getitem__(self, i): + return self.values()[i] + class XMLRPCElement(XMLRPCNode): "An XML-RPC element." @@ -110,9 +183,21 @@ self.methodNameElement.value = name def _parameterValues(self): - return [value.container.contents for value in self.xpath("./params/param/value")] + if self.params: + return self.params.values() + else: + return None + + def _setParameterValues(self, values): + params = self.createParameters(1) + for value in values: + param = params.createParameter(1) + self.serialise_value(param, value) def _parameters(self): + + "Return a list of the individual param elements." + return self.xpath("./params/param") # Node construction methods. @@ -139,16 +224,36 @@ params = property(_params) methodNameElement = property(_methodNameElement) methodName = property(_methodName, _setMethodName) - parameterValues = property(_parameterValues) + parameterValues = property(_parameterValues, _setParameterValues) parameters = property(_parameters) -class XMLRPCParametersElement(XMLRPCNode): +class XMLRPCParametersElement(SequenceEquality, XMLRPCNode): - "An XML-RPC parameters/params element." + """ + An XML-RPC parameters/params element. + + This element behaves like a list in that values can be appended to it and + these will be added as new parameters. + """ def _parameters(self): + + "Return a list of the individual param elements." + return self.xpath("./param") + # Sequence emulation. + + def append(self, value): + param = self.createParameter(1) + self.serialise_value(param, value) + + def values(self): + return [value.contents for value in self.xpath("./param/value")] + + def __repr__(self): + return "" % self.parameters + # Node construction methods. def createParameter(self, insert=0): @@ -159,13 +264,50 @@ parameters = property(_parameters) -class XMLRPCParameterElement(XMLRPCNode): +class XMLRPCParameterElement(ContentEquality, XMLRPCNode): + + """ + An XML-RPC parameter/param element. - "An XML-RPC parameter/param element." + This element behaves like a list in that values can be appended to it and + these will be added either as new elements in an existing array or struct, + or as new elements in a new array. + """ def _valueElement(self): return (self.xpath("./value") or [None])[0] + def _contents(self): + if self.valueElement is not None: + return self.valueElement.contents + else: + return self + + # Sequence emulation. + + def __len__(self): + return len(self.values()) + + def __getitem__(self, i): + return self.values()[i] + + def append(self, value): + if self.valueElement is None: + self.createValue("array", 1) + self.contents.append(value) + + def values(self): + if self.contents is not self: + return self.contents.values() + else: + return [] + + def __repr__(self): + if self.contents is self: + return "" + else: + return "" % self.contents + # Node construction methods. def createValue(self, typename=None, insert=0): @@ -178,10 +320,16 @@ return value valueElement = property(_valueElement) + contents = property(_contents) -class XMLRPCArrayElement(XMLRPCNode): +class XMLRPCArrayElement(SequenceEquality, XMLRPCNode): - "An XML-RPC array element." + """ + An XML-RPC array element. + + This element behaves like a list in that values can be appended to it and + these will be added as new elements in the array. + """ def _dataElement(self): return (self.xpath("./data") or [None])[0] @@ -191,23 +339,16 @@ # Sequence emulation. - def __len__(self): - if self.data: - return len(self.data) - else: - return 0 + def append(self, value): + if self.dataElement is None: + self.createData(1) + self.serialise_value(self.dataElement, value) - def __getitem__(self, i): - if self.data: - return self.data[i] - else: - raise IndexError, i + def values(self): + return [v.contents for v in self.xpath("./data/value")] - def __eq__(self, other): - for i, j in map(None, self, other): - if i != j: - return False - return True + def __repr__(self): + return "" % self.values() # Node construction methods. @@ -220,9 +361,14 @@ dataElement = property(_dataElement) contents = property(_contents) -class XMLRPCStructElement(XMLRPCNode): +class XMLRPCStructElement(SequenceEquality, XMLRPCNode): - "An XML-RPC structure element." + """ + An XML-RPC structure element. + + This element behaves like a list in that values can be appended to it and + these will be added as new (name, value) members in the structure. + """ def _members(self): return self.xpath("./member") @@ -238,51 +384,77 @@ def __getitem__(self, i): return self.members[i] - def __eq__(self, other): - for i, j in map(None, self, other): - if i != j: - return False - return True + def append(self, item): + name, value = item + member = self.createMember(1) + member.memberName = name + self.serialise_value(member, value) + + def __repr__(self): + return "" % self.items() + + # Dictionary emulation. + + def keys(self): + return [member.memberName for member in self.members] + + def values(self): + return [member.memberValue for member in self.members] + + def items(self): + return [(member.memberName, member.memberValue) for member in self.members] # Node construction methods. def createMember(self, insert=0): e = self.ownerDocument.createElement("member") if insert: - self.add_or_replace_element(e) + self.appendChild(e) return e members = property(_members) contents = property(_contents) -class XMLRPCDataElement(XMLRPCNode): +class XMLRPCDataElement(SequenceEquality, XMLRPCNode): + + """ + An XML-RPC array data element. - "An XML-RPC array data element." + This element behaves like a list in that values can be appended to it and + these will be added as new elements in the array. + """ - def _values(self): - return self.xpath("./value") + def _contents(self): + return self # Sequence emulation. - def __len__(self): - return len(self.values) + def append(self, value): + self.serialise_value(self, value) - def __getitem__(self, i): - return self.values[i].container.contents + def values(self): + return [v.contents for v in self.xpath("./value")] # Node construction methods. - def createValue(self, insert=0): - e = self.ownerDocument.createElement("value") + def createValue(self, typename=None, insert=0): + value = self.ownerDocument.createElement("value") + if typename is not None: + contents = self.ownerDocument.createElement(typename) + value.appendChild(contents) if insert: - self.add_or_replace_element(e) - return e + self.appendChild(value) + return value - values = property(_values) + contents = property(_contents) class XMLRPCMemberElement(XMLRPCNode): - "An XML-RPC structure member element." + """ + An XML-RPC structure member element. + + This element behaves like a tuple of the form (name, value). + """ def _valueElement(self): return (self.xpath("./value") or [None])[0] @@ -302,6 +474,12 @@ self.appendChild(nameElement) self.nameElement.value = name + def _memberValue(self): + if self.valueElement is None: + return None + else: + return self.valueElement.contents + def _contents(self): return self @@ -311,11 +489,16 @@ return 2 def __getitem__(self, i): - return (self.memberName, self.value.container.contents)[i] + return (self.memberName, self.valueElement.contents)[i] + + # Equality testing. def __eq__(self, other): return self[0] == other[0] and self[1] == other[1] + def __ne__(self, other): + return not self.__eq__(other) + # Node construction methods. def createName(self, insert=0): @@ -324,18 +507,22 @@ self.add_or_replace_element(e) return e - def createValue(self, insert=0): - e = self.ownerDocument.createElement("value") + def createValue(self, typename=None, insert=0): + value = self.ownerDocument.createElement("value") + if typename is not None: + contents = self.ownerDocument.createElement(typename) + value.appendChild(contents) if insert: - self.add_or_replace_element(e) - return e + self.add_or_replace_element(value) + return value nameElement = property(_nameElement) memberName = property(_memberName, _setMemberName) + memberValue = property(_memberValue) valueElement = property(_valueElement) contents = property(_contents) -class XMLRPCStringElement(XMLRPCNode): +class XMLRPCStringElement(ContentEquality, XMLRPCNode): "An XML-RPC string element." @@ -353,11 +540,8 @@ def _contents(self): return convert(self.typename, self.value) - def __eq__(self, other): - if hasattr(other, "contents"): - return self.contents == other.contents - else: - return self.contents == other + def __repr__(self): + return "<%s: %r>" % (self.__class__.__name__, self.contents) value = property(_value, _setValue) contents = property(_contents) @@ -368,16 +552,39 @@ pass +class XMLRPCMethodNameElement(XMLRPCNameElement): + + "An XML-RPC method element." + + pass + class XMLRPCValueElement(XMLRPCStringElement): "An XML-RPC value element." + def _value(self): + if self.container is self: + return XMLRPCStringElement._value(self) + else: + return None + + def _setValue(self, value): + if self.container is self: + return XMLRPCStringElement._setValue(self, value) + else: + raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR) + + def _contents(self): + if self.container is self: + return XMLRPCStringElement._contents(self) + else: + return self.container.contents + def _type(self): - elements = self.xpath("*") - if elements: - return elements[0].localName + if self.container is self: + return "string" else: - return "string" + return self.container.localName def _setType(self, typename): new_contents = self.ownerDocument.createElement(typename) @@ -386,14 +593,10 @@ def _container(self): return (self.xpath("*") or [self])[0] + value = property(_value, _setValue) type = property(_type, _setType) container = property(_container) - -class XMLRPCMethodNameElement(XMLRPCStringElement): - - "An XML-RPC method element." - - pass + contents = property(_contents) class XMLRPCIntegerElement(XMLRPCStringElement): @@ -457,7 +660,7 @@ elif s.lower() == "false": return False else: - raise ValueError, "String value %s not convertable to boolean." % repr(s) + raise ValueError, "String value %r not convertable to boolean." % s def iso8601(s): year, month, day, hour, minute, second = map(int, (s[:4], s[4:6], s[6:8], s[9:11], s[12:14], s[15:17])) @@ -473,6 +676,13 @@ "base64" : str } +typenames = { + "str" : "string", + "int" : "int", + "bool" : "boolean", + "float" : "double" + } + # Implementation-related functionality. class XMLRPCImplementation(libxml2dom.Implementation): diff -r 0a2899d30aa9 -r 788b7bf56c1e tests/test_xmlrpc.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_xmlrpc.py Sun Sep 14 02:30:58 2008 +0200 @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +import libxml2dom.xmlrpc + +m1 = libxml2dom.xmlrpc.createMethodCall("hello") +m1.parameterValues = ["1", [1.0, (2, "3")], 1, {4: 5, "a": "b"}] +print m1.parameterValues + +m2 = libxml2dom.xmlrpc.createMethodCall("hello") +m2.parameterValues = [[1.0, (2, "3")], [2, "3"]] +print m2.parameterValues + +print m2.parameterValues[0], m2.parameterValues[1] +assert m2.parameterValues[0] != m2.parameterValues[1] + +print m2.parameters[0], m2.parameters[1] +assert m2.parameters[0] != m2.parameters[1] + +print m2.parameterValues[0][1], m2.parameterValues[1] +assert m2.parameterValues[0][1] == m2.parameterValues[1] + +print m2.parameters[0][1], m2.parameters[1] +assert m2.parameters[0][1] == m2.parameters[1] + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 0a2899d30aa9 -r 788b7bf56c1e tests/test_xmlrpc_parse.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_xmlrpc_parse.py Sun Sep 14 02:30:58 2008 +0200 @@ -0,0 +1,192 @@ +#!/usr/bin/env python + +import libxml2dom.xmlrpc + +# Some examples from the specification. + +request = """ + + examples.getStateName + + + 41 + + +""" + +req = libxml2dom.xmlrpc.parseString(request) +print "Method name:", req.method.methodName +print "Parameter values:", req.method.parameterValues +print "Fault:", req.fault +assert req.method.methodName == "examples.getStateName" +assert req.method.parameterValues == [41] +assert req.method.parameters[0].valueElement.container == 41 +assert req.method.params.values()[0] == 41 +assert req.fault is None + +response = """ + + + + South Dakota + + +""" + +resp = libxml2dom.xmlrpc.parseString(response) +print "Method name:", resp.method.methodName +print "Parameter values:", resp.method.parameterValues +print "Fault:", resp.fault +assert resp.method.methodName is None +assert resp.method.parameterValues == ["South Dakota"] +assert resp.fault is None + +failed = """ + + + + + + faultCode + 4 + + + faultString + Too many parameters. + + + + +""" + +f = libxml2dom.xmlrpc.parseString(failed) +print "Method name:", f.method.methodName +print "Parameter values:", f.method.parameterValues +print "Fault code:", f.fault.code +assert f.method.methodName is None +assert f.method.parameterValues is None +assert f.fault.code == "4" +assert f.fault.reason == "Too many parameters." + +# Python Package Index examples. + +search = """ + + search + + + + + + name + libxml2dom + + + description + XML + + + + + + + and + + + +""" + +s = libxml2dom.xmlrpc.parseString(search) +print "Method name:", s.method.methodName +print "Parameter values:", s.method.parameterValues +print "Fault:", s.fault +assert s.method.methodName == "search" +assert s.method.parameterValues == [ + [ + ("name", "libxml2dom"), + ("description", "XML") + ], + "and" + ] +assert s.fault is None + +# Nested structure examples. + +search2 = """ + + search + + + + + + names + + + + name + libxml2dom + + + description + XML + + + + + + + + + + and + + + +""" + +s2 = libxml2dom.xmlrpc.parseString(search2) +print "Method name:", s2.method.methodName +print "Parameter values:", s2.method.parameterValues +print "Fault:", s2.fault +assert s2.method.methodName == "search" +assert s2.method.parameterValues == [ + [ + ("names", [ + ("name", "libxml2dom"), + ("description", "XML") + ]) + ], + "and" + ] +assert s2.fault is None + +arrays = """ + + + + + + + + libxml2dom + + + XSLTools + + + + + + +""" + +a = libxml2dom.xmlrpc.parseString(arrays) +print "Method name:", a.method.methodName +print "Parameter values:", a.method.parameterValues +print "Fault:", a.fault +assert a.method.methodName is None +assert a.method.parameterValues == [["libxml2dom", "XSLTools"]] +assert a.fault is None + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 0a2899d30aa9 -r 788b7bf56c1e tests/xmlrpc_test.py --- a/tests/xmlrpc_test.py Fri Sep 12 01:03:56 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,190 +0,0 @@ -#!/usr/bin/env python - -import libxml2dom.xmlrpc - -# Some examples from the specification. - -request = """ - - examples.getStateName - - - 41 - - - """ - -req = libxml2dom.xmlrpc.parseString(request) -assert req.method.methodName == "examples.getStateName" -assert req.method.parameterValues == [41] -assert req.fault is None -print "Method name:", req.method.methodName -print "Parameter values:", req.method.parameterValues -print "Fault:", req.fault - -response = """ - - - - South Dakota - - - """ - -resp = libxml2dom.xmlrpc.parseString(response) -assert resp.method.methodName is None -assert resp.method.parameterValues == ["South Dakota"] -assert resp.fault is None -print "Method name:", resp.method.methodName -print "Parameter values:", resp.method.parameterValues -print "Fault:", resp.fault - -failed = """ - - - - - - faultCode - 4 - - - faultString - Too many parameters. - - - - - """ - -f = libxml2dom.xmlrpc.parseString(failed) -assert f.method.methodName is None -assert f.method.parameterValues == [] -assert f.fault.code == "4" -assert f.fault.reason == "Too many parameters." -print "Method name:", f.method.methodName -print "Parameter values:", f.method.parameterValues -print "Fault code:", f.fault.code - -# Python Package Index examples. - -search = """ - - search - - - - - - name - libxml2dom - - - description - XML - - - - - - - and - - - -""" - -s = libxml2dom.xmlrpc.parseString(search) -assert s.method.methodName == "search" -assert s.method.parameterValues == [ - [ - ("name", "libxml2dom"), - ("description", "XML") - ], - "and" - ] -assert s.fault is None -print "Method name:", s.method.methodName -print "Parameter values:", s.method.parameterValues -print "Fault:", s.fault - -# Nested structure examples. - -search2 = """ - - search - - - - - - names - - - - name - libxml2dom - - - description - XML - - - - - - - - - - and - - - -""" - -s2 = libxml2dom.xmlrpc.parseString(search2) -assert s2.method.methodName == "search" -assert s2.method.parameterValues == [ - [ - ("names", [ - ("name", "libxml2dom"), - ("description", "XML") - ]) - ], - "and" - ] -assert s2.fault is None -print "Method name:", s2.method.methodName -print "Parameter values:", s2.method.parameterValues -print "Fault:", s2.fault - -arrays = """ - - - - - - - - libxml2dom - - - XSLTools - - - - - - - """ - -a = libxml2dom.xmlrpc.parseString(arrays) -assert a.method.methodName is None -assert a.method.parameterValues == [["libxml2dom", "XSLTools"]] -assert a.fault is None -print "Method name:", a.method.methodName -print "Parameter values:", a.method.parameterValues -print "Fault:", a.fault - -# vim: tabstop=4 expandtab shiftwidth=4