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
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
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
247 """
248 to set a new uri absolute path
249 """
250 self.uri = self._make_uri(self.uri, path)
251
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
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