Sergey Schetinin avatar Sergey Schetinin committed 3445ea1

add warning when client code uses MultiDict.update() and seemingly expects .extend() semantics

Comments (0)

Files changed (2)

tests/test_misc.py

-import cgi
-from webob import html_escape
+import cgi, sys
+from cStringIO import StringIO
+from webob import html_escape, Response
 from webob.multidict import *
 from nose.tools import eq_ as eq, assert_raises
 
     ub = UnicodeMultiDict(multi=ua, encoding='utf-8')
     eq(ub.getall('key'), [u'\xf8'])
     eq(repr(ub.getall('fs')), "[FieldStorage(None, u'\\xf8', [])]")
+
+def test_multidict_update_warning():
+    fcapture = StringIO()
+    stderr, sys.stderr = sys.stderr, fcapture
+
+    # test warning when duplicate keys are passed
+    r = Response()
+    r.headers.update([
+        ('Set-Cookie', 'a=b'),
+        ('Set-Cookie', 'x=y'),
+    ])
+    fcapture.seek(0)
+    assert 'Consider using .extend()' in fcapture.readline()
+
+    # but no warning on normal operation
+    fcapture.seek(0)
+    fcapture.truncate()
+    r.headers.update([('Set-Cookie', 'a=b')])
+    assert not fcapture.getvalue()
+
+    sys.stderr = stderr

webob/multidict.py

 """
 Gives a multi-value dictionary object (MultiDict) plus several wrappers
 """
-import cgi
-import copy
-import sys
-
+import cgi, copy, sys, warnings
 from UserDict import DictMixin
 
 
     def popitem(self):
         return self._items.pop()
 
+    def update(self, *args, **kw):
+        if args:
+            lst = args[0]
+            if len(lst) != len(dict(lst)):
+                # this does not catch the cases where we overwrite existing keys,
+                # but those would produce too many warning
+                msg = ("Behavior of MultiDict.update() has changed "
+                    "and overwrites duplicate keys. Consider using .extend()"
+                )
+                warnings.warn(msg, stacklevel=2)
+        DictMixin.update(self, *args, **kw)
+
     def extend(self, other=None, **kwargs):
         if other is None:
             pass
         result = MultiDict.popitem(self)
         self.tracker(self)
         return result
-    def update(self, other=None, **kwargs):
-        MultiDict.update(self, other, **kwargs)
+    def update(self, *args, **kwargs):
+        MultiDict.update(self, *args, **kwargs)
         self.tracker(self)
     def __repr__(self):
         items = ', '.join(['(%r, %r)' % v for v in self.iteritems()])
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.