1
2
3
4
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
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
65 u = urlparse.urlparse(uri)
66 if u.username:
67 password = u.password or ""
68
69
70 filters = client_opts.get('filters', [])
71 filters.append(BasicAuth(u.username, password))
72 client_opts['filters'] = filters
73
74
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
84 return '<%s %s>' % (self.__class__.__name__, self.uri)
85
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
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
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
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):
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
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
257 """
258 to set a new uri absolute path
259 """
260 self.uri = self._make_uri(self.uri, path)
261
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
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