Skip to content

Commit 6bc06ce

Browse files
committed
Merge pull request #888 from dhermes/fix-887
Only allowing tuple or list for ctor inputs which assume a sequence
2 parents 0dcb839 + aa2c138 commit 6bc06ce

File tree

6 files changed

+86
-4
lines changed

6 files changed

+86
-4
lines changed

gcloud/_helpers.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,29 @@ def top(self):
7272
return self._stack[-1]
7373

7474

75+
def _ensure_tuple_or_list(arg_name, tuple_or_list):
76+
"""Ensures an input is a tuple or list.
77+
78+
This effectively reduces the iterable types allowed to a very short
79+
whitelist: list and tuple.
80+
81+
:type arg_name: string
82+
:param arg_name: Name of argument to use in error message.
83+
84+
:type tuple_or_list: sequence of string
85+
:param tuple_or_list: Sequence to be verified.
86+
87+
:rtype: list of string
88+
:returns: The ``tuple_or_list`` passed in cast to a ``list``.
89+
:raises: class:`TypeError` if the ``tuple_or_list`` is not a tuple or
90+
list.
91+
"""
92+
if not isinstance(tuple_or_list, (tuple, list)):
93+
raise TypeError('Expected %s to be a tuple or list. '
94+
'Received %r' % (arg_name, tuple_or_list))
95+
return list(tuple_or_list)
96+
97+
7598
class _LazyProperty(object):
7699
"""Descriptor for lazy loaded property.
77100

gcloud/datastore/entity.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
"""Class for representing a single entity in the Cloud Datastore."""
1616

1717

18+
from gcloud._helpers import _ensure_tuple_or_list
19+
20+
1821
class Entity(dict):
1922
"""Entities are akin to rows in a relational database
2023
@@ -76,7 +79,8 @@ class Entity(dict):
7679
def __init__(self, key=None, exclude_from_indexes=()):
7780
super(Entity, self).__init__()
7881
self.key = key
79-
self._exclude_from_indexes = set(exclude_from_indexes)
82+
self._exclude_from_indexes = set(_ensure_tuple_or_list(
83+
'exclude_from_indexes', exclude_from_indexes))
8084

8185
def __eq__(self, other):
8286
"""Compare two entities for equality.

gcloud/datastore/query.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import base64
1818

19+
from gcloud._helpers import _ensure_tuple_or_list
1920
from gcloud.datastore import _implicit_environ
2021
from gcloud.datastore import _datastore_v1_pb2 as datastore_pb
2122
from gcloud.datastore import helpers
@@ -90,9 +91,9 @@ def __init__(self,
9091
self._namespace = namespace
9192
self._ancestor = ancestor
9293
self._filters = list(filters)
93-
self._projection = list(projection)
94-
self._order = list(order)
95-
self._group_by = list(group_by)
94+
self._projection = _ensure_tuple_or_list('projection', projection)
95+
self._order = _ensure_tuple_or_list('order', order)
96+
self._group_by = _ensure_tuple_or_list('group_by', group_by)
9697

9798
@property
9899
def dataset_id(self):

gcloud/datastore/test_entity.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ def test_ctor_explicit(self):
5252
self.assertEqual(sorted(entity.exclude_from_indexes),
5353
sorted(_EXCLUDE_FROM_INDEXES))
5454

55+
def test_ctor_bad_exclude_from_indexes(self):
56+
BAD_EXCLUDE_FROM_INDEXES = object()
57+
key = _Key()
58+
self.assertRaises(TypeError, self._makeOne, key=key,
59+
exclude_from_indexes=BAD_EXCLUDE_FROM_INDEXES)
60+
5561
def test___eq_____ne___w_non_entity(self):
5662
from gcloud.datastore.key import Key
5763
key = Key(_KIND, _ID, dataset_id=_DATASET_ID)

gcloud/datastore/test_query.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,27 @@ def test_ctor_explicit(self):
7979
self.assertEqual(query.order, ORDER)
8080
self.assertEqual(query.group_by, GROUP_BY)
8181

82+
def test_ctor_bad_projection(self):
83+
_DATASET = 'DATASET'
84+
_KIND = 'KIND'
85+
BAD_PROJECTION = object()
86+
self.assertRaises(TypeError, self._makeOne, _KIND, _DATASET,
87+
projection=BAD_PROJECTION)
88+
89+
def test_ctor_bad_order(self):
90+
_DATASET = 'DATASET'
91+
_KIND = 'KIND'
92+
BAD_ORDER = object()
93+
self.assertRaises(TypeError, self._makeOne, _KIND, _DATASET,
94+
order=BAD_ORDER)
95+
96+
def test_ctor_bad_group_by(self):
97+
_DATASET = 'DATASET'
98+
_KIND = 'KIND'
99+
BAD_GROUP_BY = object()
100+
self.assertRaises(TypeError, self._makeOne, _KIND, _DATASET,
101+
group_by=BAD_GROUP_BY)
102+
82103
def test_namespace_setter_w_non_string(self):
83104
_DATASET = 'DATASET'
84105
query = self._makeOne(dataset_id=_DATASET)

gcloud/test__helpers.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,33 @@ def test_it(self):
4343
self.assertEqual(list(batches), [])
4444

4545

46+
class Test__ensure_tuple_or_list(unittest2.TestCase):
47+
48+
def _callFUT(self, arg_name, tuple_or_list):
49+
from gcloud._helpers import _ensure_tuple_or_list
50+
return _ensure_tuple_or_list(arg_name, tuple_or_list)
51+
52+
def test_valid_tuple(self):
53+
valid_tuple_or_list = ('a', 'b', 'c', 'd')
54+
result = self._callFUT('ARGNAME', valid_tuple_or_list)
55+
self.assertEqual(result, ['a', 'b', 'c', 'd'])
56+
57+
def test_valid_list(self):
58+
valid_tuple_or_list = ['a', 'b', 'c', 'd']
59+
result = self._callFUT('ARGNAME', valid_tuple_or_list)
60+
self.assertEqual(result, valid_tuple_or_list)
61+
62+
def test_invalid(self):
63+
invalid_tuple_or_list = object()
64+
with self.assertRaises(TypeError):
65+
self._callFUT('ARGNAME', invalid_tuple_or_list)
66+
67+
def test_invalid_iterable(self):
68+
invalid_tuple_or_list = 'FOO'
69+
with self.assertRaises(TypeError):
70+
self._callFUT('ARGNAME', invalid_tuple_or_list)
71+
72+
4673
class Test__LazyProperty(unittest2.TestCase):
4774

4875
def _getTargetClass(self):

0 commit comments

Comments
 (0)