Package restkit :: Module wrappers
[hide private]

Source Code for Module restkit.wrappers

  1  # -*- coding: utf-8 - 
  2  # 
  3  # This file is part of restkit released under the MIT license. 
  4  # See the NOTICE for more information. 
  5   
  6  import cgi 
  7  import copy 
  8  import mimetypes 
  9  import os 
 10  from StringIO import StringIO 
 11  import types 
 12  import urlparse 
 13  import uuid 
 14   
 15  from restkit.datastructures import MultiDict 
 16  from restkit.errors import AlreadyRead, RequestError 
 17  from restkit.forms import multipart_form_encode, form_encode 
 18  from restkit.tee import ResponseTeeInput 
 19  from restkit.util import to_bytestring 
 20  from restkit.util import parse_cookie 
 21   
22 -class Request(object):
23
24 - def __init__(self, url, method='GET', body=None, headers=None):
25 headers = headers or [] 26 self.url = url 27 self.initial_url = url 28 self.method = method 29 30 self._headers = None 31 self._body = None 32 33 self.is_proxied = False 34 35 # set parsed uri 36 self.headers = headers 37 if body is not None: 38 self.body = body
39
40 - def _headers__get(self):
41 if not isinstance(self._headers, MultiDict): 42 self._headers = MultiDict(self._headers or []) 43 return self._headers
44 - def _headers__set(self, value):
45 self._headers = MultiDict(copy.copy(value))
46 headers = property(_headers__get, _headers__set, doc=_headers__get.__doc__) 47
48 - def _parsed_url(self):
49 if self.url is None: 50 raise ValueError("url isn't set") 51 return urlparse.urlparse(self.url)
52 parsed_url = property(_parsed_url, doc="parsed url") 53
54 - def _path__get(self):
55 parsed_url = self.parsed_url 56 path = parsed_url.path or '/' 57 58 return urlparse.urlunparse(('','', path, parsed_url.params, 59 parsed_url.query, parsed_url.fragment))
60 path = property(_path__get) 61
62 - def _host__get(self):
63 h = to_bytestring(self.parsed_url.netloc) 64 hdr_host = self.headers.iget("host") 65 if not hdr_host: 66 return h 67 return hdr_host
68 host = property(_host__get) 69
70 - def is_chunked(self):
71 te = self.headers.iget("transfer-encoding") 72 return (te is not None and te.lower() == "chunked")
73
74 - def is_ssl(self):
75 return self.parsed_url.scheme == "https"
76
77 - def _set_body(self, body):
78 ctype = self.headers.ipop('content-type', None) 79 clen = self.headers.ipop('content-length', None) 80 81 if isinstance(body, dict): 82 if ctype is not None and \ 83 ctype.startswith("multipart/form-data"): 84 type_, opts = cgi.parse_header(ctype) 85 boundary = opts.get('boundary', uuid.uuid4().hex) 86 self._body, self.headers = multipart_form_encode(body, 87 self.headers, boundary) 88 # at this point content-type is "multipart/form-data" 89 # we need to set the content type according to the 90 # correct boundary like 91 # "multipart/form-data; boundary=%s" % boundary 92 ctype = self.headers.ipop('content-type', None) 93 else: 94 ctype = "application/x-www-form-urlencoded; charset=utf-8" 95 self._body = form_encode(body) 96 elif hasattr(body, "boundary") and hasattr(body, "get_size"): 97 ctype = "multipart/form-data; boundary=%s" % body.boundary 98 clen = body.get_size() 99 self._body = body 100 else: 101 self._body = body 102 103 if not ctype: 104 ctype = 'application/octet-stream' 105 if hasattr(self.body, 'name'): 106 ctype = mimetypes.guess_type(body.name)[0] 107 108 if not clen: 109 if hasattr(self._body, 'fileno'): 110 try: 111 self._body.flush() 112 except IOError: 113 pass 114 try: 115 fno = self._body.fileno() 116 clen = str(os.fstat(fno)[6]) 117 except IOError: 118 if not self.is_chunked(): 119 clen = len(self._body.read()) 120 elif hasattr(self._body, 'getvalue') and not \ 121 self.is_chunked(): 122 clen = len(self._body.getvalue()) 123 elif isinstance(self._body, types.StringTypes): 124 self._body = to_bytestring(self._body) 125 clen = len(self._body) 126 127 if clen is not None: 128 self.headers['Content-Length'] = clen 129 130 # TODO: maybe it's more relevant 131 # to check if Content-Type is already set in self.headers 132 # before overiding it 133 if ctype is not None: 134 self.headers['Content-Type'] = ctype
135
136 - def _get_body(self):
137 return self._body
138 body = property(_get_body, _set_body, doc="request body") 139
140 - def maybe_rewind(self, msg=""):
141 if self.body is not None: 142 if not hasattr(self.body, 'seek') and \ 143 not isinstance(self.body, types.StringTypes): 144 raise RequestError("error: '%s', body can't be rewind." 145 % msg)
146 147
148 -class BodyWrapper(object):
149
150 - def __init__(self, resp, connection):
151 self.resp = resp 152 self.body = resp._body 153 self.connection = connection 154 self._closed = False 155 self.eof = False
156
157 - def __enter__(self):
158 return self
159
160 - def __exit__(self, exc_type, exc_val, traceback):
161 self.close()
162
163 - def close(self):
164 """ release connection """ 165 if self._closed: 166 return 167 168 if not self.eof: 169 self.body.read() 170 171 self.connection.release(self.resp.should_close) 172 self._closed = True
173
174 - def __iter__(self):
175 return self
176
177 - def next(self):
178 try: 179 return self.body.next() 180 except StopIteration: 181 self.eof = True 182 self.close() 183 raise
184
185 - def read(self, n=-1):
186 data = self.body.read(n) 187 if not data: 188 self.eof = True 189 self.close() 190 return data
191
192 - def readline(self, limit=-1):
193 line = self.body.readline(limit) 194 if not line: 195 self.eof = True 196 self.close() 197 return line
198
199 - def readlines(self, hint=None):
200 lines = self.body.readlines(hint) 201 if self.body.close: 202 self.eof = True 203 self.close() 204 return lines
205 206
207 -class Response(object):
208 209 charset = "utf8" 210 unicode_errors = 'strict' 211
212 - def __init__(self, connection, request, resp):
213 self.request = request 214 self.connection = connection 215 216 self._resp = resp 217 218 # response infos 219 self.headers = resp.headers() 220 self.status = resp.status() 221 self.status_int = resp.status_code() 222 self.version = resp.version() 223 self.headerslist = self.headers.items() 224 self.location = self.headers.get('location') 225 self.final_url = request.url 226 self.should_close = not resp.should_keep_alive() 227 228 # cookies 229 if 'set-cookie' in self.headers: 230 cookie_header = self.headers.get('set-cookie') 231 self.cookies = parse_cookie(cookie_header, self.final_url) 232 233 234 self._closed = False 235 self._already_read = False 236 237 if request.method == "HEAD": 238 """ no body on HEAD, release the connection now """ 239 self.connection.release(True) 240 self._body = StringIO("") 241 else: 242 self._body = resp.body_file()
243
244 - def __getitem__(self, key):
245 try: 246 return getattr(self, key) 247 except AttributeError: 248 pass 249 return self.headers.get(key)
250
251 - def __contains__(self, key):
252 return key in self.headers
253
254 - def __iter__(self):
255 return self.headers.iteritems()
256
257 - def can_read(self):
258 return not self._already_read
259
260 - def close(self):
261 self.connection.release(True)
262
263 - def skip_body(self):
264 """ skip the body and release the connection """ 265 if not self._already_read: 266 self._body.read() 267 self._already_read = True 268 self.connection.release(self.should_close)
269
270 - def body_string(self, charset=None, unicode_errors="strict"):
271 """ return body string, by default in bytestring """ 272 273 if not self.can_read(): 274 raise AlreadyRead() 275 276 277 body = self._body.read() 278 self._already_read = True 279 280 self.connection.release(self.should_close) 281 282 if charset is not None: 283 try: 284 body = body.decode(charset, unicode_errors) 285 except UnicodeDecodeError: 286 pass 287 return body
288
289 - def body_stream(self):
290 """ stream body """ 291 if not self.can_read(): 292 raise AlreadyRead() 293 294 self._already_read = True 295 296 return BodyWrapper(self, self.connection)
297 298
299 - def tee(self):
300 """ copy response input to standard output or a file if length > 301 sock.MAX_BODY. This make possible to reuse it in your 302 appplication. When all the input has been read, connection is 303 released """ 304 return ResponseTeeInput(self, self.connection, 305 should_close=self.should_close)
306 ClientResponse = Response 307