Skip to content

Commit fabf329

Browse files
committed
Ensure binary attrs are parsed correctly
1 parent f5b555a commit fabf329

File tree

2 files changed

+32
-116
lines changed

2 files changed

+32
-116
lines changed

pynamodb/connection/base.py

Lines changed: 4 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
"""
22
Lowest level connection
33
"""
4-
import json
54
import logging
6-
import random
7-
import sys
8-
import time
95
import uuid
106
from base64 import b64decode
117
from threading import local
@@ -15,7 +11,6 @@
1511
import botocore.exceptions
1612
from botocore.awsrequest import AWSPreparedRequest, create_request_object
1713
from botocore.client import ClientError
18-
from botocore.hooks import first_non_none_response
1914
from botocore.exceptions import BotoCoreError
2015
from botocore.session import get_session
2116

@@ -30,18 +25,14 @@
3025
PUT_ITEM, SELECT, LIMIT, QUERY, SCAN, ITEM, LOCAL_SECONDARY_INDEXES,
3126
KEYS, KEY, SEGMENT, TOTAL_SEGMENTS, CREATE_TABLE, PROVISIONED_THROUGHPUT, READ_CAPACITY_UNITS,
3227
WRITE_CAPACITY_UNITS, GLOBAL_SECONDARY_INDEXES, PROJECTION, EXCLUSIVE_START_TABLE_NAME, TOTAL,
33-
DELETE_TABLE, UPDATE_TABLE, LIST_TABLES, GLOBAL_SECONDARY_INDEX_UPDATES, ATTRIBUTES,
34-
CONSUMED_CAPACITY, CAPACITY_UNITS, ATTRIBUTE_TYPES,
35-
ITEMS, DEFAULT_ENCODING, BINARY, BINARY_SET, LAST_EVALUATED_KEY, RESPONSES, UNPROCESSED_KEYS,
36-
UNPROCESSED_ITEMS, STREAM_SPECIFICATION, STREAM_VIEW_TYPE, STREAM_ENABLED,
37-
EXPRESSION_ATTRIBUTE_NAMES, EXPRESSION_ATTRIBUTE_VALUES,
38-
CONDITION_EXPRESSION, FILTER_EXPRESSION,
28+
DELETE_TABLE, UPDATE_TABLE, LIST_TABLES, GLOBAL_SECONDARY_INDEX_UPDATES, CONSUMED_CAPACITY, CAPACITY_UNITS,
29+
ATTRIBUTE_TYPES, DEFAULT_ENCODING, BINARY, BINARY_SET, STREAM_SPECIFICATION, STREAM_VIEW_TYPE, STREAM_ENABLED,
30+
EXPRESSION_ATTRIBUTE_NAMES, EXPRESSION_ATTRIBUTE_VALUES, CONDITION_EXPRESSION, FILTER_EXPRESSION,
3931
TRANSACT_WRITE_ITEMS, TRANSACT_GET_ITEMS, CLIENT_REQUEST_TOKEN, TRANSACT_ITEMS, TRANSACT_CONDITION_CHECK,
4032
TRANSACT_GET, TRANSACT_PUT, TRANSACT_DELETE, TRANSACT_UPDATE, UPDATE_EXPRESSION,
4133
RETURN_VALUES_ON_CONDITION_FAILURE_VALUES, RETURN_VALUES_ON_CONDITION_FAILURE,
4234
AVAILABLE_BILLING_MODES, DEFAULT_BILLING_MODE, BILLING_MODE, PAY_PER_REQUEST_BILLING_MODE,
43-
PROVISIONED_BILLING_MODE,
44-
TIME_TO_LIVE_SPECIFICATION, ENABLED, UPDATE_TIME_TO_LIVE, TAGS, VALUE
35+
PROVISIONED_BILLING_MODE, TIME_TO_LIVE_SPECIFICATION, ENABLED, UPDATE_TIME_TO_LIVE, TAGS, VALUE
4536
)
4637
from pynamodb.exceptions import (
4738
TableError, QueryError, PutError, DeleteError, UpdateError, GetError, ScanError, TableDoesNotExist,
@@ -351,46 +342,6 @@ def send_pre_boto_callback(self, operation_name, req_uuid, table_name):
351342
def _make_api_call(self, operation_name: str, operation_kwargs: Dict, settings: OperationSettings = OperationSettings.default) -> Dict:
352343
return self.client._make_api_call(operation_name, operation_kwargs)
353344

354-
@staticmethod
355-
def _handle_binary_attributes(data):
356-
""" Simulate botocore's binary attribute handling """
357-
if ITEM in data:
358-
for attr in data[ITEM].values():
359-
_convert_binary(attr)
360-
if ITEMS in data:
361-
for item in data[ITEMS]:
362-
for attr in item.values():
363-
_convert_binary(attr)
364-
if RESPONSES in data:
365-
if isinstance(data[RESPONSES], list):
366-
for item in data[RESPONSES]:
367-
for attr in item.values():
368-
_convert_binary(attr)
369-
else:
370-
for item_list in data[RESPONSES].values():
371-
for item in item_list:
372-
for attr in item.values():
373-
_convert_binary(attr)
374-
if LAST_EVALUATED_KEY in data:
375-
for attr in data[LAST_EVALUATED_KEY].values():
376-
_convert_binary(attr)
377-
if UNPROCESSED_KEYS in data:
378-
for table_data in data[UNPROCESSED_KEYS].values():
379-
for item in table_data[KEYS]:
380-
for attr in item.values():
381-
_convert_binary(attr)
382-
if UNPROCESSED_ITEMS in data:
383-
for table_unprocessed_requests in data[UNPROCESSED_ITEMS].values():
384-
for request in table_unprocessed_requests:
385-
for item_mapping in request.values():
386-
for item in item_mapping.values():
387-
for attr in item.values():
388-
_convert_binary(attr)
389-
if ATTRIBUTES in data:
390-
for attr in data[ATTRIBUTES].values():
391-
_convert_binary(attr)
392-
return data
393-
394345
@property
395346
def session(self) -> botocore.session.Session:
396347
"""

tests/test_base_connection.py

Lines changed: 28 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
import json
66
from unittest import mock
77
from unittest.mock import patch
8-
from unittest.mock import ANY
98

109
import botocore.exceptions
11-
from botocore.awsrequest import AWSPreparedRequest, AWSRequest, AWSResponse
10+
from botocore.awsrequest import AWSResponse
1211
from botocore.client import ClientError
1312
from botocore.exceptions import BotoCoreError
1413

@@ -25,7 +24,6 @@
2524
from pynamodb.expressions.update import SetAction
2625
from pynamodb.settings import OperationSettings
2726
from .data import DESCRIBE_TABLE_DATA, GET_ITEM_DATA, LIST_TABLE_DATA
28-
from .deep_eq import deep_eq
2927

3028
PATCH_METHOD = 'pynamodb.connection.Connection._make_api_call'
3129
TEST_TABLE_NAME = 'ci-table'
@@ -1512,74 +1510,41 @@ def test_connection_make_api_call__throws_retry_disabled(send_mock):
15121510
assert len(send_mock.mock_calls) == 1
15131511

15141512

1515-
def test_connection_handle_binary_attributes__unprocessed_items():
1513+
@mock.patch('botocore.httpsession.URLLib3Session.send')
1514+
def test_connection_make_api_call__binary_attributes(send_mock):
15161515
binary_blob = b'\x00\xFF\x00\xFF'
1517-
1518-
unprocessed_items = []
1519-
for _ in range(0, 5):
1520-
unprocessed_items.append({
1521-
'PutRequest': {
1522-
'Item': {
1523-
'name': {STRING: 'daniel'},
1524-
'picture': {BINARY: base64.b64encode(binary_blob).decode(DEFAULT_ENCODING)}
1525-
}
1526-
}
1527-
})
1528-
1529-
expected_unprocessed_items = []
1530-
for _ in range(0, 5):
1531-
expected_unprocessed_items.append({
1532-
'PutRequest': {
1533-
'Item': {
1534-
'name': {STRING: 'daniel'},
1535-
'picture': {BINARY: binary_blob}
1516+
resp_text = json.dumps({
1517+
UNPROCESSED_ITEMS: {
1518+
'someTable': [{
1519+
'PutRequest': {
1520+
'Item': {
1521+
'name': {STRING: 'daniel'},
1522+
'picture': {BINARY: base64.b64encode(binary_blob).decode(DEFAULT_ENCODING)},
1523+
}
15361524
}
1537-
}
1538-
})
1525+
}],
1526+
}
1527+
})
15391528

1540-
assert (
1541-
Connection._handle_binary_attributes({UNPROCESSED_ITEMS: {'someTable': unprocessed_items}}) ==
1542-
{UNPROCESSED_ITEMS: {'someTable': expected_unprocessed_items}}
1529+
resp = mock.Mock(
1530+
spec=AWSResponse,
1531+
status_code=200,
1532+
headers={},
1533+
content=resp_text.encode(),
15431534
)
15441535

1536+
send_mock.return_value = resp
15451537

1546-
def test_connection_handle_binary_attributes__unprocessed_keys():
1547-
binary_blob = b'\x00\xFF\x00\xFF'
1548-
unprocessed_keys = {
1549-
'UnprocessedKeys': {
1550-
'MyTable': {
1551-
'AttributesToGet': ['ForumName'],
1552-
'Keys': [
1553-
{
1554-
'ForumName': {'S': 'FooForum'},
1555-
'Subject': {'B': base64.b64encode(binary_blob).decode(DEFAULT_ENCODING)}
1556-
},
1557-
{
1558-
'ForumName': {'S': 'FooForum'},
1559-
'Subject': {'S': 'thread-1'}
1560-
}
1561-
],
1562-
'ConsistentRead': False
1563-
},
1564-
'MyOtherTable': {
1565-
'AttributesToGet': ['ForumName'],
1566-
'Keys': [
1567-
{
1568-
'ForumName': {'S': 'FooForum'},
1569-
'Subject': {'B': base64.b64encode(binary_blob).decode(DEFAULT_ENCODING)}
1570-
},
1571-
{
1572-
'ForumName': {'S': 'FooForum'},
1573-
'Subject': {'S': 'thread-1'}
1574-
}
1575-
],
1576-
'ConsistentRead': False
1538+
resp = Connection()._make_api_call('BatchWriteItem', {})
1539+
1540+
assert resp['UnprocessedItems']['someTable'] == [{
1541+
'PutRequest': {
1542+
'Item': {
1543+
'name': {STRING: 'daniel'},
1544+
'picture': {BINARY: binary_blob}
15771545
}
15781546
}
1579-
}
1580-
data = Connection._handle_binary_attributes(unprocessed_keys)
1581-
assert data['UnprocessedKeys']['MyTable']['Keys'][0]['Subject']['B'] == binary_blob
1582-
assert data['UnprocessedKeys']['MyOtherTable']['Keys'][0]['Subject']['B'] == binary_blob
1547+
}]
15831548

15841549

15851550
def test_connection_update_time_to_live__fail():

0 commit comments

Comments
 (0)