diff --git a/HISTORY.rst b/HISTORY.rst index 2bf6e11d..0db8550f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,10 +1,19 @@ History ------- +0.7.0 (2011-10-22) +++++++++++++++++++ + +* Sessions are now the primary interface. +* Deprecated InvalidMethodException. +* PATCH fix. +* New config system (no more global settings). + + 0.6.6 (2011-10-19) ++++++++++++++++++ -* Session parameter bugfix (params merging) +* Session parameter bugfix (params merging). 0.6.5 (2011-10-18) @@ -18,10 +27,10 @@ History ++++++++++++++++++ * Automatic decoding of unicode, based on HTTP Headers. -* New ``decode_unicode`` setting -* Removal of ``r.read/close`` methods +* New ``decode_unicode`` setting. +* Removal of ``r.read/close`` methods. * New ``r.faw`` interface for advanced response usage.* -* Automatic expansion of parameterized headers +* Automatic expansion of parameterized headers. 0.6.3 (2011-10-13) @@ -33,7 +42,7 @@ History 0.6.2 (2011-10-09) ++++++++++++++++++ -* GET/HEAD obeys allow_redirects=False +* GET/HEAD obeys allow_redirects=False. 0.6.1 (2011-08-20) diff --git a/Makefile b/Makefile index 8b49b27e..9500cb76 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ init: pip install -r reqs.txt test: - nosetests test_requests.py --with-color + nosetests test_requests.py ci: init nosetests test_requests.py --with-xunit --xunit-file=junit-report.xml diff --git a/docs/community/out-there.rst b/docs/community/out-there.rst index 2e7b4715..b1584423 100644 --- a/docs/community/out-there.rst +++ b/docs/community/out-there.rst @@ -6,7 +6,7 @@ Modules - `robotframework-requests `_, a Robot Framework API wrapper. - `fullerene `_, a Graphite Dashboard. - `urbanairship-python `_, a fork of the Urban Airship API wrapper. - +- `WhitespaceBot `_, a project that automatically forks repos, strips trailing whitespace, and sends a pull request. Articles & Talks ================ diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index cc6dcb27..5c17898e 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -17,13 +17,13 @@ A session object has all the methods of the main Requests API. Let's persist some cookies across requests:: - with requests.session() as s: + s = requests.session() - s.get('http://httpbin.org/cookies/set/sessioncookie/123456789') - r = s.get("http://httpbin.org/cookies") + s.get('http://httpbin.org/cookies/set/sessioncookie/123456789') + r = s.get("http://httpbin.org/cookies") - print r.content - # '{"cookies": {"sessioncookie": "123456789"}}' + print r.content + # '{"cookies": {"sessioncookie": "123456789"}}' Sessions can also be used to provide default data to the request methods:: @@ -39,8 +39,8 @@ Sessions can also be used to provide default data to the request methods:: .. admonition:: Global Settings - Certain parameters are best set at the ``request.config`` level - (e.g.. a global proxy, user agent header). + Certain parameters are best set in the ``config`` dictionary + (e.g. user agent header). Asynchronous Requests diff --git a/docs/user/install.rst b/docs/user/install.rst index aef598dc..9100ac21 100644 --- a/docs/user/install.rst +++ b/docs/user/install.rst @@ -18,7 +18,7 @@ or, with `easy_install `_:: $ easy_install requests -But, you really `shouldn't do that `_. +But, you really `shouldn't do that `_. diff --git a/requests/__init__.py b/requests/__init__.py index 15a50506..fdaaf068 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -1,4 +1,33 @@ # -*- coding: utf-8 -*- -from core import * -from core import __version__ +# __ +# /__) _ _ _ _ _/ _ +# / ( (- (/ (/ (- _) / _) +# / + +""" +requests +~~~~~~~~ + +:copyright: (c) 2011 by Kenneth Reitz. +:license: ISC, see LICENSE for more details. + +""" + +__title__ = 'requests' +__version__ = '0.7.0' +__build__ = 0x000700 +__author__ = 'Kenneth Reitz' +__license__ = 'ISC' +__copyright__ = 'Copyright 2011 Kenneth Reitz' + + +from . import utils +from .models import HTTPError, Request, Response +from .api import request, get, head, post, patch, put, delete +from .sessions import session +from .status_codes import codes +from .exceptions import ( + RequestException, AuthenticationError, Timeout, URLRequired, + TooManyRedirects +) diff --git a/requests/api.py b/requests/api.py index fa1b9b7f..c0556444 100644 --- a/requests/api.py +++ b/requests/api.py @@ -11,96 +11,41 @@ This module implements the Requests API. """ -import config -from .models import Request, Response, AuthObject -from .status_codes import codes -from .hooks import dispatch_hook -from .utils import cookiejar_from_dict, header_expand - +from .sessions import session __all__ = ('request', 'get', 'head', 'post', 'patch', 'put', 'delete') + def request(method, url, - params=None, data=None, headers=None, cookies=None, files=None, auth=None, - timeout=None, allow_redirects=False, proxies=None, hooks=None, return_response=True): + params=None, + data=None, + headers=None, + cookies=None, + files=None, + auth=None, + timeout=None, + allow_redirects=False, + proxies=None, + hooks=None, + return_response=True, + config=None): - """Constructs and sends a :class:`Request `. - Returns :class:`Response ` object. - - :param method: method for the new :class:`Request` object. - :param url: URL for the new :class:`Request` object. - :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. - :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. - :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. - :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. - :param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload. - :param auth: (optional) AuthObject to enable Basic HTTP Auth. - :param timeout: (optional) Float describing the timeout of the request. - :param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. - :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. - :param return_response: (optional) If False, an un-sent Request object will returned. - """ - - method = str(method).upper() - - if cookies is None: - cookies = {} - - cookies = cookiejar_from_dict(cookies) - - # Expand header values - if headers: - for k, v in headers.items() or {}: - headers[k] = header_expand(v) - - args = dict( - method = method, - url = url, - data = data, - params = params, - headers = headers, - cookiejar = cookies, - files = files, - auth = auth, - hooks = hooks, - timeout = timeout or config.settings.timeout, - allow_redirects = allow_redirects, - proxies = proxies or config.settings.proxies, + s = session() + return s.request( + method, url, params, data, headers, cookies, files, auth, + timeout, allow_redirects, proxies, hooks, return_response, + config ) - # Arguments manipulation hook. - args = dispatch_hook('args', hooks, args) - - r = Request(**args) - - # Pre-request hook. - r = dispatch_hook('pre_request', hooks, r) - - # Don't send if asked nicely. - if not return_response: - return r - - # Send the HTTP Request. - r.send() - - # Post-request hook. - r = dispatch_hook('post_request', hooks, r) - - # Response manipulation hook. - r.response = dispatch_hook('response', hooks, r.response) - - return r.response 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. """ - kwargs.setdefault('allow_redirects', True) return request('GET', url, **kwargs) @@ -146,7 +91,7 @@ def patch(url, data='', **kwargs): :param **kwargs: Optional arguments that ``request`` takes. """ - return request('patch', url, **kwargs) + return request('patch', url, data='', **kwargs) def delete(url, **kwargs): diff --git a/requests/config.py b/requests/config.py deleted file mode 100644 index 794109c5..00000000 --- a/requests/config.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests.config -~~~~~~~~~~~~~~~ - -This module provides the Requests settings feature set. - -""" - -class Settings(object): - _singleton = {} - - # attributes with defaults - __attrs__ = [] - - def __init__(self, **kwargs): - super(Settings, self).__init__() - - self.__dict__ = self._singleton - - - def __call__(self, *args, **kwargs): - # new instance of class to call - r = self.__class__() - - # cache previous settings for __exit__ - r.__cache = self.__dict__.copy() - map(self.__cache.setdefault, self.__attrs__) - - # set new settings - self.__dict__.update(*args, **kwargs) - - return r - - - def __enter__(self): - pass - - - def __exit__(self, *args): - - # restore cached copy - self.__dict__.update(self.__cache.copy()) - del self.__cache - - - def __getattribute__(self, key): - if key in object.__getattribute__(self, '__attrs__'): - try: - return object.__getattribute__(self, key) - except AttributeError: - return None - return object.__getattribute__(self, key) - - -settings = Settings() - -settings.base_headers = {'User-Agent': 'python-requests.org'} -settings.accept_gzip = True -settings.proxies = None -settings.verbose = None -settings.timeout = None -settings.max_redirects = 30 -settings.decode_unicode = True - -#: Use socket.setdefaulttimeout() as fallback? -settings.timeout_fallback = True diff --git a/requests/core.py b/requests/core.py deleted file mode 100644 index 79449801..00000000 --- a/requests/core.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests.core -~~~~~~~~~~~~~ - -This module implements the main Requests system. - -:copyright: (c) 2011 by Kenneth Reitz. -:license: ISC, see LICENSE for more details. - -""" - -__title__ = 'requests' -__version__ = '0.6.6' -__build__ = 0x000606 -__author__ = 'Kenneth Reitz' -__license__ = 'ISC' -__copyright__ = 'Copyright 2011 Kenneth Reitz' - - -from models import HTTPError, Request, Response -from api import * -from exceptions import * -from sessions import session -from status_codes import codes -from config import settings - -import utils diff --git a/requests/defaults.py b/requests/defaults.py new file mode 100644 index 00000000..a5699765 --- /dev/null +++ b/requests/defaults.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +""" +requests.defaults +~~~~~~~~~~~~~~~~~ + +This module provides the Requests configuration defaults. + +settings parameters: + +- :base_headers: - Sets default User-Agent to `python-requests.org` +- :accept_gzip: - Whether or not to accept gzip-compressed data +- :proxies: - http proxies? +- :verbose: - display verbose information? +- :timeout: - timeout time until request terminates +- :max_redirects: - maximum number of allowed redirects? +- :decode_unicode: - whether or not to accept unicode? + +""" + +from . import __version__ + +defaults = dict() + + +defaults['base_headers'] = { + 'User-Agent': 'python-requests/%s' % __version__, + 'Accept-Encoding': ', '.join([ 'identity', 'deflate', 'compress', 'gzip' ]), +} + +defaults['proxies'] = {} +defaults['verbose'] = None +defaults['timeout'] = None +defaults['max_redirects'] = 30 +defaults['decode_unicode'] = True +defaults['timeout_fallback'] = True +# defaults['keep_alive'] = True +# defaults['max_connections'] = 10 \ No newline at end of file diff --git a/requests/exceptions.py b/requests/exceptions.py index 13305e50..101d1ce6 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -2,7 +2,9 @@ """ requests.exceptions -~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~ + +This module contains the set of Requests' exceptions. """ @@ -12,15 +14,12 @@ class RequestException(Exception): class AuthenticationError(RequestException): """The authentication credentials provided were invalid.""" - + class Timeout(RequestException): """The request timed out.""" class URLRequired(RequestException): """A valid URL is required to make a request.""" -class InvalidMethod(RequestException): - """An inappropriate method was attempted.""" - class TooManyRedirects(RequestException): """Too many redirects.""" diff --git a/requests/models.py b/requests/models.py index 3c952c25..a6a2c01e 100644 --- a/requests/models.py +++ b/requests/models.py @@ -1,3 +1,4 @@ + # -*- coding: utf-8 -*- """ @@ -9,22 +10,22 @@ requests.models import urllib import urllib2 import socket -import codecs import zlib - from urllib2 import HTTPError from urlparse import urlparse, urlunparse, urljoin from datetime import datetime -from .config import settings -from .monkeys import Request as _Request, HTTPBasicAuthHandler, HTTPForcedBasicAuthHandler, HTTPDigestAuthHandler, HTTPRedirectHandler from .structures import CaseInsensitiveDict from .packages.poster.encode import multipart_encode from .packages.poster.streaminghttp import register_openers, get_handlers -from .utils import dict_from_cookiejar, get_unicode_from_response, stream_decode_response_unicode, decode_gzip, stream_decode_gzip +from .utils import (dict_from_cookiejar, get_unicode_from_response, stream_decode_response_unicode, decode_gzip, stream_decode_gzip) from .status_codes import codes -from .exceptions import RequestException, AuthenticationError, Timeout, URLRequired, InvalidMethod, TooManyRedirects +from .exceptions import Timeout, URLRequired, TooManyRedirects +from .monkeys import Request as _Request +from .monkeys import ( + HTTPBasicAuthHandler, HTTPForcedBasicAuthHandler, + HTTPDigestAuthHandler, HTTPRedirectHandler) REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved) @@ -37,9 +38,20 @@ class Request(object): """ def __init__(self, - url=None, headers=dict(), files=None, method=None, data=dict(), - params=dict(), auth=None, cookiejar=None, timeout=None, redirect=False, - allow_redirects=False, proxies=None, hooks=None): + url=None, + headers=dict(), + files=None, + method=None, + data=dict(), + params=dict(), + auth=None, + cookies=None, + timeout=None, + redirect=False, + allow_redirects=False, + proxies=None, + hooks=None, + config=None): #: Float describes the timeout of the request. # (Use socket.setdefaulttimeout() as fallback) @@ -91,7 +103,10 @@ class Request(object): self.auth = auth #: CookieJar to attach to :class:`Request `. - self.cookiejar = cookiejar + self.cookies = cookies + + #: Dictionary of configurations for this request. + self.config = config #: True if Request has been sent. self.sent = False @@ -99,17 +114,12 @@ class Request(object): #: Event-handling hooks. self.hooks = hooks - # Header manipulation and defaults. - - if settings.accept_gzip: - settings.base_headers.update({'Accept-Encoding': 'gzip'}) - if headers: headers = CaseInsensitiveDict(self.headers) else: headers = CaseInsensitiveDict() - for (k, v) in settings.base_headers.items(): + for (k, v) in self.config.get('base_headers', {}).items(): if k not in headers: headers[k] = v @@ -120,20 +130,13 @@ class Request(object): return '' % (self.method) - def _checks(self): - """Deterministic checks for consistency.""" - - if not self.url: - raise URLRequired - - def _get_opener(self): """Creates appropriate opener object for urllib2.""" _handlers = [] - if self.cookiejar is not None: - _handlers.append(urllib2.HTTPCookieProcessor(self.cookiejar)) + if self.cookies is not None: + _handlers.append(urllib2.HTTPCookieProcessor(self.cookies)) if self.auth: if not isinstance(self.auth.handler, @@ -185,14 +188,15 @@ class Request(object): def build(resp): response = Response() + response.config = self.config response.status_code = getattr(resp, 'code', None) try: response.headers = CaseInsensitiveDict(getattr(resp.info(), 'dict', None)) response.raw = resp - if self.cookiejar: - response.cookies = dict_from_cookiejar(self.cookiejar) + if self.cookies: + response.cookies = dict_from_cookiejar(self.cookies) except AttributeError: @@ -219,7 +223,7 @@ class Request(object): r.raw.close() - if not len(history) < settings.max_redirects: + if not len(history) < self.config.get('max_redirects'): raise TooManyRedirects() history.append(r) @@ -243,9 +247,16 @@ class Request(object): method = self.method request = Request( - url, self.headers, self.files, method, - self.data, self.params, self.auth, self.cookiejar, - redirect=True + url=url, + headers=self.headers, + files=self.files, + method=method, + # data=self.data, + # params=self.params, + auth=self.auth, + cookies=self.cookies, + redirect=True, + config=self.config ) request.send() r = request.response @@ -310,32 +321,37 @@ class Request(object): already been sent. """ - self._checks() + # Some people... + if not self.url: + raise URLRequired # Logging - if settings.verbose: - settings.verbose.write('%s %s %s\n' % ( + if self.config.get('verbose'): + self.config.get('verbose').write('%s %s %s\n' % ( datetime.now().isoformat(), self.method, self.url )) - + # Build the URL url = self._build_url() - if self.method in ('GET', 'HEAD', 'DELETE'): - req = _Request(url, method=self.method) + + # Attach uploaded files. + if self.files: + register_openers() + + # Add form-data to the multipart. + if self.data: + self.files.update(self.data) + + data, headers = multipart_encode(self.files) + else: + data = self._enc_data + headers = {} - if self.files: - register_openers() - - if self.data: - self.files.update(self.data) - - datagen, headers = multipart_encode(self.files) - req = _Request(url, data=datagen, headers=headers, method=self.method) - - else: - req = _Request(url, data=self._enc_data, method=self.method) + # Build the Urllib2 Request. + req = _Request(url, data=data, headers=headers, method=self.method) + # Add the headers to the request. if self.headers: for k,v in self.headers.iteritems(): req.add_header(k, v) @@ -353,19 +369,19 @@ class Request(object): if not 'timeout' in str(err): raise - if settings.timeout_fallback: + if self.config.get('timeout_fallback'): # fall-back and use global socket timeout (This is not thread-safe!) old_timeout = socket.getdefaulttimeout() socket.setdefaulttimeout(self.timeout) resp = opener(req) - if settings.timeout_fallback: + if self.config.get('timeout_fallback'): # restore global timeout socket.setdefaulttimeout(old_timeout) - if self.cookiejar is not None: - self.cookiejar.extract_cookies(resp, req) + if self.cookies is not None: + self.cookies.extract_cookies(resp, req) except (urllib2.HTTPError, urllib2.URLError), why: if hasattr(why, 'reason'): @@ -429,6 +445,9 @@ class Response(object): #: A dictionary of Cookies the server sent back. self.cookies = None + #: Dictionary of configurations for this request. + self.config = None + def __repr__(self): return '' % (self.status_code) @@ -460,7 +479,7 @@ class Response(object): if 'gzip' in self.headers.get('content-encoding', ''): gen = stream_decode_gzip(gen) if decode_unicode is None: - decode_unicode = settings.decode_unicode + decode_unicode = self.config.get('decode_unicode') if decode_unicode: gen = stream_decode_response_unicode(gen, self) return gen @@ -489,7 +508,7 @@ class Response(object): pass # Decode unicode content. - if settings.decode_unicode: + if self.config.get('decode_unicode'): self._content = get_unicode_from_response(self) self._content_consumed = True diff --git a/requests/sessions.py b/requests/sessions.py index 64ef7b2f..a88a0626 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -11,9 +11,10 @@ requests (cookies, auth, proxies). import cookielib -from . import api -from .utils import add_dict_to_cookiejar - +from .defaults import defaults +from .models import Request +from .hooks import dispatch_hook +from .utils import add_dict_to_cookiejar, cookiejar_from_dict, header_expand def merge_kwargs(local_kwarg, default_kwarg): @@ -25,6 +26,9 @@ def merge_kwargs(local_kwarg, default_kwarg): if default_kwarg is None: return local_kwarg + if isinstance(local_kwarg, basestring): + return local_kwarg + if local_kwarg is None: return default_kwarg @@ -32,8 +36,6 @@ def merge_kwargs(local_kwarg, default_kwarg): if not hasattr(default_kwarg, 'items'): return local_kwarg - - # Update new values. kwargs = default_kwarg.copy() kwargs.update(local_kwarg) @@ -49,7 +51,7 @@ def merge_kwargs(local_kwarg, default_kwarg): class Session(object): """A Requests session.""" - __attrs__ = ['headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks', 'params'] + __attrs__ = ['headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks', 'params', 'config'] def __init__(self, @@ -59,7 +61,8 @@ class Session(object): timeout=None, proxies=None, hooks=None, - params=None): + params=None, + config=None): self.headers = headers or {} self.cookies = cookies or {} @@ -68,14 +71,14 @@ class Session(object): self.proxies = proxies or {} self.hooks = hooks or {} self.params = params or {} + self.config = config or {} + + for (k, v) in defaults.items(): + self.config.setdefault(k, v) # Set up a CookieJar to be used by default self.cookies = cookielib.FileCookieJar() - # Map and wrap requests.api methods - self._map_api_methods() - - def __repr__(self): return '' % (id(self)) @@ -85,48 +88,163 @@ class Session(object): def __exit__(self, *args): pass - def _map_api_methods(self): - """Reads each available method from requests.api and decorates - them with a wrapper, which inserts any instance-local attributes - (from __attrs__) that have been set, combining them with **kwargs. + def request(self, method, url, + params=None, + data=None, + headers=None, + cookies=None, + files=None, + auth=None, + timeout=None, + allow_redirects=False, + proxies=None, + hooks=None, + return_response=True, + config=None): + + """Constructs and sends a :class:`Request `. + Returns :class:`Response ` object. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. + :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. + :param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload. + :param auth: (optional) AuthObject to enable Basic HTTP Auth. + :param timeout: (optional) Float describing the timeout of the request. + :param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + :param return_response: (optional) If False, an un-sent Request object will returned. + :param config: (optional) A configuration dictionary. """ - def pass_args(func): - def wrapper_func(*args, **kwargs): + method = str(method).upper() - # Argument collector. - _kwargs = {} + if cookies is None: + cookies = {} - # If a session request has a cookie_dict, inject the - # values into the existing CookieJar instead. - if isinstance(kwargs.get('cookies', None), dict): - kwargs['cookies'] = add_dict_to_cookiejar( - self.cookies, kwargs['cookies'] - ) + if isinstance(cookies, dict): + cookies = add_dict_to_cookiejar(self.cookies, cookies) - for attr in self.__attrs__: - # for attr in ['headers',]: - s_val = self.__dict__.get(attr) - r_val = kwargs.get(attr) + cookies = cookiejar_from_dict(cookies) - new_attr = merge_kwargs(r_val, s_val) + # Expand header values + if headers: + for k, v in headers.items() or {}: + headers[k] = header_expand(v) - # Skip attributes that were set to None. - if new_attr is not None: - _kwargs[attr] = new_attr + args = dict( + method=method, + url=url, + data=data, + params=params, + headers=headers, + cookies=cookies, + files=files, + auth=auth, + hooks=hooks, + timeout=timeout, + allow_redirects=allow_redirects, + proxies=proxies, + config=config + ) - # Make sure we didn't miss anything. - for (k, v) in kwargs.items(): - if k not in _kwargs: - _kwargs[k] = v + for attr in self.__attrs__: + session_val = getattr(self, attr, None) + local_val = args.get(attr) - return func(*args, **_kwargs) + args[attr] = merge_kwargs(local_val, session_val) - return wrapper_func + # Arguments manipulation hook. + args = dispatch_hook('args', hooks, args) + + r = Request(**args) + + # Pre-request hook. + r = dispatch_hook('pre_request', hooks, r) + + # Don't send if asked nicely. + if not return_response: + return r + + # Send the HTTP Request. + r.send() + + # Post-request hook. + r = dispatch_hook('post_request', hooks, r) + + # Response manipulation hook. + r.response = dispatch_hook('response', hooks, r.response) + + return r.response + + + def get(self, 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. + """ + + kwargs.setdefault('allow_redirects', True) + return self.request('GET', url, **kwargs) + + + def head(self, 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. + """ + + kwargs.setdefault('allow_redirects', True) + return self.request('HEAD', url, **kwargs) + + + def post(self, url, data='', **kwargs): + """Sends a POST request. Returns :class:`Response` 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. + """ + + return self.request('post', url, data=data, **kwargs) + + + def put(self, url, data='', **kwargs): + """Sends a PUT request. Returns :class:`Response` 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. + """ + + return self.request('put', url, data=data, **kwargs) + + + def patch(self, url, data='', **kwargs): + """Sends a PATCH request. Returns :class:`Response` 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. + """ + + return self.request('patch', url, data='', **kwargs) + + + def delete(self, 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. + """ + + return self.request('delete', url, **kwargs) - # Map and decorate each function available in requests.api - map(lambda fn: setattr(self, fn, pass_args(getattr(api, fn))), - api.__all__) def session(**kwargs): diff --git a/requests/status_codes.py b/requests/status_codes.py index aa52b9fe..fab8e957 100644 --- a/requests/status_codes.py +++ b/requests/status_codes.py @@ -56,6 +56,9 @@ _codes = { 424: ('failed_dependency', 'dependency'), 425: ('unordered_collection', 'unordered'), 426: ('upgrade_required', 'upgrade'), + 428: ('precondition_required', 'precondition'), + 429: ('too_many_requests', 'too_many'), + 431: ('header_fields_too_large', 'fields_too_large'), 444: ('no_response', 'none'), 449: ('retry_with', 'retry'), 450: ('blocked_by_windows_parental_controls', 'parental_controls'), diff --git a/test_requests.py b/test_requests.py index a34e2338..f1211571 100755 --- a/test_requests.py +++ b/test_requests.py @@ -8,6 +8,7 @@ import cookielib import os import unittest +import requests import envoy try: @@ -15,11 +16,9 @@ try: except ImportError: import json -import requests -from requests.sessions import Session - -PORT = os.environ.get('HTTPBIN_PORT', '7045') +# TODO: Detect an open port. +PORT = os.environ.get('HTTPBIN_PORT', '7077') HTTPBIN_URL = 'http://0.0.0.0:%s/' % (PORT) # HTTPBIN_URL = 'http://127.0.0.1:8000/' @@ -47,8 +46,9 @@ class RequestsTestSuite(unittest.TestCase): if not _httpbin: - self.httpbin = envoy.connect('gunicorn httpbin:app --bind=0.0.0.0:%s' % (PORT)) + c = envoy.connect('gunicorn httpbin:app --bind=0.0.0.0:%s' % (PORT)) + self.httpbin = c _httpbin = True time.sleep(1) @@ -58,22 +58,34 @@ class RequestsTestSuite(unittest.TestCase): """Teardown.""" # self.httpbin.kill() + def test_entry_points(self): + import requests + + requests.session + requests.session().get + requests.session().head + requests.get + requests.head + requests.put + requests.patch + requests.post + + def test_invalid_url(self): self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw') - def test_HTTP_200_OK_GET(self): r = requests.get(httpbin('/get')) self.assertEqual(r.status_code, 200) def test_HTTP_302_ALLOW_REDIRECT_GET(self): - r = requests.get(httpbin('redirect', '1')) - self.assertEqual(r.status_code, 200) + r = requests.get(httpbin('redirect', '1')) + self.assertEqual(r.status_code, 200) def test_HTTP_302_GET(self): - r = requests.get(httpbin('redirect', '1'), allow_redirects=False) - self.assertEqual(r.status_code, 302) + r = requests.get(httpbin('redirect', '1'), allow_redirects=False) + self.assertEqual(r.status_code, 302) def test_HTTP_200_OK_GET_WITH_PARAMS(self): @@ -442,7 +454,7 @@ class RequestsTestSuite(unittest.TestCase): def test_session_HTTP_200_OK_GET(self): - s = Session() + s = requests.session() r = s.get(httpbin('/get')) self.assertEqual(r.status_code, 200) @@ -451,7 +463,7 @@ class RequestsTestSuite(unittest.TestCase): heads = {'User-agent': 'Mozilla/5.0'} - s = Session() + s = requests.session() s.headers = heads # Make 2 requests from Session object, should send header both times @@ -472,7 +484,7 @@ class RequestsTestSuite(unittest.TestCase): params = {'a': 'a_test'} - s = Session() + s = requests.session() s.params = params # Make 2 requests from Session object, should send header both times