~mcfletch/+junk/restclient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
"""Web client for uploading files to Turbogears web "services" (regular controllers)"""    
from restclient.encodemultipart import build_request
import simplejson
import urllib2

class RESTClient( object ):
    """Encode a REST request (potentially including files) for upload to server"""
    cookies = ()
    def __init__( self, login_url, **named ):
        self.login_url = login_url 
        self.login_params = named 
    def login( self ):
        """Get our login cookie by attempting to load url
        
        named gives the login parameters (login-form only)
        """
        if self.cookies:
            return self.cookies
        # look for a set-cookie in the result and use that going forward...
        try:
            result = urllib2.urlopen(build_request(self.login_url, self.login_params, [] ))
        except Exception, err:
            # Turbogears returns a 403 by default... sigh
            if hasattr( err, 'headers' ):
                headers = err.headers
            else:
                raise
        else:
            headers = result.info()
        if headers.has_key( 'set-cookie' ):
            cookie = headers['set-cookie']
            self.cookies = []
            for key in ('tg-visit','session_id'):
                if key in cookie:
                    cook = cookie[cookie.find( key):]
                    cook = cook.split( ';' )[0]
                    self.cookies.append( cook )
            return self.cookies
        raise RuntimeError( "Unable to retrieve cookie from server: %s"%(result,) )
    def __call__( self, url, **named ):
        """Call the given url with parameters in named
        
        Constructs a POST request and uses urllib2 to post to given url,
        if not self.cookies, will first log into the web-site.
        
        returns a response object that can be read to retrieve results
        """
        cookies = self.login()
        # add the json-return-value flag for turbogears
        fields,files = self.encode_request( **named ) 
        request = build_request( url, fields, files )
        request.add_header( 'Cookie', '; '.join( cookies) )
        result = urllib2.urlopen(request)
        return result
        
    def encode_request( self, **named ):
        """Encode the request parameters for HTTP communication"""
        fields = []
        files = []
        def simple( value ):
            if isinstance( value, (str,unicode,int,float,long)):
                return True
        def add_file( key, value ):
            filename,data = value 
            files.append( (key,filename,data) )
        for key,value in named.items():
            if isinstance( value, list):
                for item in value:
                    if simple( item ):
                        fields.append( (key, item) )
                    elif isinstance( item, tuple ):
                        add_file( key, item )
            elif simple( value ):
                fields.append( (key,value) )
            elif isinstance( value, tuple ):
                assert len(value) == 2, value # expected filename, data
                add_file( key, value )
        return fields, files

class JSONClient( RESTClient ):
    def __call__( self, url, **named ):
        """Call, decode response json"""
        result = super( JSONClient, self ).__call__( url, **named )
        data = result.read()
        return simplejson.loads( data )