Fixed #20094 - Be more careful when checking for Iterator

Python 2.6 has some different behaviour when checking
isinstance(foo, collections.Iterator).
This commit is contained in:
Marc Tamlyn 2013-03-20 10:47:56 +00:00 committed by Claude Paroz
parent f7795e968d
commit 829dc3c5a6
6 changed files with 43 additions and 6 deletions

View File

@ -1,6 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import collections
import copy import copy
import datetime import datetime
import decimal import decimal
@ -18,6 +17,7 @@ from django.core import exceptions, validators
from django.utils.datastructures import DictWrapper from django.utils.datastructures import DictWrapper
from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.functional import curry, total_ordering from django.utils.functional import curry, total_ordering
from django.utils.itercompat import is_iterator
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -488,7 +488,7 @@ class Field(object):
return bound_field_class(self, fieldmapping, original) return bound_field_class(self, fieldmapping, original)
def _get_choices(self): def _get_choices(self):
if isinstance(self._choices, collections.Iterator): if is_iterator(self._choices):
choices, self._choices = tee(self._choices) choices, self._choices = tee(self._choices)
return choices return choices
else: else:

View File

@ -4,7 +4,6 @@ Code to manage the creation and SQL rendering of 'where' constraints.
from __future__ import absolute_import from __future__ import absolute_import
import collections
import datetime import datetime
from itertools import repeat from itertools import repeat
@ -12,6 +11,7 @@ from django.conf import settings
from django.db.models.fields import DateTimeField, Field from django.db.models.fields import DateTimeField, Field
from django.db.models.sql.datastructures import EmptyResultSet, Empty from django.db.models.sql.datastructures import EmptyResultSet, Empty
from django.db.models.sql.aggregates import Aggregate from django.db.models.sql.aggregates import Aggregate
from django.utils.itercompat import is_iterator
from django.utils.six.moves import xrange from django.utils.six.moves import xrange
from django.utils import timezone from django.utils import timezone
from django.utils import tree from django.utils import tree
@ -58,7 +58,7 @@ class WhereNode(tree.Node):
if not isinstance(data, (list, tuple)): if not isinstance(data, (list, tuple)):
return data return data
obj, lookup_type, value = data obj, lookup_type, value = data
if isinstance(value, collections.Iterator): if is_iterator(value):
# Consume any generators immediately, so that we can determine # Consume any generators immediately, so that we can determine
# emptiness and transform any non-empty values correctly. # emptiness and transform any non-empty values correctly.
value = list(value) value = list(value)

View File

@ -4,10 +4,12 @@ Where possible, we try to use the system-native version and only fall back to
these implementations if necessary. these implementations if necessary.
""" """
from django.utils.six.moves import builtins import collections
import itertools import itertools
import sys
import warnings import warnings
def is_iterable(x): def is_iterable(x):
"A implementation independent way of checking for iterables" "A implementation independent way of checking for iterables"
try: try:
@ -17,6 +19,17 @@ def is_iterable(x):
else: else:
return True return True
def is_iterator(x):
"""An implementation independent way of checking for iterators
Python 2.6 has a different implementation of collections.Iterator which
accepts anything with a `next` method. 2.7+ requires and `__iter__` method
as well.
"""
if sys.version_info >= (2, 7):
return isinstance(x, collections.Iterator)
return isinstance(x, collections.Iterator) and hasattr(x, '__iter__')
def product(*args, **kwds): def product(*args, **kwds):
warnings.warn("django.utils.itercompat.product is deprecated; use the native version instead", warnings.warn("django.utils.itercompat.product is deprecated; use the native version instead",
DeprecationWarning, stacklevel=2) DeprecationWarning, stacklevel=2)

View File

@ -0,0 +1,11 @@
from django.test import TestCase
from .models import Category, Thing
class TestIsIterator(TestCase):
def test_regression(self):
"""This failed on Django 1.5/Py2.6 because category has a next method."""
category = Category.objects.create(name='category')
Thing.objects.create(category=category)
Thing.objects.filter(category=category)

View File

@ -1 +1,13 @@
# Test runner needs a models.py file. from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
def next(self):
return self
class Thing(models.Model):
name = models.CharField(max_length=100)
category = models.ForeignKey(Category)

View File

@ -18,6 +18,7 @@ from .feedgenerator import FeedgeneratorTest
from .functional import FunctionalTestCase from .functional import FunctionalTestCase
from .html import TestUtilsHtml from .html import TestUtilsHtml
from .http import TestUtilsHttp, ETagProcessingTests, HttpDateProcessingTests from .http import TestUtilsHttp, ETagProcessingTests, HttpDateProcessingTests
from .itercompat import TestIsIterator
from .ipv6 import TestUtilsIPv6 from .ipv6 import TestUtilsIPv6
from .jslex import JsToCForGettextTest, JsTokensTest from .jslex import JsToCForGettextTest, JsTokensTest
from .module_loading import (CustomLoader, DefaultLoader, EggLoader, from .module_loading import (CustomLoader, DefaultLoader, EggLoader,