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