Commit 352601ca authored by Serhiy Storchaka's avatar Serhiy Storchaka

Issue #26885: xmlrpc now supports unmarshalling additional data types used

by Apache XML-RPC implementation for numerics and None.
parent 9fab79bc
...@@ -88,9 +88,13 @@ between conformable Python objects and XML on the wire. ...@@ -88,9 +88,13 @@ between conformable Python objects and XML on the wire.
+======================+=======================================================+ +======================+=======================================================+
| ``boolean`` | :class:`bool` | | ``boolean`` | :class:`bool` |
+----------------------+-------------------------------------------------------+ +----------------------+-------------------------------------------------------+
| ``int`` or ``i4`` | :class:`int` in range from -2147483648 to 2147483647. | | ``int``, ``i1``, | :class:`int` in range from -2147483648 to 2147483647. |
| ``i2``, ``i4``, | Values get the ``<int>`` tag. |
| ``i8`` or | |
| ``biginteger`` | |
+----------------------+-------------------------------------------------------+ +----------------------+-------------------------------------------------------+
| ``double`` | :class:`float` | | ``double`` or | :class:`float`. Values get the ``<double>`` tag. |
| ``float`` | |
+----------------------+-------------------------------------------------------+ +----------------------+-------------------------------------------------------+
| ``string`` | :class:`str` | | ``string`` | :class:`str` |
+----------------------+-------------------------------------------------------+ +----------------------+-------------------------------------------------------+
...@@ -114,6 +118,8 @@ between conformable Python objects and XML on the wire. ...@@ -114,6 +118,8 @@ between conformable Python objects and XML on the wire.
| ``nil`` | The ``None`` constant. Passing is allowed only if | | ``nil`` | The ``None`` constant. Passing is allowed only if |
| | *allow_none* is true. | | | *allow_none* is true. |
+----------------------+-------------------------------------------------------+ +----------------------+-------------------------------------------------------+
| ``bigdecimal`` | :class:`decimal.Decimal`. Returned type only. |
+----------------------+-------------------------------------------------------+
This is the full set of data types supported by XML-RPC. Method calls may also This is the full set of data types supported by XML-RPC. Method calls may also
raise a special :exc:`Fault` instance, used to signal XML-RPC server errors, or raise a special :exc:`Fault` instance, used to signal XML-RPC server errors, or
...@@ -137,6 +143,13 @@ between conformable Python objects and XML on the wire. ...@@ -137,6 +143,13 @@ between conformable Python objects and XML on the wire.
.. versionchanged:: 3.5 .. versionchanged:: 3.5
Added the *context* argument. Added the *context* argument.
.. versionchanged:: 3.6
Added support of type tags with prefixes (e.g.``ex:nil``).
Added support of unmarsalling additional types used by Apache XML-RPC
implementation for numerics: ``i1``, ``i2``, ``i8``, ``biginteger``,
``float`` and ``bigdecimal``.
See http://ws.apache.org/xmlrpc/types.html for a description.
.. seealso:: .. seealso::
......
...@@ -934,6 +934,14 @@ Allowed keyword arguments to be passed to :func:`Beep <winsound.Beep>`, ...@@ -934,6 +934,14 @@ Allowed keyword arguments to be passed to :func:`Beep <winsound.Beep>`,
<winsound.PlaySound>` (:issue:`27982`). <winsound.PlaySound>` (:issue:`27982`).
xmlrpc.client
-------------
The module now supports unmarshalling additional data types used by
Apache XML-RPC implementation for numerics and ``None``.
(Contributed by Serhiy Storchaka in :issue:`26885`.)
zipfile zipfile
------- -------
......
import base64 import base64
import datetime import datetime
import decimal
import sys import sys
import time import time
import unittest import unittest
...@@ -237,6 +238,54 @@ class XMLRPCTestCase(unittest.TestCase): ...@@ -237,6 +238,54 @@ class XMLRPCTestCase(unittest.TestCase):
'</struct></value></param></params>') '</struct></value></param></params>')
self.assertRaises(ResponseError, xmlrpclib.loads, data) self.assertRaises(ResponseError, xmlrpclib.loads, data)
def check_loads(self, s, value, **kwargs):
dump = '<params><param><value>%s</value></param></params>' % s
result, m = xmlrpclib.loads(dump, **kwargs)
(newvalue,) = result
self.assertEqual(newvalue, value)
self.assertIs(type(newvalue), type(value))
self.assertIsNone(m)
def test_load_standard_types(self):
check = self.check_loads
check('string', 'string')
check('<string>string</string>', 'string')
check('<string>𝔘𝔫𝔦𝔠𝔬𝔡𝔢 string</string>', '𝔘𝔫𝔦𝔠𝔬𝔡𝔢 string')
check('<int>2056183947</int>', 2056183947)
check('<int>-2056183947</int>', -2056183947)
check('<i4>2056183947</i4>', 2056183947)
check('<double>46093.78125</double>', 46093.78125)
check('<boolean>0</boolean>', False)
check('<base64>AGJ5dGUgc3RyaW5n/w==</base64>',
xmlrpclib.Binary(b'\x00byte string\xff'))
check('<base64>AGJ5dGUgc3RyaW5n/w==</base64>',
b'\x00byte string\xff', use_builtin_types=True)
check('<dateTime.iso8601>20050210T11:41:23</dateTime.iso8601>',
xmlrpclib.DateTime('20050210T11:41:23'))
check('<dateTime.iso8601>20050210T11:41:23</dateTime.iso8601>',
datetime.datetime(2005, 2, 10, 11, 41, 23),
use_builtin_types=True)
check('<array><data>'
'<value><int>1</int></value><value><int>2</int></value>'
'</data></array>', [1, 2])
check('<struct>'
'<member><name>b</name><value><int>2</int></value></member>'
'<member><name>a</name><value><int>1</int></value></member>'
'</struct>', {'a': 1, 'b': 2})
def test_load_extension_types(self):
check = self.check_loads
check('<nil/>', None)
check('<ex:nil/>', None)
check('<i1>205</i1>', 205)
check('<i2>20561</i2>', 20561)
check('<i8>9876543210</i8>', 9876543210)
check('<biginteger>98765432100123456789</biginteger>',
98765432100123456789)
check('<float>93.78125</float>', 93.78125)
check('<bigdecimal>9876543210.0123456789</bigdecimal>',
decimal.Decimal('9876543210.0123456789'))
def test_get_host_info(self): def test_get_host_info(self):
# see bug #3613, this raised a TypeError # see bug #3613, this raised a TypeError
transp = xmlrpc.client.Transport() transp = xmlrpc.client.Transport()
......
...@@ -132,6 +132,7 @@ import base64 ...@@ -132,6 +132,7 @@ import base64
import sys import sys
import time import time
from datetime import datetime from datetime import datetime
from decimal import Decimal
import http.client import http.client
import urllib.parse import urllib.parse
from xml.parsers import expat from xml.parsers import expat
...@@ -667,6 +668,8 @@ class Unmarshaller: ...@@ -667,6 +668,8 @@ class Unmarshaller:
def start(self, tag, attrs): def start(self, tag, attrs):
# prepare to handle this element # prepare to handle this element
if ':' in tag:
tag = tag.split(':')[-1]
if tag == "array" or tag == "struct": if tag == "array" or tag == "struct":
self._marks.append(len(self._stack)) self._marks.append(len(self._stack))
self._data = [] self._data = []
...@@ -682,8 +685,12 @@ class Unmarshaller: ...@@ -682,8 +685,12 @@ class Unmarshaller:
try: try:
f = self.dispatch[tag] f = self.dispatch[tag]
except KeyError: except KeyError:
pass # unknown tag ? if ':' not in tag:
else: return # unknown tag ?
try:
f = self.dispatch[tag.split(':')[-1]]
except KeyError:
return # unknown tag ?
return f(self, "".join(self._data)) return f(self, "".join(self._data))
# #
...@@ -694,8 +701,12 @@ class Unmarshaller: ...@@ -694,8 +701,12 @@ class Unmarshaller:
try: try:
f = self.dispatch[tag] f = self.dispatch[tag]
except KeyError: except KeyError:
pass # unknown tag ? if ':' not in tag:
else: return # unknown tag ?
try:
f = self.dispatch[tag.split(':')[-1]]
except KeyError:
return # unknown tag ?
return f(self, data) return f(self, data)
# #
...@@ -721,14 +732,23 @@ class Unmarshaller: ...@@ -721,14 +732,23 @@ class Unmarshaller:
def end_int(self, data): def end_int(self, data):
self.append(int(data)) self.append(int(data))
self._value = 0 self._value = 0
dispatch["i1"] = end_int
dispatch["i2"] = end_int
dispatch["i4"] = end_int dispatch["i4"] = end_int
dispatch["i8"] = end_int dispatch["i8"] = end_int
dispatch["int"] = end_int dispatch["int"] = end_int
dispatch["biginteger"] = end_int
def end_double(self, data): def end_double(self, data):
self.append(float(data)) self.append(float(data))
self._value = 0 self._value = 0
dispatch["double"] = end_double dispatch["double"] = end_double
dispatch["float"] = end_double
def end_bigdecimal(self, data):
self.append(Decimal(data))
self._value = 0
dispatch["bigdecimal"] = end_bigdecimal
def end_string(self, data): def end_string(self, data):
if self._encoding: if self._encoding:
......
...@@ -143,6 +143,9 @@ Core and Builtins ...@@ -143,6 +143,9 @@ Core and Builtins
Library Library
------- -------
- Issue #26885: xmlrpc now supports unmarshalling additional data types used
by Apache XML-RPC implementation for numerics and None.
- Issue #28070: Fixed parsing inline verbose flag in regular expressions. - Issue #28070: Fixed parsing inline verbose flag in regular expressions.
- Issue #19500: Add client-side SSL session resumption to the ssl module. - Issue #19500: Add client-side SSL session resumption to the ssl module.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment