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