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

Source Code for Module restkit.resource

  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   
  7  """ 
  8  restkit.resource 
  9  ~~~~~~~~~~~~~~~~ 
 10   
 11  This module provide a common interface for all HTTP equest.  
 12   
 13      >>> from restkit import Resource 
 14      >>> res = Resource('http://friendpaste.com') 
 15      >>> res.get('/5rOqE9XTz7lccLgZoQS4IP',headers={'Accept': 'application/json'}).body 
 16      u'{"snippet": "hi!", "title": "", "id": "5rOqE9XTz7lccLgZoQS4IP", "language": "text", "revision": "386233396230"}' 
 17      >>> res.status 
 18      200 
 19  """ 
 20   
 21  import cgi 
 22  import mimetypes 
 23  import uuid 
 24  import urlparse 
 25   
 26  from restkit.errors import ResourceNotFound, Unauthorized, RequestFailed,\ 
 27  ParserError, RequestError 
 28  from restkit.forms import MultipartForm, multipart_form_encode, form_encode 
 29  from restkit.client import HttpConnection 
 30  from restkit.filters import BasicAuth 
 31  from restkit import util 
 32  from restkit import pool 
 33   
34 -class Resource(object):
35 """A class that can be instantiated for access to a RESTful resource, 36 including authentication. 37 """ 38 39 charset = 'utf-8' 40 encode_keys = True 41 safe = "/:" 42 pool_class = pool.ConnectionPool 43 keepalive = True 44 max_connections = 4 45 basic_auth_url = True 46
47 - def __init__(self, uri, headers=None, **client_opts):
48 """Constructor for a `Resource` object. 49 50 Resource represent an HTTP resource. 51 52 :param uri: str, full uri to the server. 53 :param headers: dict, optionnal headers that will 54 be added to HTTP request. 55 :param client_opts: `restkit.client.HttpConnection` Options 56 """ 57 58 pool_instance = client_opts.get('pool_instance') 59 if not pool_instance and self.keepalive: 60 pool = self.pool_class(max_connections=self.max_connections) 61 client_opts['pool_instance'] = pool 62 63 if self.basic_auth_url: 64 # detect credentials from url 65 u = urlparse.urlparse(uri) 66 if u.username: 67 password = u.password or "" 68 69 # add filters 70 filters = client_opts.get('filters', []) 71 filters.append(BasicAuth(u.username, password)) 72 client_opts['filters'] = filters 73 74 # update uri 75 uri = urlparse.urlunparse((u.scheme, u.netloc.split("@")[-1], 76 u.path, u.params, u.query, u.fragment)) 77 78 self.uri = uri 79 self._headers = headers or {} 80 self.client_opts = client_opts 81 self._body_parts = []
82
83 - def __repr__(self):
84 return '<%s %s>' % (self.__class__.__name__, self.uri)
85
86 - def add_filter(self, f):
87 """ add an htt filter """ 88 filters = self.client_opts.get('filters', []) 89 filters.append(f) 90 self.client_opts['filters'] = filters
91 92 add_authorization = util.deprecated_property( 93 add_filter, 'add_authorization', 'use add_filter() instead', 94 warning=False) 95
96 - def remmove_filter(self, f):
97 """ remove an http filter """ 98 filters = self.client_opts.get('filters', []) 99 for i, f1 in enumerate(filters): 100 if f == f1: del filters[i] 101 self.client_opts['filters'] = filters
102
103 - def clone(self):
104 """if you want to add a path to resource uri, you can do: 105 106 .. code-block:: python 107 108 resr2 = res.clone() 109 110 """ 111 obj = self.__class__(self.uri, headers=self._headers, 112 **self.client_opts) 113 114 for attr in ('charset', 'encode_keys', 'safe', 'pool_class', 115 'keepalive', 'max_connections', 'basic_auth_url'): 116 setattr(obj, attr, getattr(self, attr)) 117 return obj
118
119 - def __call__(self, path):
120 """if you want to add a path to resource uri, you can do: 121 122 .. code-block:: python 123 124 Resource("/path").get() 125 """ 126 127 new_uri = self._make_uri(self.uri, path) 128 obj = type(self)(new_uri, headers=self._headers, **self.client_opts) 129 for attr in ('charset', 'encode_keys', 'safe', 'pool_class', 130 'keepalive', 'max_connections', 'basic_auth_url'): 131 setattr(obj, attr, getattr(self, attr)) 132 return obj
133
134 - def get(self, path=None, headers=None, **params):
135 """ HTTP GET 136 137 :param path: string additionnal path to the uri 138 :param headers: dict, optionnal headers that will 139 be added to HTTP request. 140 :param params: Optionnal parameterss added to the request. 141 """ 142 return self.request("GET", path=path, headers=headers, **params)
143
144 - def head(self, path=None, headers=None, **params):
145 """ HTTP HEAD 146 147 see GET for params description. 148 """ 149 return self.request("HEAD", path=path, headers=headers, **params)
150
151 - def delete(self, path=None, headers=None, **params):
152 """ HTTP DELETE 153 154 see GET for params description. 155 """ 156 return self.request("DELETE", path=path, headers=headers, **params)
157
158 - def post(self, path=None, payload=None, headers=None, **params):
159 """ HTTP POST 160 161 :param payload: string passed to the body of the request 162 :param path: string additionnal path to the uri 163 :param headers: dict, optionnal headers that will 164 be added to HTTP request. 165 :param params: Optionnal parameterss added to the request 166 """ 167 168 return self.request("POST", path=path, payload=payload, 169 headers=headers, **params)
170
171 - def put(self, path=None, payload=None, headers=None, **params):
172 """ HTTP PUT 173 174 see POST for params description. 175 """ 176 return self.request("PUT", path=path, payload=payload, 177 headers=headers, **params)
178
179 - def do_request(self, url, method='GET', payload=None, headers=None):
180 http_client = HttpConnection(**self.client_opts) 181 return http_client.request(url, method=method, body=payload, 182 headers=headers)
183
184 - def request(self, method, path=None, payload=None, headers=None, 185 **params):
186 """ HTTP request 187 188 This method may be the only one you want to override when 189 subclassing `restkit.rest.Resource`. 190 191 :param payload: string or File object passed to the body of the request 192 :param path: string additionnal path to the uri 193 :param headers: dict, optionnal headers that will 194 be added to HTTP request. 195 :param params: Optionnal parameterss added to the request 196 """ 197 198 headers = headers or {} 199 headers.update(self._headers.copy()) 200 201 202 self._body_parts = [] 203 if payload is not None: 204 if isinstance(payload, dict): 205 ctype = headers.get('Content-Type') 206 if ctype is not None and \ 207 ctype.startswith("multipart/form-data"): 208 type_, opts = cgi.parse_header(ctype) 209 boundary = opts.get('boundary', uuid.uuid4().hex) 210 payload, headers = multipart_form_encode(payload, 211 headers, boundary) 212 else: 213 ctype = "application/x-www-form-urlencoded; charset=utf-8" 214 headers['Content-Type'] = ctype 215 payload = form_encode(payload) 216 headers['Content-Length'] = len(payload) 217 elif isinstance(payload, MultipartForm): 218 ctype = "multipart/form-data; boundary=%s" % payload.boundary 219 headers['Content-Type'] = ctype 220 headers['Content-Length'] = str(payload.get_size()) 221 222 if 'Content-Type' not in headers: 223 ctype = 'application/octet-stream' 224 if hasattr(payload, 'name'): 225 ctype = mimetypes.guess_type(payload.name)[0] 226 227 headers['Content-Type'] = ctype 228 229 230 uri = self._make_uri(self.uri, path, **params) 231 232 try: 233 resp = self.do_request(uri, method=method, payload=payload, 234 headers=headers) 235 except ParserError: 236 raise 237 except Exception, e: 238 raise RequestError(e) 239 240 if resp is None: 241 # race condition 242 raise RequestError("unkown error") 243 244 if resp.status_int >= 400: 245 if resp.status_int == 404: 246 raise ResourceNotFound(resp.body, http_code=404, response=resp) 247 elif resp.status_int in (401, 403): 248 raise Unauthorized(resp.body, http_code=resp.status_int, 249 response=resp) 250 else: 251 raise RequestFailed(resp.body, http_code=resp.status_int, 252 response=resp) 253 254 return resp
255
256 - def update_uri(self, path):
257 """ 258 to set a new uri absolute path 259 """ 260 self.uri = self._make_uri(self.uri, path)
261
262 - def _make_uri(self, base, *path, **query):
263 """Assemble a uri based on a base, any number of path segments, 264 and query string parameters. 265 266 """ 267 base_trailing_slash = False 268 if base and base.endswith("/"): 269 base_trailing_slash = True 270 base = base[:-1] 271 retval = [base] 272 273 # build the path 274 _path = [] 275 trailing_slash = False 276 for s in path: 277 if s is not None and isinstance(s, basestring): 278 if len(s) > 1 and s.endswith('/'): 279 trailing_slash = True 280 else: 281 trailing_slash = False 282 _path.append(util.url_quote(s.strip('/'), self.charset, self.safe)) 283 284 path_str ="" 285 if _path: 286 path_str = "/".join([''] + _path) 287 if trailing_slash: 288 path_str = path_str + "/" 289 elif base_trailing_slash: 290 path_str = path_str + "/" 291 292 if path_str: 293 retval.append(path_str) 294 295 params_str = util.url_encode(query, self.charset, self.encode_keys) 296 if params_str: 297 retval.extend(['?', params_str]) 298 299 return ''.join(retval)
300