Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Kenneth Reitz committed May 17, 2013
2 parents 538dbae + 5943afa commit 7430a49
Show file tree
Hide file tree
Showing 13 changed files with 338 additions and 60 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ nosetests.xml
junit-report.xml
pylint.txt
toy.py
tox.ini
violations.pyflakes.txt
cover/
build/
Expand Down
4 changes: 4 additions & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,7 @@ Patches and Suggestions
- Wilfred Hughes <[email protected]> @dontYetKnow
- Dmitry Medvinsky <[email protected]>
- Bryce Boe <[email protected]> @bboe
- Colin Dunklau <[email protected]> @cdunklau
- Hugo Osvaldo Barrera <[email protected]> @hobarrera
- Łukasz Langa <[email protected]> @llanga
- Dave Shawley <[email protected]>
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ Or, if you absolutely must:
But, you really shouldn't do that.


Documentation
-------------

Documentation is available at http://docs.python-requests.org/.


Contribute
----------
Expand Down
25 changes: 11 additions & 14 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,11 @@ Request Sessions
Exceptions
~~~~~~~~~~

.. module:: requests

.. autoexception:: RequestException
.. autoexception:: ConnectionError
.. autoexception:: HTTPError
.. autoexception:: URLRequired
.. autoexception:: TooManyRedirects
.. autoexception:: requests.exceptions.RequestException
.. autoexception:: requests.exceptions.ConnectionError
.. autoexception:: requests.exceptions.HTTPError
.. autoexception:: requests.exceptions.URLRequired
.. autoexception:: requests.exceptions.TooManyRedirects


Status Code Lookup
Expand All @@ -76,18 +74,17 @@ Status Code Lookup
Cookies
~~~~~~~

.. autofunction:: dict_from_cookiejar
.. autofunction:: cookiejar_from_dict
.. autofunction:: add_dict_to_cookiejar
.. autofunction:: requests.utils.dict_from_cookiejar
.. autofunction:: requests.utils.cookiejar_from_dict
.. autofunction:: requests.utils.add_dict_to_cookiejar


Encodings
~~~~~~~~~

.. autofunction:: get_encodings_from_content
.. autofunction:: get_encoding_from_headers
.. autofunction:: get_unicode_from_response
.. autofunction:: decode_gzip
.. autofunction:: requests.utils.get_encodings_from_content
.. autofunction:: requests.utils.get_encoding_from_headers
.. autofunction:: requests.utils.get_unicode_from_response


Classes
Expand Down
2 changes: 1 addition & 1 deletion docs/community/support.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Support
=======

If you have a questions or issues about Requests, there are several options:
If you have questions or issues about Requests, there are several options:

Send a Tweet
------------
Expand Down
2 changes: 1 addition & 1 deletion docs/user/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ OAuth 1 Authentication

A common form of authentication for several web APIs is OAuth. The ``requests-oauthlib`` library allows Requests users to easily make OAuth authenticated requests::

>>> import request
>>> import requests
>>> from requests_oauthlib import OAuth1

>>> url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
Expand Down
6 changes: 6 additions & 0 deletions requests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@
__license__ = 'Apache 2.0'
__copyright__ = 'Copyright 2013 Kenneth Reitz'

# Attempt to enable urllib3's SNI support, if possible
try:
from requests.packages.urllib3.contrib import pyopenssl
pyopenssl.inject_into_urllib3()
except ImportError:
pass

from . import utils
from .models import Request, Response, PreparedRequest
Expand Down
24 changes: 16 additions & 8 deletions requests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .auth import HTTPBasicAuth
from .cookies import cookiejar_from_dict, get_cookie_header
from .packages.urllib3.filepost import encode_multipart_formdata
from .packages.urllib3.util import parse_url
from .exceptions import HTTPError, RequestException, MissingSchema, InvalidURL
from .utils import (
guess_filename, get_auth_from_url, requote_uri,
Expand Down Expand Up @@ -284,19 +285,28 @@ def prepare_url(self, url, params):
pass

# Support for unicode domain names and paths.
scheme, netloc, path, _params, query, fragment = urlparse(url)
scheme, auth, host, port, path, query, fragment = parse_url(url)

if not scheme:
raise MissingSchema("Invalid URL %r: No schema supplied" % url)

if not netloc:
raise InvalidURL("Invalid URL %t: No netloc supplied" % url)
if not host:
raise InvalidURL("Invalid URL %t: No host supplied" % url)

# Only want to apply IDNA to the hostname
try:
netloc = netloc.encode('idna').decode('utf-8')
host = host.encode('idna').decode('utf-8')
except UnicodeError:
raise InvalidURL('URL has an invalid label.')

# Carefully reconstruct the network location
netloc = auth or ''
if netloc:
netloc += '@'
netloc += host
if port:
netloc += ':' + str(port)

# Bare domains aren't valid URLs.
if not path:
path = '/'
Expand All @@ -308,8 +318,6 @@ def prepare_url(self, url, params):
netloc = netloc.encode('utf-8')
if isinstance(path, str):
path = path.encode('utf-8')
if isinstance(_params, str):
_params = _params.encode('utf-8')
if isinstance(query, str):
query = query.encode('utf-8')
if isinstance(fragment, str):
Expand All @@ -322,7 +330,7 @@ def prepare_url(self, url, params):
else:
query = enc_params

url = requote_uri(urlunparse([scheme, netloc, path, _params, query, fragment]))
url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))
self.url = url

def prepare_headers(self, headers):
Expand Down Expand Up @@ -646,7 +654,7 @@ def json(self, **kwargs):
def links(self):
"""Returns the parsed header links of the response, if any."""

header = self.headers['link']
header = self.headers.get('link')

# l = MultiDict()
l = {}
Expand Down
14 changes: 9 additions & 5 deletions requests/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@
import os
from datetime import datetime

from .compat import cookielib
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 .exceptions import TooManyRedirects, InvalidSchema

from .compat import urlparse, urljoin
from .adapters import HTTPAdapter

from .utils import requote_uri, get_environ_proxies, get_netrc_auth
Expand Down Expand Up @@ -223,9 +222,9 @@ def __init__(self):
self.cookies = cookiejar_from_dict({})

# Default connection adapters.
self.adapters = {}
self.mount('http://', HTTPAdapter())
self.adapters = OrderedDict()
self.mount('https://', HTTPAdapter())
self.mount('http://', HTTPAdapter())

def __enter__(self):
return self
Expand Down Expand Up @@ -490,8 +489,13 @@ def close(self):
v.close()

def mount(self, prefix, adapter):
"""Registers a connection adapter to a prefix."""
"""Registers a connection adapter to a prefix.
Adapters are sorted in descending order by key length."""
self.adapters[prefix] = adapter
keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
for key in keys_to_move:
self.adapters[key] = self.adapters.pop(key)

def __getstate__(self):
return dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
Expand Down
89 changes: 63 additions & 26 deletions requests/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"""

import os
import collections
from itertools import islice


Expand All @@ -33,43 +34,79 @@ def read(self, n):
return "".join(islice(self.i, None, n))


class CaseInsensitiveDict(dict):
"""Case-insensitive Dictionary
class CaseInsensitiveDict(collections.MutableMapping):
"""
A case-insensitive ``dict``-like object.
Implements all methods and operations of
``collections.MutableMapping`` as well as dict's ``copy``. Also
provides ``lower_items``.
All keys are expected to be strings. The structure remembers the
case of the last key to be set, and ``iter(instance)``,
``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()``
will contain case-sensitive keys. However, querying and contains
testing is case insensitive:
cid = CaseInsensitiveDict()
cid['Accept'] = 'application/json'
cid['aCCEPT'] == 'application/json' # True
list(cid) == ['Accept'] # True
For example, ``headers['content-encoding']`` will return the
value of a ``'Content-Encoding'`` response header."""
value of a ``'Content-Encoding'`` response header, regardless
of how the header name was originally stored.
@property
def lower_keys(self):
if not hasattr(self, '_lower_keys') or not self._lower_keys:
self._lower_keys = dict((k.lower(), k) for k in list(self.keys()))
return self._lower_keys
If the constructor, ``.update``, or equality comparison
operations are given keys that have equal ``.lower()``s, the
behavior is undefined.
def _clear_lower_keys(self):
if hasattr(self, '_lower_keys'):
self._lower_keys.clear()
"""
def __init__(self, data=None, **kwargs):
self._store = dict()
if data is None:
data = {}
self.update(data, **kwargs)

def __setitem__(self, key, value):
dict.__setitem__(self, key, value)
self._clear_lower_keys()
# Use the lowercased key for lookups, but store the actual
# key alongside the value.
self._store[key.lower()] = (key, value)

def __delitem__(self, key):
dict.__delitem__(self, self.lower_keys.get(key.lower(), key))
self._lower_keys.clear()
def __getitem__(self, key):
return self._store[key.lower()][1]

def __contains__(self, key):
return key.lower() in self.lower_keys
def __delitem__(self, key):
del self._store[key.lower()]

def __getitem__(self, key):
# We allow fall-through here, so values default to None
if key in self:
return dict.__getitem__(self, self.lower_keys[key.lower()])
def __iter__(self):
return (casedkey for casedkey, mappedvalue in self._store.values())

def get(self, key, default=None):
if key in self:
return self[key]
def __len__(self):
return len(self._store)

def lower_items(self):
"""Like iteritems(), but with all lowercase keys."""
return (
(lowerkey, keyval[1])
for (lowerkey, keyval)
in self._store.items()
)

def __eq__(self, other):
if isinstance(other, collections.Mapping):
other = CaseInsensitiveDict(other)
else:
return default
return NotImplemented
# Compare insensitively
return dict(self.lower_items()) == dict(other.lower_items())

# Copy is required
def copy(self):
return CaseInsensitiveDict(self._store.values())

def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, dict(self.items()))


class LookupDict(dict):
Expand Down
5 changes: 3 additions & 2 deletions requests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .compat import parse_http_list as _parse_list_header
from .compat import quote, urlparse, bytes, str, OrderedDict, urlunparse
from .cookies import RequestsCookieJar, cookiejar_from_dict
from .structures import CaseInsensitiveDict

_hush_pyflakes = (RequestsCookieJar,)

Expand Down Expand Up @@ -449,11 +450,11 @@ def default_user_agent():


def default_headers():
return {
return CaseInsensitiveDict({
'User-Agent': default_user_agent(),
'Accept-Encoding': ', '.join(('gzip', 'deflate', 'compress')),
'Accept': '*/*'
}
})


def parse_header_links(value):
Expand Down
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
'requests.packages.charade',
'requests.packages.urllib3',
'requests.packages.urllib3.packages',
'requests.packages.urllib3.contrib',
'requests.packages.urllib3.packages.ssl_match_hostname'
]

Expand Down Expand Up @@ -51,9 +52,7 @@
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
# 'Programming Language :: Python :: 3.0',
'Programming Language :: Python :: 3.1',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',

),
)
Loading

0 comments on commit 7430a49

Please sign in to comment.