Merge branch 'develop' into key_val_lists

Conflicts:
	requests/models.py
	tests/test_requests.py

Remove some of Lukasa's duplication of my efforts in _encode_data.
This commit is contained in:
Ian Cordasco
2012-08-18 12:15:03 -04:00
27 changed files with 274 additions and 105 deletions
+1
View File
@@ -110,3 +110,4 @@ Patches and Suggestions
- Victoria Mo
- Leila Muhtasib
- Matthias Rahlf <matthias@webding.de>
- Jakub Roztocil <jakub@roztocil.name>
+1 -1
View File
@@ -1 +1 @@
include HISTORY.rst README.rst LICENSE
include HISTORY.rst README.rst LICENSE
+1 -1
View File
@@ -5,7 +5,7 @@
</p>
<p>
<iframe src="http://markdotto.github.com/github-buttons/github-btn.html?user=kennethreitz&repo=requests&type=watch&count=true&size=large"
<iframe src="http://ghbtns.com/github-btn.html?user=kennethreitz&repo=requests&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
</p>
+1 -1
View File
@@ -4,7 +4,7 @@
</a>
</p>
<p>
<iframe src="http://markdotto.github.com/github-buttons/github-btn.html?user=kennethreitz&repo=requests&type=watch&count=true&size=large"
<iframe src="http://ghbtns.com/github-btn.html?user=kennethreitz&repo=requests&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
</p>
+2 -2
View File
@@ -1,9 +1,9 @@
Modifications:
Modifications:
Copyright (c) 2011 Kenneth Reitz.
Original Project:
Original Project:
Copyright (c) 2010 by Armin Ronacher.
+1 -1
View File
@@ -1,7 +1,7 @@
krTheme Sphinx Style
====================
This repository contains sphinx styles Kenneth Reitz uses in most of
This repository contains sphinx styles Kenneth Reitz uses in most of
his projects. It is a derivative of Mitsuhiko's themes for Flask and Flask related
projects. To use this style in your Sphinx documentation, follow
this guide:
+1 -1
View File
@@ -4,4 +4,4 @@ stylesheet = flasky.css
pygments_style = flask_theme_support.FlaskyStyle
[options]
touch_icon =
touch_icon =
+22 -22
View File
@@ -8,11 +8,11 @@
* :license: BSD, see LICENSE for details.
*
*/
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: 'Georgia', serif;
font-size: 17px;
@@ -35,7 +35,7 @@ div.bodywrapper {
hr {
border: 1px solid #B1B4B6;
}
div.body {
background-color: #ffffff;
color: #3E4349;
@@ -46,7 +46,7 @@ img.floatingflask {
padding: 0 0 10px 10px;
float: right;
}
div.footer {
text-align: right;
color: #888;
@@ -55,12 +55,12 @@ div.footer {
width: 650px;
margin: 0 auto 40px auto;
}
div.footer a {
color: #888;
text-decoration: underline;
}
div.related {
line-height: 32px;
color: #888;
@@ -69,18 +69,18 @@ div.related {
div.related ul {
padding: 0 0 0 10px;
}
div.related a {
color: #444;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #004B6B;
text-decoration: underline;
}
a:hover {
color: #6D4100;
text-decoration: underline;
@@ -89,7 +89,7 @@ a:hover {
div.body {
padding-bottom: 40px; /* saved for footer */
}
div.body h1,
div.body h2,
div.body h3,
@@ -109,24 +109,24 @@ div.indexwrapper h1 {
height: {{ theme_index_logo_height }};
}
{% endif %}
div.body h2 { font-size: 180%; }
div.body h3 { font-size: 150%; }
div.body h4 { font-size: 130%; }
div.body h5 { font-size: 100%; }
div.body h6 { font-size: 100%; }
a.headerlink {
color: white;
padding: 0 4px;
text-decoration: none;
}
a.headerlink:hover {
color: #444;
background: #eaeaea;
}
div.body p, div.body dd, div.body li {
line-height: 1.4em;
}
@@ -164,25 +164,25 @@ div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
@@ -254,7 +254,7 @@ dl {
dl dd {
margin-left: 30px;
}
pre {
padding: 0;
margin: 15px -30px;
+25
View File
@@ -343,6 +343,31 @@ To use HTTP Basic Auth with your proxy, use the `http://user:password@host/` syn
"http": "http://user:pass@10.10.1.10:3128/",
}
Compliance
----------
Requests is intended to be compliant with all relevant specifications and
RFCs where that compliance will not cause difficulties for users. This
attention to the specification can lead to some behaviour that may seem
unusual to those not familiar with the relevant specification.
Encodings
^^^^^^^^^
When you receive a response, Requests makes a guess at the encoding to use for
decoding the response when you call the ``Response.text`` method. Requests
will first check for an encoding in the HTTP header, and if none is present,
will use `chardet <http://pypi.python.org/pypi/chardet>`_ to attempt to guess
the encoding.
The only time Requests will not do this is if no explicit charset is present
in the HTTP headers **and** the ``Content-Type`` header contains ``text``. In
this situation,
`RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1>`_
specifies that the default charset must be ``ISO-8859-1``. Requests follows
the specification in this case. If you require a different encoding, you can
manually set the ``Response.encoding`` property, or use the raw
``Request.content``.
HTTP Verbs
----------
+24 -14
View File
@@ -24,7 +24,7 @@ Make a Request
Making a request with Requests is very simple.
Begin by importing the Requests module::
>>> import requests
Now, let's try to get a webpage. For this example, let's get GitHub's public
@@ -37,12 +37,12 @@ information we need from this object.
Requests' simple API means that all forms of HTTP request are as obvious. For
example, this is how you make an HTTP POST request::
>>> r = requests.post("http://httpbin.org/post")
Nice, right? What about the other HTTP request types: PUT, DELETE, HEAD and
OPTIONS? These are all just as simple::
>>> r = requests.put("http://httpbin.org/put")
>>> r = requests.delete("http://httpbin.org/delete")
>>> r = requests.head("http://httpbin.org/get")
@@ -70,7 +70,7 @@ You can see that the URL has been correctly encoded by printing the URL::
>>> print r.url
u'http://httpbin.org/get?key2=value2&key1=value1'
Response Content
----------------
@@ -86,12 +86,22 @@ again::
Requests will automatically decode content from the server. Most unicode
charsets are seamlessly decoded.
When you make a request, ``r.encoding`` is set, based on the HTTP headers.
Requests will use that encoding when you access ``r.text``. If ``r.encoding``
is ``None``, Requests will make an extremely educated guess of the encoding
of the response body. You can manually set ``r.encoding`` to any encoding
you'd like, and that charset will be used.
When you make a request, Requests makes educated guesses about the encoding of
the response based on the HTTP headers. The text encoding guessed by Requests
is used when you access ``r.text``. You can find out what encoding Requests is
using, and change it, using the ``r.encoding`` property::
>>> r.encoding
'utf-8'
>>> r.encoding = 'ISO-8859-1'
If you change the encoding, Requests will use the new value of ``r.encoding``
whenever you call ``r.text``.
Requests will also use custom encodings in the event that you need them. If
you have created your own encoding and registered it with the ``codecs``
module, you can simply use the codec name as the value of ``r.encoding`` and
Requests will handle the decoding for you.
Binary Response Content
-----------------------
@@ -219,7 +229,7 @@ You can set the filename explicitly::
If you want, you can send strings to be received as files::
>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}
>>> files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}
>>> r = requests.post(url, files=files)
>>> r.text
@@ -250,11 +260,11 @@ reference::
If we made a bad request (non-200 response), we can raise it with
:class:`Response.raise_for_status()`::
>>> _r = requests.get('http://httpbin.org/status/404')
>>> _r.status_code
>>> bad_r = requests.get('http://httpbin.org/status/404')
>>> bad_r.status_code
404
>>> _r.raise_for_status()
>>> bad_r.raise_for_status()
Traceback (most recent call last):
File "requests/models.py", line 832, in raise_for_status
raise http_error
@@ -329,7 +339,7 @@ parameter::
Basic Authentication
--------------------
Many web services require authentication. There many different types of
Many web services require authentication. There are many different types of
authentication, but the most common is HTTP Basic Auth.
Making requests with Basic Auth is extremely simple::
+2
View File
@@ -14,6 +14,7 @@ This module implements the Requests API.
from . import sessions
from .safe_mode import catch_exceptions_if_in_safe_mode
@catch_exceptions_if_in_safe_mode
def request(method, url, **kwargs):
"""Constructs and sends a :class:`Request <Request>`.
@@ -52,6 +53,7 @@ def request(method, url, **kwargs):
if adhoc_session:
session.close()
def get(url, **kwargs):
"""Sends a GET request. Returns :class:`Response` object.
+3
View File
@@ -34,6 +34,7 @@ log = logging.getLogger(__name__)
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
def _basic_auth_str(username, password):
"""Returns a Basic Auth string."""
@@ -239,6 +240,7 @@ class HTTPDigestAuth(AuthBase):
r.register_hook('response', self.handle_401)
return r
def _negotiate_value(r):
"""Extracts the gssapi authentication token from the appropriate header"""
@@ -252,6 +254,7 @@ def _negotiate_value(r):
return None
class HTTPKerberosAuth(AuthBase):
"""Attaches HTTP GSSAPI/Kerberos Authentication to the given Request object."""
def __init__(self, require_mutual_auth=True):
-1
View File
@@ -112,4 +112,3 @@ elif is_py3:
bytes = bytes
basestring = (str,bytes)
numeric_types = (int, float)
+20 -10
View File
@@ -14,6 +14,7 @@ try:
except ImportError:
import dummy_threading as threading
class MockRequest(object):
"""Wraps a `requests.Request` to mimic a `urllib2.Request`.
@@ -66,6 +67,7 @@ class MockRequest(object):
def get_new_headers(self):
return self._new_headers
class MockResponse(object):
"""Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
@@ -86,6 +88,7 @@ class MockResponse(object):
def getheaders(self, name):
self._headers.getheaders(name)
def extract_cookies_to_jar(jar, request, response):
"""Extract the cookies from the response into a CookieJar.
@@ -99,12 +102,14 @@ def extract_cookies_to_jar(jar, request, response):
res = MockResponse(response._original_response.msg)
jar.extract_cookies(res, req)
def get_cookie_header(jar, request):
"""Produce an appropriate Cookie header string to be sent with `request`, or None."""
r = MockRequest(request)
jar.add_cookie_header(r)
return r.get_new_headers().get('Cookie')
def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
"""Unsets a cookie by name, by default over all domains and paths.
@@ -120,10 +125,12 @@ def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
for domain, path, name in clearables:
cookiejar.clear(domain, path, name)
class CookieConflictError(RuntimeError):
"""There are two cookies that meet the criteria specified in the cookie jar.
"""There are two cookies that meet the criteria specified in the cookie jar.
Use .get and .set and include domain and path args in order to be more specific."""
class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict interface.
@@ -181,7 +188,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
for cookie in iter(self):
values.append(cookie.value)
return values
def items(self):
"""Dict-like items() that returns a list of name-value tuples from the jar.
See keys() and values(). Allows client-code to call "dict(RequestsCookieJar)
@@ -215,14 +222,14 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
if cookie.domain is not None and cookie.domain in domains:
return True
domains.append(cookie.domain)
return False # there is only one domain in jar
return False # there is only one domain in jar
def get_dict(self, domain=None, path=None):
"""Takes as an argument an optional domain and path and returns a plain old
Python dict of name-value pairs of cookies that meet the requirements."""
dictionary = {}
for cookie in iter(self):
if (domain == None or cookie.domain == domain) and (path == None
if (domain == None or cookie.domain == domain) and (path == None
or cookie.path == path):
dictionary[cookie.name] = cookie.value
return dictionary
@@ -244,7 +251,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
remove_cookie_by_name(self, name)
def _find(self, name, domain=None, path=None):
"""Requests uses this method internally to get cookie values. Takes as args name
"""Requests uses this method internally to get cookie values. Takes as args name
and optional domain and path. Returns a cookie.value. If there are conflicting cookies,
_find arbitrarily chooses one. See _find_no_duplicates if you want an exception thrown
if there are conflicting cookies."""
@@ -257,18 +264,18 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
def _find_no_duplicates(self, name, domain=None, path=None):
"""__get_item__ and get call _find_no_duplicates -- never used in Requests internally.
Takes as args name and optional domain and path. Returns a cookie.value.
Throws KeyError if cookie is not found and CookieConflictError if there are
"""__get_item__ and get call _find_no_duplicates -- never used in Requests internally.
Takes as args name and optional domain and path. Returns a cookie.value.
Throws KeyError if cookie is not found and CookieConflictError if there are
multiple cookies that match name and optionally domain and path."""
toReturn = None
for cookie in iter(self):
if cookie.name == name:
if domain is None or cookie.domain == domain:
if path is None or cookie.path == path:
if toReturn != None: # if there are multiple cookies that meet passed in criteria
if toReturn != None: # if there are multiple cookies that meet passed in criteria
raise CookieConflictError('There are multiple cookies with name, %r' % (name))
toReturn = cookie.value # we will eventually return this as long as no cookie conflict
toReturn = cookie.value # we will eventually return this as long as no cookie conflict
if toReturn:
return toReturn
@@ -291,6 +298,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
"""This is not implemented. Calling this will throw an exception."""
raise NotImplementedError
def create_cookie(name, value, **kwargs):
"""Make a cookie from underspecified parameters.
@@ -326,6 +334,7 @@ def create_cookie(name, value, **kwargs):
return cookielib.Cookie(**result)
def morsel_to_cookie(morsel):
"""Convert a Morsel object into a Cookie containing the one k/v pair."""
c = create_cookie(
@@ -349,6 +358,7 @@ def morsel_to_cookie(morsel):
)
return c
def cookiejar_from_dict(cookie_dict, cookiejar=None):
"""Returns a CookieJar from a key/value dictionary.
+10
View File
@@ -8,34 +8,44 @@ This module contains the set of Requests' exceptions.
"""
class RequestException(RuntimeError):
"""There was an ambiguous exception that occurred while handling your
request."""
class HTTPError(RequestException):
"""An HTTP error occurred."""
response = None
class ConnectionError(RequestException):
"""A Connection error occurred."""
class SSLError(ConnectionError):
"""An SSL error occurred."""
class Timeout(RequestException):
"""The request timed out."""
class URLRequired(RequestException):
"""A valid URL is required to make a request."""
class TooManyRedirects(RequestException):
"""Too many redirects."""
class MissingSchema(RequestException, ValueError):
"""The URL schema (e.g. http or https) is missing."""
class InvalidSchema(RequestException, ValueError):
"""See defaults.py for valid schemas."""
class InvalidURL(RequestException, ValueError):
""" The URL provided was somehow invalid. """
+1
View File
@@ -30,6 +30,7 @@ import traceback
HOOKS = ('args', 'pre_request', 'pre_send', 'post_request', 'response')
def dispatch_hook(key, hooks, hook_data):
"""Dispatches a hook dictionary on a given piece of data."""
+42 -16
View File
@@ -8,7 +8,9 @@ This module contains the primary objects that power Requests.
"""
import os
import socket
from datetime import datetime
from io import BytesIO
from .hooks import dispatch_hook, HOOKS
from .structures import CaseInsensitiveDict
@@ -72,7 +74,14 @@ class Request(object):
self.timeout = timeout
#: Request URL.
self.url = url
#: Accept objects that have string representations.
try:
self.url = unicode(url)
except NameError:
# We're on Python 3.
self.url = str(url)
except UnicodeDecodeError:
self.url = url
#: Dictionary of HTTP Headers to attach to the :class:`Request <Request>`.
self.headers = dict(headers or [])
@@ -292,7 +301,8 @@ class Request(object):
proxies=self.proxies,
verify=self.verify,
session=self.session,
cert=self.cert
cert=self.cert,
prefetch=self.prefetch,
)
request.send()
@@ -328,7 +338,13 @@ class Request(object):
return data
def _encode_files(self, files):
"""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
if parameters are supplied as a dict.
"""
if (not files) or isinstance(self.data, str):
return None
@@ -342,23 +358,22 @@ class Request(object):
else:
fn = guess_filename(v) or k
fp = v
if isinstance(fp, (bytes, str)):
if isinstance(fp, str):
fp = StringIO(fp)
if isinstance(fp, bytes):
fp = BytesIO(fp)
fields.append((k, (fn, fp.read())))
new_fields = []
for field, val in fields:
if isinstance(val, float):
new_fields.append((field, str(val)))
elif isinstance(val, list):
newvalue = ', '.join(val)
new_fields.append((field, newvalue))
if isinstance(val, list):
for v in val:
new_fields.append((k, str(v)))
else:
new_fields.append((field, val))
fields = new_fields
(body, content_type) = encode_multipart_formdata(fields)
new_fields.append((field, str(val)))
body, content_type = encode_multipart_formdata(new_fields)
return (body, content_type)
return body, content_type
@property
def full_url(self):
@@ -378,7 +393,10 @@ class Request(object):
if not scheme in SCHEMAS:
raise InvalidSchema("Invalid scheme %r" % scheme)
netloc = netloc.encode('idna').decode('utf-8')
try:
netloc = netloc.encode('idna').decode('utf-8')
except UnicodeError:
raise InvalidURL('URL has an invalid label.')
if not path:
path = '/'
@@ -452,7 +470,7 @@ class Request(object):
except ValueError:
return False
def send(self, anyway=False, prefetch=True):
def send(self, anyway=False, prefetch=None):
"""Sends the request. Returns True if successful, False if not.
If there was an HTTPError during transmission,
self.response.status_code will contain the HTTPError code.
@@ -461,6 +479,9 @@ class Request(object):
:param anyway: If True, request will be sent, even if it has
already been sent.
:param prefetch: If not None, will override the request's own setting
for prefetch.
"""
# Build the URL
@@ -512,7 +533,7 @@ class Request(object):
self.__dict__.update(r.__dict__)
_p = urlparse(url)
no_proxy = filter(lambda x:x.strip(), self.proxies.get('no', '').split(','))
no_proxy = filter(lambda x: x.strip(), self.proxies.get('no', '').split(','))
proxy = self.proxies.get(_p.scheme)
if proxy and not any(map(_p.netloc.endswith, no_proxy)):
@@ -598,6 +619,9 @@ class Request(object):
)
self.sent = True
except socket.error as sockerr:
raise ConnectionError(sockerr)
except MaxRetryError as e:
raise ConnectionError(e)
@@ -620,7 +644,9 @@ class Request(object):
self.__dict__.update(r.__dict__)
# If prefetch is True, mark content as consumed.
if prefetch or self.prefetch:
if prefetch is None:
prefetch = self.prefetch
if prefetch:
# Save the response.
self.response.content
+5 -4
View File
@@ -16,15 +16,16 @@ from .packages.urllib3.response import HTTPResponse
from .exceptions import RequestException, ConnectionError, HTTPError
import socket
def catch_exceptions_if_in_safe_mode(function):
"""New implementation of safe_mode. We catch all exceptions at the API level
and then return a blank Response object with the error field filled. This decorator
wraps request() in api.py.
"""
def wrapped(method, url, **kwargs):
# if save_mode, we catch exceptions and fill error field
if (kwargs.get('config') and kwargs.get('config').get('safe_mode')) or (kwargs.get('session')
if (kwargs.get('config') and kwargs.get('config').get('safe_mode')) or (kwargs.get('session')
and kwargs.get('session').config.get('safe_mode')):
try:
return function(method, url, **kwargs)
@@ -32,8 +33,8 @@ def catch_exceptions_if_in_safe_mode(function):
socket.timeout, socket.gaierror) as e:
r = Response()
r.error = e
r.raw = HTTPResponse() # otherwise, tests fail
r.status_code = 0 # with this status_code, content returns None
r.raw = HTTPResponse() # otherwise, tests fail
r.status_code = 0 # with this status_code, content returns None
return r
return function(method, url, **kwargs)
return wrapped
-7
View File
@@ -244,7 +244,6 @@ class Session(object):
# Return the response.
return r.response
def get(self, url, **kwargs):
"""Sends a GET request. Returns :class:`Response` object.
@@ -255,7 +254,6 @@ class Session(object):
kwargs.setdefault('allow_redirects', True)
return self.request('get', url, **kwargs)
def options(self, url, **kwargs):
"""Sends a OPTIONS request. Returns :class:`Response` object.
@@ -266,7 +264,6 @@ class Session(object):
kwargs.setdefault('allow_redirects', True)
return self.request('options', url, **kwargs)
def head(self, url, **kwargs):
"""Sends a HEAD request. Returns :class:`Response` object.
@@ -277,7 +274,6 @@ class Session(object):
kwargs.setdefault('allow_redirects', False)
return self.request('head', url, **kwargs)
def post(self, url, data=None, **kwargs):
"""Sends a POST request. Returns :class:`Response` object.
@@ -288,7 +284,6 @@ class Session(object):
return self.request('post', url, data=data, **kwargs)
def put(self, url, data=None, **kwargs):
"""Sends a PUT request. Returns :class:`Response` object.
@@ -299,7 +294,6 @@ class Session(object):
return self.request('put', url, data=data, **kwargs)
def patch(self, url, data=None, **kwargs):
"""Sends a PATCH request. Returns :class:`Response` object.
@@ -310,7 +304,6 @@ class Session(object):
return self.request('patch', url, data=data, **kwargs)
def delete(self, url, **kwargs):
"""Sends a DELETE request. Returns :class:`Response` object.
+1 -1
View File
@@ -83,4 +83,4 @@ for (code, titles) in list(_codes.items()):
for title in titles:
setattr(codes, title, code)
if not title.startswith('\\'):
setattr(codes, title.upper(), code)
setattr(codes, title.upper(), code)
+1
View File
@@ -47,6 +47,7 @@ class CaseInsensitiveDict(dict):
else:
return default
class LookupDict(dict):
"""Dictionary lookup object."""
+3
View File
@@ -49,6 +49,7 @@ POSSIBLE_CA_BUNDLE_PATHS = [
'/etc/ssl/ca-bundle.pem',
]
def get_os_ca_bundle_path():
"""Try to pick an available CA certificate bundle provided by the OS."""
for path in POSSIBLE_CA_BUNDLE_PATHS:
@@ -60,6 +61,7 @@ def get_os_ca_bundle_path():
# otherwise, try and use the OS bundle
DEFAULT_CA_BUNDLE_PATH = CERTIFI_BUNDLE_PATH or get_os_ca_bundle_path()
def dict_to_sequence(d):
"""Returns an internal sequence dictionary update."""
@@ -472,6 +474,7 @@ def requote_uri(uri):
# or '%')
return quote(unquote_unreserved(uri), safe="!#$%&'()*+,/:;=?@[]~")
def get_environ_proxies():
"""Return a dict of environment proxies."""
@@ -6,6 +6,7 @@ the body of the request is not read.
import gc, os, subprocess, requests, sys
def main():
gc.disable()
+12 -9
View File
@@ -16,6 +16,7 @@ from requests.compat import cookielib
sys.path.append('.')
from test_requests import httpbin, TestBaseMixin
class CookieTests(TestBaseMixin, unittest.TestCase):
def test_cookies_from_response(self):
@@ -106,22 +107,22 @@ class CookieTests(TestBaseMixin, unittest.TestCase):
def test_disabled_cookie_persistence(self):
"""Test that cookies are not persisted when configured accordingly."""
config = {'store_cookies' : False}
config = {'store_cookies': False}
# Check the case when no cookie is passed as part of the request and the one in response is ignored
cookies = requests.get(httpbin('cookies', 'set', 'key', 'value'), config = config).cookies
cookies = requests.get(httpbin('cookies', 'set', 'key', 'value'), config=config).cookies
self.assertTrue(cookies.get("key") is None)
# Test that the cookies passed while making the request still gets used and is available in response object.
# only the ones received from server is not saved
cookies_2 = requests.get(httpbin('cookies', 'set', 'key', 'value'), config = config,\
cookies = {"key_2" : "value_2"}).cookies
cookies_2 = requests.get(httpbin('cookies', 'set', 'key', 'value'), config=config,\
cookies={"key_2": "value_2"}).cookies
self.assertEqual(len(cookies_2), 1)
self.assertEqual(cookies_2.get("key_2"), "value_2")
# Use the session and make sure that the received cookie is not used in subsequent calls
s = requests.session()
s.get(httpbin('cookies', 'set', 'key', 'value'), config = config)
s.get(httpbin('cookies', 'set', 'key', 'value'), config=config)
r = s.get(httpbin('cookies'))
self.assertEqual(json.loads(r.text)['cookies'], {})
@@ -134,7 +135,7 @@ class CookieTests(TestBaseMixin, unittest.TestCase):
self.assertEqual(len(c), len(r.cookies.keys()))
self.assertEqual(len(c), len(r.cookies.values()))
self.assertEqual(len(c), len(r.cookies.items()))
# domain and path utility functions
domain = r.cookies.list_domains()[0]
path = r.cookies.list_paths()[0]
@@ -151,13 +152,14 @@ class CookieTests(TestBaseMixin, unittest.TestCase):
# test keys, values, and items
self.assertEqual(r.cookies.keys(), ['myname'])
self.assertEqual(r.cookies.values(), ['myvalue'])
self.assertEqual(r.cookies.items(), [('myname','myvalue')])
self.assertEqual(r.cookies.items(), [('myname', 'myvalue')])
# test if we can convert jar to dict
dictOfCookies = dict(r.cookies)
self.assertEqual(dictOfCookies, {'myname':'myvalue'})
self.assertEqual(dictOfCookies, {'myname': 'myvalue'})
self.assertEqual(dictOfCookies, r.cookies.get_dict())
class LWPCookieJarTest(TestBaseMixin, unittest.TestCase):
"""Check store/load of cookies to FileCookieJar's, specifically LWPCookieJar's."""
@@ -254,6 +256,7 @@ class LWPCookieJarTest(TestBaseMixin, unittest.TestCase):
self.assertEqual(len(cookiejar_2), 1)
self.assertCookieHas(list(cookiejar_2)[0], name='Persistent', value='CookiesAreScary')
class MozCookieJarTest(LWPCookieJarTest):
"""Same test, but substitute MozillaCookieJar."""
+90 -3
View File
@@ -7,7 +7,6 @@
import sys
import os
sys.path.insert(0, os.path.abspath('..'))
import json
import os
import unittest
@@ -20,6 +19,7 @@ from requests.compat import str, StringIO
from requests import HTTPError
from requests import get, post, head, put
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
from requests.exceptions import InvalidURL
if 'HTTPBIN_URL' not in os.environ:
os.environ['HTTPBIN_URL'] = 'http://httpbin.org/'
@@ -856,6 +856,19 @@ class RequestsTestSuite(TestSetup, TestBaseMixin, unittest.TestCase):
assert ds1.prefetch
assert not ds2.prefetch
def test_connection_error(self):
try:
get('http://localhost:1/nope')
except requests.ConnectionError:
pass
else:
assert False
def test_connection_error_with_safe_mode(self):
config = {'safe_mode': True}
r = get('http://localhost:1/nope', allow_redirects=False, config=config)
assert r.content == None
# def test_invalid_content(self):
# # WARNING: if you're using a terrible DNS provider (comcast),
# # this will fail.
@@ -1019,10 +1032,10 @@ class RequestsTestSuite(TestSetup, TestBaseMixin, unittest.TestCase):
list for a value in the data argument."""
data = {'field': ['a', 'b']}
files = {'file': 'Garbled data'}
files = {'field': 'Garbled data'}
r = post(httpbin('post'), data=data, files=files)
t = json.loads(r.text)
self.assertEqual(t.get('form'), {'field': 'a, b'})
self.assertEqual(t.get('form'), {'field': ['a', 'b']})
self.assertEqual(t.get('files'), files)
r = post(httpbin('post'), data=data, files=files.items())
t = r.json
@@ -1035,6 +1048,80 @@ class RequestsTestSuite(TestSetup, TestBaseMixin, unittest.TestCase):
t = json.loads(r.text)
self.assertEqual(t.get('headers').get('Content-Type'), '')
def test_prefetch_redirect_bug(self):
"""Test that prefetch persists across redirections."""
res = get(httpbin('redirect/2'), prefetch=False)
# prefetch should persist across the redirect; if it doesn't,
# this attempt to iterate will crash because the content has already
# been read.
first_line = next(res.iter_lines())
self.assertTrue(first_line.strip().decode('utf-8').startswith('{'))
def test_prefetch_return_response_interaction(self):
"""Test that prefetch can be overridden as a kwarg to `send`."""
req = requests.get(httpbin('get'), return_response=False)
req.send(prefetch=False)
# content should not have been prefetched, and iter_lines should succeed
first_line = next(req.response.iter_lines())
self.assertTrue(first_line.strip().decode('utf-8').startswith('{'))
def test_accept_objects_with_string_representations_as_urls(self):
"""Test that URLs can be set to objects with string representations,
e.g. for use with furl."""
class URL():
def __unicode__(self):
# Can't have unicode literals in Python3, so avoid them.
# TODO: fixup when moving to Python 3.3
if (sys.version_info[0] == 2):
return 'http://httpbin.org/get'.decode('utf-8')
else:
return 'http://httpbin.org/get'
def __str__(self):
return 'http://httpbin.org/get'
r = get(URL())
self.assertEqual(r.status_code, 200)
def test_post_fields_with_multiple_values_and_files_as_tuples(self):
"""Test that it is possible to POST multiple data and file fields
with the same name.
https://github.com/kennethreitz/requests/pull/746
"""
fields = [
('__field__', '__value__'),
('__field__', '__value__'),
]
r = post(httpbin('post'), data=fields, files=fields)
t = json.loads(r.text)
self.assertEqual(t.get('form'), {
'__field__': [
'__value__',
'__value__',
]
})
# It's not currently possible to test for multiple file fields with
# the same name against httpbin so we need to inspect the encoded
# body manually.
request = r.request
body, content_type = request._encode_files(request.files)
file_field = (b'Content-Disposition: form-data;'
b' name="__field__"; filename="__field__"')
self.assertEqual(body.count(b'__value__'), 4)
self.assertEqual(body.count(file_field), 2)
def test_bytes_files(self):
"""Test that `bytes` can be used as the values of `files`."""
post(httpbin('post'), files={'test': b'test'})
def test_invalid_urls_throw_requests_exception(self):
"""Test that URLs with invalid labels throw
Requests.exceptions.InvalidURL instead of UnicodeError."""
self.assertRaises(InvalidURL, get, 'http://.google.com/')
if __name__ == '__main__':
unittest.main()
+3 -11
View File
@@ -25,17 +25,14 @@ class RequestsTestSuite(unittest.TestCase):
def test_addition(self):
assert (1 + 1) == 2
def test_ssl_hostname_ok(self):
requests.get('https://github.com', verify=True)
def test_ssl_hostname_not_ok(self):
requests.get('https://kennethreitz.com', verify=False)
self.assertRaises(requests.exceptions.SSLError, requests.get, 'https://kennethreitz.com')
def test_ssl_hostname_session_not_ok(self):
s = requests.session()
@@ -44,7 +41,6 @@ class RequestsTestSuite(unittest.TestCase):
s.get('https://kennethreitz.com', verify=False)
def test_binary_post(self):
'''We need to be careful how we build the utf-8 string since
unicode literals are a syntax error in python3
@@ -59,13 +55,10 @@ class RequestsTestSuite(unittest.TestCase):
raise EnvironmentError('Flesh out this test for your environment.')
requests.post('http://www.google.com/', data=utf8_string)
def test_unicode_error(self):
url = 'http://blip.fm/~1abvfu'
requests.get(url)
def test_chunked_head_redirect(self):
url = "http://t.co/NFrx0zLG"
r = requests.head(url, allow_redirects=True)
@@ -110,7 +103,7 @@ class RequestsTestSuite(unittest.TestCase):
s = requests.session()
s.get(url='http://tinyurl.com/preview.php?disable=1')
# we should have set a cookie for tinyurl: preview=0
self.assertIn('preview', s.cookies)
self.assertTrue('preview' in s.cookies)
self.assertEqual(s.cookies['preview'], '0')
self.assertEqual(list(s.cookies)[0].name, 'preview')
self.assertEqual(list(s.cookies)[0].domain, 'tinyurl.com')
@@ -118,14 +111,13 @@ class RequestsTestSuite(unittest.TestCase):
# get cookies on another domain
r2 = s.get(url='http://httpbin.org/cookies')
# the cookie is not there
self.assertNotIn('preview', json.loads(r2.text)['cookies'])
self.assertTrue('preview' not in json.loads(r2.text)['cookies'])
# this redirects to another domain, httpbin.org
# cookies of the first domain should NOT be sent to the next one
r3 = s.get(url='http://tinyurl.com/7zp3jnr')
assert r3.url == 'http://httpbin.org/cookies'
self.assertNotIn('preview', json.loads(r2.text)['cookies'])
self.assertTrue('preview' not in json.loads(r2.text)['cookies'])
if __name__ == '__main__':
unittest.main()
+1
View File
@@ -9,6 +9,7 @@ import unittest
sys.path.insert(0, os.path.abspath('..'))
import requests
class HTTPSTest(unittest.TestCase):
"""Smoke test for https functionality."""