diff --git a/AUTHORS.rst b/AUTHORS.rst index 9cfa7db0..1db8611d 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -88,3 +88,7 @@ Patches and Suggestions - Chris Dary - Danver Braganza - Max Countryman +- Nick Chadwick +- Jonathan Drosdeck +- Jiri Machalek +- Steve Pulec diff --git a/HISTORY.rst b/HISTORY.rst index c05a71e4..99472503 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,14 @@ History ------- +0.11.0 (2012-03-14) ++++++++++++++++++++ + +* Private SSL Certificate support +* Remove select.poll from Gevent monkeypatching +* Remove redundant generator for chunked transfer encoding +* Fix: Response.ok raises Timeout Exception in safe_mode + 0.10.8 (2012-03-09) +++++++++++++++++++ diff --git a/README.rst b/README.rst index ad88b42d..7ea6ab64 100644 --- a/README.rst +++ b/README.rst @@ -73,4 +73,4 @@ Contribute #. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_. .. _`the repository`: http://github.com/kennethreitz/requests -.. _AUTHORS: http://github.com/kennethreitz/requests/blob/master/AUTHORS +.. _AUTHORS: https://github.com/kennethreitz/requests/blob/develop/AUTHORS.rst diff --git a/docs/community/out-there.rst b/docs/community/out-there.rst index 553c7444..5ef090f3 100644 --- a/docs/community/out-there.rst +++ b/docs/community/out-there.rst @@ -50,9 +50,6 @@ Requests is available installed as a Debian package! Debian Etch Ubuntu, since O $ apt-get install python-requests -Unfortunately, the most recent version available is v0.5.0. If you're on the -Debian Python Package team, I'd love an update of that :) - Fedora and RedHat ----------------- diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 3202ee67..70ec5f13 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -30,6 +30,32 @@ Let's get GitHub's public timeline :: Now, we have a :class:`Response` object called ``r``. We can get all the information we need from this. +Typically, you want to send some sort of data in the urls query string. +To do this, simply pass a dictionary to the `params` argument. Your +dictionary of data will automatically be encoded when the request is made:: + + >>> payload = {'key1': 'value1', 'key2': 'value2'} + >>> r = requests.get("http://httpbin.org/get", params=payload) + >>> print r.text + { + "origin": "179.13.100.4", + "args": { + "key2": "value2", + "key1": "value1" + }, + "url": "http://httpbin.org/get", + "headers": { + "Connections": "keep-alive", + "Content-Length": "", + "Accept-Encoding": "identity, deflate, compress, gzip", + "Accept": "*/*", + "User-Agent": "python-requests/0.11.0", + "Host": httpbin.org", + "Content-Type": "" + }, + } + + Response Content ---------------- diff --git a/requests/__init__.py b/requests/__init__.py index 73d81f67..138e26e3 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -15,8 +15,8 @@ requests """ __title__ = 'requests' -__version__ = '0.10.8' -__build__ = 0x001008 +__version__ = '0.11.0' +__build__ = 0x001100 __author__ = 'Kenneth Reitz' __license__ = 'ISC' __copyright__ = 'Copyright 2012 Kenneth Reitz' diff --git a/requests/api.py b/requests/api.py index b079eedd..e0bf346c 100644 --- a/requests/api.py +++ b/requests/api.py @@ -33,6 +33,7 @@ def request(method, url, **kwargs): :param config: (optional) A configuration dictionary. :param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided. :param prefetch: (optional) if ``True``, the response content will be immediately downloaded. + :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. """ s = kwargs.pop('session') if 'session' in kwargs else sessions.session() @@ -44,7 +45,7 @@ def get(url, **kwargs): """Sends a GET request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ kwargs.setdefault('allow_redirects', True) @@ -55,7 +56,7 @@ def options(url, **kwargs): """Sends a OPTIONS request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ kwargs.setdefault('allow_redirects', True) @@ -66,7 +67,7 @@ def head(url, **kwargs): """Sends a HEAD request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ kwargs.setdefault('allow_redirects', False) @@ -78,7 +79,7 @@ def post(url, data=None, **kwargs): :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ return request('post', url, data=data, **kwargs) @@ -89,7 +90,7 @@ def put(url, data=None, **kwargs): :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ return request('put', url, data=data, **kwargs) @@ -100,7 +101,7 @@ def patch(url, data=None, **kwargs): :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ return request('patch', url, data=data, **kwargs) @@ -110,7 +111,7 @@ def delete(url, **kwargs): """Sends a DELETE request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ return request('delete', url, **kwargs) diff --git a/requests/async.py b/requests/async.py index f2dad694..f12cf268 100644 --- a/requests/async.py +++ b/requests/async.py @@ -17,7 +17,7 @@ except ImportError: raise RuntimeError('Gevent is required for requests.async.') # Monkey-patch. -curious_george.patch_all(thread=False) +curious_george.patch_all(thread=False, select=False) from . import api diff --git a/requests/auth.py b/requests/auth.py index 2e2bebcb..385dd27d 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -11,7 +11,7 @@ import time import hashlib from base64 import b64encode -from .compat import urlparse, str, bytes +from .compat import urlparse, str from .utils import randombytes, parse_dict_header diff --git a/requests/models.py b/requests/models.py index 753e83ab..f87a9c85 100644 --- a/requests/models.py +++ b/requests/models.py @@ -63,7 +63,8 @@ class Request(object): config=None, _poolmanager=None, verify=None, - session=None): + session=None, + cert=None): #: Dictionary of configurations for this request. self.config = dict(config or []) @@ -143,6 +144,9 @@ class Request(object): #: SSL Verification. self.verify = verify + #: SSL Certificate + self.cert = cert + if headers: headers = CaseInsensitiveDict(self.headers) else: @@ -267,7 +271,8 @@ class Request(object): _poolmanager=self._poolmanager, proxies=self.proxies, verify=self.verify, - session=self.session + session=self.session, + cert=self.cert ) request.send() @@ -507,6 +512,13 @@ class Request(object): conn.cert_reqs = 'CERT_NONE' conn.ca_certs = None + if self.cert and self.verify: + if len(self.cert) == 2: + conn.cert_file = self.cert[0] + conn.key_file = self.cert[1] + else: + conn.cert_file = self.cert + if not self.sent or anyway: if self.cookies: @@ -648,7 +660,7 @@ class Response(object): def ok(self): try: self.raise_for_status() - except HTTPError: + except RequestException: return False return True @@ -671,39 +683,7 @@ class Response(object): yield chunk self._content_consumed = True - def generate_chunked(): - resp = self.raw._original_response - fp = resp.fp - if resp.chunk_left is not None: - pending_bytes = resp.chunk_left - while pending_bytes: - chunk = fp.read(min(chunk_size, pending_bytes)) - pending_bytes -= len(chunk) - yield chunk - fp.read(2) # throw away crlf - while 1: - #XXX correct line size? (httplib has 64kb, seems insane) - pending_bytes = fp.readline(40).strip() - if not len(pending_bytes): - # No content, like a HEAD request. Break out. - break - pending_bytes = int(pending_bytes, 16) - if pending_bytes == 0: - break - while pending_bytes: - chunk = fp.read(min(chunk_size, pending_bytes)) - pending_bytes -= len(chunk) - yield chunk - fp.read(2) # throw away crlf - self._content_consumed = True - fp.close() - - if getattr(getattr(self.raw, '_original_response', None), 'chunked', False): - gen = generate_chunked() - else: - gen = generate() - - gen = stream_untransfer(gen, self) + gen = stream_untransfer(generate(), self) if decode_unicode: gen = stream_decode_response_unicode(gen, self) @@ -788,6 +768,12 @@ class Response(object): # Decode unicode from given encoding. try: content = str(self.content, encoding, errors='replace') + except LookupError: + # A LookupError is raised if the encoding was not found which could + # indicate a misspelling or similar mistake. + # + # So we try blindly encoding. + content = str(self.content, errors='replace') except (UnicodeError, TypeError): pass diff --git a/requests/packages/oreos/monkeys.py b/requests/packages/oreos/monkeys.py index 72ce68d3..2cf90163 100644 --- a/requests/packages/oreos/monkeys.py +++ b/requests/packages/oreos/monkeys.py @@ -255,7 +255,7 @@ class CookieError(Exception): # _RFC2965Forbidden = "[]:{}=" _LegalChars = ( string.ascii_letters + string.digits + - "!#$%&'*+-.^_`|~_" + _RFC2965Forbidden ) + "!#$%&'*+-.^_`|~_@" + _RFC2965Forbidden ) _Translator = { '\000' : '\\000', '\001' : '\\001', '\002' : '\\002', '\003' : '\\003', '\004' : '\\004', '\005' : '\\005', diff --git a/requests/sessions.py b/requests/sessions.py index 87320d6e..0aeeef1e 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -52,7 +52,7 @@ class Session(object): __attrs__ = [ 'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks', - 'params', 'config', 'verify'] + 'params', 'config', 'verify', 'cert'] def __init__(self, @@ -65,7 +65,8 @@ class Session(object): params=None, config=None, prefetch=False, - verify=True): + verify=True, + cert=None): self.headers = headers or {} self.cookies = cookies or {} @@ -77,6 +78,7 @@ class Session(object): self.config = config or {} self.prefetch = prefetch self.verify = verify + self.cert = cert for (k, v) in list(defaults.items()): self.config.setdefault(k, v) @@ -119,7 +121,8 @@ class Session(object): return_response=True, config=None, prefetch=False, - verify=None): + verify=None, + cert=None): """Constructs and sends a :class:`Request `. Returns :class:`Response ` object. @@ -139,6 +142,7 @@ class Session(object): :param config: (optional) A configuration dictionary. :param prefetch: (optional) if ``True``, the response content will be immediately downloaded. :param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided. + :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. """ method = str(method).upper() @@ -176,6 +180,7 @@ class Session(object): proxies=proxies, config=config, verify=verify, + cert=cert, _poolmanager=self.poolmanager ) @@ -213,7 +218,7 @@ class Session(object): """Sends a GET request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ kwargs.setdefault('allow_redirects', True) @@ -224,7 +229,7 @@ class Session(object): """Sends a OPTIONS request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ kwargs.setdefault('allow_redirects', True) @@ -235,7 +240,7 @@ class Session(object): """Sends a HEAD request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ kwargs.setdefault('allow_redirects', False) @@ -247,7 +252,7 @@ class Session(object): :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ return self.request('post', url, data=data, **kwargs) @@ -258,7 +263,7 @@ class Session(object): :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ return self.request('put', url, data=data, **kwargs) @@ -269,7 +274,7 @@ class Session(object): :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ return self.request('patch', url, data=data, **kwargs) @@ -279,7 +284,7 @@ class Session(object): """Sends a DELETE request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param **kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. """ return self.request('delete', url, **kwargs) diff --git a/requests/utils.py b/requests/utils.py index 6952a996..4c94c661 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -14,7 +14,6 @@ import codecs import os import random import re -import traceback import zlib from netrc import netrc, NetrcParseError @@ -26,6 +25,15 @@ from .compat import basestring, bytes, str NETRC_FILES = ('.netrc', '_netrc') +def dict_to_sequence(d): + """Returns an internal sequence dictionary update.""" + + if hasattr(d, 'items'): + d = d.items() + + return d + + def get_netrc_auth(url): """Returns the Requests tuple auth for a given url from netrc.""" diff --git a/tests/test_requests_async.py b/tests/test_requests_async.py old mode 100644 new mode 100755 index 2d37bbb0..1d282616 --- a/tests/test_requests_async.py +++ b/tests/test_requests_async.py @@ -8,6 +8,9 @@ sys.path.insert(0, os.path.abspath('..')) import sys import unittest +import select +has_poll = hasattr(select, "poll") + from requests import async import envoy @@ -57,6 +60,9 @@ class RequestsTestSuiteUsingAsyncApi(RequestsTestSuite): async.map async.send + def test_select_poll(self): + """Test to make sure we don't overwrite the poll""" + self.assertEqual(hasattr(select, "poll"), has_poll) if __name__ == '__main__': unittest.main()