From cff70e75b77ab61a8a57a25a4921acc5a5ef6ce1 Mon Sep 17 00:00:00 2001 From: Josh Imhoff Date: Wed, 9 May 2012 14:47:29 -0400 Subject: [PATCH] New implementation of safe_mode. Now, we throw exceptions in models.py regardless of safe_mode. We catch those exceptions at the API level and return a blank Response. See safe_mode.py for details. --- AUTHORS.rst | 1 + requests/api.py | 2 ++ requests/cookies.py | 11 +++----- requests/models.py | 75 +++++++++++++++++++-------------------------------- requests/safe_mode.py | 37 +++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 55 deletions(-) create mode 100644 requests/safe_mode.py diff --git a/AUTHORS.rst b/AUTHORS.rst index b6002ec..d24cf21 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -100,3 +100,4 @@ Patches and Suggestions - Rohan Jain (crodjer) - Justin Barber - Roman Haritonov <@reclosedev> +- Josh Imhoff diff --git a/requests/api.py b/requests/api.py index e0bf346..4fc3070 100644 --- a/requests/api.py +++ b/requests/api.py @@ -12,7 +12,9 @@ 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 `. Returns :class:`Response ` object. diff --git a/requests/cookies.py b/requests/cookies.py index 0e0dd67..41d0fab 100644 --- a/requests/cookies.py +++ b/requests/cookies.py @@ -94,13 +94,10 @@ def extract_cookies_to_jar(jar, request, response): :param response: urllib3.HTTPResponse object """ # the _original_response field is the wrapped httplib.HTTPResponse object, - # and in safe mode, it may be None if the request didn't actually complete. - # in that case, just skip the cookie extraction. - if response._original_response is not None: - req = MockRequest(request) - # pull out the HTTPMessage with the headers and put it in the mock: - res = MockResponse(response._original_response.msg) - jar.extract_cookies(res, req) + req = MockRequest(request) + # pull out the HTTPMessage with the headers and put it in the mock: + 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.""" diff --git a/requests/models.py b/requests/models.py index beb8f14..d9bee4e 100644 --- a/requests/models.py +++ b/requests/models.py @@ -204,10 +204,8 @@ class Request(object): response.cookies = self.cookies # Save cookies in Session. - # (in safe mode, cookies may be None if the request didn't succeed) - if self.cookies is not None: - for cookie in self.cookies: - self.session.cookies.set_cookie(cookie) + for cookie in self.cookies: + self.session.cookies.set_cookie(cookie) # No exceptions were harmed in the making of this request. response.error = getattr(resp, 'error', None) @@ -587,53 +585,34 @@ class Request(object): r = dispatch_hook('pre_send', self.hooks, self) self.__dict__.update(r.__dict__) + # catch urllib3 exceptions and throw Requests exceptions try: - # The inner try .. except re-raises certain exceptions as - # internal exception types; the outer suppresses exceptions - # when safe mode is set. - try: - # Send the request. - r = conn.urlopen( - method=self.method, - url=self.path_url, - body=body, - headers=self.headers, - redirect=False, - assert_same_host=False, - preload_content=False, - decode_content=False, - retries=self.config.get('max_retries', 0), - timeout=self.timeout, - ) - self.sent = True - - except MaxRetryError as e: - raise ConnectionError(e) - - except (_SSLError, _HTTPError) as e: - if self.verify and isinstance(e, _SSLError): - raise SSLError(e) - - raise Timeout('Request timed out.') - - except RequestException as e: - if self.config.get('safe_mode', False): - # In safe mode, catch the exception and attach it to - # a blank urllib3.HTTPResponse object. - r = HTTPResponse() - r.error = e - else: - raise + # Send the request. + r = conn.urlopen( + method=self.method, + url=self.path_url, + body=body, + headers=self.headers, + redirect=False, + assert_same_host=False, + preload_content=False, + decode_content=False, + retries=self.config.get('max_retries', 0), + timeout=self.timeout, + ) + self.sent = True + + except MaxRetryError as e: + raise ConnectionError(e) + + except (_SSLError, _HTTPError) as e: + if self.verify and isinstance(e, _SSLError): + raise SSLError(e) + + raise Timeout('Request timed out.') # build_response can throw TooManyRedirects - try: - self._build_response(r) - except RequestException as e: - if self.config.get('safe_mode', False): - # In safe mode, catch the exception - self.response.error = e - else: - raise + self._build_response(r) # Response manipulation hook. self.response = dispatch_hook('response', self.hooks, self.response) diff --git a/requests/safe_mode.py b/requests/safe_mode.py new file mode 100644 index 0000000..7a0f218 --- /dev/null +++ b/requests/safe_mode.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +""" +requests.safe_mode +~~~~~~~~~~~~ + +This module contains a decorator that implements safe_mode. + +:copyright: (c) 2012 by Kenneth Reitz. +:license: ISC, see LICENSE for more details. + +""" + +from .models import Response +from .exceptions import RequestException, ConnectionError, HTTPError +from .packages.urllib3.response import HTTPResponse + +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') + and kwargs.get('session').config.get('safe_mode')): + try: + return function(method, url, **kwargs) + except (RequestException, ConnectionError, HTTPError) as e: + r = Response() + r.error = e + 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 -- 2.7.4