Ticket #3219: xmlrpc_datetime_r28853.diff

File xmlrpc_datetime_r28853.diff, 13.6 KB (added by chadmaine, 6 years ago)

Updated diff with exarkun recommended changes, please review

  • twisted/web/test/test_xmlrpc.py

     
    66Tests for  XML-RPC support in L{twisted.web.xmlrpc}.
    77"""
    88
     9import datetime
    910import xmlrpclib
    1011
    1112from twisted.trial import unittest
     
    350351        return d
    351352
    352353
     354class XMLRPCUseDateTimeTestCase(unittest.TestCase):
     355    """
     356    Test with useDateTime set to True.
    353357
     358    These are not meant to be exhaustive serialization tests, since
     359    L{xmlrpclib} does all of the actual serialization work.  They are just
     360    meant to exercise a few codepaths to make sure we are calling into
     361    xmlrpclib correctly.
     362    """
     363
     364    def setUp(self):
     365        self.p = reactor.listenTCP(
     366            0, server.Site(Test(useDateTime=True)), interface="127.0.0.1")
     367        self.port = self.p.getHost().port
     368
     369
     370    def tearDown(self):
     371        return self.p.stopListening()
     372
     373
     374    def proxy(self):
     375        return xmlrpc.Proxy("http://127.0.0.1:%d" % (self.port,),
     376                            useDateTime=True)
     377
     378
     379    def test_deferredNone(self):
     380        """
     381        Test that passing a datetime.datetime as an argument to a remote method
     382        and returning a L{Deferred} which fires with datetime.datetime properly
     383        passes over the network if useDateTime is set to True.
     384        """
     385        dt1 = datetime.datetime(2000, 12, 28, 3, 45, 59)
     386        d = self.proxy().callRemote('defer', dt1)
     387        d.addCallback(self.assertEquals, dt1)
     388        return d
     389
     390
     391    def test_dictWithDatetimeValue(self):
     392        """
     393        Test that return a C{dict} with datetime.datetime as a value works
     394        properly.
     395        """
     396        dt1 = datetime.datetime(1964, 10, 18, 3, 59, 13)
     397        d = self.proxy().callRemote('defer', {'a': dt1})
     398        d.addCallback(self.assertEquals, {'a': dt1})
     399        return d
     400
     401
    354402class XMLRPCTestAuthenticated(XMLRPCTestCase):
    355403    """
    356404    Test with authenticated proxy. We run this with the same inout/ouput as
  • twisted/web/xmlrpc.py

     
    8181    Sub-handlers for prefixed methods (e.g., system.listMethods)
    8282    can be added with putSubHandler. By default, prefixes are
    8383    separated with a '.'. Override self.separator to change this.
     84
     85    @ivar allowNone: Permit XML translating of Python constant None.
     86    @type allowNone: C{bool}
     87
     88    @ivar useDateTime: Present datetime values as datetime.datetime objects?
     89        Requires Python <= 2.5.
     90    @type useDateTime: C{bool}
    8491    """
    8592
    8693    # Error codes for Twisted, if they conflict with yours then
     
    9299    separator = '.'
    93100    allowedMethods = ('POST',)
    94101
    95     def __init__(self, allowNone=False):
     102    def __init__(self, allowNone=False, useDateTime=False):
    96103        resource.Resource.__init__(self)
    97104        self.subHandlers = {}
    98105        self.allowNone = allowNone
     106        self.useDateTime = useDateTime
    99107
     108    def _setUseDateTime(self, value=False):
     109        if value and sys.version_info[:2] < (2, 5):
     110            raise RuntimeError(
     111                "useDateTime requires Python 2.5 or later.")
     112        self._useDateTime = value
     113
     114    def _getUseDateTime(self):
     115        return self._useDateTime
     116
     117    useDateTime = property(fget=_getUseDateTime, fset=_setUseDateTime)
     118
    100119    def putSubHandler(self, prefix, handler):
    101120        self.subHandlers[prefix] = handler
    102121
     
    110129        request.content.seek(0, 0)
    111130        request.setHeader("content-type", "text/xml")
    112131        try:
    113             args, functionPath = xmlrpclib.loads(request.content.read())
     132            if self.useDateTime:
     133                args, functionPath = xmlrpclib.loads(request.content.read(),
     134                    use_datetime=True)
     135            else:
     136                # Maintain backwards compatibility with Python < 2.5
     137                args, functionPath = xmlrpclib.loads(request.content.read())
    114138        except Exception, e:
    115139            f = Fault(self.FAILURE, "Can't deserialize input: %s" % (e,))
    116140            self._cbRender(f, request)
     
    203227    To enable the methodSignature method, add a 'signature' method attribute
    204228    containing a list of lists. See methodSignature's documentation for the
    205229    format. Note the type strings should be XML-RPC types, not Python types.
     230
     231    @param parent: the XMLRPC server to add Introspection support to.
     232    @type parent: L{XMLRPC}
    206233    """
    207234
    208235    def __init__(self, parent):
    209236        """
    210237        Implement Introspection support for an XMLRPC server.
    211 
    212         @param parent: the XMLRPC server to add Introspection support to.
    213238        """
    214 
    215239        XMLRPC.__init__(self)
    216240        self._xmlrpc_parent = parent
    217241
     
    261285    """
    262286    Add Introspection support to an XMLRPC server.
    263287
    264     @param xmlrpc: The xmlrpc server to add Introspection support to.
     288    @param parent: the XMLRPC server to add Introspection support to.
     289    @type parent: L{XMLRPC}
    265290    """
    266291    xmlrpc.putSubHandler('system', XMLRPCIntrospection(xmlrpc))
    267292
     
    298323
    299324
    300325class _QueryFactory(protocol.ClientFactory):
     326    """
     327    XML-RPC Client Factory
    301328
    302     deferred = None
    303     protocol = QueryProtocol
     329    @ivar path: The path portion of the URL to which to post method calls.
     330    @type path: C{str}
    304331
    305     def __init__(self, path, host, method, user=None, password=None,
    306                  allowNone=False, args=(), canceller=None):
    307         """
    308         @type path: C{str}
    309         @param path: The path portion of the URL to which to post method calls.
     332    @ivar host: The value to use for the Host HTTP header.
     333    @type host: C{str}
    310334
    311         @type host: C{str}
    312         @param host: The value to use for the Host HTTP header.
     335    @ivar method: The name of the method to call.
     336    @type method: C{str}
    313337
    314         @type method: C{str}
    315         @param method: The name of the method to call.
     338    @ivar user: The username with which to authenticate with the server
     339        when making calls.
     340    @type user: C{str} or C{NoneType}
    316341
    317         @type user: C{str} or C{NoneType}
    318         @param user: The username with which to authenticate with the server
    319             when making calls.
     342    @ivar password: The password with which to authenticate with the server
     343        when making calls.
     344    @type password: C{str} or C{NoneType}
    320345
    321         @type password: C{str} or C{NoneType}
    322         @param password: The password with which to authenticate with the server
    323             when making calls.
     346    @ivar allowNone: allow the use of None values in parameters. It's
     347        passed to the underlying xmlrpclib implementation. Default to False.
     348    @type allowNone: C{bool} or C{NoneType}
    324349
    325         @type allowNone: C{bool} or C{NoneType}
    326         @param allowNone: allow the use of None values in parameters. It's
    327             passed to the underlying xmlrpclib implementation. Default to False.
     350    @ivar useDateTime: Accept datetime values as datetime.datetime objects.
     351        also passed to the underlying xmlrpclib implementation.  Default to
     352        False.  Requires Python <= 2.5.
     353    @type useDateTime: C{bool}
    328354
    329         @type args: C{tuple}
    330         @param args: the arguments to pass to the method.
     355    @ivar args: the arguments to pass to the method.
     356    @type args: C{tuple}
    331357
    332         @type canceller: C{callable} or C{NoneType}
    333         @param canceller: a 1-argument callable passed to the deferred as the
    334             canceller callback.
    335         """
     358    @ivar canceller: a 1-argument callable passed to the deferred as the
     359        canceller callback.
     360    @type canceller: C{callable} or C{NoneType}
     361
     362    """
     363
     364    deferred = None
     365    protocol = QueryProtocol
     366
     367    def __init__(self, path, host, method, user=None, password=None,
     368                 allowNone=False, args=(), canceller=None, useDateTime=False):
    336369        self.path, self.host = path, host
    337370        self.user, self.password = user, password
    338371        self.payload = payloadTemplate % (method,
    339372            xmlrpclib.dumps(args, allow_none=allowNone))
    340373        self.deferred = defer.Deferred(canceller)
     374        self.useDateTime = useDateTime
    341375
     376    def _setUseDateTime(self, value=False):
     377        if value and sys.version_info[:2] < (2, 5):
     378            raise RuntimeError(
     379                "useDateTime requires Python 2.5 or later.")
     380        self._useDateTime = value
     381
     382    def _getUseDateTime(self):
     383        return self._useDateTime
     384
     385    useDateTime = property(fget=_getUseDateTime, fset=_setUseDateTime)
     386
    342387    def parseResponse(self, contents):
    343388        if not self.deferred:
    344389            return
    345390        try:
    346             response = xmlrpclib.loads(contents)[0][0]
     391            if self.useDateTime:
     392                response = xmlrpclib.loads(contents,
     393                    use_datetime=True)[0][0]
     394            else:
     395                # Maintain backwards compatibility with Python < 2.5
     396                response = xmlrpclib.loads(contents)[0][0]
    347397        except:
    348398            deferred, self.deferred = self.deferred, None
    349399            deferred.errback(failure.Failure())
     
    373423    Use proxy.callRemote('foobar', *args) to call remote method
    374424    'foobar' with *args.
    375425
     426    @param url: The URL to which to post method calls.  Calls will be made
     427        over SSL if the scheme is HTTPS.  If netloc contains username or
     428        password information, these will be used to authenticate, as long as
     429        the C{user} and C{password} arguments are not specified.
     430    @type url: C{str}
     431
     432    @ivar user: The username with which to authenticate with the server
     433        when making calls.  If specified, overrides any username information
     434        embedded in C{url}.  If not specified, a value may be taken from
     435        C{url} if present.
     436    @type user: C{str} or C{NoneType}
     437
     438    @ivar password: The password with which to authenticate with the server
     439        when making calls.  If specified, overrides any password information
     440        embedded in C{url}.  If not specified, a value may be taken from
     441        C{url} if present.
     442    @type password: C{str} or C{NoneType}
     443
     444    @ivar allowNone: allow the use of None values in parameters. It's
     445        passed to the underlying xmlrpclib implementation. Default to False.
     446    @type allowNone: C{bool} or C{NoneType}
     447
     448    @ivar useDateTime: Accept datetime values as datetime.datetime objects.
     449        also passed to the underlying xmlrpclib implementation.  Default to
     450        False.  Requires Python <= 2.5.
     451    @type useDateTime: C{bool}
     452
    376453    @ivar queryFactory: object returning a factory for XML-RPC protocol. Mainly
    377454        useful for tests.
    378455    """
    379456    queryFactory = _QueryFactory
    380457
    381     def __init__(self, url, user=None, password=None, allowNone=False):
    382         """
    383         @type url: C{str}
    384         @param url: The URL to which to post method calls.  Calls will be made
    385             over SSL if the scheme is HTTPS.  If netloc contains username or
    386             password information, these will be used to authenticate, as long as
    387             the C{user} and C{password} arguments are not specified.
    388 
    389         @type user: C{str} or C{NoneType}
    390         @param user: The username with which to authenticate with the server
    391             when making calls.  If specified, overrides any username information
    392             embedded in C{url}.  If not specified, a value may be taken from
    393             C{url} if present.
    394 
    395         @type password: C{str} or C{NoneType}
    396         @param password: The password with which to authenticate with the server
    397             when making calls.  If specified, overrides any password information
    398             embedded in C{url}.  If not specified, a value may be taken from
    399             C{url} if present.
    400 
    401         @type allowNone: C{bool} or C{NoneType}
    402         @param allowNone: allow the use of None values in parameters. It's
    403             passed to the underlying xmlrpclib implementation. Default to False.
    404         """
     458    def __init__(self, url, user=None, password=None, allowNone=False,
     459                 useDateTime=False):
    405460        scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
    406461        netlocParts = netloc.split('@')
    407462        if len(netlocParts) == 2:
     
    428483        if password is not None:
    429484            self.password = password
    430485        self.allowNone = allowNone
     486        self.useDateTime = useDateTime
    431487
     488    def _setUseDateTime(self, value=False):
     489        if value and sys.version_info[:2] < (2, 5):
     490            raise RuntimeError(
     491                "useDateTime requires Python 2.5 or later.")
     492        self._useDateTime = value
     493
     494    def _getUseDateTime(self):
     495        return self._useDateTime
     496
     497    useDateTime = property(fget=_getUseDateTime, fset=_setUseDateTime)
     498
    432499    def callRemote(self, method, *args):
    433500        """
    434501        Call remote XML-RPC C{method} with given arguments.
     
    447514            connector.disconnect()
    448515        factory = self.queryFactory(
    449516            self.path, self.host, method, self.user,
    450             self.password, self.allowNone, args, cancel)
     517            self.password, self.allowNone, args, cancel, self.useDateTime)
    451518        if self.secure:
    452519            from twisted.internet import ssl
    453520            connector = reactor.connectSSL(self.host, self.port or 443,
  • twisted/web/topfiles/3219.feature

     
     1twisted.web.xmlrpc.XMLRPC and twisted.web.xmlrpc.Proxy now expose xmlrpclib's support of datetime.datetime objects if useDateTime is set to True.