Commits

jason kirtland committed 8161193

- util.Headers is now mostly compatible with the stdlib
wsgiref.headers.Headers class, with one difference in __getitem__ behavior.
Passes the stdlib test suite with that exception.

- Added Headers.setdefault().

- Headers.add() accepts keyword parameters for constructing structured header
values such as 'attachment; filename="foo.bar"'.

Comments (0)

Files changed (2)

tests/test_utils.py

         ('Content-Type', 'foo/bar'),
         ('X-Foo', 'bar')
     ]
+    assert str(headers) == (
+        "Content-Type: foo/bar\r\n"
+        "X-Foo: bar\r\n"
+        "\r\n")
+    assert str(Headers()) == "\r\n"
+
+    # extended add
+    headers.add('Content-Disposition', 'attachment', filename='foo')
+    assert headers['Content-Disposition'] == 'attachment; filename="foo"'
+
+    headers.add('x', 'y', z='"')
+    assert headers['x'] == r'y; z="\""'
 
     # defaults
     headers = Headers({
     assert headers.get('x-Bar') == '1'
     assert headers.get('Content-Type') == 'text/plain'
 
+    assert headers.setdefault('X-Foo', 'nope') == 'bar'
+    assert headers.setdefault('X-Bar', 'nope') == '1'
+    assert headers.setdefault('X-Baz', 'quux') == 'quux'
+    assert headers.setdefault('X-Baz', 'nope') == 'quux'
+    headers.pop('X-Baz')
+
     # type conversion
     assert headers.get('x-bar', type=int) == 1
     assert headers.getlist('x-bar', type=int) == [1, 2]

werkzeug/utils.py

     From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
     subclass of the `BadRequest` HTTP exception and will render a page for a
     ``400 BAD REQUEST`` if catched in a catch-all for HTTP exceptions.
+
+    Headers is mostly compatible with the Python wsgiref.headers.Headers
+    class, with the exception of __getitem__.  wsgiref will return None for
+    `headers['missing']`, whereas `Headers` will raise a KeyError.
     """
 
     #: the key error this class raises.  Because of circular dependencies
                 result.append(v)
         return result
 
+    def get_all(self, name):
+        """Return a list of all the values for the named field.
+
+        This method is compatible with the wsgiref `Headers` method
+        of the same name.
+        """
+        return self.getlist(name)
+
     def iteritems(self, lower=False):
         for key, value in self:
             if lower:
         """Yield ``(key, value)`` tuples."""
         return iter(self._list)
 
-    def add(self, key, value):
-        """add a new header tuple to the list"""
-        self._list.append((key, value))
+    def __len__(self):
+        return len(self._list)
+
+    def add(self, _key, _value, **_kw):
+        """Add a new header tuple to the list.
+
+        Keyword arguments can specify additional parameters for the header
+        value, with underscores converted to dashes::
+
+        >>> d = Headers()
+        >>> d.add('Content-Type', 'text/plain')
+        >>> d.add('Content-Disposition', 'attachment', filename='foo.png')
+
+        """
+        if not _kw:
+            self._list.append((_key, _value))
+        else:
+            segments = []
+            if _value is not None:
+                segments.append(_value)
+            for key, value in _kw.iteritems():
+                key = key.replace('_', '-')
+                if value is None:
+                    segments.append(key)
+                else:
+                    value = value.replace('\\', r'\\').replace('"', r'\"')
+                    segments.append('%s="%s"' % (key, value))
+            self._list.append((_key, '; '.join(segments)))
+
+    def add_header(self, _key, _value, **_kw):
+        """Add a new header tuple to the list.
+
+        An alias for `add`, compatible with the wsgiref `Headers` method of
+        the same name.
+        """
+        self.add(_key, _value, **_kw)
 
     def clear(self):
         """clears all headers"""
         self._list[idx + 1:] = [(k, v) for k, v in self._list[idx + 1:]
                                 if k.lower() != lc_key]
 
+    def setdefault(self, key, value):
+        """Return the first value of key, setting to value if not already
+        present."""
+        if key in self:
+            return self[key]
+        self.set(key, value)
+        return value
+
     def __setitem__(self, key, value):
         """Like `set()` but also supports index/slice based setting."""
         if isinstance(key, (slice, int, long)):
     def __copy__(self):
         return self.copy()
 
+    def __str__(self, charset='utf-8'):
+        """Returns formatted headers suitable for HTTP transmission."""
+        strs = []
+        for key, value in self.to_list(charset):
+            strs.append('%s: %s' % (key, value))
+        strs.append('\r\n')
+        return '\r\n'.join(strs)
+
     def __repr__(self):
         return '%s(%r)' % (
             self.__class__.__name__,