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 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
64 u = urlparse.urlparse(uri)
65 if u.username:
66 password = u.password or ""
67
68
69 filters = client_opts.get('filters', [])
70 filters.append(BasicAuth(u.username, password))
71 client_opts['filters'] = filters
72
73
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
83 return '<%s %s>' % (self.__class__.__name__, self.uri)
84
87
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
99
100
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
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):
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
244 """
245 to set a new uri absolute path
246 """
247 self.uri = self._make_uri(self.uri, path)
248
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
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