Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #20094 - Be more careful when checking for Iterator

Python 2.6 has some different behaviour when checking
isinstance(foo, collections.Iterator).
  • Loading branch information...
commit 829dc3c5a64d3fa203b8cc0055e83cf23addfee3 1 parent f7795e9
Marc Tamlyn authored March 20, 2013 claudep committed March 22, 2013
4  django/db/models/fields/__init__.py
... ...
@@ -1,6 +1,5 @@
1 1
 from __future__ import unicode_literals
2 2
 
3  
-import collections
4 3
 import copy
5 4
 import datetime
6 5
 import decimal
@@ -18,6 +17,7 @@
18 17
 from django.utils.datastructures import DictWrapper
19 18
 from django.utils.dateparse import parse_date, parse_datetime, parse_time
20 19
 from django.utils.functional import curry, total_ordering
  20
+from django.utils.itercompat import is_iterator
21 21
 from django.utils.text import capfirst
22 22
 from django.utils import timezone
23 23
 from django.utils.translation import ugettext_lazy as _
@@ -488,7 +488,7 @@ def bind(self, fieldmapping, original, bound_field_class):
488 488
         return bound_field_class(self, fieldmapping, original)
489 489
 
490 490
     def _get_choices(self):
491  
-        if isinstance(self._choices, collections.Iterator):
  491
+        if is_iterator(self._choices):
492 492
             choices, self._choices = tee(self._choices)
493 493
             return choices
494 494
         else:
4  django/db/models/sql/where.py
@@ -4,7 +4,6 @@
4 4
 
5 5
 from __future__ import absolute_import
6 6
 
7  
-import collections
8 7
 import datetime
9 8
 from itertools import repeat
10 9
 
@@ -12,6 +11,7 @@
12 11
 from django.db.models.fields import DateTimeField, Field
13 12
 from django.db.models.sql.datastructures import EmptyResultSet, Empty
14 13
 from django.db.models.sql.aggregates import Aggregate
  14
+from django.utils.itercompat import is_iterator
15 15
 from django.utils.six.moves import xrange
16 16
 from django.utils import timezone
17 17
 from django.utils import tree
@@ -58,7 +58,7 @@ def _prepare_data(self, data):
58 58
         if not isinstance(data, (list, tuple)):
59 59
             return data
60 60
         obj, lookup_type, value = data
61  
-        if isinstance(value, collections.Iterator):
  61
+        if is_iterator(value):
62 62
             # Consume any generators immediately, so that we can determine
63 63
             # emptiness and transform any non-empty values correctly.
64 64
             value = list(value)
15  django/utils/itercompat.py
@@ -4,10 +4,12 @@
4 4
 these implementations if necessary.
5 5
 """
6 6
 
7  
-from django.utils.six.moves import builtins
  7
+import collections
8 8
 import itertools
  9
+import sys
9 10
 import warnings
10 11
 
  12
+
11 13
 def is_iterable(x):
12 14
     "A implementation independent way of checking for iterables"
13 15
     try:
@@ -17,6 +19,17 @@ def is_iterable(x):
17 19
     else:
18 20
         return True
19 21
 
  22
+def is_iterator(x):
  23
+    """An implementation independent way of checking for iterators
  24
+
  25
+    Python 2.6 has a different implementation of collections.Iterator which
  26
+    accepts anything with a `next` method. 2.7+ requires and `__iter__` method
  27
+    as well.
  28
+    """
  29
+    if sys.version_info >= (2, 7):
  30
+        return isinstance(x, collections.Iterator)
  31
+    return isinstance(x, collections.Iterator) and hasattr(x, '__iter__')
  32
+
20 33
 def product(*args, **kwds):
21 34
     warnings.warn("django.utils.itercompat.product is deprecated; use the native version instead",
22 35
                   DeprecationWarning, stacklevel=2)
11  tests/utils_tests/itercompat.py
... ...
@@ -0,0 +1,11 @@
  1
+from django.test import TestCase
  2
+
  3
+from .models import Category, Thing
  4
+
  5
+
  6
+class TestIsIterator(TestCase):
  7
+    def test_regression(self):
  8
+        """This failed on Django 1.5/Py2.6 because category has a next method."""
  9
+        category = Category.objects.create(name='category')
  10
+        Thing.objects.create(category=category)
  11
+        Thing.objects.filter(category=category)
14  tests/utils_tests/models.py
... ...
@@ -1 +1,13 @@
1  
-# Test runner needs a models.py file.
  1
+from django.db import models
  2
+
  3
+
  4
+class Category(models.Model):
  5
+    name = models.CharField(max_length=100)
  6
+
  7
+    def next(self):
  8
+        return self
  9
+
  10
+
  11
+class Thing(models.Model):
  12
+    name = models.CharField(max_length=100)
  13
+    category = models.ForeignKey(Category)
1  tests/utils_tests/tests.py
@@ -18,6 +18,7 @@
18 18
 from .functional import FunctionalTestCase
19 19
 from .html import TestUtilsHtml
20 20
 from .http import TestUtilsHttp, ETagProcessingTests, HttpDateProcessingTests
  21
+from .itercompat import TestIsIterator
21 22
 from .ipv6 import TestUtilsIPv6
22 23
 from .jslex import JsToCForGettextTest, JsTokensTest
23 24
 from .module_loading import (CustomLoader, DefaultLoader, EggLoader,

0 notes on commit 829dc3c

Please sign in to comment.
Something went wrong with that request. Please try again.