Allow lists to be generated from any non-string iterable

The type check on the list field requires a list, though many
objects can be trivially iterated as lists, like sqlalchemy
lazy query objects. Relax the check slightly to fit a broader
range of "lists".

Change-Id: I514a0e6d445e9a14e3c0279b4e9bebfa0940337d
This commit is contained in:
Doug Wiegley 2019-02-16 11:29:02 -07:00
parent 8c989ebc64
commit bc203115b1
No known key found for this signature in database
GPG Key ID: 4D3C112B76BBDB5F
2 changed files with 42 additions and 1 deletions

View File

@ -13,6 +13,7 @@
# under the License.
import abc
import collections
import datetime
from distutils import versionpredicate
import re
@ -642,7 +643,9 @@ class CompoundFieldType(FieldType):
class List(CompoundFieldType):
def coerce(self, obj, attr, value):
if not isinstance(value, list):
if (not isinstance(value, collections.Iterable) or
isinstance(value, six.string_types + (collections.Mapping,))):
raise ValueError(_('A list is required in field %(attr)s, '
'not a %(type)s') %
{'attr': attr, 'type': type(value).__name__})

View File

@ -1229,3 +1229,41 @@ class TestIPV6Network(TestField):
invalid_vals = [x for x in self.coerce_bad_values]
for invalid_val in invalid_vals:
self.assertNotRegex(str(invalid_val), pattern)
class FakeCounter(six.Iterator):
def __init__(self):
self.n = 0
def __iter__(self):
return self
def __next__(self):
if self.n <= 4:
self.n += 1
return self.n
else:
raise StopIteration
class TestListTypes(test.TestCase):
def test_regular_list(self):
fields.List(fields.Integer).coerce(None, None, [1, 2])
def test_non_iterable(self):
self.assertRaises(ValueError,
fields.List(fields.Integer).coerce, None, None, 2)
def test_string_iterable(self):
self.assertRaises(ValueError,
fields.List(fields.Integer).coerce, None, None,
'hello')
def test_mapping_iterable(self):
self.assertRaises(ValueError,
fields.List(fields.Integer).coerce, None, None,
{'a': 1, 'b': 2})
def test_iter_class(self):
fields.List(fields.Integer).coerce(None, None, FakeCounter())