• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 from tests.compat import mock, unittest
2 from boto.dynamodb2 import exceptions
3 from boto.dynamodb2.fields import (HashKey, RangeKey,
4                                    AllIndex, KeysOnlyIndex, IncludeIndex,
5                                    GlobalAllIndex, GlobalKeysOnlyIndex,
6                                    GlobalIncludeIndex)
7 from boto.dynamodb2.items import Item
8 from boto.dynamodb2.layer1 import DynamoDBConnection
9 from boto.dynamodb2.results import ResultSet, BatchGetResultSet
10 from boto.dynamodb2.table import Table
11 from boto.dynamodb2.types import (STRING, NUMBER, BINARY,
12                                   FILTER_OPERATORS, QUERY_OPERATORS)
13 from boto.exception import JSONResponseError
14 from boto.compat import six, long_type
15 
16 
17 FakeDynamoDBConnection = mock.create_autospec(DynamoDBConnection)
18 
19 
20 class SchemaFieldsTestCase(unittest.TestCase):
21     def test_hash_key(self):
22         hash_key = HashKey('hello')
23         self.assertEqual(hash_key.name, 'hello')
24         self.assertEqual(hash_key.data_type, STRING)
25         self.assertEqual(hash_key.attr_type, 'HASH')
26 
27         self.assertEqual(hash_key.definition(), {
28             'AttributeName': 'hello',
29             'AttributeType': 'S'
30         })
31         self.assertEqual(hash_key.schema(), {
32             'AttributeName': 'hello',
33             'KeyType': 'HASH'
34         })
35 
36     def test_range_key(self):
37         range_key = RangeKey('hello')
38         self.assertEqual(range_key.name, 'hello')
39         self.assertEqual(range_key.data_type, STRING)
40         self.assertEqual(range_key.attr_type, 'RANGE')
41 
42         self.assertEqual(range_key.definition(), {
43             'AttributeName': 'hello',
44             'AttributeType': 'S'
45         })
46         self.assertEqual(range_key.schema(), {
47             'AttributeName': 'hello',
48             'KeyType': 'RANGE'
49         })
50 
51     def test_alternate_type(self):
52         alt_key = HashKey('alt', data_type=NUMBER)
53         self.assertEqual(alt_key.name, 'alt')
54         self.assertEqual(alt_key.data_type, NUMBER)
55         self.assertEqual(alt_key.attr_type, 'HASH')
56 
57         self.assertEqual(alt_key.definition(), {
58             'AttributeName': 'alt',
59             'AttributeType': 'N'
60         })
61         self.assertEqual(alt_key.schema(), {
62             'AttributeName': 'alt',
63             'KeyType': 'HASH'
64         })
65 
66 
67 class IndexFieldTestCase(unittest.TestCase):
68     def test_all_index(self):
69         all_index = AllIndex('AllKeys', parts=[
70             HashKey('username'),
71             RangeKey('date_joined')
72         ])
73         self.assertEqual(all_index.name, 'AllKeys')
74         self.assertEqual([part.attr_type for part in all_index.parts], [
75             'HASH',
76             'RANGE'
77         ])
78         self.assertEqual(all_index.projection_type, 'ALL')
79 
80         self.assertEqual(all_index.definition(), [
81             {'AttributeName': 'username', 'AttributeType': 'S'},
82             {'AttributeName': 'date_joined', 'AttributeType': 'S'}
83         ])
84         self.assertEqual(all_index.schema(), {
85             'IndexName': 'AllKeys',
86             'KeySchema': [
87                 {
88                     'AttributeName': 'username',
89                     'KeyType': 'HASH'
90                 },
91                 {
92                     'AttributeName': 'date_joined',
93                     'KeyType': 'RANGE'
94                 }
95             ],
96             'Projection': {
97                 'ProjectionType': 'ALL'
98             }
99         })
100 
101     def test_keys_only_index(self):
102         keys_only = KeysOnlyIndex('KeysOnly', parts=[
103             HashKey('username'),
104             RangeKey('date_joined')
105         ])
106         self.assertEqual(keys_only.name, 'KeysOnly')
107         self.assertEqual([part.attr_type for part in keys_only.parts], [
108             'HASH',
109             'RANGE'
110         ])
111         self.assertEqual(keys_only.projection_type, 'KEYS_ONLY')
112 
113         self.assertEqual(keys_only.definition(), [
114             {'AttributeName': 'username', 'AttributeType': 'S'},
115             {'AttributeName': 'date_joined', 'AttributeType': 'S'}
116         ])
117         self.assertEqual(keys_only.schema(), {
118             'IndexName': 'KeysOnly',
119             'KeySchema': [
120                 {
121                     'AttributeName': 'username',
122                     'KeyType': 'HASH'
123                 },
124                 {
125                     'AttributeName': 'date_joined',
126                     'KeyType': 'RANGE'
127                 }
128             ],
129             'Projection': {
130                 'ProjectionType': 'KEYS_ONLY'
131             }
132         })
133 
134     def test_include_index(self):
135         include_index = IncludeIndex('IncludeKeys', parts=[
136             HashKey('username'),
137             RangeKey('date_joined')
138         ], includes=[
139             'gender',
140             'friend_count'
141         ])
142         self.assertEqual(include_index.name, 'IncludeKeys')
143         self.assertEqual([part.attr_type for part in include_index.parts], [
144             'HASH',
145             'RANGE'
146         ])
147         self.assertEqual(include_index.projection_type, 'INCLUDE')
148 
149         self.assertEqual(include_index.definition(), [
150             {'AttributeName': 'username', 'AttributeType': 'S'},
151             {'AttributeName': 'date_joined', 'AttributeType': 'S'}
152         ])
153         self.assertEqual(include_index.schema(), {
154             'IndexName': 'IncludeKeys',
155             'KeySchema': [
156                 {
157                     'AttributeName': 'username',
158                     'KeyType': 'HASH'
159                 },
160                 {
161                     'AttributeName': 'date_joined',
162                     'KeyType': 'RANGE'
163                 }
164             ],
165             'Projection': {
166                 'ProjectionType': 'INCLUDE',
167                 'NonKeyAttributes': [
168                     'gender',
169                     'friend_count',
170                 ]
171             }
172         })
173 
174     def test_global_all_index(self):
175         all_index = GlobalAllIndex('AllKeys', parts=[
176             HashKey('username'),
177             RangeKey('date_joined')
178         ],
179         throughput={
180             'read': 6,
181             'write': 2,
182         })
183         self.assertEqual(all_index.name, 'AllKeys')
184         self.assertEqual([part.attr_type for part in all_index.parts], [
185             'HASH',
186             'RANGE'
187         ])
188         self.assertEqual(all_index.projection_type, 'ALL')
189 
190         self.assertEqual(all_index.definition(), [
191             {'AttributeName': 'username', 'AttributeType': 'S'},
192             {'AttributeName': 'date_joined', 'AttributeType': 'S'}
193         ])
194         self.assertEqual(all_index.schema(), {
195             'IndexName': 'AllKeys',
196             'KeySchema': [
197                 {
198                     'AttributeName': 'username',
199                     'KeyType': 'HASH'
200                 },
201                 {
202                     'AttributeName': 'date_joined',
203                     'KeyType': 'RANGE'
204                 }
205             ],
206             'Projection': {
207                 'ProjectionType': 'ALL'
208             },
209             'ProvisionedThroughput': {
210                 'ReadCapacityUnits': 6,
211                 'WriteCapacityUnits': 2
212             }
213         })
214 
215     def test_global_keys_only_index(self):
216         keys_only = GlobalKeysOnlyIndex('KeysOnly', parts=[
217             HashKey('username'),
218             RangeKey('date_joined')
219         ],
220         throughput={
221             'read': 3,
222             'write': 4,
223         })
224         self.assertEqual(keys_only.name, 'KeysOnly')
225         self.assertEqual([part.attr_type for part in keys_only.parts], [
226             'HASH',
227             'RANGE'
228         ])
229         self.assertEqual(keys_only.projection_type, 'KEYS_ONLY')
230 
231         self.assertEqual(keys_only.definition(), [
232             {'AttributeName': 'username', 'AttributeType': 'S'},
233             {'AttributeName': 'date_joined', 'AttributeType': 'S'}
234         ])
235         self.assertEqual(keys_only.schema(), {
236             'IndexName': 'KeysOnly',
237             'KeySchema': [
238                 {
239                     'AttributeName': 'username',
240                     'KeyType': 'HASH'
241                 },
242                 {
243                     'AttributeName': 'date_joined',
244                     'KeyType': 'RANGE'
245                 }
246             ],
247             'Projection': {
248                 'ProjectionType': 'KEYS_ONLY'
249             },
250             'ProvisionedThroughput': {
251                 'ReadCapacityUnits': 3,
252                 'WriteCapacityUnits': 4
253             }
254         })
255 
256     def test_global_include_index(self):
257         # Lean on the default throughput
258         include_index = GlobalIncludeIndex('IncludeKeys', parts=[
259             HashKey('username'),
260             RangeKey('date_joined')
261         ], includes=[
262             'gender',
263             'friend_count'
264         ])
265         self.assertEqual(include_index.name, 'IncludeKeys')
266         self.assertEqual([part.attr_type for part in include_index.parts], [
267             'HASH',
268             'RANGE'
269         ])
270         self.assertEqual(include_index.projection_type, 'INCLUDE')
271 
272         self.assertEqual(include_index.definition(), [
273             {'AttributeName': 'username', 'AttributeType': 'S'},
274             {'AttributeName': 'date_joined', 'AttributeType': 'S'}
275         ])
276         self.assertEqual(include_index.schema(), {
277             'IndexName': 'IncludeKeys',
278             'KeySchema': [
279                 {
280                     'AttributeName': 'username',
281                     'KeyType': 'HASH'
282                 },
283                 {
284                     'AttributeName': 'date_joined',
285                     'KeyType': 'RANGE'
286                 }
287             ],
288             'Projection': {
289                 'ProjectionType': 'INCLUDE',
290                 'NonKeyAttributes': [
291                     'gender',
292                     'friend_count',
293                 ]
294             },
295             'ProvisionedThroughput': {
296                 'ReadCapacityUnits': 5,
297                 'WriteCapacityUnits': 5
298             }
299         })
300 
301     def test_global_include_index_throughput(self):
302         include_index = GlobalIncludeIndex('IncludeKeys', parts=[
303             HashKey('username'),
304             RangeKey('date_joined')
305         ], includes=[
306             'gender',
307             'friend_count'
308         ], throughput={
309             'read': 10,
310             'write': 8
311         })
312 
313         self.assertEqual(include_index.schema(), {
314             'IndexName': 'IncludeKeys',
315             'KeySchema': [
316                 {
317                     'AttributeName': 'username',
318                     'KeyType': 'HASH'
319                 },
320                 {
321                     'AttributeName': 'date_joined',
322                     'KeyType': 'RANGE'
323                 }
324             ],
325             'Projection': {
326                 'ProjectionType': 'INCLUDE',
327                 'NonKeyAttributes': [
328                     'gender',
329                     'friend_count',
330                 ]
331             },
332             'ProvisionedThroughput': {
333                 'ReadCapacityUnits': 10,
334                 'WriteCapacityUnits': 8
335             }
336         })
337 
338 
339 class ItemTestCase(unittest.TestCase):
340     if six.PY2:
341         assertCountEqual = unittest.TestCase.assertItemsEqual
342 
343     def setUp(self):
344         super(ItemTestCase, self).setUp()
345         self.table = Table('whatever', connection=FakeDynamoDBConnection())
346         self.johndoe = self.create_item({
347             'username': 'johndoe',
348             'first_name': 'John',
349             'date_joined': 12345,
350         })
351 
352     def create_item(self, data):
353         return Item(self.table, data=data)
354 
355     def test_initialization(self):
356         empty_item = Item(self.table)
357         self.assertEqual(empty_item.table, self.table)
358         self.assertEqual(empty_item._data, {})
359 
360         full_item = Item(self.table, data={
361             'username': 'johndoe',
362             'date_joined': 12345,
363         })
364         self.assertEqual(full_item.table, self.table)
365         self.assertEqual(full_item._data, {
366             'username': 'johndoe',
367             'date_joined': 12345,
368         })
369 
370     # The next couple methods make use of ``sorted(...)`` so we get consistent
371     # ordering everywhere & no erroneous failures.
372 
373     def test_keys(self):
374         self.assertCountEqual(self.johndoe.keys(), [
375             'date_joined',
376             'first_name',
377             'username',
378         ])
379 
380     def test_values(self):
381         self.assertCountEqual(self.johndoe.values(),
382                               [12345, 'John', 'johndoe'])
383 
384     def test_contains(self):
385         self.assertIn('username', self.johndoe)
386         self.assertIn('first_name', self.johndoe)
387         self.assertIn('date_joined', self.johndoe)
388         self.assertNotIn('whatever', self.johndoe)
389 
390     def test_iter(self):
391         self.assertCountEqual(self.johndoe,
392                               ['johndoe', 'John', 12345])
393 
394     def test_get(self):
395         self.assertEqual(self.johndoe.get('username'), 'johndoe')
396         self.assertEqual(self.johndoe.get('first_name'), 'John')
397         self.assertEqual(self.johndoe.get('date_joined'), 12345)
398 
399         # Test a missing key. No default yields ``None``.
400         self.assertEqual(self.johndoe.get('last_name'), None)
401         # This time with a default.
402         self.assertEqual(self.johndoe.get('last_name', True), True)
403 
404     def test_items(self):
405         self.assertCountEqual(
406             self.johndoe.items(),
407             [
408                 ('date_joined', 12345),
409                 ('first_name', 'John'),
410                 ('username', 'johndoe'),
411             ])
412 
413     def test_attribute_access(self):
414         self.assertEqual(self.johndoe['username'], 'johndoe')
415         self.assertEqual(self.johndoe['first_name'], 'John')
416         self.assertEqual(self.johndoe['date_joined'], 12345)
417 
418         # Test a missing key.
419         self.assertEqual(self.johndoe['last_name'], None)
420 
421         # Set a key.
422         self.johndoe['last_name'] = 'Doe'
423         # Test accessing the new key.
424         self.assertEqual(self.johndoe['last_name'], 'Doe')
425 
426         # Delete a key.
427         del self.johndoe['last_name']
428         # Test the now-missing-again key.
429         self.assertEqual(self.johndoe['last_name'], None)
430 
431     def test_needs_save(self):
432         self.johndoe.mark_clean()
433         self.assertFalse(self.johndoe.needs_save())
434         self.johndoe['last_name'] = 'Doe'
435         self.assertTrue(self.johndoe.needs_save())
436 
437     def test_needs_save_set_changed(self):
438         # First, ensure we're clean.
439         self.johndoe.mark_clean()
440         self.assertFalse(self.johndoe.needs_save())
441         # Add a friends collection.
442         self.johndoe['friends'] = set(['jane', 'alice'])
443         self.assertTrue(self.johndoe.needs_save())
444         # Now mark it clean, then change the collection.
445         # This does NOT call ``__setitem__``, so the item used to be
446         # incorrectly appearing to be clean, when it had in fact been changed.
447         self.johndoe.mark_clean()
448         self.assertFalse(self.johndoe.needs_save())
449         self.johndoe['friends'].add('bob')
450         self.assertTrue(self.johndoe.needs_save())
451 
452     def test_mark_clean(self):
453         self.johndoe['last_name'] = 'Doe'
454         self.assertTrue(self.johndoe.needs_save())
455         self.johndoe.mark_clean()
456         self.assertFalse(self.johndoe.needs_save())
457 
458     def test_load(self):
459         empty_item = Item(self.table)
460         empty_item.load({
461             'Item': {
462                 'username': {'S': 'johndoe'},
463                 'first_name': {'S': 'John'},
464                 'last_name': {'S': 'Doe'},
465                 'date_joined': {'N': '1366056668'},
466                 'friend_count': {'N': '3'},
467                 'friends': {'SS': ['alice', 'bob', 'jane']},
468             }
469         })
470         self.assertEqual(empty_item['username'], 'johndoe')
471         self.assertEqual(empty_item['date_joined'], 1366056668)
472         self.assertEqual(sorted(empty_item['friends']), sorted([
473             'alice',
474             'bob',
475             'jane'
476         ]))
477 
478     def test_get_keys(self):
479         # Setup the data.
480         self.table.schema = [
481             HashKey('username'),
482             RangeKey('date_joined'),
483         ]
484         self.assertEqual(self.johndoe.get_keys(), {
485             'username': 'johndoe',
486             'date_joined': 12345,
487         })
488 
489     def test_get_raw_keys(self):
490         # Setup the data.
491         self.table.schema = [
492             HashKey('username'),
493             RangeKey('date_joined'),
494         ]
495         self.assertEqual(self.johndoe.get_raw_keys(), {
496             'username': {'S': 'johndoe'},
497             'date_joined': {'N': '12345'},
498         })
499 
500     def test_build_expects(self):
501         # Pristine.
502         self.assertEqual(self.johndoe.build_expects(), {
503             'first_name': {
504                 'Exists': False,
505             },
506             'username': {
507                 'Exists': False,
508             },
509             'date_joined': {
510                 'Exists': False,
511             },
512         })
513 
514         # Without modifications.
515         self.johndoe.mark_clean()
516         self.assertEqual(self.johndoe.build_expects(), {
517             'first_name': {
518                 'Exists': True,
519                 'Value': {
520                     'S': 'John',
521                 },
522             },
523             'username': {
524                 'Exists': True,
525                 'Value': {
526                     'S': 'johndoe',
527                 },
528             },
529             'date_joined': {
530                 'Exists': True,
531                 'Value': {
532                     'N': '12345',
533                 },
534             },
535         })
536 
537         # Change some data.
538         self.johndoe['first_name'] = 'Johann'
539         # Add some data.
540         self.johndoe['last_name'] = 'Doe'
541         # Delete some data.
542         del self.johndoe['date_joined']
543 
544         # All fields (default).
545         self.assertEqual(self.johndoe.build_expects(), {
546             'first_name': {
547                 'Exists': True,
548                 'Value': {
549                     'S': 'John',
550                 },
551             },
552             'last_name': {
553                 'Exists': False,
554             },
555             'username': {
556                 'Exists': True,
557                 'Value': {
558                     'S': 'johndoe',
559                 },
560             },
561             'date_joined': {
562                 'Exists': True,
563                 'Value': {
564                     'N': '12345',
565                 },
566             },
567         })
568 
569         # Only a subset of the fields.
570         self.assertEqual(self.johndoe.build_expects(fields=[
571             'first_name',
572             'last_name',
573             'date_joined',
574         ]), {
575             'first_name': {
576                 'Exists': True,
577                 'Value': {
578                     'S': 'John',
579                 },
580             },
581             'last_name': {
582                 'Exists': False,
583             },
584             'date_joined': {
585                 'Exists': True,
586                 'Value': {
587                     'N': '12345',
588                 },
589             },
590         })
591 
592     def test_prepare_full(self):
593         self.assertEqual(self.johndoe.prepare_full(), {
594             'username': {'S': 'johndoe'},
595             'first_name': {'S': 'John'},
596             'date_joined': {'N': '12345'}
597         })
598 
599         self.johndoe['friends'] = set(['jane', 'alice'])
600         data = self.johndoe.prepare_full()
601         self.assertEqual(data['username'], {'S': 'johndoe'})
602         self.assertEqual(data['first_name'], {'S': 'John'})
603         self.assertEqual(data['date_joined'], {'N': '12345'})
604         self.assertCountEqual(data['friends']['SS'],
605                               ['jane', 'alice'])
606 
607     def test_prepare_full_empty_set(self):
608         self.johndoe['friends'] = set()
609         self.assertEqual(self.johndoe.prepare_full(), {
610             'username': {'S': 'johndoe'},
611             'first_name': {'S': 'John'},
612             'date_joined': {'N': '12345'}
613         })
614 
615     def test_prepare_partial(self):
616         self.johndoe.mark_clean()
617         # Change some data.
618         self.johndoe['first_name'] = 'Johann'
619         # Add some data.
620         self.johndoe['last_name'] = 'Doe'
621         # Delete some data.
622         del self.johndoe['date_joined']
623 
624         final_data, fields = self.johndoe.prepare_partial()
625         self.assertEqual(final_data, {
626             'date_joined': {
627                 'Action': 'DELETE',
628             },
629             'first_name': {
630                 'Action': 'PUT',
631                 'Value': {'S': 'Johann'},
632             },
633             'last_name': {
634                 'Action': 'PUT',
635                 'Value': {'S': 'Doe'},
636             },
637         })
638         self.assertEqual(fields, set([
639             'first_name',
640             'last_name',
641             'date_joined'
642         ]))
643 
644     def test_prepare_partial_empty_set(self):
645         self.johndoe.mark_clean()
646         # Change some data.
647         self.johndoe['first_name'] = 'Johann'
648         # Add some data.
649         self.johndoe['last_name'] = 'Doe'
650         # Delete some data.
651         del self.johndoe['date_joined']
652         # Put an empty set on the ``Item``.
653         self.johndoe['friends'] = set()
654 
655         final_data, fields = self.johndoe.prepare_partial()
656         self.assertEqual(final_data, {
657             'date_joined': {
658                 'Action': 'DELETE',
659             },
660             'first_name': {
661                 'Action': 'PUT',
662                 'Value': {'S': 'Johann'},
663             },
664             'last_name': {
665                 'Action': 'PUT',
666                 'Value': {'S': 'Doe'},
667             },
668         })
669         self.assertEqual(fields, set([
670             'first_name',
671             'last_name',
672             'date_joined'
673         ]))
674 
675     def test_save_no_changes(self):
676         # Unchanged, no save.
677         with mock.patch.object(self.table, '_put_item', return_value=True) \
678                 as mock_put_item:
679             # Pretend we loaded it via ``get_item``...
680             self.johndoe.mark_clean()
681             self.assertFalse(self.johndoe.save())
682 
683         self.assertFalse(mock_put_item.called)
684 
685     def test_save_with_changes(self):
686         # With changed data.
687         with mock.patch.object(self.table, '_put_item', return_value=True) \
688                 as mock_put_item:
689             self.johndoe.mark_clean()
690             self.johndoe['first_name'] = 'J'
691             self.johndoe['new_attr'] = 'never_seen_before'
692             self.assertTrue(self.johndoe.save())
693             self.assertFalse(self.johndoe.needs_save())
694 
695         self.assertTrue(mock_put_item.called)
696         mock_put_item.assert_called_once_with({
697             'username': {'S': 'johndoe'},
698             'first_name': {'S': 'J'},
699             'new_attr': {'S': 'never_seen_before'},
700             'date_joined': {'N': '12345'}
701         }, expects={
702             'username': {
703                 'Value': {
704                     'S': 'johndoe',
705                 },
706                 'Exists': True,
707             },
708             'first_name': {
709                 'Value': {
710                     'S': 'John',
711                 },
712                 'Exists': True,
713             },
714             'new_attr': {
715                 'Exists': False,
716             },
717             'date_joined': {
718                 'Value': {
719                     'N': '12345',
720                 },
721                 'Exists': True,
722             },
723         })
724 
725     def test_save_with_changes_overwrite(self):
726         # With changed data.
727         with mock.patch.object(self.table, '_put_item', return_value=True) \
728                 as mock_put_item:
729             self.johndoe['first_name'] = 'J'
730             self.johndoe['new_attr'] = 'never_seen_before'
731             # OVERWRITE ALL THE THINGS
732             self.assertTrue(self.johndoe.save(overwrite=True))
733             self.assertFalse(self.johndoe.needs_save())
734 
735         self.assertTrue(mock_put_item.called)
736         mock_put_item.assert_called_once_with({
737             'username': {'S': 'johndoe'},
738             'first_name': {'S': 'J'},
739             'new_attr': {'S': 'never_seen_before'},
740             'date_joined': {'N': '12345'}
741         }, expects=None)
742 
743     def test_partial_no_changes(self):
744         # Unchanged, no save.
745         with mock.patch.object(self.table, '_update_item', return_value=True) \
746                 as mock_update_item:
747             # Pretend we loaded it via ``get_item``...
748             self.johndoe.mark_clean()
749             self.assertFalse(self.johndoe.partial_save())
750 
751         self.assertFalse(mock_update_item.called)
752 
753     def test_partial_with_changes(self):
754         # Setup the data.
755         self.table.schema = [
756             HashKey('username'),
757         ]
758 
759         # With changed data.
760         with mock.patch.object(self.table, '_update_item', return_value=True) \
761                 as mock_update_item:
762             # Pretend we loaded it via ``get_item``...
763             self.johndoe.mark_clean()
764             # Now... MODIFY!!!
765             self.johndoe['first_name'] = 'J'
766             self.johndoe['last_name'] = 'Doe'
767             del self.johndoe['date_joined']
768             self.assertTrue(self.johndoe.partial_save())
769             self.assertFalse(self.johndoe.needs_save())
770 
771         self.assertTrue(mock_update_item.called)
772         mock_update_item.assert_called_once_with({
773             'username': 'johndoe',
774         }, {
775             'first_name': {
776                 'Action': 'PUT',
777                 'Value': {'S': 'J'},
778             },
779             'last_name': {
780                 'Action': 'PUT',
781                 'Value': {'S': 'Doe'},
782             },
783             'date_joined': {
784                 'Action': 'DELETE',
785             }
786         }, expects={
787             'first_name': {
788                 'Value': {
789                     'S': 'John',
790                 },
791                 'Exists': True
792             },
793             'last_name': {
794                 'Exists': False
795             },
796             'date_joined': {
797                 'Value': {
798                     'N': '12345',
799                 },
800                 'Exists': True
801             },
802         })
803 
804     def test_delete(self):
805         # Setup the data.
806         self.table.schema = [
807             HashKey('username'),
808             RangeKey('date_joined'),
809         ]
810 
811         with mock.patch.object(self.table, 'delete_item', return_value=True) \
812                 as mock_delete_item:
813             self.johndoe.delete()
814 
815         self.assertTrue(mock_delete_item.called)
816         mock_delete_item.assert_called_once_with(
817             username='johndoe',
818             date_joined=12345
819         )
820 
821     def test_nonzero(self):
822         self.assertTrue(self.johndoe)
823         self.assertFalse(self.create_item({}))
824 
825 
826 class ItemFromItemTestCase(ItemTestCase):
827     def setUp(self):
828         super(ItemFromItemTestCase, self).setUp()
829         self.johndoe = self.create_item(self.johndoe)
830 
831 
832 def fake_results(name, greeting='hello', exclusive_start_key=None, limit=None):
833     if exclusive_start_key is None:
834         exclusive_start_key = -1
835 
836     if limit == 0:
837         raise Exception("Web Service Returns '400 Bad Request'")
838 
839     end_cap = 13
840     results = []
841     start_key = exclusive_start_key + 1
842 
843     for i in range(start_key, start_key + 5):
844         if i < end_cap:
845             results.append("%s %s #%s" % (greeting, name, i))
846 
847     # Don't return more than limit results
848     if limit < len(results):
849         results = results[:limit]
850 
851     retval = {
852         'results': results,
853     }
854 
855     if exclusive_start_key + 5 < end_cap:
856         retval['last_key'] = exclusive_start_key + 5
857 
858     return retval
859 
860 
861 class ResultSetTestCase(unittest.TestCase):
862     def setUp(self):
863         super(ResultSetTestCase, self).setUp()
864         self.results = ResultSet()
865         self.result_function = mock.MagicMock(side_effect=fake_results)
866         self.results.to_call(self.result_function, 'john', greeting='Hello', limit=20)
867 
868     def test_first_key(self):
869         self.assertEqual(self.results.first_key, 'exclusive_start_key')
870 
871     def test_max_page_size_fetch_more(self):
872         self.results = ResultSet(max_page_size=10)
873         self.results.to_call(self.result_function, 'john', greeting='Hello')
874         self.results.fetch_more()
875         self.result_function.assert_called_with('john', greeting='Hello', limit=10)
876         self.result_function.reset_mock()
877 
878     def test_max_page_size_and_smaller_limit_fetch_more(self):
879         self.results = ResultSet(max_page_size=10)
880         self.results.to_call(self.result_function, 'john', greeting='Hello', limit=5)
881         self.results.fetch_more()
882         self.result_function.assert_called_with('john', greeting='Hello', limit=5)
883         self.result_function.reset_mock()
884 
885     def test_max_page_size_and_bigger_limit_fetch_more(self):
886         self.results = ResultSet(max_page_size=10)
887         self.results.to_call(self.result_function, 'john', greeting='Hello', limit=15)
888         self.results.fetch_more()
889         self.result_function.assert_called_with('john', greeting='Hello', limit=10)
890         self.result_function.reset_mock()
891 
892     def test_fetch_more(self):
893         # First "page".
894         self.results.fetch_more()
895         self.assertEqual(self.results._results, [
896             'Hello john #0',
897             'Hello john #1',
898             'Hello john #2',
899             'Hello john #3',
900             'Hello john #4',
901         ])
902 
903         self.result_function.assert_called_with('john', greeting='Hello', limit=20)
904         self.result_function.reset_mock()
905 
906         # Fake in a last key.
907         self.results._last_key_seen = 4
908         # Second "page".
909         self.results.fetch_more()
910         self.assertEqual(self.results._results, [
911             'Hello john #5',
912             'Hello john #6',
913             'Hello john #7',
914             'Hello john #8',
915             'Hello john #9',
916         ])
917 
918         self.result_function.assert_called_with('john', greeting='Hello', limit=20, exclusive_start_key=4)
919         self.result_function.reset_mock()
920 
921         # Fake in a last key.
922         self.results._last_key_seen = 9
923         # Last "page".
924         self.results.fetch_more()
925         self.assertEqual(self.results._results, [
926             'Hello john #10',
927             'Hello john #11',
928             'Hello john #12',
929         ])
930 
931         # Fake in a key outside the range.
932         self.results._last_key_seen = 15
933         # Empty "page". Nothing new gets added
934         self.results.fetch_more()
935         self.assertEqual(self.results._results, [])
936 
937         # Make sure we won't check for results in the future.
938         self.assertFalse(self.results._results_left)
939 
940     def test_iteration(self):
941         # First page.
942         self.assertEqual(next(self.results), 'Hello john #0')
943         self.assertEqual(next(self.results), 'Hello john #1')
944         self.assertEqual(next(self.results), 'Hello john #2')
945         self.assertEqual(next(self.results), 'Hello john #3')
946         self.assertEqual(next(self.results), 'Hello john #4')
947         self.assertEqual(self.results._limit, 15)
948         # Second page.
949         self.assertEqual(next(self.results), 'Hello john #5')
950         self.assertEqual(next(self.results), 'Hello john #6')
951         self.assertEqual(next(self.results), 'Hello john #7')
952         self.assertEqual(next(self.results), 'Hello john #8')
953         self.assertEqual(next(self.results), 'Hello john #9')
954         self.assertEqual(self.results._limit, 10)
955         # Third page.
956         self.assertEqual(next(self.results), 'Hello john #10')
957         self.assertEqual(next(self.results), 'Hello john #11')
958         self.assertEqual(next(self.results), 'Hello john #12')
959         self.assertRaises(StopIteration, self.results.next)
960         self.assertEqual(self.results._limit, 7)
961 
962     def test_limit_smaller_than_first_page(self):
963         results = ResultSet()
964         results.to_call(fake_results, 'john', greeting='Hello', limit=2)
965         self.assertEqual(next(results), 'Hello john #0')
966         self.assertEqual(next(results), 'Hello john #1')
967         self.assertRaises(StopIteration, results.next)
968 
969     def test_limit_equals_page(self):
970         results = ResultSet()
971         results.to_call(fake_results, 'john', greeting='Hello', limit=5)
972         # First page
973         self.assertEqual(next(results), 'Hello john #0')
974         self.assertEqual(next(results), 'Hello john #1')
975         self.assertEqual(next(results), 'Hello john #2')
976         self.assertEqual(next(results), 'Hello john #3')
977         self.assertEqual(next(results), 'Hello john #4')
978         self.assertRaises(StopIteration, results.next)
979 
980     def test_limit_greater_than_page(self):
981         results = ResultSet()
982         results.to_call(fake_results, 'john', greeting='Hello', limit=6)
983         # First page
984         self.assertEqual(next(results), 'Hello john #0')
985         self.assertEqual(next(results), 'Hello john #1')
986         self.assertEqual(next(results), 'Hello john #2')
987         self.assertEqual(next(results), 'Hello john #3')
988         self.assertEqual(next(results), 'Hello john #4')
989         # Second page
990         self.assertEqual(next(results), 'Hello john #5')
991         self.assertRaises(StopIteration, results.next)
992 
993     def test_iteration_noresults(self):
994         def none(limit=10):
995             return {
996                 'results': [],
997             }
998 
999         results = ResultSet()
1000         results.to_call(none, limit=20)
1001         self.assertRaises(StopIteration, results.next)
1002 
1003     def test_iteration_sporadic_pages(self):
1004         # Some pages have no/incomplete results but have a ``LastEvaluatedKey``
1005         # (for instance, scans with filters), so we need to accommodate that.
1006         def sporadic():
1007             # A dict, because Python closures have read-only access to the
1008             # reference itself.
1009             count = {'value': -1}
1010 
1011             def _wrapper(limit=10, exclusive_start_key=None):
1012                 count['value'] = count['value'] + 1
1013 
1014                 if count['value'] == 0:
1015                     # Full page.
1016                     return {
1017                         'results': [
1018                             'Result #0',
1019                             'Result #1',
1020                             'Result #2',
1021                             'Result #3',
1022                         ],
1023                         'last_key': 'page-1'
1024                     }
1025                 elif count['value'] == 1:
1026                     # Empty page but continue.
1027                     return {
1028                         'results': [],
1029                         'last_key': 'page-2'
1030                     }
1031                 elif count['value'] == 2:
1032                     # Final page.
1033                     return {
1034                         'results': [
1035                             'Result #4',
1036                             'Result #5',
1037                             'Result #6',
1038                         ],
1039                     }
1040 
1041             return _wrapper
1042 
1043         results = ResultSet()
1044         results.to_call(sporadic(), limit=20)
1045         # First page
1046         self.assertEqual(next(results), 'Result #0')
1047         self.assertEqual(next(results), 'Result #1')
1048         self.assertEqual(next(results), 'Result #2')
1049         self.assertEqual(next(results), 'Result #3')
1050         # Second page (misses!)
1051         # Moves on to the third page
1052         self.assertEqual(next(results), 'Result #4')
1053         self.assertEqual(next(results), 'Result #5')
1054         self.assertEqual(next(results), 'Result #6')
1055         self.assertRaises(StopIteration, results.next)
1056 
1057     def test_list(self):
1058         self.assertEqual(list(self.results), [
1059             'Hello john #0',
1060             'Hello john #1',
1061             'Hello john #2',
1062             'Hello john #3',
1063             'Hello john #4',
1064             'Hello john #5',
1065             'Hello john #6',
1066             'Hello john #7',
1067             'Hello john #8',
1068             'Hello john #9',
1069             'Hello john #10',
1070             'Hello john #11',
1071             'Hello john #12'
1072         ])
1073 
1074 
1075 def fake_batch_results(keys):
1076     results = []
1077     simulate_unprocessed = True
1078 
1079     if len(keys) and keys[0] == 'johndoe':
1080         simulate_unprocessed = False
1081 
1082     for key in keys:
1083         if simulate_unprocessed and key == 'johndoe':
1084             continue
1085 
1086         results.append("hello %s" % key)
1087 
1088     retval = {
1089         'results': results,
1090         'last_key': None,
1091     }
1092 
1093     if simulate_unprocessed:
1094         retval['unprocessed_keys'] = ['johndoe']
1095 
1096     return retval
1097 
1098 
1099 class BatchGetResultSetTestCase(unittest.TestCase):
1100     def setUp(self):
1101         super(BatchGetResultSetTestCase, self).setUp()
1102         self.results = BatchGetResultSet(keys=[
1103             'alice',
1104             'bob',
1105             'jane',
1106             'johndoe',
1107         ])
1108         self.results.to_call(fake_batch_results)
1109 
1110     def test_fetch_more(self):
1111         # First "page".
1112         self.results.fetch_more()
1113         self.assertEqual(self.results._results, [
1114             'hello alice',
1115             'hello bob',
1116             'hello jane',
1117         ])
1118         self.assertEqual(self.results._keys_left, ['johndoe'])
1119 
1120         # Second "page".
1121         self.results.fetch_more()
1122         self.assertEqual(self.results._results, [
1123             'hello johndoe',
1124         ])
1125 
1126         # Empty "page". Nothing new gets added
1127         self.results.fetch_more()
1128         self.assertEqual(self.results._results, [])
1129 
1130         # Make sure we won't check for results in the future.
1131         self.assertFalse(self.results._results_left)
1132 
1133     def test_fetch_more_empty(self):
1134         self.results.to_call(lambda keys: {'results': [], 'last_key': None})
1135 
1136         self.results.fetch_more()
1137         self.assertEqual(self.results._results, [])
1138         self.assertRaises(StopIteration, self.results.next)
1139 
1140     def test_iteration(self):
1141         # First page.
1142         self.assertEqual(next(self.results), 'hello alice')
1143         self.assertEqual(next(self.results), 'hello bob')
1144         self.assertEqual(next(self.results), 'hello jane')
1145         self.assertEqual(next(self.results), 'hello johndoe')
1146         self.assertRaises(StopIteration, self.results.next)
1147 
1148 
1149 class TableTestCase(unittest.TestCase):
1150     def setUp(self):
1151         super(TableTestCase, self).setUp()
1152         self.users = Table('users', connection=FakeDynamoDBConnection())
1153         self.default_connection = DynamoDBConnection(
1154             aws_access_key_id='access_key',
1155             aws_secret_access_key='secret_key'
1156         )
1157 
1158     def test__introspect_schema(self):
1159         raw_schema_1 = [
1160             {
1161                 "AttributeName": "username",
1162                 "KeyType": "HASH"
1163             },
1164             {
1165                 "AttributeName": "date_joined",
1166                 "KeyType": "RANGE"
1167             }
1168         ]
1169         raw_attributes_1 = [
1170             {
1171                 'AttributeName': 'username',
1172                 'AttributeType': 'S'
1173             },
1174             {
1175                 'AttributeName': 'date_joined',
1176                 'AttributeType': 'S'
1177             },
1178         ]
1179         schema_1 = self.users._introspect_schema(raw_schema_1, raw_attributes_1)
1180         self.assertEqual(len(schema_1), 2)
1181         self.assertTrue(isinstance(schema_1[0], HashKey))
1182         self.assertEqual(schema_1[0].name, 'username')
1183         self.assertTrue(isinstance(schema_1[1], RangeKey))
1184         self.assertEqual(schema_1[1].name, 'date_joined')
1185 
1186         raw_schema_2 = [
1187             {
1188                 "AttributeName": "username",
1189                 "KeyType": "BTREE"
1190             },
1191         ]
1192         raw_attributes_2 = [
1193             {
1194                 'AttributeName': 'username',
1195                 'AttributeType': 'S'
1196             },
1197         ]
1198         self.assertRaises(
1199             exceptions.UnknownSchemaFieldError,
1200             self.users._introspect_schema,
1201             raw_schema_2,
1202             raw_attributes_2
1203         )
1204 
1205         # Test a complex schema & ensure the types come back correctly.
1206         raw_schema_3 = [
1207             {
1208                 "AttributeName": "user_id",
1209                 "KeyType": "HASH"
1210             },
1211             {
1212                 "AttributeName": "junk",
1213                 "KeyType": "RANGE"
1214             }
1215         ]
1216         raw_attributes_3 = [
1217             {
1218                 'AttributeName': 'user_id',
1219                 'AttributeType': 'N'
1220             },
1221             {
1222                 'AttributeName': 'junk',
1223                 'AttributeType': 'B'
1224             },
1225         ]
1226         schema_3 = self.users._introspect_schema(raw_schema_3, raw_attributes_3)
1227         self.assertEqual(len(schema_3), 2)
1228         self.assertTrue(isinstance(schema_3[0], HashKey))
1229         self.assertEqual(schema_3[0].name, 'user_id')
1230         self.assertEqual(schema_3[0].data_type, NUMBER)
1231         self.assertTrue(isinstance(schema_3[1], RangeKey))
1232         self.assertEqual(schema_3[1].name, 'junk')
1233         self.assertEqual(schema_3[1].data_type, BINARY)
1234 
1235     def test__introspect_indexes(self):
1236         raw_indexes_1 = [
1237             {
1238                 "IndexName": "MostRecentlyJoinedIndex",
1239                 "KeySchema": [
1240                     {
1241                         "AttributeName": "username",
1242                         "KeyType": "HASH"
1243                     },
1244                     {
1245                         "AttributeName": "date_joined",
1246                         "KeyType": "RANGE"
1247                     }
1248                 ],
1249                 "Projection": {
1250                     "ProjectionType": "KEYS_ONLY"
1251                 }
1252             },
1253             {
1254                 "IndexName": "EverybodyIndex",
1255                 "KeySchema": [
1256                     {
1257                         "AttributeName": "username",
1258                         "KeyType": "HASH"
1259                     },
1260                 ],
1261                 "Projection": {
1262                     "ProjectionType": "ALL"
1263                 }
1264             },
1265             {
1266                 "IndexName": "GenderIndex",
1267                 "KeySchema": [
1268                     {
1269                         "AttributeName": "username",
1270                         "KeyType": "HASH"
1271                     },
1272                     {
1273                         "AttributeName": "date_joined",
1274                         "KeyType": "RANGE"
1275                     }
1276                 ],
1277                 "Projection": {
1278                     "ProjectionType": "INCLUDE",
1279                     "NonKeyAttributes": [
1280                         'gender',
1281                     ]
1282                 }
1283             }
1284         ]
1285         indexes_1 = self.users._introspect_indexes(raw_indexes_1)
1286         self.assertEqual(len(indexes_1), 3)
1287         self.assertTrue(isinstance(indexes_1[0], KeysOnlyIndex))
1288         self.assertEqual(indexes_1[0].name, 'MostRecentlyJoinedIndex')
1289         self.assertEqual(len(indexes_1[0].parts), 2)
1290         self.assertTrue(isinstance(indexes_1[1], AllIndex))
1291         self.assertEqual(indexes_1[1].name, 'EverybodyIndex')
1292         self.assertEqual(len(indexes_1[1].parts), 1)
1293         self.assertTrue(isinstance(indexes_1[2], IncludeIndex))
1294         self.assertEqual(indexes_1[2].name, 'GenderIndex')
1295         self.assertEqual(len(indexes_1[2].parts), 2)
1296         self.assertEqual(indexes_1[2].includes_fields, ['gender'])
1297 
1298         raw_indexes_2 = [
1299             {
1300                 "IndexName": "MostRecentlyJoinedIndex",
1301                 "KeySchema": [
1302                     {
1303                         "AttributeName": "username",
1304                         "KeyType": "HASH"
1305                     },
1306                     {
1307                         "AttributeName": "date_joined",
1308                         "KeyType": "RANGE"
1309                     }
1310                 ],
1311                 "Projection": {
1312                     "ProjectionType": "SOMETHING_CRAZY"
1313                 }
1314             },
1315         ]
1316         self.assertRaises(
1317             exceptions.UnknownIndexFieldError,
1318             self.users._introspect_indexes,
1319             raw_indexes_2
1320         )
1321 
1322     def test_initialization(self):
1323         users = Table('users', connection=self.default_connection)
1324         self.assertEqual(users.table_name, 'users')
1325         self.assertTrue(isinstance(users.connection, DynamoDBConnection))
1326         self.assertEqual(users.throughput['read'], 5)
1327         self.assertEqual(users.throughput['write'], 5)
1328         self.assertEqual(users.schema, None)
1329         self.assertEqual(users.indexes, None)
1330 
1331         groups = Table('groups', connection=FakeDynamoDBConnection())
1332         self.assertEqual(groups.table_name, 'groups')
1333         self.assertTrue(hasattr(groups.connection, 'assert_called_once_with'))
1334 
1335     def test_create_simple(self):
1336         conn = FakeDynamoDBConnection()
1337 
1338         with mock.patch.object(conn, 'create_table', return_value={}) \
1339                 as mock_create_table:
1340             retval = Table.create('users', schema=[
1341                 HashKey('username'),
1342                 RangeKey('date_joined', data_type=NUMBER)
1343             ], connection=conn)
1344             self.assertTrue(retval)
1345 
1346         self.assertTrue(mock_create_table.called)
1347         mock_create_table.assert_called_once_with(attribute_definitions=[
1348             {
1349                 'AttributeName': 'username',
1350                 'AttributeType': 'S'
1351             },
1352             {
1353                 'AttributeName': 'date_joined',
1354                 'AttributeType': 'N'
1355             }
1356         ],
1357         table_name='users',
1358         key_schema=[
1359             {
1360                 'KeyType': 'HASH',
1361                 'AttributeName': 'username'
1362             },
1363             {
1364                 'KeyType': 'RANGE',
1365                 'AttributeName': 'date_joined'
1366             }
1367         ],
1368         provisioned_throughput={
1369             'WriteCapacityUnits': 5,
1370             'ReadCapacityUnits': 5
1371         })
1372 
1373     def test_create_full(self):
1374         conn = FakeDynamoDBConnection()
1375 
1376         with mock.patch.object(conn, 'create_table', return_value={}) \
1377                 as mock_create_table:
1378             retval = Table.create('users', schema=[
1379                 HashKey('username'),
1380                 RangeKey('date_joined', data_type=NUMBER)
1381             ], throughput={
1382                 'read':20,
1383                 'write': 10,
1384             }, indexes=[
1385                 KeysOnlyIndex('FriendCountIndex', parts=[
1386                     RangeKey('friend_count')
1387                 ]),
1388             ], global_indexes=[
1389                 GlobalKeysOnlyIndex('FullFriendCountIndex', parts=[
1390                     RangeKey('friend_count')
1391                 ], throughput={
1392                     'read': 10,
1393                     'write': 8,
1394                 }),
1395             ], connection=conn)
1396             self.assertTrue(retval)
1397 
1398         self.assertTrue(mock_create_table.called)
1399         mock_create_table.assert_called_once_with(attribute_definitions=[
1400             {
1401                 'AttributeName': 'username',
1402                 'AttributeType': 'S'
1403             },
1404             {
1405                 'AttributeName': 'date_joined',
1406                 'AttributeType': 'N'
1407             },
1408             {
1409                 'AttributeName': 'friend_count',
1410                 'AttributeType': 'S'
1411             }
1412         ],
1413         key_schema=[
1414             {
1415                 'KeyType': 'HASH',
1416                 'AttributeName': 'username'
1417             },
1418             {
1419                 'KeyType': 'RANGE',
1420                 'AttributeName': 'date_joined'
1421             }
1422         ],
1423         table_name='users',
1424         provisioned_throughput={
1425             'WriteCapacityUnits': 10,
1426             'ReadCapacityUnits': 20
1427         },
1428         global_secondary_indexes=[
1429             {
1430                 'KeySchema': [
1431                     {
1432                         'KeyType': 'RANGE',
1433                         'AttributeName': 'friend_count'
1434                     }
1435                 ],
1436                 'IndexName': 'FullFriendCountIndex',
1437                 'Projection': {
1438                     'ProjectionType': 'KEYS_ONLY'
1439                 },
1440                 'ProvisionedThroughput': {
1441                     'WriteCapacityUnits': 8,
1442                     'ReadCapacityUnits': 10
1443                 }
1444             }
1445         ],
1446         local_secondary_indexes=[
1447             {
1448                 'KeySchema': [
1449                     {
1450                         'KeyType': 'RANGE',
1451                         'AttributeName': 'friend_count'
1452                     }
1453                 ],
1454                 'IndexName': 'FriendCountIndex',
1455                 'Projection': {
1456                     'ProjectionType': 'KEYS_ONLY'
1457                 }
1458             }
1459         ])
1460 
1461     def test_describe(self):
1462         expected = {
1463             "Table": {
1464                 "AttributeDefinitions": [
1465                     {
1466                         "AttributeName": "username",
1467                         "AttributeType": "S"
1468                     }
1469                 ],
1470                 "ItemCount": 5,
1471                 "KeySchema": [
1472                     {
1473                         "AttributeName": "username",
1474                         "KeyType": "HASH"
1475                     }
1476                 ],
1477                 "LocalSecondaryIndexes": [
1478                     {
1479                         "IndexName": "UsernameIndex",
1480                         "KeySchema": [
1481                             {
1482                                 "AttributeName": "username",
1483                                 "KeyType": "HASH"
1484                             }
1485                         ],
1486                         "Projection": {
1487                             "ProjectionType": "KEYS_ONLY"
1488                         }
1489                     }
1490                 ],
1491                 "ProvisionedThroughput": {
1492                     "ReadCapacityUnits": 20,
1493                     "WriteCapacityUnits": 6
1494                 },
1495                 "TableName": "Thread",
1496                 "TableStatus": "ACTIVE"
1497             }
1498         }
1499 
1500         with mock.patch.object(
1501                 self.users.connection,
1502                 'describe_table',
1503                 return_value=expected) as mock_describe:
1504             self.assertEqual(self.users.throughput['read'], 5)
1505             self.assertEqual(self.users.throughput['write'], 5)
1506             self.assertEqual(self.users.schema, None)
1507             self.assertEqual(self.users.indexes, None)
1508 
1509             self.users.describe()
1510 
1511             self.assertEqual(self.users.throughput['read'], 20)
1512             self.assertEqual(self.users.throughput['write'], 6)
1513             self.assertEqual(len(self.users.schema), 1)
1514             self.assertEqual(isinstance(self.users.schema[0], HashKey), 1)
1515             self.assertEqual(len(self.users.indexes), 1)
1516 
1517         mock_describe.assert_called_once_with('users')
1518 
1519     def test_update(self):
1520         with mock.patch.object(
1521                 self.users.connection,
1522                 'update_table',
1523                 return_value={}) as mock_update:
1524             self.assertEqual(self.users.throughput['read'], 5)
1525             self.assertEqual(self.users.throughput['write'], 5)
1526             self.users.update(throughput={
1527                 'read': 7,
1528                 'write': 2,
1529             })
1530             self.assertEqual(self.users.throughput['read'], 7)
1531             self.assertEqual(self.users.throughput['write'], 2)
1532 
1533         mock_update.assert_called_once_with(
1534             'users',
1535             global_secondary_index_updates=None,
1536             provisioned_throughput={
1537                 'WriteCapacityUnits': 2,
1538                 'ReadCapacityUnits': 7
1539             }
1540         )
1541 
1542         with mock.patch.object(
1543                 self.users.connection,
1544                 'update_table',
1545                 return_value={}) as mock_update:
1546             self.assertEqual(self.users.throughput['read'], 7)
1547             self.assertEqual(self.users.throughput['write'], 2)
1548             self.users.update(throughput={
1549                 'read': 9,
1550                 'write': 5,
1551             },
1552             global_indexes={
1553                 'WhateverIndex': {
1554                     'read': 6,
1555                     'write': 1
1556                 },
1557                 'AnotherIndex': {
1558                     'read': 1,
1559                     'write': 2
1560                 }
1561             })
1562             self.assertEqual(self.users.throughput['read'], 9)
1563             self.assertEqual(self.users.throughput['write'], 5)
1564 
1565         args, kwargs = mock_update.call_args
1566         self.assertEqual(args, ('users',))
1567         self.assertEqual(kwargs['provisioned_throughput'], {
1568             'WriteCapacityUnits': 5,
1569             'ReadCapacityUnits': 9,
1570             })
1571         update = kwargs['global_secondary_index_updates'][:]
1572         update.sort(key=lambda x: x['Update']['IndexName'])
1573         self.assertDictEqual(
1574             update[0],
1575             {
1576                 'Update': {
1577                     'IndexName': 'AnotherIndex',
1578                     'ProvisionedThroughput': {
1579                         'WriteCapacityUnits': 2,
1580                         'ReadCapacityUnits': 1
1581                     }
1582                 }
1583             })
1584         self.assertDictEqual(
1585             update[1],
1586             {
1587                 'Update': {
1588                     'IndexName': 'WhateverIndex',
1589                     'ProvisionedThroughput': {
1590                         'WriteCapacityUnits': 1,
1591                         'ReadCapacityUnits': 6
1592                     }
1593                 }
1594             })
1595 
1596     def test_create_global_secondary_index(self):
1597         with mock.patch.object(
1598                 self.users.connection,
1599                 'update_table',
1600                 return_value={}) as mock_update:
1601             self.users.create_global_secondary_index(
1602                 global_index=GlobalAllIndex(
1603                     'JustCreatedIndex',
1604                     parts=[
1605                         HashKey('requiredHashKey')
1606                     ],
1607                     throughput={
1608                         'read': 2,
1609                         'write': 2
1610                     }
1611                 )
1612             )
1613 
1614         mock_update.assert_called_once_with(
1615             'users',
1616             global_secondary_index_updates=[
1617                 {
1618                     'Create': {
1619                         'IndexName': 'JustCreatedIndex',
1620                         'KeySchema': [
1621                             {
1622                                 'KeyType': 'HASH',
1623                                 'AttributeName': 'requiredHashKey'
1624                             }
1625                         ],
1626                         'Projection': {
1627                             'ProjectionType': 'ALL'
1628                         },
1629                         'ProvisionedThroughput': {
1630                             'WriteCapacityUnits': 2,
1631                             'ReadCapacityUnits': 2
1632                         }
1633                     }
1634                 }
1635             ],
1636             attribute_definitions=[
1637                 {
1638                     'AttributeName': 'requiredHashKey',
1639                     'AttributeType': 'S'
1640                 }
1641             ]
1642         )
1643 
1644     def test_delete_global_secondary_index(self):
1645         with mock.patch.object(
1646                 self.users.connection,
1647                 'update_table',
1648                 return_value={}) as mock_update:
1649             self.users.delete_global_secondary_index('RandomGSIIndex')
1650 
1651         mock_update.assert_called_once_with(
1652             'users',
1653             global_secondary_index_updates=[
1654                 {
1655                     'Delete': {
1656                         'IndexName': 'RandomGSIIndex',
1657                     }
1658                 }
1659             ]
1660         )
1661 
1662     def test_update_global_secondary_index(self):
1663         # Updating a single global secondary index
1664         with mock.patch.object(
1665                 self.users.connection,
1666                 'update_table',
1667                 return_value={}) as mock_update:
1668             self.users.update_global_secondary_index(global_indexes={
1669                 'A_IndexToBeUpdated': {
1670                     'read': 5,
1671                     'write': 5
1672                 }
1673             })
1674 
1675         mock_update.assert_called_once_with(
1676             'users',
1677             global_secondary_index_updates=[
1678                 {
1679                     'Update': {
1680                         'IndexName': 'A_IndexToBeUpdated',
1681                         "ProvisionedThroughput": {
1682                             "ReadCapacityUnits": 5,
1683                             "WriteCapacityUnits": 5
1684                         },
1685                     }
1686                 }
1687             ]
1688         )
1689 
1690         # Updating multiple global secondary indexes
1691         with mock.patch.object(
1692                 self.users.connection,
1693                 'update_table',
1694                 return_value={}) as mock_update:
1695             self.users.update_global_secondary_index(global_indexes={
1696                 'A_IndexToBeUpdated': {
1697                     'read': 5,
1698                     'write': 5
1699                 },
1700                 'B_IndexToBeUpdated': {
1701                     'read': 9,
1702                     'write': 9
1703                 }
1704             })
1705 
1706         args, kwargs = mock_update.call_args
1707         self.assertEqual(args, ('users',))
1708         update = kwargs['global_secondary_index_updates'][:]
1709         update.sort(key=lambda x: x['Update']['IndexName'])
1710         self.assertDictEqual(
1711             update[0],
1712             {
1713                 'Update': {
1714                     'IndexName': 'A_IndexToBeUpdated',
1715                     'ProvisionedThroughput': {
1716                         'WriteCapacityUnits': 5,
1717                         'ReadCapacityUnits': 5
1718                     }
1719                 }
1720             })
1721         self.assertDictEqual(
1722             update[1],
1723             {
1724                 'Update': {
1725                     'IndexName': 'B_IndexToBeUpdated',
1726                     'ProvisionedThroughput': {
1727                         'WriteCapacityUnits': 9,
1728                         'ReadCapacityUnits': 9
1729                     }
1730                 }
1731             })
1732 
1733     def test_delete(self):
1734         with mock.patch.object(
1735                 self.users.connection,
1736                 'delete_table',
1737                 return_value={}) as mock_delete:
1738             self.assertTrue(self.users.delete())
1739 
1740         mock_delete.assert_called_once_with('users')
1741 
1742     def test_get_item(self):
1743         expected = {
1744             'Item': {
1745                 'username': {'S': 'johndoe'},
1746                 'first_name': {'S': 'John'},
1747                 'last_name': {'S': 'Doe'},
1748                 'date_joined': {'N': '1366056668'},
1749                 'friend_count': {'N': '3'},
1750                 'friends': {'SS': ['alice', 'bob', 'jane']},
1751             }
1752         }
1753 
1754         with mock.patch.object(
1755                 self.users.connection,
1756                 'get_item',
1757                 return_value=expected) as mock_get_item:
1758             item = self.users.get_item(username='johndoe')
1759             self.assertEqual(item['username'], 'johndoe')
1760             self.assertEqual(item['first_name'], 'John')
1761 
1762         mock_get_item.assert_called_once_with('users', {
1763             'username': {'S': 'johndoe'}
1764         }, consistent_read=False, attributes_to_get=None)
1765 
1766         with mock.patch.object(
1767                 self.users.connection,
1768                 'get_item',
1769                 return_value=expected) as mock_get_item:
1770             item = self.users.get_item(username='johndoe', attributes=[
1771                 'username',
1772                 'first_name',
1773             ])
1774 
1775         mock_get_item.assert_called_once_with('users', {
1776             'username': {'S': 'johndoe'}
1777         }, consistent_read=False, attributes_to_get=['username', 'first_name'])
1778 
1779     def test_has_item(self):
1780         expected = {
1781             'Item': {
1782                 'username': {'S': 'johndoe'},
1783                 'first_name': {'S': 'John'},
1784                 'last_name': {'S': 'Doe'},
1785                 'date_joined': {'N': '1366056668'},
1786                 'friend_count': {'N': '3'},
1787                 'friends': {'SS': ['alice', 'bob', 'jane']},
1788             }
1789         }
1790 
1791         with mock.patch.object(
1792                 self.users.connection,
1793                 'get_item',
1794                 return_value=expected) as mock_get_item:
1795             found = self.users.has_item(username='johndoe')
1796             self.assertTrue(found)
1797 
1798         with mock.patch.object(
1799                 self.users.connection,
1800                 'get_item') as mock_get_item:
1801             mock_get_item.side_effect = JSONResponseError("Nope.", None, None)
1802             found = self.users.has_item(username='mrsmith')
1803             self.assertFalse(found)
1804 
1805     def test_lookup_hash(self):
1806         """Tests the "lookup" function with just a hash key"""
1807         expected = {
1808             'Item': {
1809                 'username': {'S': 'johndoe'},
1810                 'first_name': {'S': 'John'},
1811                 'last_name': {'S': 'Doe'},
1812                 'date_joined': {'N': '1366056668'},
1813                 'friend_count': {'N': '3'},
1814                 'friends': {'SS': ['alice', 'bob', 'jane']},
1815             }
1816         }
1817 
1818         # Set the Schema
1819         self.users.schema = [
1820             HashKey('username'),
1821             RangeKey('date_joined', data_type=NUMBER),
1822         ]
1823 
1824         with mock.patch.object(
1825                 self.users,
1826                 'get_item',
1827                 return_value=expected) as mock_get_item:
1828             self.users.lookup('johndoe')
1829 
1830         mock_get_item.assert_called_once_with(
1831             username= 'johndoe')
1832 
1833     def test_lookup_hash_and_range(self):
1834         """Test the "lookup" function with a hash and range key"""
1835         expected = {
1836             'Item': {
1837                 'username': {'S': 'johndoe'},
1838                 'first_name': {'S': 'John'},
1839                 'last_name': {'S': 'Doe'},
1840                 'date_joined': {'N': '1366056668'},
1841                 'friend_count': {'N': '3'},
1842                 'friends': {'SS': ['alice', 'bob', 'jane']},
1843             }
1844         }
1845 
1846         # Set the Schema
1847         self.users.schema = [
1848             HashKey('username'),
1849             RangeKey('date_joined', data_type=NUMBER),
1850         ]
1851 
1852         with mock.patch.object(
1853                 self.users,
1854                 'get_item',
1855                 return_value=expected) as mock_get_item:
1856             self.users.lookup('johndoe', 1366056668)
1857 
1858         mock_get_item.assert_called_once_with(
1859             username= 'johndoe',
1860             date_joined= 1366056668)
1861 
1862     def test_put_item(self):
1863         with mock.patch.object(
1864                 self.users.connection,
1865                 'put_item',
1866                 return_value={}) as mock_put_item:
1867             self.users.put_item(data={
1868                 'username': 'johndoe',
1869                 'last_name': 'Doe',
1870                 'date_joined': 12345,
1871             })
1872 
1873         mock_put_item.assert_called_once_with('users', {
1874             'username': {'S': 'johndoe'},
1875             'last_name': {'S': 'Doe'},
1876             'date_joined': {'N': '12345'}
1877         }, expected={
1878             'username': {
1879                 'Exists': False,
1880             },
1881             'last_name': {
1882                 'Exists': False,
1883             },
1884             'date_joined': {
1885                 'Exists': False,
1886             }
1887         })
1888 
1889     def test_private_put_item(self):
1890         with mock.patch.object(
1891                 self.users.connection,
1892                 'put_item',
1893                 return_value={}) as mock_put_item:
1894             self.users._put_item({'some': 'data'})
1895 
1896         mock_put_item.assert_called_once_with('users', {'some': 'data'})
1897 
1898     def test_private_update_item(self):
1899         with mock.patch.object(
1900                 self.users.connection,
1901                 'update_item',
1902                 return_value={}) as mock_update_item:
1903             self.users._update_item({
1904                 'username': 'johndoe'
1905             }, {
1906                 'some': 'data',
1907             })
1908 
1909         mock_update_item.assert_called_once_with('users', {
1910             'username': {'S': 'johndoe'},
1911         }, {
1912             'some': 'data',
1913         })
1914 
1915     def test_delete_item(self):
1916         with mock.patch.object(
1917                 self.users.connection,
1918                 'delete_item',
1919                 return_value={}) as mock_delete_item:
1920             self.assertTrue(self.users.delete_item(username='johndoe', date_joined=23456))
1921 
1922         mock_delete_item.assert_called_once_with('users', {
1923             'username': {
1924                 'S': 'johndoe'
1925             },
1926             'date_joined': {
1927                 'N': '23456'
1928             }
1929         }, expected=None, conditional_operator=None)
1930 
1931     def test_delete_item_conditionally(self):
1932         with mock.patch.object(
1933                 self.users.connection,
1934                 'delete_item',
1935                 return_value={}) as mock_delete_item:
1936             self.assertTrue(self.users.delete_item(expected={'balance__eq': 0},
1937                                                    username='johndoe', date_joined=23456))
1938 
1939         mock_delete_item.assert_called_once_with('users', {
1940             'username': {
1941                 'S': 'johndoe'
1942             },
1943             'date_joined': {
1944                 'N': '23456'
1945             }
1946         },
1947         expected={
1948             'balance': {
1949                 'ComparisonOperator': 'EQ', 'AttributeValueList': [{'N': '0'}]
1950                 },
1951             },
1952         conditional_operator=None)
1953 
1954         def side_effect(*args, **kwargs):
1955             raise exceptions.ConditionalCheckFailedException(400, '', {})
1956 
1957         with mock.patch.object(
1958                 self.users.connection,
1959                 'delete_item',
1960                 side_effect=side_effect) as mock_delete_item:
1961             self.assertFalse(self.users.delete_item(expected={'balance__eq': 0},
1962                                                     username='johndoe', date_joined=23456))
1963 
1964     def test_get_key_fields_no_schema_populated(self):
1965         expected = {
1966             "Table": {
1967                 "AttributeDefinitions": [
1968                     {
1969                         "AttributeName": "username",
1970                         "AttributeType": "S"
1971                     },
1972                     {
1973                         "AttributeName": "date_joined",
1974                         "AttributeType": "N"
1975                     }
1976                 ],
1977                 "ItemCount": 5,
1978                 "KeySchema": [
1979                     {
1980                         "AttributeName": "username",
1981                         "KeyType": "HASH"
1982                     },
1983                     {
1984                         "AttributeName": "date_joined",
1985                         "KeyType": "RANGE"
1986                     }
1987                 ],
1988                 "LocalSecondaryIndexes": [
1989                     {
1990                         "IndexName": "UsernameIndex",
1991                         "KeySchema": [
1992                             {
1993                                 "AttributeName": "username",
1994                                 "KeyType": "HASH"
1995                             }
1996                         ],
1997                         "Projection": {
1998                             "ProjectionType": "KEYS_ONLY"
1999                         }
2000                     }
2001                 ],
2002                 "ProvisionedThroughput": {
2003                     "ReadCapacityUnits": 20,
2004                     "WriteCapacityUnits": 6
2005                 },
2006                 "TableName": "Thread",
2007                 "TableStatus": "ACTIVE"
2008             }
2009         }
2010 
2011         with mock.patch.object(
2012                 self.users.connection,
2013                 'describe_table',
2014                 return_value=expected) as mock_describe:
2015             self.assertEqual(self.users.schema, None)
2016 
2017             key_fields = self.users.get_key_fields()
2018             self.assertEqual(key_fields, ['username', 'date_joined'])
2019 
2020             self.assertEqual(len(self.users.schema), 2)
2021 
2022         mock_describe.assert_called_once_with('users')
2023 
2024     def test_batch_write_no_writes(self):
2025         with mock.patch.object(
2026                 self.users.connection,
2027                 'batch_write_item',
2028                 return_value={}) as mock_batch:
2029             with self.users.batch_write() as batch:
2030                 pass
2031 
2032         self.assertFalse(mock_batch.called)
2033 
2034     def test_batch_write(self):
2035         with mock.patch.object(
2036                 self.users.connection,
2037                 'batch_write_item',
2038                 return_value={}) as mock_batch:
2039             with self.users.batch_write() as batch:
2040                 batch.put_item(data={
2041                     'username': 'jane',
2042                     'date_joined': 12342547
2043                 })
2044                 batch.delete_item(username='johndoe')
2045                 batch.put_item(data={
2046                     'username': 'alice',
2047                     'date_joined': 12342888
2048                 })
2049 
2050         mock_batch.assert_called_once_with({
2051             'users': [
2052                 {
2053                     'PutRequest': {
2054                         'Item': {
2055                             'username': {'S': 'jane'},
2056                             'date_joined': {'N': '12342547'}
2057                         }
2058                     }
2059                 },
2060                 {
2061                     'PutRequest': {
2062                         'Item': {
2063                             'username': {'S': 'alice'},
2064                             'date_joined': {'N': '12342888'}
2065                         }
2066                     }
2067                 },
2068                 {
2069                     'DeleteRequest': {
2070                         'Key': {
2071                             'username': {'S': 'johndoe'},
2072                         }
2073                     }
2074                 },
2075             ]
2076         })
2077 
2078     def test_batch_write_dont_swallow_exceptions(self):
2079         with mock.patch.object(
2080                 self.users.connection,
2081                 'batch_write_item',
2082                 return_value={}) as mock_batch:
2083             try:
2084                 with self.users.batch_write() as batch:
2085                     raise Exception('OH NOES')
2086             except Exception as e:
2087                 self.assertEqual(str(e), 'OH NOES')
2088 
2089         self.assertFalse(mock_batch.called)
2090 
2091     def test_batch_write_flushing(self):
2092         with mock.patch.object(
2093                 self.users.connection,
2094                 'batch_write_item',
2095                 return_value={}) as mock_batch:
2096             with self.users.batch_write() as batch:
2097                 batch.put_item(data={
2098                     'username': 'jane',
2099                     'date_joined': 12342547
2100                 })
2101                 # This would only be enough for one batch.
2102                 batch.delete_item(username='johndoe1')
2103                 batch.delete_item(username='johndoe2')
2104                 batch.delete_item(username='johndoe3')
2105                 batch.delete_item(username='johndoe4')
2106                 batch.delete_item(username='johndoe5')
2107                 batch.delete_item(username='johndoe6')
2108                 batch.delete_item(username='johndoe7')
2109                 batch.delete_item(username='johndoe8')
2110                 batch.delete_item(username='johndoe9')
2111                 batch.delete_item(username='johndoe10')
2112                 batch.delete_item(username='johndoe11')
2113                 batch.delete_item(username='johndoe12')
2114                 batch.delete_item(username='johndoe13')
2115                 batch.delete_item(username='johndoe14')
2116                 batch.delete_item(username='johndoe15')
2117                 batch.delete_item(username='johndoe16')
2118                 batch.delete_item(username='johndoe17')
2119                 batch.delete_item(username='johndoe18')
2120                 batch.delete_item(username='johndoe19')
2121                 batch.delete_item(username='johndoe20')
2122                 batch.delete_item(username='johndoe21')
2123                 batch.delete_item(username='johndoe22')
2124                 batch.delete_item(username='johndoe23')
2125 
2126                 # We're only at 24 items. No flushing yet.
2127                 self.assertEqual(mock_batch.call_count, 0)
2128 
2129                 # This pushes it over the edge. A flush happens then we start
2130                 # queuing objects again.
2131                 batch.delete_item(username='johndoe24')
2132                 self.assertEqual(mock_batch.call_count, 1)
2133                 # Since we add another, there's enough for a second call to
2134                 # flush.
2135                 batch.delete_item(username='johndoe25')
2136 
2137         self.assertEqual(mock_batch.call_count, 2)
2138 
2139     def test_batch_write_unprocessed_items(self):
2140         unprocessed = {
2141             'UnprocessedItems': {
2142                 'users': [
2143                     {
2144                         'PutRequest': {
2145                             'username': {
2146                                 'S': 'jane',
2147                             },
2148                             'date_joined': {
2149                                 'N': 12342547
2150                             }
2151                         },
2152                     },
2153                 ],
2154             },
2155         }
2156 
2157         # Test enqueuing the unprocessed bits.
2158         with mock.patch.object(
2159                 self.users.connection,
2160                 'batch_write_item',
2161                 return_value=unprocessed) as mock_batch:
2162             with self.users.batch_write() as batch:
2163                 self.assertEqual(len(batch._unprocessed), 0)
2164 
2165                 # Trash the ``resend_unprocessed`` method so that we don't
2166                 # infinite loop forever here.
2167                 batch.resend_unprocessed = lambda: True
2168 
2169                 batch.put_item(data={
2170                     'username': 'jane',
2171                     'date_joined': 12342547
2172                 })
2173                 batch.delete_item(username='johndoe')
2174                 batch.put_item(data={
2175                     'username': 'alice',
2176                     'date_joined': 12342888
2177                 })
2178 
2179             self.assertEqual(len(batch._unprocessed), 1)
2180 
2181         # Now test resending those unprocessed items.
2182         with mock.patch.object(
2183                 self.users.connection,
2184                 'batch_write_item',
2185                 return_value={}) as mock_batch:
2186             with self.users.batch_write() as batch:
2187                 self.assertEqual(len(batch._unprocessed), 0)
2188 
2189                 # Toss in faked unprocessed items, as though a previous batch
2190                 # had failed.
2191                 batch._unprocessed = [
2192                     {
2193                         'PutRequest': {
2194                             'username': {
2195                                 'S': 'jane',
2196                             },
2197                             'date_joined': {
2198                                 'N': 12342547
2199                             }
2200                         },
2201                     },
2202                 ]
2203 
2204                 batch.put_item(data={
2205                     'username': 'jane',
2206                     'date_joined': 12342547
2207                 })
2208                 batch.delete_item(username='johndoe')
2209                 batch.put_item(data={
2210                     'username': 'alice',
2211                     'date_joined': 12342888
2212                 })
2213 
2214                 # Flush, to make sure everything has been processed.
2215                 # Unprocessed items should still be hanging around.
2216                 batch.flush()
2217                 self.assertEqual(len(batch._unprocessed), 1)
2218 
2219             # Post-exit, this should be emptied.
2220             self.assertEqual(len(batch._unprocessed), 0)
2221 
2222     def test__build_filters(self):
2223         filters = self.users._build_filters({
2224             'username__eq': 'johndoe',
2225             'date_joined__gte': 1234567,
2226             'age__in': [30, 31, 32, 33],
2227             'last_name__between': ['danzig', 'only'],
2228             'first_name__null': False,
2229             'gender__null': True,
2230         }, using=FILTER_OPERATORS)
2231         self.assertEqual(filters, {
2232             'username': {
2233                 'AttributeValueList': [
2234                     {
2235                         'S': 'johndoe',
2236                     },
2237                 ],
2238                 'ComparisonOperator': 'EQ',
2239             },
2240             'date_joined': {
2241                 'AttributeValueList': [
2242                     {
2243                         'N': '1234567',
2244                     },
2245                 ],
2246                 'ComparisonOperator': 'GE',
2247             },
2248             'age': {
2249                 'AttributeValueList': [
2250                     {'N': '30'},
2251                     {'N': '31'},
2252                     {'N': '32'},
2253                     {'N': '33'},
2254                 ],
2255                 'ComparisonOperator': 'IN',
2256             },
2257             'last_name': {
2258                 'AttributeValueList': [{'S': 'danzig'}, {'S': 'only'}],
2259                 'ComparisonOperator': 'BETWEEN',
2260             },
2261             'first_name': {
2262                 'ComparisonOperator': 'NOT_NULL'
2263             },
2264             'gender': {
2265                 'ComparisonOperator': 'NULL'
2266             },
2267         })
2268 
2269         self.assertRaises(exceptions.UnknownFilterTypeError,
2270             self.users._build_filters,
2271             {
2272                 'darling__die': True,
2273             }
2274         )
2275 
2276         q_filters = self.users._build_filters({
2277             'username__eq': 'johndoe',
2278             'date_joined__gte': 1234567,
2279             'last_name__between': ['danzig', 'only'],
2280             'gender__beginswith': 'm',
2281         }, using=QUERY_OPERATORS)
2282         self.assertEqual(q_filters, {
2283             'username': {
2284                 'AttributeValueList': [
2285                     {
2286                         'S': 'johndoe',
2287                     },
2288                 ],
2289                 'ComparisonOperator': 'EQ',
2290             },
2291             'date_joined': {
2292                 'AttributeValueList': [
2293                     {
2294                         'N': '1234567',
2295                     },
2296                 ],
2297                 'ComparisonOperator': 'GE',
2298             },
2299             'last_name': {
2300                 'AttributeValueList': [{'S': 'danzig'}, {'S': 'only'}],
2301                 'ComparisonOperator': 'BETWEEN',
2302             },
2303             'gender': {
2304                 'AttributeValueList': [{'S': 'm'}],
2305                 'ComparisonOperator': 'BEGINS_WITH',
2306             },
2307         })
2308 
2309         self.assertRaises(exceptions.UnknownFilterTypeError,
2310             self.users._build_filters,
2311             {
2312                 'darling__die': True,
2313             },
2314             using=QUERY_OPERATORS
2315         )
2316         self.assertRaises(exceptions.UnknownFilterTypeError,
2317             self.users._build_filters,
2318             {
2319                 'first_name__null': True,
2320             },
2321             using=QUERY_OPERATORS
2322         )
2323 
2324     def test_private_query(self):
2325         expected = {
2326             "ConsumedCapacity": {
2327                 "CapacityUnits": 0.5,
2328                 "TableName": "users"
2329             },
2330             "Count": 4,
2331             "Items": [
2332                 {
2333                     'username': {'S': 'johndoe'},
2334                     'first_name': {'S': 'John'},
2335                     'last_name': {'S': 'Doe'},
2336                     'date_joined': {'N': '1366056668'},
2337                     'friend_count': {'N': '3'},
2338                     'friends': {'SS': ['alice', 'bob', 'jane']},
2339                 },
2340                 {
2341                     'username': {'S': 'jane'},
2342                     'first_name': {'S': 'Jane'},
2343                     'last_name': {'S': 'Doe'},
2344                     'date_joined': {'N': '1366057777'},
2345                     'friend_count': {'N': '2'},
2346                     'friends': {'SS': ['alice', 'johndoe']},
2347                 },
2348                 {
2349                     'username': {'S': 'alice'},
2350                     'first_name': {'S': 'Alice'},
2351                     'last_name': {'S': 'Expert'},
2352                     'date_joined': {'N': '1366056680'},
2353                     'friend_count': {'N': '1'},
2354                     'friends': {'SS': ['jane']},
2355                 },
2356                 {
2357                     'username': {'S': 'bob'},
2358                     'first_name': {'S': 'Bob'},
2359                     'last_name': {'S': 'Smith'},
2360                     'date_joined': {'N': '1366056888'},
2361                     'friend_count': {'N': '1'},
2362                     'friends': {'SS': ['johndoe']},
2363                 },
2364             ],
2365             "ScannedCount": 4
2366         }
2367 
2368         with mock.patch.object(
2369                 self.users.connection,
2370                 'query',
2371                 return_value=expected) as mock_query:
2372             results = self.users._query(
2373                 limit=4,
2374                 reverse=True,
2375                 username__between=['aaa', 'mmm']
2376             )
2377             usernames = [res['username'] for res in results['results']]
2378             self.assertEqual(usernames, ['johndoe', 'jane', 'alice', 'bob'])
2379             self.assertEqual(len(results['results']), 4)
2380             self.assertEqual(results['last_key'], None)
2381 
2382         mock_query.assert_called_once_with('users',
2383             consistent_read=False,
2384             scan_index_forward=False,
2385             index_name=None,
2386             attributes_to_get=None,
2387             limit=4,
2388             key_conditions={
2389                 'username': {
2390                     'AttributeValueList': [{'S': 'aaa'}, {'S': 'mmm'}],
2391                     'ComparisonOperator': 'BETWEEN',
2392                 }
2393             },
2394             select=None,
2395             query_filter=None,
2396             conditional_operator=None
2397         )
2398 
2399         # Now alter the expected.
2400         expected['LastEvaluatedKey'] = {
2401             'username': {
2402                 'S': 'johndoe',
2403             },
2404         }
2405 
2406         with mock.patch.object(
2407                 self.users.connection,
2408                 'query',
2409                 return_value=expected) as mock_query_2:
2410             results = self.users._query(
2411                 limit=4,
2412                 reverse=True,
2413                 username__between=['aaa', 'mmm'],
2414                 exclusive_start_key={
2415                     'username': 'adam',
2416                 },
2417                 consistent=True,
2418                 query_filter=None,
2419                 conditional_operator='AND'
2420             )
2421             usernames = [res['username'] for res in results['results']]
2422             self.assertEqual(usernames, ['johndoe', 'jane', 'alice', 'bob'])
2423             self.assertEqual(len(results['results']), 4)
2424             self.assertEqual(results['last_key'], {'username': 'johndoe'})
2425 
2426         mock_query_2.assert_called_once_with('users',
2427             key_conditions={
2428                 'username': {
2429                     'AttributeValueList': [{'S': 'aaa'}, {'S': 'mmm'}],
2430                     'ComparisonOperator': 'BETWEEN',
2431                 }
2432             },
2433             index_name=None,
2434             attributes_to_get=None,
2435             scan_index_forward=False,
2436             limit=4,
2437             exclusive_start_key={
2438                 'username': {
2439                     'S': 'adam',
2440                 },
2441             },
2442             consistent_read=True,
2443             select=None,
2444             query_filter=None,
2445             conditional_operator='AND'
2446         )
2447 
2448     def test_private_scan(self):
2449         expected = {
2450             "ConsumedCapacity": {
2451                 "CapacityUnits": 0.5,
2452                 "TableName": "users"
2453             },
2454             "Count": 4,
2455             "Items": [
2456                 {
2457                     'username': {'S': 'alice'},
2458                     'first_name': {'S': 'Alice'},
2459                     'last_name': {'S': 'Expert'},
2460                     'date_joined': {'N': '1366056680'},
2461                     'friend_count': {'N': '1'},
2462                     'friends': {'SS': ['jane']},
2463                 },
2464                 {
2465                     'username': {'S': 'bob'},
2466                     'first_name': {'S': 'Bob'},
2467                     'last_name': {'S': 'Smith'},
2468                     'date_joined': {'N': '1366056888'},
2469                     'friend_count': {'N': '1'},
2470                     'friends': {'SS': ['johndoe']},
2471                 },
2472                 {
2473                     'username': {'S': 'jane'},
2474                     'first_name': {'S': 'Jane'},
2475                     'last_name': {'S': 'Doe'},
2476                     'date_joined': {'N': '1366057777'},
2477                     'friend_count': {'N': '2'},
2478                     'friends': {'SS': ['alice', 'johndoe']},
2479                 },
2480             ],
2481             "ScannedCount": 4
2482         }
2483 
2484         with mock.patch.object(
2485                 self.users.connection,
2486                 'scan',
2487                 return_value=expected) as mock_scan:
2488             results = self.users._scan(
2489                 limit=2,
2490                 friend_count__lte=2
2491             )
2492             usernames = [res['username'] for res in results['results']]
2493             self.assertEqual(usernames, ['alice', 'bob', 'jane'])
2494             self.assertEqual(len(results['results']), 3)
2495             self.assertEqual(results['last_key'], None)
2496 
2497         mock_scan.assert_called_once_with('users',
2498             scan_filter={
2499                 'friend_count': {
2500                     'AttributeValueList': [{'N': '2'}],
2501                     'ComparisonOperator': 'LE',
2502                 }
2503             },
2504             limit=2,
2505             segment=None,
2506             attributes_to_get=None,
2507             total_segments=None,
2508             conditional_operator=None
2509         )
2510 
2511         # Now alter the expected.
2512         expected['LastEvaluatedKey'] = {
2513             'username': {
2514                 'S': 'jane',
2515             },
2516         }
2517 
2518         with mock.patch.object(
2519                 self.users.connection,
2520                 'scan',
2521                 return_value=expected) as mock_scan_2:
2522             results = self.users._scan(
2523                 limit=3,
2524                 friend_count__lte=2,
2525                 exclusive_start_key={
2526                     'username': 'adam',
2527                 },
2528                 segment=None,
2529                 total_segments=None
2530             )
2531             usernames = [res['username'] for res in results['results']]
2532             self.assertEqual(usernames, ['alice', 'bob', 'jane'])
2533             self.assertEqual(len(results['results']), 3)
2534             self.assertEqual(results['last_key'], {'username': 'jane'})
2535 
2536         mock_scan_2.assert_called_once_with('users',
2537             scan_filter={
2538                 'friend_count': {
2539                     'AttributeValueList': [{'N': '2'}],
2540                     'ComparisonOperator': 'LE',
2541                 }
2542             },
2543             limit=3,
2544             exclusive_start_key={
2545                 'username': {
2546                     'S': 'adam',
2547                 },
2548             },
2549             segment=None,
2550             attributes_to_get=None,
2551             total_segments=None,
2552             conditional_operator=None
2553         )
2554 
2555     def test_query(self):
2556         items_1 = {
2557             'results': [
2558                 Item(self.users, data={
2559                     'username': 'johndoe',
2560                     'first_name': 'John',
2561                     'last_name': 'Doe',
2562                 }),
2563                 Item(self.users, data={
2564                     'username': 'jane',
2565                     'first_name': 'Jane',
2566                     'last_name': 'Doe',
2567                 }),
2568             ],
2569             'last_key': 'jane',
2570         }
2571 
2572         results = self.users.query_2(last_name__eq='Doe')
2573         self.assertTrue(isinstance(results, ResultSet))
2574         self.assertEqual(len(results._results), 0)
2575         self.assertEqual(results.the_callable, self.users._query)
2576 
2577         with mock.patch.object(
2578                 results,
2579                 'the_callable',
2580                 return_value=items_1) as mock_query:
2581             res_1 = next(results)
2582             # Now it should be populated.
2583             self.assertEqual(len(results._results), 2)
2584             self.assertEqual(res_1['username'], 'johndoe')
2585             res_2 = next(results)
2586             self.assertEqual(res_2['username'], 'jane')
2587 
2588         self.assertEqual(mock_query.call_count, 1)
2589 
2590         items_2 = {
2591             'results': [
2592                 Item(self.users, data={
2593                     'username': 'foodoe',
2594                     'first_name': 'Foo',
2595                     'last_name': 'Doe',
2596                 }),
2597             ],
2598         }
2599 
2600         with mock.patch.object(
2601                 results,
2602                 'the_callable',
2603                 return_value=items_2) as mock_query_2:
2604             res_3 = next(results)
2605             # New results should have been found.
2606             self.assertEqual(len(results._results), 1)
2607             self.assertEqual(res_3['username'], 'foodoe')
2608 
2609             self.assertRaises(StopIteration, results.next)
2610 
2611         self.assertEqual(mock_query_2.call_count, 1)
2612 
2613     def test_query_with_specific_attributes(self):
2614         items_1 = {
2615             'results': [
2616                 Item(self.users, data={
2617                     'username': 'johndoe',
2618                 }),
2619                 Item(self.users, data={
2620                     'username': 'jane',
2621                 }),
2622             ],
2623             'last_key': 'jane',
2624         }
2625 
2626         results = self.users.query_2(last_name__eq='Doe',
2627                                    attributes=['username'])
2628         self.assertTrue(isinstance(results, ResultSet))
2629         self.assertEqual(len(results._results), 0)
2630         self.assertEqual(results.the_callable, self.users._query)
2631 
2632         with mock.patch.object(
2633                 results,
2634                 'the_callable',
2635                 return_value=items_1) as mock_query:
2636             res_1 = next(results)
2637             # Now it should be populated.
2638             self.assertEqual(len(results._results), 2)
2639             self.assertEqual(res_1['username'], 'johndoe')
2640             self.assertEqual(list(res_1.keys()), ['username'])
2641             res_2 = next(results)
2642             self.assertEqual(res_2['username'], 'jane')
2643 
2644         self.assertEqual(mock_query.call_count, 1)
2645 
2646     def test_scan(self):
2647         items_1 = {
2648             'results': [
2649                 Item(self.users, data={
2650                     'username': 'johndoe',
2651                     'first_name': 'John',
2652                     'last_name': 'Doe',
2653                 }),
2654                 Item(self.users, data={
2655                     'username': 'jane',
2656                     'first_name': 'Jane',
2657                     'last_name': 'Doe',
2658                 }),
2659             ],
2660             'last_key': 'jane',
2661         }
2662 
2663         results = self.users.scan(last_name__eq='Doe')
2664         self.assertTrue(isinstance(results, ResultSet))
2665         self.assertEqual(len(results._results), 0)
2666         self.assertEqual(results.the_callable, self.users._scan)
2667 
2668         with mock.patch.object(
2669                 results,
2670                 'the_callable',
2671                 return_value=items_1) as mock_scan:
2672             res_1 = next(results)
2673             # Now it should be populated.
2674             self.assertEqual(len(results._results), 2)
2675             self.assertEqual(res_1['username'], 'johndoe')
2676             res_2 = next(results)
2677             self.assertEqual(res_2['username'], 'jane')
2678 
2679         self.assertEqual(mock_scan.call_count, 1)
2680 
2681         items_2 = {
2682             'results': [
2683                 Item(self.users, data={
2684                     'username': 'zoeydoe',
2685                     'first_name': 'Zoey',
2686                     'last_name': 'Doe',
2687                 }),
2688             ],
2689         }
2690 
2691         with mock.patch.object(
2692                 results,
2693                 'the_callable',
2694                 return_value=items_2) as mock_scan_2:
2695             res_3 = next(results)
2696             # New results should have been found.
2697             self.assertEqual(len(results._results), 1)
2698             self.assertEqual(res_3['username'], 'zoeydoe')
2699 
2700             self.assertRaises(StopIteration, results.next)
2701 
2702         self.assertEqual(mock_scan_2.call_count, 1)
2703 
2704     def test_scan_with_specific_attributes(self):
2705         items_1 = {
2706             'results': [
2707                 Item(self.users, data={
2708                     'username': 'johndoe',
2709                 }),
2710                 Item(self.users, data={
2711                     'username': 'jane',
2712                 }),
2713             ],
2714             'last_key': 'jane',
2715         }
2716 
2717         results = self.users.scan(attributes=['username'])
2718         self.assertTrue(isinstance(results, ResultSet))
2719         self.assertEqual(len(results._results), 0)
2720         self.assertEqual(results.the_callable, self.users._scan)
2721 
2722         with mock.patch.object(
2723                 results,
2724                 'the_callable',
2725                 return_value=items_1) as mock_query:
2726             res_1 = next(results)
2727             # Now it should be populated.
2728             self.assertEqual(len(results._results), 2)
2729             self.assertEqual(res_1['username'], 'johndoe')
2730             self.assertEqual(list(res_1.keys()), ['username'])
2731             res_2 = next(results)
2732             self.assertEqual(res_2['username'], 'jane')
2733 
2734         self.assertEqual(mock_query.call_count, 1)
2735 
2736     def test_count(self):
2737         expected = {
2738             "Table": {
2739                 "AttributeDefinitions": [
2740                     {
2741                         "AttributeName": "username",
2742                         "AttributeType": "S"
2743                     }
2744                 ],
2745                 "ItemCount": 5,
2746                 "KeySchema": [
2747                     {
2748                         "AttributeName": "username",
2749                         "KeyType": "HASH"
2750                     }
2751                 ],
2752                 "LocalSecondaryIndexes": [
2753                     {
2754                         "IndexName": "UsernameIndex",
2755                         "KeySchema": [
2756                             {
2757                                 "AttributeName": "username",
2758                                 "KeyType": "HASH"
2759                             }
2760                         ],
2761                         "Projection": {
2762                             "ProjectionType": "KEYS_ONLY"
2763                         }
2764                     }
2765                 ],
2766                 "ProvisionedThroughput": {
2767                     "ReadCapacityUnits": 20,
2768                     "WriteCapacityUnits": 6
2769                 },
2770                 "TableName": "Thread",
2771                 "TableStatus": "ACTIVE"
2772             }
2773         }
2774 
2775         with mock.patch.object(
2776                 self.users,
2777                 'describe',
2778                 return_value=expected) as mock_count:
2779             self.assertEqual(self.users.count(), 5)
2780 
2781     def test_query_count_simple(self):
2782         expected_0 = {
2783             'Count': 0.0,
2784         }
2785 
2786         expected_1 = {
2787             'Count': 10.0,
2788         }
2789 
2790         with mock.patch.object(
2791                 self.users.connection,
2792                 'query',
2793                 return_value=expected_0) as mock_query:
2794             results = self.users.query_count(username__eq='notmyname')
2795             self.assertTrue(isinstance(results, int))
2796             self.assertEqual(results, 0)
2797         self.assertEqual(mock_query.call_count, 1)
2798         self.assertIn('scan_index_forward', mock_query.call_args[1])
2799         self.assertEqual(True, mock_query.call_args[1]['scan_index_forward'])
2800         self.assertIn('limit', mock_query.call_args[1])
2801         self.assertEqual(None, mock_query.call_args[1]['limit'])
2802 
2803         with mock.patch.object(
2804                 self.users.connection,
2805                 'query',
2806                 return_value=expected_1) as mock_query:
2807             results = self.users.query_count(username__gt='somename', consistent=True, scan_index_forward=False, limit=10)
2808             self.assertTrue(isinstance(results, int))
2809             self.assertEqual(results, 10)
2810         self.assertEqual(mock_query.call_count, 1)
2811         self.assertIn('scan_index_forward', mock_query.call_args[1])
2812         self.assertEqual(False, mock_query.call_args[1]['scan_index_forward'])
2813         self.assertIn('limit', mock_query.call_args[1])
2814         self.assertEqual(10, mock_query.call_args[1]['limit'])
2815 
2816     def test_query_count_paginated(self):
2817         def return_side_effect(*args, **kwargs):
2818             if kwargs.get('exclusive_start_key'):
2819                 return {'Count': 10, 'LastEvaluatedKey': None}
2820             else:
2821                 return {
2822                     'Count': 20,
2823                     'LastEvaluatedKey': {
2824                         'username': {
2825                             'S': 'johndoe'
2826                         },
2827                         'date_joined': {
2828                             'N': '4118642633'
2829                         }
2830                     }
2831                 }
2832 
2833         with mock.patch.object(
2834                 self.users.connection,
2835                 'query',
2836                 side_effect=return_side_effect
2837         ) as mock_query:
2838             count = self.users.query_count(username__eq='johndoe')
2839             self.assertTrue(isinstance(count, int))
2840             self.assertEqual(30, count)
2841             self.assertEqual(mock_query.call_count, 2)
2842 
2843     def test_private_batch_get(self):
2844         expected = {
2845             "ConsumedCapacity": {
2846                 "CapacityUnits": 0.5,
2847                 "TableName": "users"
2848             },
2849             'Responses': {
2850                 'users': [
2851                     {
2852                         'username': {'S': 'alice'},
2853                         'first_name': {'S': 'Alice'},
2854                         'last_name': {'S': 'Expert'},
2855                         'date_joined': {'N': '1366056680'},
2856                         'friend_count': {'N': '1'},
2857                         'friends': {'SS': ['jane']},
2858                     },
2859                     {
2860                         'username': {'S': 'bob'},
2861                         'first_name': {'S': 'Bob'},
2862                         'last_name': {'S': 'Smith'},
2863                         'date_joined': {'N': '1366056888'},
2864                         'friend_count': {'N': '1'},
2865                         'friends': {'SS': ['johndoe']},
2866                     },
2867                     {
2868                         'username': {'S': 'jane'},
2869                         'first_name': {'S': 'Jane'},
2870                         'last_name': {'S': 'Doe'},
2871                         'date_joined': {'N': '1366057777'},
2872                         'friend_count': {'N': '2'},
2873                         'friends': {'SS': ['alice', 'johndoe']},
2874                     },
2875                 ],
2876             },
2877             "UnprocessedKeys": {
2878             },
2879         }
2880 
2881         with mock.patch.object(
2882                 self.users.connection,
2883                 'batch_get_item',
2884                 return_value=expected) as mock_batch_get:
2885             results = self.users._batch_get(keys=[
2886                 {'username': 'alice', 'friend_count': 1},
2887                 {'username': 'bob', 'friend_count': 1},
2888                 {'username': 'jane'},
2889             ])
2890             usernames = [res['username'] for res in results['results']]
2891             self.assertEqual(usernames, ['alice', 'bob', 'jane'])
2892             self.assertEqual(len(results['results']), 3)
2893             self.assertEqual(results['last_key'], None)
2894             self.assertEqual(results['unprocessed_keys'], [])
2895 
2896         mock_batch_get.assert_called_once_with(request_items={
2897             'users': {
2898                 'Keys': [
2899                     {
2900                         'username': {'S': 'alice'},
2901                         'friend_count': {'N': '1'}
2902                     },
2903                     {
2904                         'username': {'S': 'bob'},
2905                         'friend_count': {'N': '1'}
2906                     }, {
2907                         'username': {'S': 'jane'},
2908                     }
2909                 ]
2910             }
2911         })
2912 
2913         # Now alter the expected.
2914         del expected['Responses']['users'][2]
2915         expected['UnprocessedKeys'] = {
2916             'Keys': [
2917                 {'username': {'S': 'jane',}},
2918             ],
2919         }
2920 
2921         with mock.patch.object(
2922                 self.users.connection,
2923                 'batch_get_item',
2924                 return_value=expected) as mock_batch_get_2:
2925             results = self.users._batch_get(keys=[
2926                 {'username': 'alice', 'friend_count': 1},
2927                 {'username': 'bob', 'friend_count': 1},
2928                 {'username': 'jane'},
2929             ])
2930             usernames = [res['username'] for res in results['results']]
2931             self.assertEqual(usernames, ['alice', 'bob'])
2932             self.assertEqual(len(results['results']), 2)
2933             self.assertEqual(results['last_key'], None)
2934             self.assertEqual(results['unprocessed_keys'], [
2935                 {'username': 'jane'}
2936             ])
2937 
2938         mock_batch_get_2.assert_called_once_with(request_items={
2939             'users': {
2940                 'Keys': [
2941                     {
2942                         'username': {'S': 'alice'},
2943                         'friend_count': {'N': '1'}
2944                     },
2945                     {
2946                         'username': {'S': 'bob'},
2947                         'friend_count': {'N': '1'}
2948                     }, {
2949                         'username': {'S': 'jane'},
2950                     }
2951                 ]
2952             }
2953         })
2954 
2955     def test_private_batch_get_attributes(self):
2956         # test if AttributesToGet parameter is passed to DynamoDB API
2957         expected = {
2958             "ConsumedCapacity": {
2959                 "CapacityUnits": 0.5,
2960                 "TableName": "users"
2961             },
2962             'Responses': {
2963                 'users': [
2964                     {
2965                         'username': {'S': 'alice'},
2966                         'first_name': {'S': 'Alice'},
2967                     },
2968                     {
2969                         'username': {'S': 'bob'},
2970                         'first_name': {'S': 'Bob'},
2971                     },
2972                 ],
2973             },
2974             "UnprocessedKeys": {},
2975         }
2976 
2977         with mock.patch.object(
2978                 self.users.connection,
2979                 'batch_get_item',
2980                 return_value=expected) as mock_batch_get_attr:
2981             results = self.users._batch_get(keys=[
2982                     {'username': 'alice'},
2983                     {'username': 'bob'},
2984                 ], attributes=['username', 'first_name'])
2985             usernames = [res['username'] for res in results['results']]
2986             first_names = [res['first_name'] for res in results['results']]
2987             self.assertEqual(usernames, ['alice', 'bob'])
2988             self.assertEqual(first_names, ['Alice', 'Bob'])
2989             self.assertEqual(len(results['results']), 2)
2990             self.assertEqual(results['last_key'], None)
2991             self.assertEqual(results['unprocessed_keys'], [])
2992 
2993         mock_batch_get_attr.assert_called_once_with(request_items={
2994             'users': {
2995                 'Keys': [ { 'username': {'S': 'alice'} },
2996                           { 'username': {'S': 'bob'} }, ],
2997                 'AttributesToGet': ['username', 'first_name'],
2998             },
2999         })
3000 
3001     def test_batch_get(self):
3002         items_1 = {
3003             'results': [
3004                 Item(self.users, data={
3005                     'username': 'johndoe',
3006                     'first_name': 'John',
3007                     'last_name': 'Doe',
3008                 }),
3009                 Item(self.users, data={
3010                     'username': 'jane',
3011                     'first_name': 'Jane',
3012                     'last_name': 'Doe',
3013                 }),
3014             ],
3015             'last_key': None,
3016             'unprocessed_keys': [
3017                 'zoeydoe',
3018             ]
3019         }
3020 
3021         results = self.users.batch_get(keys=[
3022             {'username': 'johndoe'},
3023             {'username': 'jane'},
3024             {'username': 'zoeydoe'},
3025         ])
3026         self.assertTrue(isinstance(results, BatchGetResultSet))
3027         self.assertEqual(len(results._results), 0)
3028         self.assertEqual(results.the_callable, self.users._batch_get)
3029 
3030         with mock.patch.object(
3031                 results,
3032                 'the_callable',
3033                 return_value=items_1) as mock_batch_get:
3034             res_1 = next(results)
3035             # Now it should be populated.
3036             self.assertEqual(len(results._results), 2)
3037             self.assertEqual(res_1['username'], 'johndoe')
3038             res_2 = next(results)
3039             self.assertEqual(res_2['username'], 'jane')
3040 
3041         self.assertEqual(mock_batch_get.call_count, 1)
3042         self.assertEqual(results._keys_left, ['zoeydoe'])
3043 
3044         items_2 = {
3045             'results': [
3046                 Item(self.users, data={
3047                     'username': 'zoeydoe',
3048                     'first_name': 'Zoey',
3049                     'last_name': 'Doe',
3050                 }),
3051             ],
3052         }
3053 
3054         with mock.patch.object(
3055                 results,
3056                 'the_callable',
3057                 return_value=items_2) as mock_batch_get_2:
3058             res_3 = next(results)
3059             # New results should have been found.
3060             self.assertEqual(len(results._results), 1)
3061             self.assertEqual(res_3['username'], 'zoeydoe')
3062 
3063             self.assertRaises(StopIteration, results.next)
3064 
3065         self.assertEqual(mock_batch_get_2.call_count, 1)
3066         self.assertEqual(results._keys_left, [])
3067