Repository: python-pillow/Pillow

Problem statement:
RuntimeError: dictionary changed size during iteration 
I got this error:

```/home/user/.virtualenvs/myproj/lib/python3.2/site-packages/Pillow-1.7.8-py3.2-linux-x86_64.egg/PIL/JpegImagePlugin.py in _getexif(self)
    379         info = TiffImagePlugin.ImageFileDirectory(head)
    380         info.load(file)
--> 381         for key, value in list(info.items()):
    382             exif[key] = fixup(value)
    383         # get exif extension

/home/user/.virtualenvs/myproj/lib/python3.2/_abcoll.py in __iter__(self)
    431 
    432     def **iter**(self):
--> 433         for key in self._mapping:
    434             yield (key, self._mapping[key])
    435 

RuntimeError: dictionary changed size during iteration

``````

When execute this code:
```from sorl.thumbnail import get_thumbnail; im = get_thumbnail('/home/user/webapps/myproj/yourpics/media/users/user/images/after rain.jpg', '32x43')```
``````



Hints:
Any idea why?

When i commented line 236 `del self.tagdata[tag]` in file `PIL/TiffImagePlugin.py` error disappeared.

OK…


Patch:
diff --git a/PIL/Image.py b/PIL/Image.py
--- a/PIL/Image.py
+++ b/PIL/Image.py
@@ -541,7 +541,11 @@ def tobytes(self, encoder_name="raw", *args):
     if bytes is str:
         # Declare tostring as alias to tobytes
         def tostring(self, *args, **kw):
-            warnings.warn('tostring() is deprecated. Please call tobytes() instead.', DeprecationWarning)
+            warnings.warn(
+                'tostring() is deprecated. Please call tobytes() instead.',
+                DeprecationWarning,
+                stacklevel=2,
+            )
             return self.tobytes(*args, **kw)
 
     ##
@@ -1809,7 +1813,11 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
 if bytes is str:
     # Declare fromstring as an alias for frombytes
     def fromstring(*args, **kw):
-        warnings.warn('fromstring() is deprecated. Please call frombytes() instead.', DeprecationWarning)
+        warnings.warn(
+            'fromstring() is deprecated. Please call frombytes() instead.',
+            DeprecationWarning,
+            stacklevel=2
+        )
         return frombytes(*args, **kw)
 
 ##
diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py
--- a/PIL/ImageFile.py
+++ b/PIL/ImageFile.py
@@ -205,10 +205,10 @@ def load(self):
                             break
                         else:
                             raise IndexError(ie)
-                            
+
                     if not s: # truncated jpeg
                         self.tile = []
-                        
+
                         if LOAD_TRUNCATED_IMAGES:
                             break
                         else:
@@ -226,7 +226,7 @@ def load(self):
 
         self.fp = None # might be shared
 
-        if (t == 0 or not LOAD_TRUNCATED_IMAGES) and not self.map and e < 0:
+        if (not LOAD_TRUNCATED_IMAGES or t == 0) and not self.map and e < 0:
             # still raised if decoder fails to return anything
             raise_ioerror(e)
 
diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py
--- a/PIL/TiffImagePlugin.py
+++ b/PIL/TiffImagePlugin.py
@@ -269,6 +269,11 @@ def __delitem__(self, tag):
     def __iter__(self):
         return itertools.chain(self.tags.__iter__(), self.tagdata.__iter__())
 
+    def items(self):
+        keys = list(self.__iter__())
+        values = [self[key] for key in keys]
+        return zip(keys, values)
+
     # load primitives
 
     load_dispatch = {}


Test patch:
diff --git a/Tests/run.py b/Tests/run.py
--- a/Tests/run.py
+++ b/Tests/run.py
@@ -91,5 +91,6 @@ def tests(n):
     print(skipped)
 if failure:
     print("***", tests(failure), "of", (success + failure), "failed.")
+    sys.exit(1)
 else:
     print(tests(success), "passed.")
diff --git a/Tests/test_000_sanity.py b/Tests/test_000_sanity.py
--- a/Tests/test_000_sanity.py
+++ b/Tests/test_000_sanity.py
@@ -12,7 +12,7 @@
 # Create an image and do stuff with it.
 im = PIL.Image.new("1", (100, 100))
 assert (im.mode, im.size) == ('1', (100, 100))
-assert len(im.tobytes() if py3 else im.tostring()) == 1300
+assert len(im.tobytes()) == 1300
 
 # Create images in all remaining major modes.
 im = PIL.Image.new("L", (100, 100))
diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py
--- a/Tests/test_file_jpeg.py
+++ b/Tests/test_file_jpeg.py
@@ -177,3 +177,8 @@ def test(junk):
     assert_no_exception(lambda: test(4))
     assert_no_exception(lambda: test(8))
     assert_exception(IOError, lambda: test(10))
+
+def test_exif():
+    im = Image.open("Tests/images/pil_sample_rgb.jpg")
+    info = im._getexif()
+    assert_equal(info[305], 'Adobe Photoshop CS Macintosh')
diff --git a/Tests/test_image_frombytes.py b/Tests/test_image_frombytes.py
new file mode 100644
--- /dev/null
+++ b/Tests/test_image_frombytes.py
@@ -0,0 +1,10 @@
+from tester import *
+
+from PIL import Image
+
+def test_sanity():
+    im1 = lena()
+    im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes())
+
+    assert_image_equal(im1, im2)
+
diff --git a/Tests/test_image_fromstring.py b/Tests/test_image_fromstring.py
deleted file mode 100644
--- a/Tests/test_image_fromstring.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from tester import *
-
-from PIL import Image
-
-def test_sanity():
-
-    im1 = lena()
-    im2 = None
-
-    if py3:
-        im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes())
-    else:
-        im2 = Image.fromstring(im1.mode, im1.size, im1.tostring())
-
-    assert_image_equal(im1, im2)
-
diff --git a/Tests/test_image_tostring.py b/Tests/test_image_tobytes.py
similarity index 64%
rename from Tests/test_image_tostring.py
rename to Tests/test_image_tobytes.py
--- a/Tests/test_image_tostring.py
+++ b/Tests/test_image_tobytes.py
@@ -3,5 +3,5 @@
 from PIL import Image
 
 def test_sanity():
-    data = lena().tobytes() if py3 else lena().tostring()
+    data = lena().tobytes()
     assert_true(isinstance(data, bytes))
diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py
--- a/Tests/test_lib_pack.py
+++ b/Tests/test_lib_pack.py
@@ -16,7 +16,7 @@ def pack(mode, rawmode):
         if py3:
             return list(im.tobytes("raw", rawmode))
         else:
-            return [ord(c) for c in im.tostring("raw", rawmode)]
+            return [ord(c) for c in im.tobytes("raw", rawmode)]
 
     assert_equal(pack("1", "1"), [128])
     assert_equal(pack("1", "1;I"), [0])
@@ -53,10 +53,10 @@ def unpack(mode, rawmode, bytes_):
 
         if py3:
             data = bytes(range(1,bytes_+1))
-            im = Image.frombytes(mode, (1, 1), data, "raw", rawmode, 0, 1)
         else:
             data = ''.join(chr(i) for i in range(1,bytes_+1))
-            im = Image.fromstring(mode, (1, 1), data, "raw", rawmode, 0, 1)
+
+        im = Image.frombytes(mode, (1, 1), data, "raw", rawmode, 0, 1)
 
         return im.getpixel((0, 0))
 
@@ -67,7 +67,7 @@ def unpack_1(mode, rawmode, value):
         if py3:
             im = Image.frombytes(mode, (8, 1), bytes([value]), "raw", rawmode, 0, 1)
         else:
-            im = Image.fromstring(mode, (8, 1), chr(value), "raw", rawmode, 0, 1)
+            im = Image.frombytes(mode, (8, 1), chr(value), "raw", rawmode, 0, 1)
 
         return tuple(im.getdata())
 
diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py
--- a/Tests/test_mode_i16.py
+++ b/Tests/test_mode_i16.py
@@ -79,18 +79,15 @@ def basic(mode):
     basic("I")
 
 
-def test_tostring():
+def test_tobytes():
 
-    def tostring(mode):
-        if py3:
-            return Image.new(mode, (1, 1), 1).tobytes()
-        else:
-            return Image.new(mode, (1, 1), 1).tostring()
+    def tobytes(mode):
+        return Image.new(mode, (1, 1), 1).tobytes()
 
-    assert_equal(tostring("L"), b"\x01")
-    assert_equal(tostring("I;16"), b"\x01\x00")
-    assert_equal(tostring("I;16B"), b"\x00\x01")
-    assert_equal(tostring("I"), b"\x01\x00\x00\x00")
+    assert_equal(tobytes("L"), b"\x01")
+    assert_equal(tobytes("I;16"), b"\x01\x00")
+    assert_equal(tobytes("I;16B"), b"\x00\x01")
+    assert_equal(tobytes("I"), b"\x01\x00\x00\x00")
 
 
 def test_convert():
diff --git a/Tests/tester.py b/Tests/tester.py
--- a/Tests/tester.py
+++ b/Tests/tester.py
@@ -144,12 +144,9 @@ def assert_image_equal(a, b, msg=None):
         failure(msg or "got mode %r, expected %r" % (a.mode, b.mode))
     elif a.size != b.size:
         failure(msg or "got size %r, expected %r" % (a.size, b.size))
-    elif py3 and a.tobytes() != b.tobytes():
+    elif a.tobytes() != b.tobytes():
         failure(msg or "got different content")
         # generate better diff?
-    elif not py3 and a.tostring() != b.tostring():
-        failure(msg or "got different content")
-        # same complaint?
     else:
         success()