diff options
Diffstat (limited to 'requests/models.py')
-rw-r--r-- | requests/models.py | 101 |
1 files changed, 76 insertions, 25 deletions
diff --git a/requests/models.py b/requests/models.py index 8fd9735..ae46a83 100644 --- a/requests/models.py +++ b/requests/models.py @@ -17,11 +17,13 @@ from .structures import CaseInsensitiveDict from .auth import HTTPBasicAuth from .cookies import cookiejar_from_dict, get_cookie_header +from .packages.urllib3.fields import RequestField from .packages.urllib3.filepost import encode_multipart_formdata from .packages.urllib3.util import parse_url +from .packages.urllib3.exceptions import DecodeError from .exceptions import ( HTTPError, RequestException, MissingSchema, InvalidURL, - ChunkedEncodingError) + ChunkedEncodingError, ContentDecodingError) from .utils import ( guess_filename, get_auth_from_url, requote_uri, stream_decode_response_unicode, to_key_val_list, parse_header_links, @@ -90,7 +92,7 @@ class RequestEncodingMixin(object): """Build the body for a multipart/form-data request. Will successfully encode files when passed as a dict or a list of - 2-tuples. Order is retained if data is a list of 2-tuples but abritrary + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary if parameters are supplied as a dict. """ @@ -119,11 +121,14 @@ class RequestEncodingMixin(object): for (k, v) in files: # support for explicit filename ft = None + fh = None if isinstance(v, (tuple, list)): if len(v) == 2: fn, fp = v - else: + elif len(v) == 3: fn, fp, ft = v + else: + fn, fp, ft, fh = v else: fn = guess_filename(v) or k fp = v @@ -132,11 +137,10 @@ class RequestEncodingMixin(object): if isinstance(fp, bytes): fp = BytesIO(fp) - if ft: - new_v = (fn, fp.read(), ft) - else: - new_v = (fn, fp.read()) - new_fields.append((k, new_v)) + rf = RequestField(name=k, data=fp.read(), + filename=fn, headers=fh) + rf.make_multipart(content_type=ft) + new_fields.append(rf) body, content_type = encode_multipart_formdata(new_fields) @@ -267,6 +271,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): self.url = None #: dictionary of HTTP headers. self.headers = None + # The `CookieJar` used to create the Cookie header will be stored here + # after prepare_cookies is called + self._cookies = None #: request body to send to the server. self.body = None #: dictionary of callback hooks, for internal usage. @@ -274,7 +281,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): def prepare(self, method=None, url=None, headers=None, files=None, data=None, params=None, auth=None, cookies=None, hooks=None): - """Prepares the the entire request with the given parameters.""" + """Prepares the entire request with the given parameters.""" self.prepare_method(method) self.prepare_url(url, params) @@ -295,7 +302,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): p = PreparedRequest() p.method = self.method p.url = self.url - p.headers = self.headers + p.headers = self.headers.copy() + p._cookies = self._cookies.copy() p.body = self.body p.hooks = self.hooks return p @@ -317,11 +325,17 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): except UnicodeDecodeError: pass + # Don't do any URL preparation for oddball schemes + if ':' in url and not url.lower().startswith('http'): + self.url = url + return + # Support for unicode domain names and paths. scheme, auth, host, port, path, query, fragment = parse_url(url) if not scheme: - raise MissingSchema("Invalid URL %r: No schema supplied" % url) + raise MissingSchema("Invalid URL {0!r}: No schema supplied. " + "Perhaps you meant http://{0}?".format(url)) if not host: raise InvalidURL("Invalid URL %r: No host supplied" % url) @@ -404,7 +418,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): raise NotImplementedError('Streamed bodies and files are mutually exclusive.') if length is not None: - self.headers['Content-Length'] = str(length) + self.headers['Content-Length'] = builtin_str(length) else: self.headers['Transfer-Encoding'] = 'chunked' else: @@ -430,12 +444,12 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): def prepare_content_length(self, body): if hasattr(body, 'seek') and hasattr(body, 'tell'): body.seek(0, 2) - self.headers['Content-Length'] = str(body.tell()) + self.headers['Content-Length'] = builtin_str(body.tell()) body.seek(0, 0) elif body is not None: l = super_len(body) if l: - self.headers['Content-Length'] = str(l) + self.headers['Content-Length'] = builtin_str(l) elif self.method not in ('GET', 'HEAD'): self.headers['Content-Length'] = '0' @@ -465,14 +479,13 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): """Prepares the given HTTP cookie data.""" if isinstance(cookies, cookielib.CookieJar): - cookies = cookies + self._cookies = cookies else: - cookies = cookiejar_from_dict(cookies) + self._cookies = cookiejar_from_dict(cookies) - if 'cookie' not in self.headers: - cookie_header = get_cookie_header(cookies, self) - if cookie_header is not None: - self.headers['Cookie'] = cookie_header + cookie_header = get_cookie_header(self._cookies, self) + if cookie_header is not None: + self.headers['Cookie'] = cookie_header def prepare_hooks(self, hooks): """Prepares the given hooks.""" @@ -485,6 +498,19 @@ class Response(object): server's response to an HTTP request. """ + __attrs__ = [ + '_content', + 'status_code', + 'headers', + 'url', + 'history', + 'encoding', + 'reason', + 'cookies', + 'elapsed', + 'request', + ] + def __init__(self): super(Response, self).__init__() @@ -500,7 +526,7 @@ class Response(object): self.headers = CaseInsensitiveDict() #: File-like object representation of response (for advanced usage). - #: Requires that ``stream=True` on the request. + #: Use of ``raw`` requires that ``stream=True`` be set on the request. # This requirement does not apply for use internally to Requests. self.raw = None @@ -524,6 +550,24 @@ class Response(object): #: and the arrival of the response (as a timedelta) self.elapsed = datetime.timedelta(0) + def __getstate__(self): + # Consume everything; accessing the content attribute makes + # sure the content has been fully read. + if not self._content_consumed: + self.content + + return dict( + (attr, getattr(self, attr, None)) + for attr in self.__attrs__ + ) + + def __setstate__(self, state): + for name, value in state.items(): + setattr(self, name, value) + + # pickled objects do not have .raw + setattr(self, '_content_consumed', True) + def __repr__(self): return '<Response [%s]>' % (self.status_code) @@ -573,9 +617,11 @@ class Response(object): yield chunk except IncompleteRead as e: raise ChunkedEncodingError(e) + except DecodeError as e: + raise ContentDecodingError(e) except AttributeError: # Standard file-like object. - while 1: + while True: chunk = self.raw.read(chunk_size) if not chunk: break @@ -644,8 +690,13 @@ class Response(object): def text(self): """Content of the response, in unicode. - if Response.encoding is None and chardet module is available, encoding - will be guessed. + If Response.encoding is None, encoding will be guessed using + ``chardet``. + + The encoding of the response content is determined based soley on HTTP + headers, following RFC 2616 to the letter. If you can take advantage of + non-HTTP knowledge to make a better guess at the encoding, you should + set ``r.encoding`` appropriately before accessing this property. """ # Try charset from content-type @@ -687,7 +738,7 @@ class Response(object): encoding = guess_json_utf(self.content) if encoding is not None: return json.loads(self.content.decode(encoding), **kwargs) - return json.loads(self.text or self.content, **kwargs) + return json.loads(self.text, **kwargs) @property def links(self): |