Commit 174dd221 authored by Skip Montanaro's avatar Skip Montanaro

Add better datetime support to xmlrpclib module. Closes patch #1120353.

parent 186e739d
...@@ -19,7 +19,7 @@ objects and XML on the wire. ...@@ -19,7 +19,7 @@ objects and XML on the wire.
\begin{classdesc}{ServerProxy}{uri\optional{, transport\optional{, \begin{classdesc}{ServerProxy}{uri\optional{, transport\optional{,
encoding\optional{, verbose\optional{, encoding\optional{, verbose\optional{,
allow_none}}}}} allow_none\optional{, use_datetime}}}}}}
A \class{ServerProxy} instance is an object that manages communication A \class{ServerProxy} instance is an object that manages communication
with a remote XML-RPC server. The required first argument is a URI with a remote XML-RPC server. The required first argument is a URI
(Uniform Resource Indicator), and will normally be the URL of the (Uniform Resource Indicator), and will normally be the URL of the
...@@ -33,6 +33,13 @@ default behaviour is for \code{None} to raise a \exception{TypeError}. ...@@ -33,6 +33,13 @@ default behaviour is for \code{None} to raise a \exception{TypeError}.
This is a commonly-used extension to the XML-RPC specification, but isn't This is a commonly-used extension to the XML-RPC specification, but isn't
supported by all clients and servers; see supported by all clients and servers; see
\url{http://ontosys.com/xml-rpc/extensions.php} for a description. \url{http://ontosys.com/xml-rpc/extensions.php} for a description.
The \var{use_datetime} flag can be used to cause date/time values to be
presented as \class{\refmodule{datetime}.datetime} objects; this is false
by default. \class{\refmodule{datetime}.datetime},
\class{\refmodule{datetime}.date} and \class{\refmodule{datetime}.time}
objects may be passed to calls. \class{\refmodule{datetime}.date} objects
are converted with a time of ``00:00:00''.
\class{\refmodule{datetime}.time} objects are converted using today's date.
Both the HTTP and HTTPS transports support the URL syntax extension for Both the HTTP and HTTPS transports support the URL syntax extension for
HTTP Basic Authentication: \code{http://user:pass@host:port/path}. The HTTP Basic Authentication: \code{http://user:pass@host:port/path}. The
...@@ -62,8 +69,11 @@ Python type): ...@@ -62,8 +69,11 @@ Python type):
elements. Arrays are returned as lists} elements. Arrays are returned as lists}
\lineii{structures}{A Python dictionary. Keys must be strings, \lineii{structures}{A Python dictionary. Keys must be strings,
values may be any conformable type.} values may be any conformable type.}
\lineii{dates}{in seconds since the epoch; pass in an instance of the \lineii{dates}{in seconds since the epoch (pass in an instance of the
\class{DateTime} wrapper class} \class{DateTime} class) or a
\class{\refmodule{datetime}.datetime},
\class{\refmodule{datetime}.date} or
\class{\refmodule{datetime}.time} instance}
\lineii{binary data}{pass in an instance of the \class{Binary} \lineii{binary data}{pass in an instance of the \class{Binary}
wrapper class} wrapper class}
\end{tableii} \end{tableii}
...@@ -87,6 +97,7 @@ described below. ...@@ -87,6 +97,7 @@ described below.
\class{Server} is retained as an alias for \class{ServerProxy} for backwards \class{Server} is retained as an alias for \class{ServerProxy} for backwards
compatibility. New code should use \class{ServerProxy}. compatibility. New code should use \class{ServerProxy}.
\versionchanged[The \var{use_datetime} flag was added]{2.5}
\end{classdesc} \end{classdesc}
...@@ -96,7 +107,7 @@ compatibility. New code should use \class{ServerProxy}. ...@@ -96,7 +107,7 @@ compatibility. New code should use \class{ServerProxy}.
client software in several languages. Contains pretty much client software in several languages. Contains pretty much
everything an XML-RPC client developer needs to know.} everything an XML-RPC client developer needs to know.}
\seetitle[http://xmlrpc-c.sourceforge.net/hacks.php] \seetitle[http://xmlrpc-c.sourceforge.net/hacks.php]
{XML-RPC-Hacks page}{Extensions for various open-source {XML-RPC Hacks page}{Extensions for various open-source
libraries to support introspection and multicall.} libraries to support introspection and multicall.}
\end{seealso} \end{seealso}
...@@ -149,7 +160,8 @@ returned. The documentation string may contain HTML markup. ...@@ -149,7 +160,8 @@ returned. The documentation string may contain HTML markup.
Introspection methods are currently supported by servers written in Introspection methods are currently supported by servers written in
PHP, C and Microsoft .NET. Partial introspection support is included PHP, C and Microsoft .NET. Partial introspection support is included
in recent updates to UserLand Frontier. Introspection support for in recent updates to UserLand Frontier. Introspection support for
Perl, Python and Java is available at the XML-RPC Hacks page. Perl, Python and Java is available at the \ulink{XML-RPC
Hacks}{http://xmlrpc-c.sourceforge.net/hacks.php} page.
\subsection{Boolean Objects \label{boolean-objects}} \subsection{Boolean Objects \label{boolean-objects}}
...@@ -170,21 +182,23 @@ Write the XML-RPC encoding of this Boolean item to the out stream object. ...@@ -170,21 +182,23 @@ Write the XML-RPC encoding of this Boolean item to the out stream object.
\subsection{DateTime Objects \label{datetime-objects}} \subsection{DateTime Objects \label{datetime-objects}}
This class may be initialized with seconds since the epoch, a This class may be initialized with seconds since the epoch, a time tuple, an
time tuple, or an ISO 8601 time/date string. It has the following ISO 8601 time/date string, or a {}\class{\refmodule{datetime}.datetime},
methods, supported mainly for internal use by the {}\class{\refmodule{datetime}.date} or {}\class{\refmodule{datetime}.time}
marshalling/unmarshalling code: instance. It has the following methods, supported mainly for internal use
by the marshalling/unmarshalling code:
\begin{methoddesc}{decode}{string} \begin{methoddesc}{decode}{string}
Accept a string as the instance's new time value. Accept a string as the instance's new time value.
\end{methoddesc} \end{methoddesc}
\begin{methoddesc}{encode}{out} \begin{methoddesc}{encode}{out}
Write the XML-RPC encoding of this DateTime item to the out stream object. Write the XML-RPC encoding of this \class{DateTime} item to the
\var{out} stream object.
\end{methoddesc} \end{methoddesc}
It also supports certain of Python's built-in operators through It also supports certain of Python's built-in operators through
\method{__cmp__} and \method{__repr__} methods. \method{__cmp__()} and \method{__repr__()} methods.
\subsection{Binary Objects \label{binary-objects}} \subsection{Binary Objects \label{binary-objects}}
...@@ -296,7 +310,6 @@ Trivially convert any Python string to a \class{Binary} object. ...@@ -296,7 +310,6 @@ Trivially convert any Python string to a \class{Binary} object.
\begin{funcdesc}{dumps}{params\optional{, methodname\optional{, \begin{funcdesc}{dumps}{params\optional{, methodname\optional{,
methodresponse\optional{, encoding\optional{, methodresponse\optional{, encoding\optional{,
allow_none}}}}} allow_none}}}}}
Convert \var{params} into an XML-RPC request. Convert \var{params} into an XML-RPC request.
or into a response if \var{methodresponse} is true. or into a response if \var{methodresponse} is true.
\var{params} can be either a tuple of arguments or an instance of the \var{params} can be either a tuple of arguments or an instance of the
...@@ -308,12 +321,21 @@ used in standard XML-RPC; to allow using it via an extension, ...@@ -308,12 +321,21 @@ used in standard XML-RPC; to allow using it via an extension,
provide a true value for \var{allow_none}. provide a true value for \var{allow_none}.
\end{funcdesc} \end{funcdesc}
\begin{funcdesc}{loads}{data} \begin{funcdesc}{loads}{data\optional{, use_datetime}}
Convert an XML-RPC request or response into Python objects, a Convert an XML-RPC request or response into Python objects, a
\code{(\var{params}, \var{methodname})}. \var{params} is a tuple of argument; \var{methodname} \code{(\var{params}, \var{methodname})}. \var{params} is a tuple of argument; \var{methodname}
is a string, or \code{None} if no method name is present in the packet. is a string, or \code{None} if no method name is present in the packet.
If the XML-RPC packet represents a fault condition, this If the XML-RPC packet represents a fault condition, this
function will raise a \exception{Fault} exception. function will raise a \exception{Fault} exception.
The \var{use_datetime} flag can be used to cause date/time values to be
presented as \class{\refmodule{datetime}.datetime} objects; this is false
by default.
Note that even if you call an XML-RPC method with
\class{\refmodule{datetime}.date} or \class{\refmodule{datetime}.time}
objects, they are converted to \class{DateTime} objects internally, so only
{}\class{\refmodule{datetime}.datetime} objects will be returned.
\versionchanged[The \var{use_datetime} flag was added]{2.5}
\end{funcdesc} \end{funcdesc}
......
...@@ -34,16 +34,48 @@ class XMLRPCTestCase(unittest.TestCase): ...@@ -34,16 +34,48 @@ class XMLRPCTestCase(unittest.TestCase):
xmlrpclib.loads(xmlrpclib.dumps((alist,)))[0][0]) xmlrpclib.loads(xmlrpclib.dumps((alist,)))[0][0])
def test_dump_bare_datetime(self): def test_dump_bare_datetime(self):
# This checks that an unwrapped datetime object can be handled # This checks that an unwrapped datetime.date object can be handled
# by the marshalling code. This can't be done via # by the marshalling code. This can't be done via test_dump_load()
# test_dump_load() since the unmarshaller doesn't produce base # since with use_datetime set to 1 the unmarshaller would create
# datetime instances. # datetime objects for the 'datetime[123]' keys as well
dt = datetime.datetime(2005, 02, 10, 11, 41, 23) dt = datetime.datetime(2005, 02, 10, 11, 41, 23)
s = xmlrpclib.dumps((dt,)) s = xmlrpclib.dumps((dt,))
r, m = xmlrpclib.loads(s) (newdt,), m = xmlrpclib.loads(s, use_datetime=1)
self.assertEquals(r, (xmlrpclib.DateTime('20050210T11:41:23'),)) self.assertEquals(newdt, dt)
self.assertEquals(m, None) self.assertEquals(m, None)
(newdt,), m = xmlrpclib.loads(s, use_datetime=0)
self.assertEquals(newdt, xmlrpclib.DateTime('20050210T11:41:23'))
def test_dump_bare_date(self):
# This checks that an unwrapped datetime.date object can be handled
# by the marshalling code. This can't be done via test_dump_load()
# since the unmarshaller produces a datetime object
d = datetime.datetime(2005, 02, 10, 11, 41, 23).date()
s = xmlrpclib.dumps((d,))
(newd,), m = xmlrpclib.loads(s, use_datetime=1)
self.assertEquals(newd.date(), d)
self.assertEquals(newd.time(), datetime.time(0, 0, 0))
self.assertEquals(m, None)
(newdt,), m = xmlrpclib.loads(s, use_datetime=0)
self.assertEquals(newdt, xmlrpclib.DateTime('20050210T00:00:00'))
def test_dump_bare_time(self):
# This checks that an unwrapped datetime.time object can be handled
# by the marshalling code. This can't be done via test_dump_load()
# since the unmarshaller produces a datetime object
t = datetime.datetime(2005, 02, 10, 11, 41, 23).time()
s = xmlrpclib.dumps((t,))
(newt,), m = xmlrpclib.loads(s, use_datetime=1)
today = datetime.datetime.now().date().strftime("%Y%m%d")
self.assertEquals(newt.time(), t)
self.assertEquals(newt.date(), datetime.datetime.now().date())
self.assertEquals(m, None)
(newdt,), m = xmlrpclib.loads(s, use_datetime=0)
self.assertEquals(newdt, xmlrpclib.DateTime('%sT11:41:23'%today))
def test_dump_big_long(self): def test_dump_big_long(self):
self.assertRaises(OverflowError, xmlrpclib.dumps, (2L**99,)) self.assertRaises(OverflowError, xmlrpclib.dumps, (2L**99,))
......
...@@ -357,7 +357,14 @@ class DateTime: ...@@ -357,7 +357,14 @@ class DateTime:
if datetime and isinstance(value, datetime.datetime): if datetime and isinstance(value, datetime.datetime):
self.value = value.strftime("%Y%m%dT%H:%M:%S") self.value = value.strftime("%Y%m%dT%H:%M:%S")
return return
elif not isinstance(value, (TupleType, time.struct_time)): if datetime and isinstance(value, datetime.date):
self.value = value.strftime("%Y%m%dT%H:%M:%S")
return
if datetime and isinstance(value, datetime.time):
today = datetime.datetime.now().strftime("%Y%m%d")
self.value = value.strftime(today+"T%H:%M:%S")
return
if not isinstance(value, (TupleType, time.struct_time)):
if value == 0: if value == 0:
value = time.time() value = time.time()
value = time.localtime(value) value = time.localtime(value)
...@@ -394,6 +401,10 @@ def _datetime(data): ...@@ -394,6 +401,10 @@ def _datetime(data):
value.decode(data) value.decode(data)
return value return value
def _datetime_type(data):
t = time.strptime(data, "%Y%m%dT%H:%M:%S")
return datetime.datetime(*tuple(t)[:6])
## ##
# Wrapper for binary data. This can be used to transport any kind # Wrapper for binary data. This can be used to transport any kind
# of binary data over XML-RPC, using BASE64 encoding. # of binary data over XML-RPC, using BASE64 encoding.
...@@ -714,6 +725,19 @@ class Marshaller: ...@@ -714,6 +725,19 @@ class Marshaller:
write("</dateTime.iso8601></value>\n") write("</dateTime.iso8601></value>\n")
dispatch[datetime.datetime] = dump_datetime dispatch[datetime.datetime] = dump_datetime
def dump_date(self, value, write):
write("<value><dateTime.iso8601>")
write(value.strftime("%Y%m%dT00:00:00"))
write("</dateTime.iso8601></value>\n")
dispatch[datetime.date] = dump_date
def dump_time(self, value, write):
write("<value><dateTime.iso8601>")
write(datetime.datetime.now().date().strftime("%Y%m%dT"))
write(value.strftime("%H:%M:%S"))
write("</dateTime.iso8601></value>\n")
dispatch[datetime.time] = dump_time
def dump_instance(self, value, write): def dump_instance(self, value, write):
# check for special wrappers # check for special wrappers
if value.__class__ in WRAPPERS: if value.__class__ in WRAPPERS:
...@@ -742,7 +766,7 @@ class Unmarshaller: ...@@ -742,7 +766,7 @@ class Unmarshaller:
# and again, if you don't understand what's going on in here, # and again, if you don't understand what's going on in here,
# that's perfectly ok. # that's perfectly ok.
def __init__(self): def __init__(self, use_datetime=0):
self._type = None self._type = None
self._stack = [] self._stack = []
self._marks = [] self._marks = []
...@@ -750,6 +774,9 @@ class Unmarshaller: ...@@ -750,6 +774,9 @@ class Unmarshaller:
self._methodname = None self._methodname = None
self._encoding = "utf-8" self._encoding = "utf-8"
self.append = self._stack.append self.append = self._stack.append
self._use_datetime = use_datetime
if use_datetime and not datetime:
raise ValueError, "the datetime module is not available"
def close(self): def close(self):
# return response tuple and target method # return response tuple and target method
...@@ -867,6 +894,8 @@ class Unmarshaller: ...@@ -867,6 +894,8 @@ class Unmarshaller:
def end_dateTime(self, data): def end_dateTime(self, data):
value = DateTime() value = DateTime()
value.decode(data) value.decode(data)
if self._use_datetime:
value = _datetime_type(data)
self.append(value) self.append(value)
dispatch["dateTime.iso8601"] = end_dateTime dispatch["dateTime.iso8601"] = end_dateTime
...@@ -968,17 +997,23 @@ class MultiCall: ...@@ -968,17 +997,23 @@ class MultiCall:
# #
# return A (parser, unmarshaller) tuple. # return A (parser, unmarshaller) tuple.
def getparser(): def getparser(use_datetime=0):
"""getparser() -> parser, unmarshaller """getparser() -> parser, unmarshaller
Create an instance of the fastest available parser, and attach it Create an instance of the fastest available parser, and attach it
to an unmarshalling object. Return both objects. to an unmarshalling object. Return both objects.
""" """
if use_datetime and not datetime:
raise ValueError, "the datetime module is not available"
if FastParser and FastUnmarshaller: if FastParser and FastUnmarshaller:
target = FastUnmarshaller(True, False, _binary, _datetime, Fault) if use_datetime:
mkdatetime = _datetime_type
else:
mkdatetime = _datetime
target = FastUnmarshaller(True, False, _binary, mkdatetime, Fault)
parser = FastParser(target) parser = FastParser(target)
else: else:
target = Unmarshaller() target = Unmarshaller(use_datetime=use_datetime)
if FastParser: if FastParser:
parser = FastParser(target) parser = FastParser(target)
elif SgmlopParser: elif SgmlopParser:
...@@ -1081,7 +1116,7 @@ def dumps(params, methodname=None, methodresponse=None, encoding=None, ...@@ -1081,7 +1116,7 @@ def dumps(params, methodname=None, methodresponse=None, encoding=None,
# (None if not present). # (None if not present).
# @see Fault # @see Fault
def loads(data): def loads(data, use_datetime=0):
"""data -> unmarshalled data, method name """data -> unmarshalled data, method name
Convert an XML-RPC packet to unmarshalled data plus a method Convert an XML-RPC packet to unmarshalled data plus a method
...@@ -1090,7 +1125,7 @@ def loads(data): ...@@ -1090,7 +1125,7 @@ def loads(data):
If the XML-RPC packet represents a fault condition, this function If the XML-RPC packet represents a fault condition, this function
raises a Fault exception. raises a Fault exception.
""" """
p, u = getparser() p, u = getparser(use_datetime=use_datetime)
p.feed(data) p.feed(data)
p.close() p.close()
return u.close(), u.getmethodname() return u.close(), u.getmethodname()
...@@ -1122,6 +1157,9 @@ class Transport: ...@@ -1122,6 +1157,9 @@ class Transport:
# client identifier (may be overridden) # client identifier (may be overridden)
user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__ user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__
def __init__(self, use_datetime=0):
self._use_datetime = use_datetime
## ##
# Send a complete request, and parse the response. # Send a complete request, and parse the response.
# #
...@@ -1168,7 +1206,7 @@ class Transport: ...@@ -1168,7 +1206,7 @@ class Transport:
def getparser(self): def getparser(self):
# get parser and unmarshaller # get parser and unmarshaller
return getparser() return getparser(use_datetime=self._use_datetime)
## ##
# Get authorization info from host parameter # Get authorization info from host parameter
...@@ -1362,7 +1400,7 @@ class ServerProxy: ...@@ -1362,7 +1400,7 @@ class ServerProxy:
""" """
def __init__(self, uri, transport=None, encoding=None, verbose=0, def __init__(self, uri, transport=None, encoding=None, verbose=0,
allow_none=0): allow_none=0, use_datetime=0):
# establish a "logical" server connection # establish a "logical" server connection
# get the url # get the url
...@@ -1376,9 +1414,9 @@ class ServerProxy: ...@@ -1376,9 +1414,9 @@ class ServerProxy:
if transport is None: if transport is None:
if type == "https": if type == "https":
transport = SafeTransport() transport = SafeTransport(use_datetime=use_datetime)
else: else:
transport = Transport() transport = Transport(use_datetime=use_datetime)
self.__transport = transport self.__transport = transport
self.__encoding = encoding self.__encoding = encoding
......
...@@ -123,6 +123,11 @@ Extension Modules ...@@ -123,6 +123,11 @@ Extension Modules
Library Library
------- -------
- Patch #1120353: The xmlrpclib module provides better, more transparent,
support for datetime.{datetime,date,time} objects. With use_datetime set
to True, applications shouldn't have to fiddle with the DateTime wrapper
class at all.
- distutils.commands.upload was added to support uploading distribution - distutils.commands.upload was added to support uploading distribution
files to PyPI. files to PyPI.
......
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