Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

rewrite Headers' class

- headers need to be handled as a list AND a dict
- add new methods to add/get/set headers value
- change the way to do normalization (lower is enough)
- add more tests
- start documentation for headers (and set the autodoc for doc generation)
  • Loading branch information...
commit 28de023c62b31d60448cf9b170bae1e26f055bf6 1 parent 1f82c78
@franckcuny authored
View
6 docs/conf.py
@@ -12,6 +12,10 @@
# serve to show the default.
import sys, os
+try:
+ import fluffyhttp
+except ImportError:
+ sys.path.append(os.path.abspath('../'))
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@@ -22,7 +26,7 @@
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = []
+extensions = ['sphinx.ext.autodoc']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
View
1  docs/contents.rst
@@ -9,3 +9,4 @@
client
request
response
+ headers
View
20 docs/headers.rst
@@ -0,0 +1,20 @@
+.. _headers:
+
+Headers
+=======
+
+.. module:: fluffyhttp.headers
+
+Synopsis
+--------
+
+::
+
+ >>> headers = Headers()
+ >>> headers = Headers([('Content-Type', 'application/json')])
+
+Interface
+---------
+
+.. autoclass:: Headers([defaults])
+ :members:
View
11 eg/simple.py
@@ -2,22 +2,23 @@
sys.path.append('.')
from datetime import datetime
-from fluffyhttp import *
+from fluffyhttp import Client, Request
client = Client(agent='my uber agent')
-request = Request('GET', 'http://lumberjaph.net')
+
+request = Request('HEAD', 'http://lumberjaph.net')
request.if_modified_since(datetime(2011, 12, 1, 0, 0))
+
response = client.request(request)
if response.is_success:
print "yeah, success!"
print "status: {status}".format(status=response.status)
print "message: {message}".format(message=response.message)
- print "content length: {length}".format(length=response.content_length())
+ print "content length: {length}".format(length=response.content_length)
print "last modified in epoch: {last_modified}".format(last_modified=response.last_modified())
print "last modified in string: {last_modified}".format(last_modified=response.header('Last-Modified'))
if response.content_is_text:
- #print response.content
- pass
+ print response.content
else:
print "oups! {status_line}".format(status_line=response.status_line)
View
13 fluffyhttp/client.py
@@ -23,15 +23,16 @@ def __init__(self, agent=None, timeout=10, keep_alive=1,
self.agent = agent
if len(default_headers) == 0:
- default_headers = {
- 'Connection': 'keep-alive',
- }
-
- if 'User-Agent' not in default_headers:
- default_headers['User-Agent'] = self.agent
+ default_headers = [(
+ 'Connection', 'keep-alive',
+ )]
self._default_headers = Headers(default_headers)
+ if 'User-Agent' not in default_headers:
+ self._default_headers.add('User-Agent', self.agent)
+ self._default_headers.add('User-Agent', 'foooo')
+
self._poolmanager = PoolManager(
maxsize=keep_alive
)
View
248 fluffyhttp/headers.py
@@ -2,46 +2,182 @@
import re
-class Headers(dict):
+class Headers(object):
+ """Class to manipulate HTTP headers
- @property
- def _normalize_keys(self):
- if not hasattr(self, '_normalized_keys') or not self._normalized_keys:
- self._normalized_keys = dict(
- (k.lower(), k) for k in self.iterkeys())
- return self._normalized_keys
+ :param headers: a list or a dict of headers
+ """
- def __setitem__(self, key, value):
- dict.__setitem__(self, key, value)
- if hasattr(self, '_normalized_keys'):
- self._normalized_keys.clear()
+ def __init__(self, headers=None):
+ if headers is None:
+ headers = []
- def __delitem__(self, key):
- dict.__delitem__(self, key)
- self._normalized_keys.clear()
+ if isinstance(headers, dict):
+ _headers = []
+ for k in headers:
+ _headers.append((k, headers[k]))
+ headers = _headers
- def __contains__(self, key):
- return key.lower() in self._normalize_keys
+ self._headers = headers
def __getitem__(self, key):
- if key in self:
- return dict.__getitem__(self, self._normalize_keys[key.lower()])
+ if isinstance(key, (int, long)):
+ return self._headers[key][0]
+ for k, v in self._headers:
+ if k.lower() == key.lower():
+ return v
+ return None
- def get(self, key, default=None):
- if key in self:
- return self[key]
- else:
- return default
+ def __delitem__(self, key):
+ key = key.lower()
+ new = []
+ for k, v in self._headers:
+ if k.lower() != key:
+ new.append((k, v))
+ self._headers[:] = new
+
+ def __str__(self):
+ strs = []
+ for key, value in self.to_list():
+ strs.append('%s: %s' % (key, value))
+ strs.append('\r\n')
+ return '\r\n'.join(strs)
+
+ def items(self):
+ return list(self.iteritems())
+
+ def iteritems(self):
+ """ return an iterator of headers
+
+ >>> h = Headers([('Content-Type', 'application/json')])
+ >>> for k, v in h.iteritems():
+ ... print k
+ ...
+ 'Content-Type'
+
+ :return: iterator of headers
+ """
+
+ for k, v in self._headers:
+ yield k, v
+
+ def to_list(self):
+ return [(k, str(v)) for k, v in self._headers]
+
+ def add(self, key, *values):
+ """add a new header and a value
+
+ >>> headers = Headers()
+ >>> headers.add('Content-Type', 'application/json')
+ >>> headers.add('X-Custom', 'foo', 'bar')
+
+ :param key: header's name
+ :param \*values: one or many values for this header
+ """
+
+ for value in values:
+ self._headers.append((key, value))
+
+ def get(self, key):
+ """get the value for a given header
+
+ >>> headers = Headers()
+ >>> headers.add('Content-Type', 'application/json')
+ >>> headers.get('Content-Type')
+ 'application/json'
+
+ :param key: header's name
+ :return: string
+ """
+
+ value = self.__getitem__(key)
+ return value
+
+ def get_all(self, key):
+ """get all the values for a given header
+
+ >>> headers = Headers()
+ >>> headers.add('X-Custom', 'foo', 'bar')
+ >>> headers.get_all('X-Custom')
+ ['foo', 'bar']
+
+ :param key: header's name
+ :return: list
+ """
+
+ return self.get_list(key)
+
+ def get_list(self, key):
+ values = []
+ for header_values in self._headers:
+ values.append(header_values[1])
+ return values
+
+ def set(self, key, value):
+ """set a header to some specific value. If this header already exists, and there is more than one value for this header, the new value replace the first one
+
+ >>> h = Headers([('Content-Type', 'application/json')])
+ >>> h.add('Content-Type', 'application/xml')
+ >>> h.set('Content-Type', 'application/yaml')
+ >>> headers.get_all('Content-Type')
+ ['application/yaml', 'application/xml']
+
+ :param key: header's name
+ :param value: new value
+ """
+
+ if not self._headers:
+ self._headers.append((key, value))
+ return
+
+ lkey = key.lower()
+ for idx, (prev_key, prev_value) in enumerate(self.iteritems()):
+ if prev_key.lower() == lkey:
+ self._headers[idx] = (key, value)
+ break
+
+ def remove(self, key):
+ """remove a header
+ :param: header's name
+ """
+
+ self.__delitem__(key)
@property
def content_type(self):
+ """Returns the value for the *Content-Type* header
+
+ >>> h = Headers([('Content-Type', 'application/json')])
+ >>> h.content_type
+ 'application/json'
+
+ :return: string
+ """
+
return self.get('Content-Type')
+ @property
def content_length(self):
+ """Returns the value for the *Content-Length* header
+
+ >>> h = Headers([('Content-Length', '23')])
+ >>> h.content_length
+ '23'
+ """
+
return self.get('Content-Length')
@property
def content_is_text(self):
+ """This is an helper that will return True if the *Content-Type* header is set to *text*
+
+ >>> h = Headers([('Content-Type', 'text/x-yaml')])
+ >>> h.content_is_text
+ True
+
+ :return: boolean
+ """
+
ct = self.content_type
if ct is None:
return False
@@ -51,6 +187,15 @@ def content_is_text(self):
@property
def content_is_xhtml(self):
+ """This is an helper that will return True if the *Content-Type* header is set to *xhtml*
+
+ >>> h = Headers([('Content-Type', 'application/xhtml+xml')])
+ >>> h.content_is_xhtml
+ True
+
+ :return: boolean
+ """
+
ct = self.content_type
if ct is None:
return False
@@ -62,6 +207,15 @@ def content_is_xhtml(self):
@property
def content_is_xml(self):
+ """This is an helper that will return True if the *Content-Type* header is set to *xml*
+
+ >>> h = Headers([('Content-Type', 'application/xml')])
+ >>> h.content_is_xml
+ True
+
+ :return: boolean
+ """
+
ct = self.content_type
if ct is None:
return False
@@ -74,24 +228,70 @@ def content_is_xml(self):
return False
def last_modified(self, date=None):
+ """Get or set the value for the *Last-Modified* header
+
+ >>> headers = Headers()
+ >>> headers.if_modified_since(datetime(2011, 12, 1, 0, 0))
+
+ :param date: datetime object
+ :return: string
+ """
+
return self.date_header('Last-Modified', date)
def date(self, date=None):
+ """Get or set the value for the *Date* header
+
+ >>> headers = Headers()
+ >>> headers.date(2011, 12, 1, 0, 0))
+
+ :param date: datetime object
+ :return: string
+ """
+
return self.date_header('Date', date)
def expires(self, date=None):
+ """Get or set the value for the *Expires* header
+
+ >>> headers = Headers()
+ >>> headers.expires(2011, 12, 1, 0, 0))
+
+ :param date: datetime object
+ :return: string
+ """
+
return self.date_header('Expires', date)
def if_modified_since(self, date=None):
+ """Get or set the value for the *If-Modified-Since* header
+
+ >>> headers = Headers()
+ >>> headers.if_modified_since(2011, 12, 1, 0, 0))
+
+ :param date: datetime object
+ :return: string
+ """
+
return self.date_header('If-Modified-Since', date)
def if_unmodified_since(self, date=None):
+ """Get or set the value for the *If-Unmodified-Since* header
+
+ >>> headers = Headers()
+ >>> headers.if_unmodified_since(2011, 12, 1, 0, 0))
+
+ :param date: datetime object
+ :return: string
+ """
+
return self.date_header('If-Unmodified-Since', date)
def date_header(self, key, date=None):
if date:
+ # XXX check
date = Date.time2str(date)
- self[key] = date
+ self.set(key, date)
else:
value = self.get(key)
if value is None:
View
81 tests/test_headers.py
@@ -4,18 +4,79 @@
class TestHeaders(TestCase):
+ ct_headers = (
+ 'Content-Type', 'application/json'
+ )
+
def test_simple(self):
- headers = {
- 'Content-Type': 'application/json'
- }
+ headers = Headers([self.ct_headers])
+ self.assertTrue(headers)
+ self.assertEqual(headers.content_type, 'application/json')
+
+ def test_normalization(self):
+ headers = Headers([self.ct_headers])
+
+ self.assertTrue(headers.get('Content-Type'))
+ self.assertTrue(headers.get('content-type'))
+ self.assertEqual(headers.get('content-type'), 'application/json')
+
+ headers.add('User-Agent', 'fluffy')
+ self.assertTrue(headers.get('User-Agent'))
+ self.assertTrue(headers.get('user-agent'))
+ self.assertEqual(headers.get('user-agent'), 'fluffy')
+
+ def test_remove(self):
+ headers = Headers([self.ct_headers])
+ headers.remove('Content-Type')
+ ct = headers.content_type
+ self.assertFalse(ct)
+
+ def test_multi(self):
+ headers = Headers()
+ self.assertTrue(headers)
+
+ headers.add('X-Foo', 'bar')
+ headers.add('X-Foo', 'baz')
+
+ self.assertEqual(headers.get('X-Foo'), 'bar')
+ self.assertEqual(headers.get_all('X-Foo'), ['bar', 'baz'])
+
+ headers = Headers()
+ headers.add('X-Foo', 'bar', 'baz', 'foo')
+ self.assertEqual(headers.get_all('X-Foo'), ['bar', 'baz', 'foo'])
+ self.assertEqual(headers.get_all('x-fOo'), ['bar', 'baz', 'foo'])
+
+ def test_content(self):
+ headers = Headers([self.ct_headers])
+ self.assertFalse(headers.content_is_text)
+
+ def test_ct(ct, should_be):
+ headers.remove('Content-Type')
+ headers.add('Content-Type', ct)
+ method = getattr(headers, should_be)
+ self.assertTrue(method)
+
+ test_ct('text/html', 'content_is_text')
+ test_ct('application/xhtml+xml', 'content_is_xhtml')
+ test_ct('text/xml', 'content_is_xml')
+ test_ct('application/xml', 'content_is_xml')
+
+ def test_date_header(self):
+ pass
- f_headers = Headers(headers)
- self.assertTrue(f_headers)
+ def test_str(self):
+ headers = Headers([self.ct_headers])
+ pass
- self.assertTrue(f_headers.get('Content-Type'))
- self.assertTrue(f_headers.get('content-type'))
+ def test_iteritems(self):
+ headers = Headers([self.ct_headers])
- f_headers['User-Agent'] = 'fluffy'
- self.assertTrue(f_headers.get('User-Agent'))
- self.assertTrue(f_headers.get('user-agent'))
+ def test_set(self):
+ headers = Headers()
+ headers.add('Content-Type', 'application/json')
+ headers.set('Content-Type', 'application/xml')
+ self.assertEqual(headers.get('Content-Type'), 'application/xml')
+ headers = Headers()
+ headers.set('Content-Type', 'application/xml')
+ self.assertEqual(headers.get('Content-Type'), 'application/xml')
Please sign in to comment.
Something went wrong with that request. Please try again.