PageRenderTime 73ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/tests/unit/dynamodb2/test_table.py

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