Compare commits

..

21 Commits

Author SHA1 Message Date
Kenneth Reitz
00ab8fbfea Merge remote-tracking branch 'origin/master' 2013-05-21 17:43:53 -04:00
Kenneth Reitz
9930c9c737 v1.2.2 2013-05-21 17:43:13 -04:00
Kenneth Reitz
2ee814d924 Merge pull request #1375 from t-8ch/fix_digest_auth
don't replace 'Digest' in digest header value
2013-05-21 14:33:25 -07:00
Kenneth Reitz
8b55a253b1 Merge remote-tracking branch 'origin/master' 2013-05-21 17:27:15 -04:00
Kenneth Reitz
786fe94ac4 nope 2013-05-21 17:27:06 -04:00
Kenneth Reitz
fede8e5af3 Merge pull request #1376 from papaeye/master
Fix typo, %t -> %r
2013-05-21 14:25:49 -07:00
papaeye
2a34335dc3 Fix #1374 2013-05-22 04:30:19 +09:00
Thomas Weißschuh
3b0d8b8e39 don't replace 'Digest' in digest header value
See https://github.com/kennethreitz/requests/issues/1358
2013-05-21 17:48:30 +00:00
papaeye
715a57dec8 Fix typo, %t -> %r 2013-05-22 02:20:51 +09:00
Kenneth Reitz
2aabb71dc8 Merge pull request #1373 from ib-lundgren/unicode_fields_to_urllib
Only pass unicode multipart fieldnames to urllib3.
2013-05-21 06:19:48 -07:00
Ib Lundgren
003c795afe Only pass unicode fieldnames to urllib3. 2013-05-21 09:46:28 +01:00
Kenneth Reitz
1621015e00 Merge pull request #1343 from gazpachoking/#1321
Refactor merge_kwargs
2013-05-20 19:14:54 -07:00
Chase Sterling
98114245c6 Refactor merge_kwargs for clarity and to fix a few bugs 2013-05-20 21:20:51 -04:00
Kenneth Reitz
8eb4243f12 Merge remote-tracking branch 'origin/master' 2013-05-20 16:10:43 -04:00
Kenneth Reitz
8f42369aa9 Merge pull request #1356 from Zoramite/ConnectionPoolArgs
Adding an argument to the adapter for passing a block argument
2013-05-20 13:05:02 -07:00
Kenneth Reitz
f4e7809e7c Merge pull request #1361 from Lukasa/1360
Always percent-encode location headers.
2013-05-20 13:04:08 -07:00
Cory Benfield
2b6ebd2521 Always percent-encode location headers. 2013-05-16 12:02:46 +01:00
Randy Merrill
c03e14242b Adding the _pool_bloc to the list of attrs. 2013-05-11 18:30:31 -07:00
Randy Merrill
9cb3d6444d Fixing the call to init_poolmanagers to correctly unpickle the adapter. 2013-05-11 18:13:08 -07:00
Randy Merrill
053613688b Moving the order of the arguments in the init to not interfere with existing usage. 2013-05-11 18:04:43 -07:00
Randy Merrill
2eb682671d Adding an argument to the adapter for passing a block argument to the connection pool.
This allows for blocking when using threading to prevent the pool from creating more connections that the max-size allows.

Specifically was seeing the following errors without the block=True:

    WARNING:requests.packages.urllib3.connectionpool:HttpConnectionPool is full, discarding connection: www.example.com
2013-05-10 23:46:54 -07:00
8 changed files with 84 additions and 59 deletions

View File

@@ -42,8 +42,8 @@ is at <http://python-requests.org>.
"""
__title__ = 'requests'
__version__ = '1.2.1'
__build__ = 0x010201
__version__ = '1.2.2'
__build__ = 0x010202
__author__ = 'Kenneth Reitz'
__license__ = 'Apache 2.0'
__copyright__ = 'Copyright 2013 Kenneth Reitz'

View File

@@ -25,6 +25,7 @@ from .cookies import extract_cookies_to_jar
from .exceptions import ConnectionError, Timeout, SSLError
from .auth import _basic_auth_str
DEFAULT_POOLBLOCK = False
DEFAULT_POOLSIZE = 10
DEFAULT_RETRIES = 0
@@ -53,6 +54,7 @@ class HTTPAdapter(BaseAdapter):
:param pool_connections: The number of urllib3 connection pools to cache.
:param pool_maxsize: The maximum number of connections to save in the pool.
:param max_retries: The maximum number of retries each connection should attempt.
:param pool_block: Whether the connection pool should block for connections.
Usage::
@@ -61,10 +63,12 @@ class HTTPAdapter(BaseAdapter):
>>> a = requests.adapters.HTTPAdapter()
>>> s.mount('http://', a)
"""
__attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize']
__attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize',
'_pool_block']
def __init__(self, pool_connections=DEFAULT_POOLSIZE,
pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES):
pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES,
pool_block=DEFAULT_POOLBLOCK):
self.max_retries = max_retries
self.config = {}
@@ -72,8 +76,9 @@ class HTTPAdapter(BaseAdapter):
self._pool_connections = pool_connections
self._pool_maxsize = pool_maxsize
self._pool_block = pool_block
self.init_poolmanager(pool_connections, pool_maxsize)
self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)
def __getstate__(self):
return dict((attr, getattr(self, attr, None)) for attr in
@@ -83,21 +88,25 @@ class HTTPAdapter(BaseAdapter):
for attr, value in state.items():
setattr(self, attr, value)
self.init_poolmanager(self._pool_connections, self._pool_maxsize)
self.init_poolmanager(self._pool_connections, self._pool_maxsize,
block=self._pool_block)
def init_poolmanager(self, connections, maxsize):
def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK):
"""Initializes a urllib3 PoolManager. This method should not be called
from user code, and is only exposed for use when subclassing the
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
:param connections: The number of urllib3 connection pools to cache.
:param maxsize: The maximum number of connections to save in the pool.
:param block: Block when no free connections are available.
"""
# save these values for pickling
self._pool_connections = connections
self._pool_maxsize = maxsize
self._pool_block = block
self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize)
self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize,
block=block)
def cert_verify(self, conn, url, verify, cert):
"""Verify a SSL certificate. This method should not be called from user

View File

@@ -153,7 +153,7 @@ class HTTPDigestAuth(AuthBase):
setattr(self, 'num_401_calls', num_401_calls + 1)
pat = re.compile(r'digest ', flags=re.IGNORECASE)
self.chal = parse_dict_header(pat.sub('', s_auth))
self.chal = parse_dict_header(pat.sub('', s_auth, count=1))
# Consume content and release the original connection
# to allow our new request to reuse the same one.

View File

@@ -105,7 +105,7 @@ class RequestEncodingMixin(object):
for v in val:
if v is not None:
new_fields.append(
(field.encode('utf-8') if isinstance(field, str) else field,
(field.decode('utf-8') if isinstance(field, bytes) else field,
v.encode('utf-8') if isinstance(v, str) else v))
for (k, v) in files:
@@ -291,7 +291,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
raise MissingSchema("Invalid URL %r: No schema supplied" % url)
if not host:
raise InvalidURL("Invalid URL %t: No host supplied" % url)
raise InvalidURL("Invalid URL %r: No host supplied" % url)
# Only want to apply IDNA to the hostname
try:

View File

@@ -9,14 +9,16 @@ requests (cookies, auth, proxies).
"""
import os
from collections import Mapping
from datetime import datetime
from .compat import cookielib, OrderedDict, urljoin, urlparse
from .cookies import cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar
from .models import Request, PreparedRequest
from .hooks import default_hooks, dispatch_hook
from .utils import from_key_val_list, default_headers
from .utils import to_key_val_list, default_headers
from .exceptions import TooManyRedirects, InvalidSchema
from .structures import CaseInsensitiveDict
from .adapters import HTTPAdapter
@@ -32,49 +34,35 @@ REDIRECT_STATI = (
DEFAULT_REDIRECT_LIMIT = 30
def merge_kwargs(local_kwarg, default_kwarg):
"""Merges kwarg dictionaries.
If a local key in the dictionary is set to None, it will be removed.
def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
"""
Determines appropriate setting for a given request, taking into account the
explicit setting on that request, and the setting in the session. If a
setting is a dictionary, they will be merged together using `dict_class`
"""
if default_kwarg is None:
return local_kwarg
if session_setting is None:
return request_setting
if isinstance(local_kwarg, str):
return local_kwarg
if request_setting is None:
return session_setting
if local_kwarg is None:
return default_kwarg
# Bypass if not a dictionary (e.g. verify)
if not (
isinstance(session_setting, Mapping) and
isinstance(request_setting, Mapping)
):
return request_setting
# Bypass if not a dictionary (e.g. timeout)
if not hasattr(default_kwarg, 'items'):
return local_kwarg
default_kwarg = from_key_val_list(default_kwarg)
local_kwarg = from_key_val_list(local_kwarg)
# Update new values in a case-insensitive way
def get_original_key(original_keys, new_key):
"""
Finds the key from original_keys that case-insensitive matches new_key.
"""
for original_key in original_keys:
if key.lower() == original_key.lower():
return original_key
return new_key
kwargs = default_kwarg.copy()
original_keys = kwargs.keys()
for key, value in local_kwarg.items():
kwargs[get_original_key(original_keys, key)] = value
merged_setting = dict_class(to_key_val_list(session_setting))
merged_setting.update(to_key_val_list(request_setting))
# Remove keys that are set to None.
for (k, v) in local_kwarg.items():
for (k, v) in request_setting.items():
if v is None:
del kwargs[k]
del merged_setting[k]
return kwargs
return merged_setting
class SessionRedirectMixin(object):
@@ -111,9 +99,11 @@ class SessionRedirectMixin(object):
# Facilitate non-RFC2616-compliant 'location' headers
# (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
# Compliant with RFC3986, we percent encode the url.
if not urlparse(url).netloc:
# Compliant with RFC3986, we percent encode the url.
url = urljoin(resp.url, requote_uri(url))
else:
url = requote_uri(url)
prepared_request.url = url
@@ -309,14 +299,14 @@ class Session(SessionRedirectMixin):
verify = os.environ.get('CURL_CA_BUNDLE')
# Merge all the kwargs.
params = merge_kwargs(params, self.params)
headers = merge_kwargs(headers, self.headers)
auth = merge_kwargs(auth, self.auth)
proxies = merge_kwargs(proxies, self.proxies)
hooks = merge_kwargs(hooks, self.hooks)
stream = merge_kwargs(stream, self.stream)
verify = merge_kwargs(verify, self.verify)
cert = merge_kwargs(cert, self.cert)
params = merge_setting(params, self.params)
headers = merge_setting(headers, self.headers, dict_class=CaseInsensitiveDict)
auth = merge_setting(auth, self.auth)
proxies = merge_setting(proxies, self.proxies)
hooks = merge_setting(hooks, self.hooks)
stream = merge_setting(stream, self.stream)
verify = merge_setting(verify, self.verify)
cert = merge_setting(cert, self.cert)
# Create the Request.
req = Request()

View File

@@ -11,11 +11,11 @@ that are also useful for external consumption.
import cgi
import codecs
import collections
import os
import platform
import re
import sys
import zlib
from netrc import netrc, NetrcParseError
from . import __version__
@@ -135,7 +135,7 @@ def to_key_val_list(value):
if isinstance(value, (str, bytes, bool, int)):
raise ValueError('cannot encode objects that are not 2-tuples')
if isinstance(value, dict):
if isinstance(value, collections.Mapping):
value = value.items()
return list(value)

View File

@@ -40,7 +40,6 @@ setup(
package_dir={'requests': 'requests'},
include_package_data=True,
install_requires=requires,
setup_requires=['sphinx'],
license=open('LICENSE').read(),
zip_safe=False,
classifiers=(

29
test_requests.py Normal file → Executable file
View File

@@ -14,6 +14,7 @@ from requests.auth import HTTPDigestAuth
from requests.adapters import HTTPAdapter
from requests.compat import str, cookielib
from requests.cookies import cookiejar_from_dict
from requests.exceptions import InvalidURL, MissingSchema
from requests.structures import CaseInsensitiveDict
try:
@@ -53,7 +54,8 @@ class RequestsTestCase(unittest.TestCase):
requests.post
def test_invalid_url(self):
self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
self.assertRaises(MissingSchema, requests.get, 'hiwpefhipowhefopw')
self.assertRaises(InvalidURL, requests.get, 'http://')
def test_basic_building(self):
req = requests.Request()
@@ -342,6 +344,17 @@ class RequestsTestCase(unittest.TestCase):
files={'file': ('test_requests.py', open(__file__, 'rb'))})
self.assertEqual(r.status_code, 200)
def test_unicode_multipart_post_fieldnames(self):
filename = os.path.splitext(__file__)[0] + '.py'
r = requests.Request(method='POST',
url=httpbin('post'),
data={'stuff'.encode('utf-8'): 'elixr'},
files={'file': ('test_requests.py',
open(filename, 'rb'))})
prep = r.prepare()
self.assertTrue(b'name="stuff"' in prep.body)
self.assertFalse(b'name="b\'stuff\'"' in prep.body)
def test_custom_content_type(self):
r = requests.post(httpbin('post'),
data={'stuff': json.dumps({'a': 123})},
@@ -521,6 +534,20 @@ class RequestsTestCase(unittest.TestCase):
self.assertTrue('http://' in s2.adapters)
self.assertTrue('https://' in s2.adapters)
def test_header_remove_is_case_insensitive(self):
# From issue #1321
s = requests.Session()
s.headers['foo'] = 'bar'
r = s.get(httpbin('get'), headers={'FOO': None})
assert 'foo' not in r.request.headers
def test_params_are_merged_case_sensitive(self):
s = requests.Session()
s.params['foo'] = 'bar'
r = s.get(httpbin('get'), params={'FOO': 'bar'})
assert r.json()['args'] == {'foo': 'bar', 'FOO': 'bar'}
def test_long_authinfo_in_url(self):
url = 'http://{0}:{1}@{2}:9000/path?query#frag'.format(
'E8A3BE87-9E3F-4620-8858-95478E385B5B',