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 222 try: 223 resp = self.do_request(uri, method=method, payload=payload, 224 headers=headers) 225 except ParserError: 226 raise 227 except Exception, e: 228 raise RequestError(e) 229 230 if resp is None: 231 # race condition 232 raise RequestError("unkown error") 233 234 if resp.status_int >= 400: 235 if resp.status_int == 404: 236 raise ResourceNotFound(resp.body, http_code=404, response=resp) 237 elif resp.status_int in (401, 403): 238 raise Unauthorized(resp.body, http_code=resp.status_int, 239 response=resp) 240 else: 241 raise RequestFailed(resp.body, http_code=resp.status_int, 242 response=resp) 243 244 return resp
245
246 - def update_uri(self, path):
247 """ 248 to set a new uri absolute path 249 """ 250 self.uri = self._make_uri(self.uri, path)
251
252 - def _make_uri(self, base, *path, **query):
253 """Assemble a uri based on a base, any number of path segments, 254 and query string parameters. 255 256 """ 257 base_trailing_slash = False 258 if base and base.endswith("/"): 259 base_trailing_slash = True 260 base = base[:-1] 261 retval = [base] 262 263 # build the path 264 _path = [] 265 trailing_slash = False 266 for s in path: 267 if s is not None and isinstance(s, basestring): 268 if len(s) > 1 and s.endswith('/'): 269 trailing_slash = True 270 else: 271 trailing_slash = False 272 _path.append(util.url_quote(s.strip('/'), self.charset, self.safe)) 273 274 path_str ="" 275 if _path: 276 path_str = "/".join([''] + _path) 277 if trailing_slash: 278 path_str = path_str + "/" 279 elif base_trailing_slash: 280 path_str = path_str + "/" 281 282 if path_str: 283 retval.append(path_str) 284 285 params_str = util.url_encode(query, self.charset, self.encode_keys) 286 if params_str: 287 retval.extend(['?', params_str]) 288 289 return ''.join(retval)
290