Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Side by Side Diff: third_party/requests/models.py

Issue 24076010: Add 'requests' library to third_party. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/swarm_client
Patch Set: Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- coding: utf-8 -*-
2
3 """
4 requests.models
5 ~~~~~~~~~~~~~~~
6
7 This module contains the primary objects that power Requests.
8 """
9
10 import collections
11 import logging
12 import datetime
13
14 from io import BytesIO
15 from .hooks import default_hooks
16 from .structures import CaseInsensitiveDict
17
18 from .auth import HTTPBasicAuth
19 from .cookies import cookiejar_from_dict, get_cookie_header
20 from .packages.urllib3.filepost import encode_multipart_formdata
21 from .packages.urllib3.util import parse_url
22 from .exceptions import HTTPError, RequestException, MissingSchema, InvalidURL
23 from .utils import (
24 guess_filename, get_auth_from_url, requote_uri,
25 stream_decode_response_unicode, to_key_val_list, parse_header_links,
26 iter_slices, guess_json_utf, super_len)
27 from .compat import (
28 cookielib, urlparse, urlunparse, urlsplit, urlencode, str, bytes, StringIO,
29 is_py2, chardet, json, builtin_str, basestring)
30
31 CONTENT_CHUNK_SIZE = 10 * 1024
32 ITER_CHUNK_SIZE = 512
33
34 log = logging.getLogger(__name__)
35
36
37 class RequestEncodingMixin(object):
38 @property
39 def path_url(self):
40 """Build the path URL to use."""
41
42 url = []
43
44 p = urlsplit(self.url)
45
46 path = p.path
47 if not path:
48 path = '/'
49
50 url.append(path)
51
52 query = p.query
53 if query:
54 url.append('?')
55 url.append(query)
56
57 return ''.join(url)
58
59 @staticmethod
60 def _encode_params(data):
61 """Encode parameters in a piece of data.
62
63 Will successfully encode parameters when passed as a dict or a list of
64 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary
65 if parameters are supplied as a dict.
66 """
67
68 if isinstance(data, (str, bytes)):
69 return data
70 elif hasattr(data, 'read'):
71 return data
72 elif hasattr(data, '__iter__'):
73 result = []
74 for k, vs in to_key_val_list(data):
75 if isinstance(vs, basestring) or not hasattr(vs, '__iter__'):
76 vs = [vs]
77 for v in vs:
78 if v is not None:
79 result.append(
80 (k.encode('utf-8') if isinstance(k, str) else k,
81 v.encode('utf-8') if isinstance(v, str) else v))
82 return urlencode(result, doseq=True)
83 else:
84 return data
85
86 @staticmethod
87 def _encode_files(files, data):
88 """Build the body for a multipart/form-data request.
89
90 Will successfully encode files when passed as a dict or a list of
91 2-tuples. Order is retained if data is a list of 2-tuples but abritrary
92 if parameters are supplied as a dict.
93
94 """
95 if (not files) or isinstance(data, str):
96 return None
97
98 new_fields = []
99 fields = to_key_val_list(data or {})
100 files = to_key_val_list(files or {})
101
102 for field, val in fields:
103 if isinstance(val, basestring) or not hasattr(val, '__iter__'):
104 val = [val]
105 for v in val:
106 if v is not None:
107 new_fields.append(
108 (field.decode('utf-8') if isinstance(field, bytes) else field,
109 v.encode('utf-8') if isinstance(v, str) else v))
110
111 for (k, v) in files:
112 # support for explicit filename
113 ft = None
114 if isinstance(v, (tuple, list)):
115 if len(v) == 2:
116 fn, fp = v
117 else:
118 fn, fp, ft = v
119 else:
120 fn = guess_filename(v) or k
121 fp = v
122 if isinstance(fp, str):
123 fp = StringIO(fp)
124 if isinstance(fp, bytes):
125 fp = BytesIO(fp)
126
127 if ft:
128 new_v = (fn, fp.read(), ft)
129 else:
130 new_v = (fn, fp.read())
131 new_fields.append((k, new_v))
132
133 body, content_type = encode_multipart_formdata(new_fields)
134
135 return body, content_type
136
137
138 class RequestHooksMixin(object):
139 def register_hook(self, event, hook):
140 """Properly register a hook."""
141
142 if isinstance(hook, collections.Callable):
143 self.hooks[event].append(hook)
144 elif hasattr(hook, '__iter__'):
145 self.hooks[event].extend(h for h in hook if isinstance(h, collection s.Callable))
146
147 def deregister_hook(self, event, hook):
148 """Deregister a previously registered hook.
149 Returns True if the hook existed, False if not.
150 """
151
152 try:
153 self.hooks[event].remove(hook)
154 return True
155 except ValueError:
156 return False
157
158
159 class Request(RequestHooksMixin):
160 """A user-created :class:`Request <Request>` object.
161
162 Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent to the server.
163
164 :param method: HTTP method to use.
165 :param url: URL to send.
166 :param headers: dictionary of headers to send.
167 :param files: dictionary of {filename: fileobject} files to multipart upload .
168 :param data: the body to attach the request. If a dictionary is provided, fo rm-encoding will take place.
169 :param params: dictionary of URL parameters to append to the URL.
170 :param auth: Auth handler or (user, pass) tuple.
171 :param cookies: dictionary or CookieJar of cookies to attach to this request .
172 :param hooks: dictionary of callback hooks, for internal usage.
173
174 Usage::
175
176 >>> import requests
177 >>> req = requests.Request('GET', 'http://httpbin.org/get')
178 >>> req.prepare()
179 <PreparedRequest [GET]>
180
181 """
182 def __init__(self,
183 method=None,
184 url=None,
185 headers=None,
186 files=None,
187 data=dict(),
188 params=dict(),
189 auth=None,
190 cookies=None,
191 hooks=None):
192
193 # Default empty dicts for dict params.
194 data = [] if data is None else data
195 files = [] if files is None else files
196 headers = {} if headers is None else headers
197 params = {} if params is None else params
198 hooks = {} if hooks is None else hooks
199
200 self.hooks = default_hooks()
201 for (k, v) in list(hooks.items()):
202 self.register_hook(event=k, hook=v)
203
204 self.method = method
205 self.url = url
206 self.headers = headers
207 self.files = files
208 self.data = data
209 self.params = params
210 self.auth = auth
211 self.cookies = cookies
212 self.hooks = hooks
213
214 def __repr__(self):
215 return '<Request [%s]>' % (self.method)
216
217 def prepare(self):
218 """Constructs a :class:`PreparedRequest <PreparedRequest>` for transmiss ion and returns it."""
219 p = PreparedRequest()
220
221 p.prepare_method(self.method)
222 p.prepare_url(self.url, self.params)
223 p.prepare_headers(self.headers)
224 p.prepare_cookies(self.cookies)
225 p.prepare_body(self.data, self.files)
226 p.prepare_auth(self.auth, self.url)
227 # Note that prepare_auth must be last to enable authentication schemes
228 # such as OAuth to work on a fully prepared request.
229
230 # This MUST go after prepare_auth. Authenticators could add a hook
231 p.prepare_hooks(self.hooks)
232
233 return p
234
235
236 class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
237 """The fully mutable :class:`PreparedRequest <PreparedRequest>` object,
238 containing the exact bytes that will be sent to the server.
239
240 Generated from either a :class:`Request <Request>` object or manually.
241
242 Usage::
243
244 >>> import requests
245 >>> req = requests.Request('GET', 'http://httpbin.org/get')
246 >>> r = req.prepare()
247 <PreparedRequest [GET]>
248
249 >>> s = requests.Session()
250 >>> s.send(r)
251 <Response [200]>
252
253 """
254
255 def __init__(self):
256 #: HTTP verb to send to the server.
257 self.method = None
258 #: HTTP URL to send the request to.
259 self.url = None
260 #: dictionary of HTTP headers.
261 self.headers = None
262 #: request body to send to the server.
263 self.body = None
264 #: dictionary of callback hooks, for internal usage.
265 self.hooks = default_hooks()
266
267 def __repr__(self):
268 return '<PreparedRequest [%s]>' % (self.method)
269
270 def prepare_method(self, method):
271 """Prepares the given HTTP method."""
272 self.method = method
273 if self.method is not None:
274 self.method = self.method.upper()
275
276 def prepare_url(self, url, params):
277 """Prepares the given HTTP URL."""
278 #: Accept objects that have string representations.
279 try:
280 url = unicode(url)
281 except NameError:
282 # We're on Python 3.
283 url = str(url)
284 except UnicodeDecodeError:
285 pass
286
287 # Support for unicode domain names and paths.
288 scheme, auth, host, port, path, query, fragment = parse_url(url)
289
290 if not scheme:
291 raise MissingSchema("Invalid URL %r: No schema supplied" % url)
292
293 if not host:
294 raise InvalidURL("Invalid URL %r: No host supplied" % url)
295
296 # Only want to apply IDNA to the hostname
297 try:
298 host = host.encode('idna').decode('utf-8')
299 except UnicodeError:
300 raise InvalidURL('URL has an invalid label.')
301
302 # Carefully reconstruct the network location
303 netloc = auth or ''
304 if netloc:
305 netloc += '@'
306 netloc += host
307 if port:
308 netloc += ':' + str(port)
309
310 # Bare domains aren't valid URLs.
311 if not path:
312 path = '/'
313
314 if is_py2:
315 if isinstance(scheme, str):
316 scheme = scheme.encode('utf-8')
317 if isinstance(netloc, str):
318 netloc = netloc.encode('utf-8')
319 if isinstance(path, str):
320 path = path.encode('utf-8')
321 if isinstance(query, str):
322 query = query.encode('utf-8')
323 if isinstance(fragment, str):
324 fragment = fragment.encode('utf-8')
325
326 enc_params = self._encode_params(params)
327 if enc_params:
328 if query:
329 query = '%s&%s' % (query, enc_params)
330 else:
331 query = enc_params
332
333 url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragmen t]))
334 self.url = url
335
336 def prepare_headers(self, headers):
337 """Prepares the given HTTP headers."""
338
339 if headers:
340 headers = dict((name.encode('ascii'), value) for name, value in head ers.items())
341 self.headers = CaseInsensitiveDict(headers)
342 else:
343 self.headers = CaseInsensitiveDict()
344
345 def prepare_body(self, data, files):
346 """Prepares the given HTTP body data."""
347
348 # Check if file, fo, generator, iterator.
349 # If not, run through normal process.
350
351 # Nottin' on you.
352 body = None
353 content_type = None
354 length = None
355 is_stream = False
356
357 is_stream = all([
358 hasattr(data, '__iter__'),
359 not isinstance(data, basestring),
360 not isinstance(data, list),
361 not isinstance(data, dict)
362 ])
363
364 try:
365 length = super_len(data)
366 except (TypeError, AttributeError):
367 length = False
368
369 if is_stream:
370 body = data
371
372 if files:
373 raise NotImplementedError('Streamed bodies and files are mutuall y exclusive.')
374
375 if length:
376 self.headers['Content-Length'] = str(length)
377 else:
378 self.headers['Transfer-Encoding'] = 'chunked'
379 # Check if file, fo, generator, iterator.
380 # If not, run through normal process.
381
382 else:
383 # Multi-part file uploads.
384 if files:
385 (body, content_type) = self._encode_files(files, data)
386 else:
387 if data:
388 body = self._encode_params(data)
389 if isinstance(data, str) or isinstance(data, builtin_str) or hasattr(data, 'read'):
390 content_type = None
391 else:
392 content_type = 'application/x-www-form-urlencoded'
393
394 self.prepare_content_length(body)
395
396 # Add content-type if it wasn't explicitly provided.
397 if (content_type) and (not 'content-type' in self.headers):
398 self.headers['Content-Type'] = content_type
399
400 self.body = body
401
402 def prepare_content_length(self, body):
403 if hasattr(body, 'seek') and hasattr(body, 'tell'):
404 body.seek(0, 2)
405 self.headers['Content-Length'] = str(body.tell())
406 body.seek(0, 0)
407 elif body is not None:
408 l = super_len(body)
409 if l:
410 self.headers['Content-Length'] = str(l)
411 elif self.method not in ('GET', 'HEAD'):
412 self.headers['Content-Length'] = '0'
413
414 def prepare_auth(self, auth, url=''):
415 """Prepares the given HTTP auth data."""
416
417 # If no Auth is explicitly provided, extract it from the URL first.
418 if auth is None:
419 url_auth = get_auth_from_url(self.url)
420 auth = url_auth if any(url_auth) else None
421
422 if auth:
423 if isinstance(auth, tuple) and len(auth) == 2:
424 # special-case basic HTTP auth
425 auth = HTTPBasicAuth(*auth)
426
427 # Allow auth to make its changes.
428 r = auth(self)
429
430 # Update self to reflect the auth changes.
431 self.__dict__.update(r.__dict__)
432
433 # Recompute Content-Length
434 self.prepare_content_length(self.body)
435
436 def prepare_cookies(self, cookies):
437 """Prepares the given HTTP cookie data."""
438
439 if isinstance(cookies, cookielib.CookieJar):
440 cookies = cookies
441 else:
442 cookies = cookiejar_from_dict(cookies)
443
444 if 'cookie' not in self.headers:
445 cookie_header = get_cookie_header(cookies, self)
446 if cookie_header is not None:
447 self.headers['Cookie'] = cookie_header
448
449 def prepare_hooks(self, hooks):
450 """Prepares the given hooks."""
451 for event in hooks:
452 self.register_hook(event, hooks[event])
453
454
455 class Response(object):
456 """The :class:`Response <Response>` object, which contains a
457 server's response to an HTTP request.
458 """
459
460 def __init__(self):
461 super(Response, self).__init__()
462
463 self._content = False
464 self._content_consumed = False
465
466 #: Integer Code of responded HTTP Status.
467 self.status_code = None
468
469 #: Case-insensitive Dictionary of Response Headers.
470 #: For example, ``headers['content-encoding']`` will return the
471 #: value of a ``'Content-Encoding'`` response header.
472 self.headers = CaseInsensitiveDict()
473
474 #: File-like object representation of response (for advanced usage).
475 #: Requires that ``stream=True` on the request.
476 # This requirement does not apply for use internally to Requests.
477 self.raw = None
478
479 #: Final URL location of Response.
480 self.url = None
481
482 #: Encoding to decode with when accessing r.text.
483 self.encoding = None
484
485 #: A list of :class:`Response <Response>` objects from
486 #: the history of the Request. Any redirect responses will end
487 #: up here. The list is sorted from the oldest to the most recent reques t.
488 self.history = []
489
490 self.reason = None
491
492 #: A CookieJar of Cookies the server sent back.
493 self.cookies = cookiejar_from_dict({})
494
495 #: The amount of time elapsed between sending the request
496 #: and the arrival of the response (as a timedelta)
497 self.elapsed = datetime.timedelta(0)
498
499 def __repr__(self):
500 return '<Response [%s]>' % (self.status_code)
501
502 def __bool__(self):
503 """Returns true if :attr:`status_code` is 'OK'."""
504 return self.ok
505
506 def __nonzero__(self):
507 """Returns true if :attr:`status_code` is 'OK'."""
508 return self.ok
509
510 def __iter__(self):
511 """Allows you to use a response as an iterator."""
512 return self.iter_content(128)
513
514 @property
515 def ok(self):
516 try:
517 self.raise_for_status()
518 except RequestException:
519 return False
520 return True
521
522 @property
523 def apparent_encoding(self):
524 """The apparent encoding, provided by the lovely Charade library
525 (Thanks, Ian!)."""
526 return chardet.detect(self.content)['encoding']
527
528 def iter_content(self, chunk_size=1, decode_unicode=False):
529 """Iterates over the response data. When stream=True is set on the
530 request, this avoids reading the content at once into memory for
531 large responses. The chunk size is the number of bytes it should
532 read into memory. This is not necessarily the length of each item
533 returned as decoding can take place.
534 """
535 if self._content_consumed:
536 # simulate reading small chunks of the content
537 return iter_slices(self._content, chunk_size)
538
539 def generate():
540 while 1:
541 chunk = self.raw.read(chunk_size, decode_content=True)
542 if not chunk:
543 break
544 yield chunk
545 self._content_consumed = True
546
547 gen = generate()
548
549 if decode_unicode:
550 gen = stream_decode_response_unicode(gen, self)
551
552 return gen
553
554 def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None):
555 """Iterates over the response data, one line at a time. When
556 stream=True is set on the request, this avoids reading the
557 content at once into memory for large responses.
558 """
559
560 pending = None
561
562 for chunk in self.iter_content(chunk_size=chunk_size,
563 decode_unicode=decode_unicode):
564
565 if pending is not None:
566 chunk = pending + chunk
567 lines = chunk.splitlines()
568
569 if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]:
570 pending = lines.pop()
571 else:
572 pending = None
573
574 for line in lines:
575 yield line
576
577 if pending is not None:
578 yield pending
579
580 @property
581 def content(self):
582 """Content of the response, in bytes."""
583
584 if self._content is False:
585 # Read the contents.
586 try:
587 if self._content_consumed:
588 raise RuntimeError(
589 'The content for this response was already consumed')
590
591 if self.status_code == 0:
592 self._content = None
593 else:
594 self._content = bytes().join(self.iter_content(CONTENT_CHUNK _SIZE)) or bytes()
595
596 except AttributeError:
597 self._content = None
598
599 self._content_consumed = True
600 # don't need to release the connection; that's been handled by urllib3
601 # since we exhausted the data.
602 return self._content
603
604 @property
605 def text(self):
606 """Content of the response, in unicode.
607
608 if Response.encoding is None and chardet module is available, encoding
609 will be guessed.
610 """
611
612 # Try charset from content-type
613 content = None
614 encoding = self.encoding
615
616 if not self.content:
617 return str('')
618
619 # Fallback to auto-detected encoding.
620 if self.encoding is None:
621 encoding = self.apparent_encoding
622
623 # Decode unicode from given encoding.
624 try:
625 content = str(self.content, encoding, errors='replace')
626 except (LookupError, TypeError):
627 # A LookupError is raised if the encoding was not found which could
628 # indicate a misspelling or similar mistake.
629 #
630 # A TypeError can be raised if encoding is None
631 #
632 # So we try blindly encoding.
633 content = str(self.content, errors='replace')
634
635 return content
636
637 def json(self, **kwargs):
638 """Returns the json-encoded content of a response, if any.
639
640 :param \*\*kwargs: Optional arguments that ``json.loads`` takes.
641 """
642
643 if not self.encoding and len(self.content) > 3:
644 # No encoding set. JSON RFC 4627 section 3 states we should expect
645 # UTF-8, -16 or -32. Detect which one to use; If the detection or
646 # decoding fails, fall back to `self.text` (using chardet to make
647 # a best guess).
648 encoding = guess_json_utf(self.content)
649 if encoding is not None:
650 return json.loads(self.content.decode(encoding), **kwargs)
651 return json.loads(self.text or self.content, **kwargs)
652
653 @property
654 def links(self):
655 """Returns the parsed header links of the response, if any."""
656
657 header = self.headers.get('link')
658
659 # l = MultiDict()
660 l = {}
661
662 if header:
663 links = parse_header_links(header)
664
665 for link in links:
666 key = link.get('rel') or link.get('url')
667 l[key] = link
668
669 return l
670
671 def raise_for_status(self):
672 """Raises stored :class:`HTTPError`, if one occurred."""
673
674 http_error_msg = ''
675
676 if 400 <= self.status_code < 500:
677 http_error_msg = '%s Client Error: %s' % (self.status_code, self.rea son)
678
679 elif 500 <= self.status_code < 600:
680 http_error_msg = '%s Server Error: %s' % (self.status_code, self.rea son)
681
682 if http_error_msg:
683 raise HTTPError(http_error_msg, response=self)
684
685 def close(self):
686 return self.raw.release_conn()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698