diff --git a/requests/core.py b/requests/core.py index 9292a9a6..e511b84c 100644 --- a/requests/core.py +++ b/requests/core.py @@ -13,26 +13,13 @@ from __future__ import absolute_import import urllib import urllib2 - from urllib2 import HTTPError -try: - import eventlet - eventlet.monkey_patch() -except ImportError: - pass - -if not 'eventlet' in locals(): - try: - from gevent import monkey - monkey.patch_all() - except ImportError: - pass - from .packages.poster.encode import multipart_encode from .packages.poster.streaminghttp import register_openers - +__all__ = ['Request', 'Response', 'request', 'get', 'head', 'post', 'put', 'delete', 'add_autoauth', 'AUTOAUTHS', + 'RequestException', 'AuthenticationError', 'URLRequired', 'InvalidMethod', 'HTTPError'] __title__ = 'requests' __version__ = '0.2.4' __build__ = 0x000204 @@ -43,13 +30,11 @@ __copyright__ = 'Copyright 2011 Kenneth Reitz' AUTOAUTHS = [] - - class _Request(urllib2.Request): """Hidden wrapper around the urllib2.Request object. Allows for manual setting of HTTP methods. """ - + def __init__(self, url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None): urllib2.Request.__init__(self, url, data, headers, origin_req_host, unverifiable) @@ -66,61 +51,59 @@ class Request(object): """The :class:`Request` object. It carries out all functionality of Requests. Recommended interface is with the Requests functions. """ - + _METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE') - + def __init__(self, url=None, headers=dict(), files=None, method=None, - params=dict(), data=dict(), auth=None, cookiejar=None): + data=dict(), auth=None, cookiejar=None): self.url = url self.headers = headers self.files = files self.method = method - self.params = params - self.data = data + + # url encode data if it's a dict + if isinstance(data, dict): + self.data = urllib.urlencode(data) + else: + self.data = data + self.response = Response() - + self.auth = auth self.cookiejar = cookiejar self.sent = False - - + def __repr__(self): return '' % (self.method) - - + def __setattr__(self, name, value): if (name == 'method') and (value): if not value in self._METHODS: raise InvalidMethod() - + object.__setattr__(self, name, value) - - + 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.auth or self.cookiejar: - if self.auth: - authr = urllib2.HTTPPasswordMgrWithDefaultRealm() - authr.add_password(None, self.url, self.auth.username, self.auth.password) + authr.add_password(None, self.url, self.auth[0], self.auth[1]) auth_handler = urllib2.HTTPBasicAuthHandler(authr) _handlers.append(auth_handler) if self.cookiejar: - cookie_handler = urllib2.HTTPCookieProcessor(cookiejar) _handlers.append(cookie_handler) @@ -133,13 +116,13 @@ class Request(object): def _build_response(self, resp): """Build internal Response object from given response.""" - + self.response.status_code = resp.code self.response.headers = resp.info().dict self.response.content = resp.read() self.response.url = resp.url - + def send(self, anyway=False): """Sends the request. Returns True of successful, false if not. If there was an HTTPError during transmission, @@ -150,106 +133,44 @@ class Request(object): :param anyway: If True, request will be sent, even if it has already been sent. """ - self._checks() - success = False - if self.method in ('GET', 'HEAD', 'DELETE'): - if (not self.sent) or anyway: + req = _Request(("%s?%s" % (self.url, self.data)), method=self.method) + else: + if self.files: + register_openers() + datagen, headers = multipart_encode(self.files) + req = _Request(self.url, data=datagen, headers=headers, method=self.method) + else: + req = _Request(self.url, method=self.method) - # url encode GET params if it's a dict - if isinstance(self.params, dict): - params = urllib.urlencode(self.params) - else: - params = self.params + if self.data: + req.data = self.data - req = _Request(("%s?%s" % (self.url, params)), method=self.method) - - if self.headers: - req.headers = self.headers + if self.headers: + req.headers = self.headers + if not self.sent or anyway: + try: opener = self._get_opener() - - try: - resp = opener(req) - self._build_response(resp) - self.response.ok = True - - except urllib2.HTTPError, why: - self._build_response(why) - self.response.error = why + resp = opener(req) + except urllib2.HTTPError, why: + self._build_response(why) + self.response.error = why + else: + self._build_response(resp) + self.response.ok = True + self.response.cached = False + else: + self.response.cached = True - elif self.method == 'PUT': - if (not self.sent) or anyway: - - if self.files: - register_openers() - datagen, headers = multipart_encode(self.files) - req = _Request(self.url, data=datagen, headers=headers, method='PUT') - - if self.headers: - req.headers.update(self.headers) - - else: - - req = _Request(self.url, method='PUT') - - if self.headers: - req.headers = self.headers - - req.data = self.data - - try: - opener = self._get_opener() - resp = opener(req) - - self._build_response(resp) - self.response.ok = True - - except urllib2.HTTPError, why: - self._build_response(why) - self.response.error = why - - - elif self.method == 'POST': - if (not self.sent) or anyway: - - if self.files: - register_openers() - datagen, headers = multipart_encode(self.files) - req = _Request(self.url, data=datagen, headers=headers, method='POST') - - if self.headers: - req.headers.update(self.headers) - - else: - req = _Request(self.url, method='POST') - req.headers = self.headers - - # url encode form data if it's a dict - if isinstance(self.data, dict): - req.data = urllib.urlencode(self.data) - else: - req.data = self.data - - try: - opener = self._get_opener() - resp = opener(req) - - self._build_response(resp) - self.response.ok = True - - except urllib2.HTTPError, why: - self._build_response(why) - self.response.error = why - self.sent = self.response.ok - + return self.sent - + class Response(object): """The :class:`Request` object. All :class:`Request` objects contain a @@ -264,36 +185,41 @@ class Response(object): self.url = None self.ok = False self.error = None - + self.cached = False + def __repr__(self): return '' % (self.status_code) - + def __nonzero__(self): """Returns true if status_code is 'OK'.""" return not self.error - + def raise_for_status(self): """Raises stored HTTPError if one exists.""" if self.error: raise self.error - -class AuthObject(object): - """The :class:`AuthObject` is a simple HTTP Authentication token. When - given to a Requests function, it enables Basic HTTP Authentication for that - Request. You can also enable Authorization for domain realms with AutoAuth. - See AutoAuth for more details. - - :param username: Username to authenticate with. - :param password: Password for given username. +def request(method, url, **kwargs): + """Sends a `method` 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 of GET/HEAD/DELETE Parameters to send with the :class:`Request`. + :param data: (optional) Bytes/Dictionary of PUT/POST Data to send with the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param cookies: (optional) 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. """ - - def __init__(self, username, password): - self.username = username - self.password = password + data = kwargs.pop('data', {}) or kwargs.pop('params', {}) + r = Request(method=method, url=url, data=data, headers=kwargs.pop('headers', {}), + cookiejar=kwargs.pop('cookies', None), files=kwargs.pop('files', None), + auth=_detect_auth(url, kwargs.pop('auth', None))) + r.send() + return r.response def get(url, params={}, headers={}, cookies=None, auth=None): """Sends a GET request. Returns :class:`Response` object. @@ -304,12 +230,9 @@ def get(url, params={}, headers={}, cookies=None, auth=None): :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. """ - - r = Request(method='GET', url=url, params=params, headers=headers, - cookiejar=cookies, auth=_detect_auth(url, auth)) - r.send() - - return r.response + + return request('GET', url, params=params, headers=headers, cookiejar=cookies, + auth=_detect_auth(url, auth)) def head(url, params={}, headers={}, cookies=None, auth=None): @@ -321,64 +244,53 @@ def head(url, params={}, headers={}, cookies=None, auth=None): :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. """ - r = Request(method='HEAD', url=url, params=params, headers=headers, - cookiejar=cookies, auth=_detect_auth(url, auth)) - r.send() - - return r.response + + return request('HEAD', url, params=params, headers=headers, cookiejar=cookies, + auth=_detect_auth(url, auth)) def post(url, data={}, headers={}, files=None, cookies=None, auth=None): """Sends a POST request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param data: (optional) Dictionary of POST Data to send with the :class:`Request`. + :param data: (optional) Dictionary of POST data to send with the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`. :param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload. :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. """ - - r = Request(method='POST', url=url, data=data, headers=headers, - files=files, cookiejar=cookies, auth=_detect_auth(url, auth)) - r.send() - - return r.response - - -def put(url, data='', headers={}, files={}, cookies=None, auth=None): + + return request('POST', url, data=data, headers=headers, files=files, cookiejar=cookies, + auth=_detect_auth(url, auth)) + + +def put(url, data=b'', headers={}, files={}, cookies=None, auth=None): """Sends a PUT request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param data: (optional) Bytes of PUT Data to send with the :class:`Request`. + :param params: (optional) Bytes of PUT Data to send with the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`. :param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload. :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. """ - r = Request(method='PUT', url=url, data=data, headers=headers, files=files, - cookiejar=cookies, auth=_detect_auth(url, auth)) - r.send() - - return r.response + return request('PUT', url, data=data, headers=headers, files=files, cookiejar=cookies, + auth=_detect_auth(url, auth)) + - def delete(url, params={}, headers={}, cookies=None, auth=None): """Sends a DELETE request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`. + :param params: (optional) Dictionary of DELETE Parameters to send with the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`. :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. """ - - r = Request(method='DELETE', url=url, params=params, headers=headers, - cookiejar=cookies, auth=_detect_auth(url, auth)) - r.send() - - return r.response + + return request('DELETE', url, params=params, headers=headers, cookiejar=cookies, + auth=_detect_auth(url, auth)) def add_autoauth(url, authobject): @@ -398,7 +310,7 @@ def add_autoauth(url, authobject): """ global AUTOAUTHS - + AUTOAUTHS.append((url, authobject)) @@ -409,14 +321,14 @@ def _detect_auth(url, auth): return _get_autoauth(url) if not auth else auth - + def _get_autoauth(url): """Returns registered AuthObject for given url if available.""" - + for (autoauth_url, auth) in AUTOAUTHS: - if autoauth_url in url: + if autoauth_url in url: return auth - + return None @@ -426,9 +338,9 @@ class RequestException(Exception): class AuthenticationError(RequestException): """The authentication credentials provided were invalid.""" - + class URLRequired(RequestException): """A valid URL is required to make a request.""" - + class InvalidMethod(RequestException): """An inappropriate method was attempted.""" diff --git a/test_requests.py b/test_requests.py index e365c6ef..7ea67986 100644 --- a/test_requests.py +++ b/test_requests.py @@ -8,46 +8,40 @@ import requests class RequestsTestSuite(unittest.TestCase): """Requests test cases.""" - + def setUp(self): pass def tearDown(self): """Teardown.""" pass - + def test_invalid_url(self): self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw') - def test_HTTP_200_OK_GET(self): r = requests.get('http://google.com') self.assertEqual(r.status_code, 200) - def test_HTTPS_200_OK_GET(self): r = requests.get('https://google.com') self.assertEqual(r.status_code, 200) - def test_HTTP_200_OK_HEAD(self): r = requests.head('http://google.com') self.assertEqual(r.status_code, 200) - def test_HTTPS_200_OK_HEAD(self): r = requests.head('https://google.com') self.assertEqual(r.status_code, 200) - def test_AUTH_HTTPS_200_OK_GET(self): - auth = requests.AuthObject('requeststest', 'requeststest') + auth = ('requeststest', 'requeststest') url = 'https://convore.com/api/account/verify.json' r = requests.get(url, auth=auth) self.assertEqual(r.status_code, 200) - requests.add_autoauth(url, auth) r = requests.get(url) @@ -56,33 +50,27 @@ class RequestsTestSuite(unittest.TestCase): # reset auto authentication requests.AUTOAUTHS = [] - - def test_POSTBIN_GET_POST_FILES(self): - - bin = requests.post('http://www.postbin.org/') + bin = requests.get('http://www.postbin.org/') self.assertEqual(bin.status_code, 200) post = requests.post(bin.url, data={'some': 'data'}) - self.assertEqual(post.status_code, 201) + self.assertEqual(post.status_code, 200) post2 = requests.post(bin.url, files={'some': open('test_requests.py')}) - self.assertEqual(post2.status_code, 201) - + self.assertEqual(post2.status_code, 200) def test_nonzero_evaluation(self): r = requests.get('http://google.com/some-404-url') self.assertEqual(bool(r), False) - + r = requests.get('http://google.com/') self.assertEqual(bool(r), True) - def test_request_ok_set(self): r = requests.get('http://google.com/some-404-url') self.assertEqual(r.ok, False) - def test_status_raising(self): r = requests.get('http://google.com/some-404-url') self.assertRaises(requests.HTTPError, r.raise_for_status)