summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAsk Solem <ask@celeryproject.org>2014-02-20 19:25:03 +0000
committerAsk Solem <ask@celeryproject.org>2014-05-03 22:27:17 +0100
commit19af91d48e08d29cf7dde87f4a0b25a0dd674701 (patch)
tree8aa12573ab92820881517cd62548fad87b975b4c
parentb5df9fbe27a3350d4e0bb317a2254f0baa8d8bd7 (diff)
downloadkombu-19af91d48e08d29cf7dde87f4a0b25a0dd674701.tar.gz
100% coverage for kombu.async.http.base
-rw-r--r--kombu/async/http/base.py81
-rw-r--r--kombu/tests/async/test_http.py119
2 files changed, 163 insertions, 37 deletions
diff --git a/kombu/async/http/base.py b/kombu/async/http/base.py
index 4b1c56cc..219f2763 100644
--- a/kombu/async/http/base.py
+++ b/kombu/async/http/base.py
@@ -83,12 +83,11 @@ class Request(object):
"""
- url = headers = body = user_agent = network_interface = \
- on_stream = on_timeout = on_ready = on_header = on_prepare = \
+ body = user_agent = network_interface = \
+ on_stream = on_timeout = on_header = on_prepare = \
proxy_host = proxy_port = proxy_username = proxy_password = \
ca_certs = client_key = client_cert = None
- method = 'GET'
connect_timeout = 30.0
request_timeout = 30.0
follow_redirects = True
@@ -96,18 +95,20 @@ class Request(object):
use_gzip = True
validate_cert = True
- if PYPY:
- __slots__ = ('__weakref__', )
+ if not PYPY: # pragma: no cover
+ __slots__ = ('url', 'method', 'on_ready', 'headers',
+ '__weakref__', '__dict__')
- def __init__(self, url, method='GET', on_ready=None, **kwargs):
+ def __init__(self, url, method='GET', on_ready=None, headers=None, **kwargs):
self.url = url
self.method = method or self.method
self.on_ready = on_ready or promise()
if kwargs:
for k, v in items(kwargs):
setattr(self, k, v)
- if not isinstance(self.headers, Headers):
- self.headers = Headers(self.headers or {})
+ if not isinstance(headers, Headers):
+ headers = Headers(headers or {})
+ self.headers = headers
def then(self, callback, errback=None):
self.on_ready.then(callback, errback)
@@ -123,31 +124,40 @@ class Response(object):
:keyword effective_url: See :attr:`effective_url`.
:keyword status: See :attr:`status`.
- """
+ .. attribute:: request
+
+ :class:`Request` object used to get this response.
+
+ .. attribute:: code
+
+ HTTP response code (e.g. 200, 404, or 500).
+
+ .. attribute:: headers
+
+ HTTP headers for this response (:class:`Headers`).
- # :class:`Request` object used to get this response.
- request = None
+ .. attribute:: buffer
- #: HTTP response code (e.g. 200, 404, or 500).
- code = None
+ Socket read buffer.
- #: HTTP headers for this response (:class:`Headers`).
- headers = None
+ .. attribute:: effective_url
- #: Socket read buffer.
- buffer = None
+ The destination url for this request after following redirects.
- #: The destination url for this request after following redirects.
- effective_url = None
+ .. attribute:: error
- #: Error instance if the request resulted in a HTTP error code.
+ Error instance if the request resulted in a HTTP error code.
- #: Human equivalent of :attr:`code`, e.g. ``OK``, `Not found`, or
- #: 'Internal Server Error'.
- status = None
+ .. attribute:: status
- if PYPY:
- __slots__ = ('__weakref__', )
+ Human equivalent of :attr:`code`, e.g. ``OK``, `Not found`, or
+ 'Internal Server Error'.
+
+ """
+
+ if not PYPY: # pragma: no cover
+ __slots__ = ('request', 'code', 'headers', 'buffer', 'effective_url',
+ 'error', 'status', '_body', '__weakref__')
def __init__(self, request, code, headers=None, buffer=None,
effective_url=None, error=None, status=None):
@@ -156,6 +166,7 @@ class Response(object):
self.headers = headers if headers is not None else Headers()
self.buffer = buffer
self.effective_url = effective_url or request.url
+ self._body = None
self.status = status or responses.get(self.code, 'Unknown')
self.error = error
@@ -168,7 +179,7 @@ class Response(object):
if self.error:
raise self.error
- @cached_property
+ @property
def body(self):
"""The full contents of the response body.
@@ -176,8 +187,10 @@ class Response(object):
and subsequent accesses will be cached.
"""
- if self.buffer is not None:
- return self.buffer.getvalue()
+ if self._body is None:
+ if self.buffer is not None:
+ self._body = self.buffer.getvalue()
+ return self._body
@coro
@@ -190,7 +203,8 @@ def header_parser(keyt=normalize_header):
headers.complete = True
continue
elif line[0].isspace():
- headers[headers._prev_key] = ' '.join(['', line.lstrip()])
+ pkey = headers._prev_key
+ headers[pkey] = ' '.join([headers.get(pkey) or '', line.lstrip()])
else:
key, value = line.split(':', 1)
key = headers._prev_key = keyt(key)
@@ -211,6 +225,9 @@ class BaseClient(object):
request = self.Request(request, **kwargs)
self.add_request(request)
+ def add_request(self, request):
+ raise NotImplementedError('must implement add_request')
+
def close(self):
pass
@@ -219,3 +236,9 @@ class BaseClient(object):
self._header_parser.send((bytes_to_str(line), headers))
except StopIteration:
self._header_parser = header_parser()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *exc_info):
+ self.close()
diff --git a/kombu/tests/async/test_http.py b/kombu/tests/async/test_http.py
index 785f9e82..837aaf7c 100644
--- a/kombu/tests/async/test_http.py
+++ b/kombu/tests/async/test_http.py
@@ -1,12 +1,23 @@
from __future__ import absolute_import
+from io import BytesIO
+
from amqp import promise
from kombu.async import Hub
from kombu.async import http
+from kombu.async.http.base import BaseClient, normalize_header, header_parser
+from kombu.exceptions import HttpError
from kombu.tests.case import Case, Mock
+class test_Headers(Case):
+
+ def test_normalize(self):
+ self.assertEqual(normalize_header('accept-encoding'),
+ 'Accept-Encoding')
+
+
class test_Request(Case):
def test_init(self):
@@ -32,37 +43,129 @@ class test_Request(Case):
callback.assert_called_with(1)
+class test_Response(Case):
+
+ def test_init(self):
+ req = http.Request('http://foo')
+ r = http.Response(req, 200)
+
+ self.assertEqual(r.status, 'OK')
+ self.assertEqual(r.effective_url, 'http://foo')
+ r.raise_for_error()
+
+ def test_raise_for_error(self):
+ req = http.Request('http://foo')
+ r = http.Response(req, 404)
+ self.assertEqual(r.status, 'Not Found')
+ self.assertTrue(r.error)
+
+ with self.assertRaises(HttpError):
+ r.raise_for_error()
+
+ def test_get_body(self):
+ req = http.Request('http://foo')
+ req.buffer = BytesIO()
+ req.buffer.write(b'hello')
+
+ rn = http.Response(req, 200, buffer=None)
+ self.assertIsNone(rn.body)
+
+ r = http.Response(req, 200, buffer=req.buffer)
+ self.assertIsNone(r._body)
+ self.assertEqual(r.body, b'hello')
+ self.assertEqual(r._body, b'hello')
+ self.assertEqual(r.body, b'hello')
+
+
+class test_BaseClient(Case):
+
+ def test_init(self):
+ c = BaseClient(Mock(name='hub'))
+ self.assertTrue(c.hub)
+ self.assertTrue(c._header_parser)
+
+ def test_perform(self):
+ c = BaseClient(Mock(name='hub'))
+ c.add_request = Mock(name='add_request')
+
+ c.perform('http://foo')
+ self.assertTrue(c.add_request.called)
+ self.assertIsInstance(c.add_request.call_args[0][0], http.Request)
+
+ req = http.Request('http://bar')
+ c.perform(req)
+ c.add_request.assert_called_with(req)
+
+ def test_add_request(self):
+ c = BaseClient(Mock(name='hub'))
+ with self.assertRaises(NotImplementedError):
+ c.add_request(Mock(name='request'))
+
+ def test_header_parser(self):
+ c = BaseClient(Mock(name='hub'))
+ parser = c._header_parser
+ headers = http.Headers()
+
+ c.on_header(headers, 'HTTP/1.1')
+ c.on_header(headers, 'x-foo-bar: 123')
+ c.on_header(headers, 'People: George Costanza')
+ self.assertEqual(headers._prev_key, 'People')
+ c.on_header(headers, ' Jerry Seinfeld')
+ c.on_header(headers, ' Elaine Benes')
+ c.on_header(headers, ' Cosmo Kramer')
+ self.assertFalse(headers.complete)
+ c.on_header(headers, '')
+ self.assertTrue(headers.complete)
+
+ with self.assertRaises(KeyError):
+ parser.throw(KeyError('foo'))
+ c.on_header(headers, '')
+
+ self.assertEqual(headers['X-Foo-Bar'], '123')
+ self.assertEqual(
+ headers['People'],
+ 'George Costanza Jerry Seinfeld Elaine Benes Cosmo Kramer',
+ )
+
+ def test_close(self):
+ BaseClient(Mock(name='hub')).close()
+
+ def test_as_context(self):
+ c = BaseClient(Mock(name='hub'))
+ c.close = Mock(name='close')
+ with c:
+ pass
+ c.close.assert_called_with()
+
+
class test_Client(Case):
- def test_get_request(self):
+ def xxx_get_request(self):
hub = Hub()
callback = Mock(name='callback')
def on_ready(response):
print('{0.effective_url} -> {0.code}'.format(response))
- on_ready = promise(on_ready)
- on_ready.then(callback)
requests = [
http.Request(
'http://localhost:8000/README.rst',
- on_ready=on_ready,
+ on_ready=promise(on_ready, callback=callback),
),
http.Request(
'http://localhost:8000/AUTHORS',
- on_ready=on_ready,
+ on_ready=promise(on_ready, callback=callback),
),
http.Request(
'http://localhost:8000/pavement.py',
- on_ready=on_ready,
+ on_ready=promise(on_ready, callback=callback),
),
http.Request(
'http://localhost:8000/setup.py',
- on_ready=on_ready,
+ on_ready=promise(on_ready, callback=callback),
),
]
client = http.Client(hub)
for request in requests:
- request.then(promise(callback))
client.perform(request)
print('START PERFORM')