Commits

Sergey Schetinin  committed 0b6029a

* add AcceptCharset and AcceptLanguage subclasses instead of branching in Accept.__init__
* that allows us to remove Accept.header_name
* and change Accept.__repr__ to be more straightforward

  • Participants
  • Parent commits 1fd0a1d

Comments (0)

Files changed (5)

File docs/reference.txt

 
     >>> req.accept = 'text/html;q=0.5, application/xhtml+xml;q=1'
     >>> req.accept
-    <MIMEAccept at ... Accept: text/html;q=0.5, application/xhtml+xml>
+    <MIMEAccept('text/html;q=0.5, application/xhtml+xml')>
     >>> 'text/html' in req.accept
     True
 

File docs/test_request.txt

     >>> req = Request.blank('/')
     >>> req.environ['HTTP_ACCEPT'] = "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.1"
     >>> req.accept # doctest: +ELLIPSIS
-    <MIMEAccept at ... Accept: text/*;q=0.3, text/html;q=0.7, text/html, text/html;q=0.4, */*;q=0.1>
+    <MIMEAccept('text/*;q=0.3, text/html;q=0.7, text/html, text/html;q=0.4, */*;q=0.1')>
     >>> for item, quality in req.accept._parsed:
     ...     print '%s: %0.1f' % (item, quality)
     text/*: 0.3
     True
     >>> req.environ['HTTP_ACCEPT'] = "text/html, application/xml; q=0.7, text/*; q=0.5, */*; q=0.1"
     >>> req.accept # doctest: +ELLIPSIS
-    <MIMEAccept at ... Accept: text/html, application/xml;q=0.7, text/*;q=0.5, */*;q=0.1>
+    <MIMEAccept('text/html, application/xml;q=0.7, text/*;q=0.5, */*;q=0.1')>
     >>> req.accept.best_match(['text/plain', 'application/xml'])
     'application/xml'
     >>> req.accept.first_match(['application/xml', 'text/html'])

File tests/test_acceptparse.py

 from webob import Request
-from webob.acceptparse import Accept, MIMEAccept, NilAccept, NoAccept, accept_property, parse_accept
+from webob.acceptparse import Accept, MIMEAccept, NilAccept, NoAccept, accept_property, AcceptLanguage, AcceptCharset
 from nose.tools import eq_, assert_raises
 
 def test_parse_accept_badq():
-    assert parse_accept("value1; q=0.1.2") == [('value1', 1)]
+    assert list(Accept.parse("value1; q=0.1.2")) == [('value1', 1)]
 
 def test_init_accept_content_type():
-    name, value = ('Content-Type', 'text/html')
-    accept = Accept(name, value)
-    assert accept.header_name == name
-    assert accept.header_value == value
+    accept = Accept('text/html')
     assert accept._parsed == [('text/html', 1)]
 
 def test_init_accept_accept_charset():
-    name, value = ('Accept-Charset', 'iso-8859-5, unicode-1-1;q=0.8')
-    accept = Accept(name, value)
-    assert accept.header_name == name
-    assert accept.header_value == value
+    accept = AcceptCharset('iso-8859-5, unicode-1-1;q=0.8')
     assert accept._parsed == [('iso-8859-5', 1),
                               ('unicode-1-1', 0.80000000000000004),
                               ('iso-8859-1', 1)]
 
 def test_init_accept_accept_charset_with_iso_8859_1():
-    name, value = ('Accept-Charset', 'iso-8859-1')
-    accept = Accept(name, value)
-    assert accept.header_name == name
-    assert accept.header_value == value
+    accept = Accept('iso-8859-1')
     assert accept._parsed == [('iso-8859-1', 1)]
 
 def test_init_accept_accept_charset_wildcard():
-    name, value = ('Accept-Charset', '*')
-    accept = Accept(name, value)
-    assert accept.header_name == name
-    assert accept.header_value == value
+    accept = Accept('*')
     assert accept._parsed == [('*', 1)]
 
 def test_init_accept_accept_language():
-    name, value = ('Accept-Language', 'da, en-gb;q=0.8, en;q=0.7')
-    accept = Accept(name, value)
-    assert accept.header_name == name
-    assert accept.header_value == value
+    accept = AcceptLanguage('da, en-gb;q=0.8, en;q=0.7')
     assert accept._parsed == [('da', 1),
                               ('en-gb', 0.80000000000000004),
                               ('en', 0.69999999999999996)]
 
 def test_init_accept_invalid_value():
-    name, value = ('Accept-Language', 'da, q, en-gb;q=0.8')
-    accept = Accept(name, value)
+    accept = AcceptLanguage('da, q, en-gb;q=0.8')
     # The "q" value should not be there.
     assert accept._parsed == [('da', 1),
                               ('en-gb', 0.80000000000000004)]
 
 def test_init_accept_invalid_q_value():
-    name, value = ('Accept-Language', 'da, en-gb;q=foo')
-    accept = Accept(name, value)
+    accept = AcceptLanguage('da, en-gb;q=foo')
     # I can't get to cover line 40-41 (webob.acceptparse) as the regex
     # will prevent from hitting these lines (aconrad)
     assert accept._parsed == [('da', 1), ('en-gb', 1)]
 
 def test_accept_repr():
-    name, value = ('Content-Type', 'text/html')
-    accept = Accept(name, value)
-    assert repr(accept) == '<%s at 0x%x %s: %s>' % ('Accept',
-                                                    abs(id(accept)),
-                                                    name,
-                                                    str(accept))
+    accept = Accept('text/html')
+    assert repr(accept) == "<Accept('text/html')>"
 
 def test_accept_str():
-    name, value = ('Content-Type', 'text/html')
-    accept = Accept(name, value)
-    assert str(accept) == value
+    accept = Accept('text/html')
+    assert str(accept) == 'text/html'
 
 def test_zero_quality():
-    assert Accept('Accept-Encoding', 'bar, *;q=0').best_match(['foo']) is None
-    assert 'foo' not in Accept('Accept-Encoding', '*;q=0')
-    assert Accept('Accept-Encoding', 'foo, *;q=0').first_match(['bar', 'foo']) == 'foo'
+    assert Accept('bar, *;q=0').best_match(['foo']) is None
+    assert 'foo' not in Accept('*;q=0')
+    assert Accept('foo, *;q=0').first_match(['bar', 'foo']) == 'foo'
 
 
 def test_accept_str_with_q_not_1():
-    name, value = ('Content-Type', 'text/html;q=0.5')
-    accept = Accept(name, value)
+    value = 'text/html;q=0.5'
+    accept = Accept(value)
     assert str(accept) == value
 
 def test_accept_str_with_q_not_1_multiple():
-    name, value = ('Content-Type', 'text/html;q=0.5, foo/bar')
-    accept = Accept(name, value)
+    value = 'text/html;q=0.5, foo/bar'
+    accept = Accept(value)
     assert str(accept) == value
 
 def test_accept_add_other_accept():
-    accept = Accept('Content-Type', 'text/html') + \
-             Accept('Content-Type', 'foo/bar')
+    accept = Accept('text/html') + Accept('foo/bar')
     assert str(accept) == 'text/html, foo/bar'
-    accept += Accept('Content-Type', 'bar/baz;q=0.5')
+    accept += Accept('bar/baz;q=0.5')
     assert str(accept) == 'text/html, foo/bar, bar/baz;q=0.5'
 
 def test_accept_add_other_list_of_tuples():
-    accept = Accept('Content-Type', 'text/html')
+    accept = Accept('text/html')
     accept += [('foo/bar', 1)]
     assert str(accept) == 'text/html, foo/bar'
     accept += [('bar/baz', 0.5)]
                            'she/bangs, the/house')
 
 def test_accept_add_other_dict():
-    accept = Accept('Content-Type', 'text/html')
+    accept = Accept('text/html')
     accept += {'foo/bar': 1}
     assert str(accept) == 'text/html, foo/bar'
     accept += {'bar/baz': 0.5}
     assert str(accept) == 'text/html, foo/bar, bar/baz;q=0.5'
 
 def test_accept_add_other_empty_str():
-    accept = Accept('Content-Type', 'text/html')
+    accept = Accept('text/html')
     accept += ''
     assert str(accept) == 'text/html'
 
 def test_accept_with_no_value_add_other_str():
-    accept = Accept('Content-Type', '')
+    accept = Accept('')
     accept += 'text/html'
     assert str(accept) == 'text/html'
 
 def test_contains():
-    accept = Accept('Content-Type', 'text/html')
+    accept = Accept('text/html')
     assert 'text/html' in accept
 
 def test_contains_not():
-    accept = Accept('Content-Type', 'text/html')
+    accept = Accept('text/html')
     assert not 'foo/bar' in accept
 
 def test_quality():
-    accept = Accept('Content-Type', 'text/html')
+    accept = Accept('text/html')
     assert accept.quality('text/html') == 1
-    accept = Accept('Content-Type', 'text/html;q=0.5')
+    accept = Accept('text/html;q=0.5')
     assert accept.quality('text/html') == 0.5
 
 def test_quality_not_found():
-    accept = Accept('Content-Type', 'text/html')
+    accept = Accept('text/html')
     assert accept.quality('foo/bar') is None
 
 def test_first_match():
-    accept = Accept('Content-Type', 'text/html, foo/bar')
+    accept = Accept('text/html, foo/bar')
     assert accept.first_match(['text/html', 'foo/bar']) == 'text/html'
     assert accept.first_match(['foo/bar', 'text/html']) == 'foo/bar'
     assert accept.first_match(['xxx/xxx', 'text/html']) == 'text/html'
     assert_raises(ValueError, accept.first_match, [])
 
 def test_best_match():
-    accept = Accept('Content-Type', 'text/html, foo/bar')
+    accept = Accept('text/html, foo/bar')
     assert accept.best_match(['text/html', 'foo/bar']) == 'text/html'
     assert accept.best_match(['foo/bar', 'text/html']) == 'foo/bar'
     assert accept.best_match([('foo/bar', 0.5),
     assert_raises(ValueError, accept.best_match, ['text/*'])
 
 def test_best_match_with_one_lower_q():
-    accept = Accept('Content-Type', 'text/html, foo/bar;q=0.5')
+    accept = Accept('text/html, foo/bar;q=0.5')
     assert accept.best_match(['text/html', 'foo/bar']) == 'text/html'
-    accept = Accept('Content-Type', 'text/html;q=0.5, foo/bar')
+    accept = Accept('text/html;q=0.5, foo/bar')
     assert accept.best_match(['text/html', 'foo/bar']) == 'foo/bar'
 
 def test_best_matches():
-    accept = Accept('Content-Type', 'text/html, foo/bar')
+    accept = Accept('text/html, foo/bar')
     assert accept.best_matches() == ['text/html', 'foo/bar']
-    accept = Accept('Content-Type', 'text/html, foo/bar;q=0.5')
+    accept = Accept('text/html, foo/bar;q=0.5')
     assert accept.best_matches() == ['text/html', 'foo/bar']
-    accept = Accept('Content-Type', 'text/html;q=0.5, foo/bar')
+    accept = Accept('text/html;q=0.5, foo/bar')
     assert accept.best_matches() == ['foo/bar', 'text/html']
 
 def test_best_matches_with_fallback():
-    accept = Accept('Content-Type', 'text/html, foo/bar')
+    accept = Accept('text/html, foo/bar')
     assert accept.best_matches('xxx/yyy') == ['text/html',
                                               'foo/bar',
                                               'xxx/yyy']
-    accept = Accept('Content-Type', 'text/html;q=0.5, foo/bar')
+    accept = Accept('text/html;q=0.5, foo/bar')
     assert accept.best_matches('xxx/yyy') == ['foo/bar',
                                               'text/html',
                                               'xxx/yyy']
 
 def test_accept_match():
     for mask in ['*', 'text/html', 'TEXT/HTML']:
-        assert 'text/html' in Accept('Content-Type', mask)
-    assert 'text/html' not in Accept('Content-Type', 'foo/bar')
+        assert 'text/html' in Accept(mask)
+    assert 'text/html' not in Accept('foo/bar')
 
 def test_accept_match_lang():
     for mask, lang in [
         ('en', 'en-gb'),
         ('en-gb', 'en-gb'),
     ]:
-        assert lang in Accept('Accept-Language', mask)
-    assert 'fr-fr' not in Accept('Accept-Language', 'en-gb')
+        assert lang in AcceptLanguage(mask)
+    assert 'fr-fr' not in AcceptLanguage('en-gb')
 
 # NilAccept tests
 
 def test_nil():
-    nilaccept = NilAccept('Connection-Close')
-    assert nilaccept.header_name == 'Connection-Close'
+    nilaccept = NilAccept()
     eq_(repr(nilaccept),
-        "<NilAccept for Connection-Close: <class 'webob.acceptparse.Accept'>>"
+        "<NilAccept: <class 'webob.acceptparse.Accept'>>"
     )
     assert not nilaccept
     assert str(nilaccept) == ''
 
 
 def test_nil_add():
-    nilaccept = NilAccept('Connection-Close')
-    accept = Accept('Content-Type', 'text/html')
+    nilaccept = NilAccept()
+    accept = Accept('text/html')
     assert nilaccept + accept is accept
     new_accept = nilaccept + nilaccept
     assert isinstance(new_accept, accept.__class__)
-    assert new_accept.header_name == 'Connection-Close'
     assert new_accept.header_value == ''
     new_accept = nilaccept + 'foo'
     assert isinstance(new_accept, accept.__class__)
-    assert new_accept.header_name == 'Connection-Close'
     assert new_accept.header_value == 'foo'
 
 def test_nil_radd():
-    nilaccept = NilAccept('Connection-Close')
-    accept = Accept('Content-Type', 'text/html')
+    nilaccept = NilAccept()
+    accept = Accept('text/html')
     assert isinstance('foo' + nilaccept, accept.__class__)
     assert ('foo' + nilaccept).header_value == 'foo'
     # How to test ``if isinstance(item, self.MasterClass): return item``
 
 def test_nil_radd_masterclass():
     # Is this "reaching into" __radd__ legit?
-    nilaccept = NilAccept('Connection-Close')
-    accept = Accept('Content-Type', 'text/html')
+    nilaccept = NilAccept()
+    accept = Accept('text/html')
     assert nilaccept.__radd__(accept) is accept
 
 def test_nil_contains():
-    nilaccept = NilAccept('Connection-Close')
+    nilaccept = NilAccept()
     assert 'anything' in nilaccept
 
 def test_nil_first_match():
-    nilaccept = NilAccept('Connection-Close')
+    nilaccept = NilAccept()
     # NilAccept.first_match always returns element 0 of the list
     assert nilaccept.first_match(['dummy', '']) == 'dummy'
     assert nilaccept.first_match(['', 'dummy']) == ''
 
 def test_nil_best_match():
-    nilaccept = NilAccept('Connection-Close')
+    nilaccept = NilAccept()
     assert nilaccept.best_match(['foo', 'bar']) == 'foo'
     assert nilaccept.best_match([('foo', 1), ('bar', 0.5)]) == 'foo'
     assert nilaccept.best_match([('foo', 0.5), ('bar', 1)]) == 'bar'
 
 # NoAccept tests
 def test_noaccept_contains():
-    assert 'text/plain' not in NoAccept('Connection-Close')
+    assert 'text/plain' not in NoAccept()
 
 
 # MIMEAccept tests
 
 def test_mime_init():
-    mimeaccept = MIMEAccept('Content-Type', 'image/jpg')
+    mimeaccept = MIMEAccept('image/jpg')
     assert mimeaccept._parsed == [('image/jpg', 1)]
-    mimeaccept = MIMEAccept('Content-Type', 'image/png, image/jpg;q=0.5')
+    mimeaccept = MIMEAccept('image/png, image/jpg;q=0.5')
     assert mimeaccept._parsed == [('image/png', 1), ('image/jpg', 0.5)]
-    mimeaccept = MIMEAccept('Content-Type', 'image, image/jpg;q=0.5')
+    mimeaccept = MIMEAccept('image, image/jpg;q=0.5')
     assert mimeaccept._parsed == [('image/jpg', 0.5)]
-    mimeaccept = MIMEAccept('Content-Type', '*/*')
+    mimeaccept = MIMEAccept('*/*')
     assert mimeaccept._parsed == [('*/*', 1)]
-    mimeaccept = MIMEAccept('Content-Type', '*/png')
+    mimeaccept = MIMEAccept('*/png')
     assert mimeaccept._parsed == []
-    mimeaccept = MIMEAccept('Content-Type', 'image/*')
+    mimeaccept = MIMEAccept('image/*')
     assert mimeaccept._parsed == [('image/*', 1)]
 
 def test_accept_html():
-    mimeaccept = MIMEAccept('Content-Type', 'image/jpg')
+    mimeaccept = MIMEAccept('image/jpg')
     assert not mimeaccept.accept_html()
-    mimeaccept = MIMEAccept('Content-Type', 'image/jpg, text/html')
+    mimeaccept = MIMEAccept('image/jpg, text/html')
     assert mimeaccept.accept_html()
 
 def test_match():
-    mimeaccept = MIMEAccept('Content-Type', 'image/jpg')
+    mimeaccept = MIMEAccept('image/jpg')
     assert mimeaccept._match('image/jpg', 'image/jpg')
     assert mimeaccept._match('image/*', 'image/jpg')
     assert mimeaccept._match('*/*', 'image/jpg')
     assert_raises(ValueError, mimeaccept._match, 'image/jpg', '*/*')
 
 def test_accept_json():
-    mimeaccept = MIMEAccept('Accept', 'text/html, *; q=.2, */*; q=.2')
+    mimeaccept = MIMEAccept('text/html, *; q=.2, */*; q=.2')
     assert mimeaccept.best_match(['application/json']) == 'application/json'
 
 # property tests
     eq_(desc.fget(req).header_value, 'baz')
 
 def test_accept_property_fset_acceptclass():
-    desc = accept_property('Accept-Charset', '14.2')
     req = Request.blank('/', environ={'envkey': 'envval'})
-    desc.fset(req, ['utf-8', 'latin-11'])
-    eq_(desc.fget(req).header_value, 'utf-8, latin-11, iso-8859-1')
+    req.accept_charset = ['utf-8', 'latin-11']
+    eq_(req.accept_charset.header_value, 'utf-8, latin-11, iso-8859-1')
 
 def test_accept_property_fdel():
     desc = accept_property('Accept-Charset', '14.2')

File webob/acceptparse.py

 
 
 
-def parse_accept(value):
-    """
-    Parses an ``Accept-*`` style header.
-
-    A list of ``[(value, quality), ...]`` is returned.  ``quality``
-    will be 1 if it was not given.
-    """
-    result = []
-    for match in part_re.finditer(','+value):
-        name = match.group(1)
-        if name == 'q':
-            continue
-        quality = match.group(2) or ''
-        if not quality:
-            quality = 1
-        else:
-            try:
-                quality = max(min(float(quality), 1), 0)
-            except ValueError:
-                quality = 1
-        result.append((name, quality))
-    return result
 
 def _warn_first_match():
     # TODO: remove .first_match in version 1.3
     ``accept_obj + 'accept_thing'`` to get a new object
     """
 
-    def __init__(self, header_name, header_value):
-        self.header_name = header_name
+    def __init__(self, header_value):
         self.header_value = header_value
-        self._parsed = parse_accept(header_value)
-        if header_name == 'Accept-Charset':
-            for k, v in self._parsed:
-                if k == '*' or k == 'iso-8859-1':
-                    break
-            else:
-                self._parsed.append(('iso-8859-1', 1))
-        elif header_name == 'Accept-Language':
-            self._match = self._match_lang
+        self._parsed = list(self.parse(header_value))
         self._parsed_nonzero = [(m,q) for (m,q) in self._parsed if q]
 
+    @staticmethod
+    def parse(value):
+        """
+        Parse ``Accept-*`` style header.
+
+        Return iterator of ``(value, quality)`` pairs.
+        ``quality`` defaults to 1.
+        """
+        for match in part_re.finditer(','+value):
+            name = match.group(1)
+            if name == 'q':
+                continue
+            quality = match.group(2) or ''
+            if quality:
+                try:
+                    quality = max(min(float(quality), 1), 0)
+                    yield (name, quality)
+                    continue
+                except ValueError:
+                    pass
+            yield (name, 1)
+
 
     def __repr__(self):
-        return '<%s at 0x%x %s: %s>' % (
-            self.__class__.__name__,
-            abs(id(self)),
-            self.header_name, str(self))
+        return '<%s(%r)>' % (self.__class__.__name__, str(self))
 
     def __str__(self):
         result = []
             new_value = other
         else:
             new_value = my_value + ', ' + other
-        return self.__class__(self.header_name, new_value)
+        return self.__class__(new_value)
 
     def __radd__(self, other):
         return self.__add__(other, True)
         _check_offer(offer)
         return mask == '*' or offer.lower() == mask.lower()
 
-    def _match_lang(self, mask, item):
-        return (mask == '*'
-            or item.lower() == mask.lower()
-            or item.lower().split('-')[0] == mask.lower()
-        )
-
 
 
 class NilAccept(object):
 
     MasterClass = Accept
 
-    def __init__(self, header_name):
-        self.header_name = header_name
-
     def __repr__(self):
-        return '<%s for %s: %s>' % (
-            self.__class__.__name__, self.header_name, self.MasterClass)
+        return '<%s: %s>' % (self.__class__.__name__, self.MasterClass)
 
     def __str__(self):
         return ''
         if isinstance(item, self.MasterClass):
             return item
         else:
-            return self.MasterClass(self.header_name, '') + item
+            return self.MasterClass('') + item
 
     def __radd__(self, item):
         if isinstance(item, self.MasterClass):
             return item
         else:
-            return item + self.MasterClass(self.header_name, '')
+            return item + self.MasterClass('')
 
     def __contains__(self, item):
         _check_offer(item)
     def __contains__(self, item):
         return False
 
+class AcceptCharset(Accept):
+    @staticmethod
+    def parse(value):
+        latin1_found = False
+        for m, q in Accept.parse(value):
+            if m == '*' or m == 'iso-8859-1':
+                latin1_found = True
+            yield m, q
+        if not latin1_found:
+            yield ('iso-8859-1', 1)
+
+class AcceptLanguage(Accept):
+    def _match(self, mask, item):
+        return (mask == '*'
+            or item.lower() == mask.lower()
+            or item.lower().split('-')[0] == mask.lower()
+        )
+
 
 class MIMEAccept(Accept):
     """
 
         This class knows about mime wildcards, like ``image/*``
     """
-    def __init__(self, header_name, header_value):
-        Accept.__init__(self, header_name, header_value)
-        parsed = []
-        for mask, q in self._parsed:
+    @staticmethod
+    def parse(value):
+        for mask, q in Accept.parse(value):
             try:
                 mask_major, mask_minor = mask.split('/')
             except ValueError:
                 continue
             if mask_major == '*' and mask_minor != '*':
                 continue
-            parsed.append((mask, q))
-        self._parsed = parsed
-        self._parsed_nonzero = [(m,q) for (m,q) in self._parsed if q]
+            yield (mask, q)
 
     def accept_html(self):
         """
     def fget(req):
         value = req.environ.get(key)
         if not value:
-            return NilClass(header)
-        return AcceptClass(header, value)
+            return NilClass()
+        return AcceptClass(value)
     def fset(req, val):
         if val:
             if isinstance(val, (list, tuple, dict)):
-                val = AcceptClass(header, '') + val
+                val = AcceptClass('') + val
             val = str(val)
         req.environ[key] = val or None
     def fdel(req):

File webob/request.py

     from cStringIO import StringIO # pragma nocover
 
 from webob.headers import EnvironHeaders
-from webob.acceptparse import accept_property, Accept, MIMEAccept, NilAccept, MIMENilAccept, NoAccept
+from webob.acceptparse import accept_property, Accept, MIMEAccept, AcceptCharset, NilAccept, MIMENilAccept, NoAccept, AcceptLanguage
 from webob.multidict import TrackableMultiDict, MultiDict, UnicodeMultiDict, NestedMultiDict, NoVars
 from webob.cachecontrol import CacheControl, serialize_cache_control
 from webob.etag import etag_property, AnyETag, NoETag
 
 
     accept = accept_property('Accept', '14.1', MIMEAccept, MIMENilAccept)
-    accept_charset = accept_property('Accept-Charset', '14.2')
+    accept_charset = accept_property('Accept-Charset', '14.2', AcceptCharset)
     accept_encoding = accept_property('Accept-Encoding', '14.3', NilClass=NoAccept)
-    accept_language = accept_property('Accept-Language', '14.4')
+    accept_language = accept_property('Accept-Language', '14.4', AcceptLanguage)
 
     authorization = converter(
         environ_getter('HTTP_AUTHORIZATION', None, '14.8'),