Package restkit :: Module wrappers
[hide private]
[frames] | no frames]

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