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
144 -class BodyWrapper(object):
145
146 - def __init__(self, resp, connection):
147 self.resp = resp 148 self.body = resp._body 149 self.connection = connection
150
151 - def __enter__(self):
152 return self
153
154 - def __exit__(self, exc_type, exc_val, traceback):
155 self.close()
156
157 - def close(self):
158 """ release connection """ 159 self.connection.release(self.resp.should_close)
160
161 - def __iter__(self):
162 return self
163
164 - def next(self):
165 try: 166 return self.body.next() 167 except StopIteration: 168 self.close() 169 raise
170
171 - def read(self, n=-1):
172 data = self.body.read(n) 173 if not data: 174 self.close() 175 return data
176
177 - def readline(self, limit=-1):
178 line = self.body.readline(limit) 179 if not line: 180 self.close() 181 return line
182
183 - def readlines(self, hint=None):
184 lines = self.body.readlines(hint) 185 if self.body.close: 186 self.close() 187 return lines
188 189
190 -class Response(object):
191 192 charset = "utf8" 193 unicode_errors = 'strict' 194
195 - def __init__(self, connection, request, resp):
196 self.request = request 197 self.connection = connection 198 199 self._resp = resp 200 201 # response infos 202 self.headers = resp.headers() 203 self.status = resp.status() 204 self.status_int = resp.status_code() 205 self.version = resp.version() 206 self.headerslist = self.headers.items() 207 self.location = self.headers.get('location') 208 self.final_url = request.url 209 self.should_close = not resp.should_keep_alive() 210 211 212 self._closed = False 213 self._already_read = False 214 215 if request.method == "HEAD": 216 """ no body on HEAD, release the connection now """ 217 self.connection.release() 218 self._body = StringIO("") 219 else: 220 self._body = resp.body_file()
221
222 - def __getitem__(self, key):
223 try: 224 return getattr(self, key) 225 except AttributeError: 226 pass 227 return self.headers.get(key)
228
229 - def __contains__(self, key):
230 return key in self.headers
231
232 - def __iter__(self):
233 return self.headers.iteritems()
234
235 - def can_read(self):
236 return not self._already_read
237
238 - def body_string(self, charset=None, unicode_errors="strict"):
239 """ return body string, by default in bytestring """ 240 241 if not self.can_read(): 242 raise AlreadyRead() 243 244 245 body = self._body.read() 246 self._already_read = True 247 248 # release connection 249 self.connection.release(self.should_close) 250 251 if charset is not None: 252 try: 253 body = body.decode(charset, unicode_errors) 254 except UnicodeDecodeError: 255 pass 256 return body
257
258 - def body_stream(self):
259 """ stream body """ 260 if not self.can_read(): 261 raise AlreadyRead() 262 263 self._already_read = True 264 265 return BodyWrapper(self, self.connection)
266 267
268 - def tee(self):
269 """ copy response input to standard output or a file if length > 270 sock.MAX_BODY. This make possible to reuse it in your 271 appplication. When all the input has been read, connection is 272 released """ 273 return ResponseTeeInput(self, self.connection, 274 should_close=self.should_close)
275 ClientResponse = Response 276