Ticket #3219: xmlrpc_datetime_r28853.diff

File xmlrpc_datetime_r28853.diff, 13.6 KB (added by chadmaine, 4 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.