comparison lemuriformes/client.py @ 7:92ae11e33f85

abstract API client
author Jeff Hammel <k0scist@gmail.com>
date Sun, 10 Dec 2017 11:54:10 -0800
parents
children
comparison
equal deleted inserted replaced
6:244c29f46554 7:92ae11e33f85
1 """
2 abstract ReSTful HTTP client
3 """
4
5 # imports
6 import requests
7 import sys
8 import time
9 from urlparse import urlparse
10 from .cli import ConfigurationParser
11
12
13 def isurl(string):
14 """is `string` a URL?"""
15
16 return bool(urlparse(string).scheme)
17
18
19 def serialize_headers(headers):
20 return '\n'.join(["{key}: {value}".format(key=key, value=value)
21 for key, value in sorted(headers.items(),
22 key=lambda x: x[0])
23 ])
24
25
26 def serialize_request(request):
27 """serialize a request object to a string"""
28
29 template = u"""{method} {url}
30 {headers}
31
32 {body}
33 """
34 headers = '\n'.join(['{key}: {value}'.format(key=key, value=value)
35 for key, value in request.headers.items()])
36 retval = template.format(method=request.method,
37 url=request.url,
38 headers=headers,
39 body=request.body or '')
40 return retval
41
42
43 def serialize_response(response):
44 """serialize a response object to a string"""
45
46 template = u"""{url}
47 {status_code}
48 {headers}
49
50 {text}
51 """
52
53 retval = template.format(url=response.url,
54 status_code=response.status_code,
55 headers=serialize_headers(response.headers),
56 text=response.text.strip())
57 return retval
58
59
60 class Endpoint(object):
61 """abstract base class for a RESTful API client"""
62
63 path = ''
64 endpoints = []
65
66 def __init__(self, base_url, session=None, timeout=60.):
67 base_url = base_url.rstrip('/')
68 self.url = '{}/{}'.format(base_url, self.path)
69 self.timeout = timeout
70 if session is None:
71 session = requests.Session()
72 self.session = session
73 for endpoint in self.endpoints:
74 path = endpoint.path
75 setattr(self,
76 path,
77 endpoint(self.url, session=self.session, timeout=timeout))
78
79 def __call__(self, method, url, **kwargs):
80 """make an HTTP request"""
81
82 kwargs.setdefault('timeout', self.timeout)
83 start = time.time()
84 response = self.session.request(method, url, **kwargs)
85 end = time.time()
86 response.duration = end - start
87 try:
88 response.raise_for_status()
89 except requests.HTTPError as e:
90 sys.stderr.write(serialize_response(response) + '\n')
91 sys.stderr.write("=>\n")
92 sys.stderr.write(serialize_request(response.request) + '\n')
93 raise
94 return response
95
96 def POST(self, data, **kwargs):
97 return self('POST', self.url, data=data, **kwargs)
98
99
100 class ClientParser(ConfigurationParser):
101 """abstract argument parser for HTTP client"""
102
103 client_class = Endpoint
104
105 def add_arguments(self):
106 self.add_argument('base_url', help="base URL")
107 self.add_argument('--timeout', dest='timeout',
108 type=float, default=60.,
109 help='per request timeout in seconds [DEFAULT: %(default)s]')
110
111 def base_url(self):
112 return self.options.base_url
113
114 def client(self):
115 """return argument specified requests HTTP client"""
116
117 if self.options is None:
118 raise Exception("options not yet parsed!")
119
120 return self.client_class(self.base_url(),
121 timeout=self.options.timeout)