diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index 0854f428f..8a061041e 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -108,14 +108,23 @@ class DynamoType(object): self.value.pop(key) def filter(self, projection_expressions): - nested_projections = [expr[0:expr.index('.')] for expr in projection_expressions if '.' in expr] + nested_projections = [ + expr[0 : expr.index(".")] for expr in projection_expressions if "." in expr + ] if self.is_map(): expressions_to_delete = [] for attr in self.value: - if attr not in projection_expressions and attr not in nested_projections: + if ( + attr not in projection_expressions + and attr not in nested_projections + ): expressions_to_delete.append(attr) elif attr in nested_projections: - relevant_expressions = [expr[len(attr + '.'):] for expr in projection_expressions if expr.startswith(attr + '.')] + relevant_expressions = [ + expr[len(attr + ".") :] + for expr in projection_expressions + if expr.startswith(attr + ".") + ] self.value[attr].filter(relevant_expressions) for expr in expressions_to_delete: self.value.pop(expr) @@ -493,13 +502,19 @@ class Item(BaseModel): # Filter using projection_expression # Ensure a deep copy is used to filter, otherwise actual data will be removed def filter(self, projection_expression): - expressions = [x.strip() for x in projection_expression.split(',')] - top_level_expressions = [expr[0:expr.index('.')] for expr in expressions if '.' in expr] + expressions = [x.strip() for x in projection_expression.split(",")] + top_level_expressions = [ + expr[0 : expr.index(".")] for expr in expressions if "." in expr + ] for attr in list(self.attrs): if attr not in expressions and attr not in top_level_expressions: self.attrs.pop(attr) if attr in top_level_expressions: - relevant_expressions = [expr[len(attr + '.'):] for expr in expressions if expr.startswith(attr + '.')] + relevant_expressions = [ + expr[len(attr + ".") :] + for expr in expressions + if expr.startswith(attr + ".") + ] self.attrs[attr].filter(relevant_expressions) diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py index 9c7bcd1f8..0e39a1da1 100644 --- a/moto/dynamodb2/responses.py +++ b/moto/dynamodb2/responses.py @@ -346,7 +346,9 @@ class DynamoHandler(BaseResponse): projection_expression = self.body.get("ProjectionExpression") expression_attribute_names = self.body.get("ExpressionAttributeNames", {}) - projection_expression = self._adjust_projection_expression(projection_expression, expression_attribute_names) + projection_expression = self._adjust_projection_expression( + projection_expression, expression_attribute_names + ) try: item = self.dynamodb_backend.get_item(name, key, projection_expression) @@ -413,7 +415,9 @@ class DynamoHandler(BaseResponse): filter_expression = self.body.get("FilterExpression") expression_attribute_values = self.body.get("ExpressionAttributeValues", {}) - projection_expression = self._adjust_projection_expression(projection_expression, expression_attribute_names) + projection_expression = self._adjust_projection_expression( + projection_expression, expression_attribute_names + ) filter_kwargs = {} @@ -569,10 +573,20 @@ class DynamoHandler(BaseResponse): def _adjust_projection_expression(self, projection_expression, expr_attr_names): def _adjust(expression): - return expr_attr_names[expression] if expression in expr_attr_names else expression + return ( + expr_attr_names[expression] + if expression in expr_attr_names + else expression + ) + if projection_expression and expr_attr_names: - expressions = [x.strip() for x in projection_expression.split(',')] - return ','.join(['.'.join([_adjust(expr) for expr in nested_expr.split('.')]) for nested_expr in expressions]) + expressions = [x.strip() for x in projection_expression.split(",")] + return ",".join( + [ + ".".join([_adjust(expr) for expr in nested_expr.split(".")]) + for nested_expr in expressions + ] + ) return projection_expression diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index d6802f66d..d492b0135 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -561,174 +561,304 @@ def test_basic_projection_expressions_using_scan(): @mock_dynamodb2 def test_nested_projection_expression_using_get_item(): - dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + dynamodb = boto3.resource("dynamodb", region_name="us-east-1") # Create the DynamoDB table. - dynamodb.create_table(TableName='users', - KeySchema=[{'AttributeName': 'forum_name', 'KeyType': 'HASH'}], - AttributeDefinitions=[{'AttributeName': 'forum_name', 'AttributeType': 'S'}], - ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5}) - table = dynamodb.Table('users') - table.put_item(Item={'forum_name': 'key1', 'nested': {'level1': {'id': 'id1', 'att': 'irrelevant'}, - 'level2': {'id': 'id2', 'include': 'all'}, - 'level3': {'id': 'irrelevant'}}, - 'foo': 'bar'}) - table.put_item(Item={'forum_name': 'key2', 'nested': {'id': 'id2', 'incode': 'code2'}, 'foo': 'bar'}) + dynamodb.create_table( + TableName="users", + KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + table = dynamodb.Table("users") + table.put_item( + Item={ + "forum_name": "key1", + "nested": { + "level1": {"id": "id1", "att": "irrelevant"}, + "level2": {"id": "id2", "include": "all"}, + "level3": {"id": "irrelevant"}, + }, + "foo": "bar", + } + ) + table.put_item( + Item={ + "forum_name": "key2", + "nested": {"id": "id2", "incode": "code2"}, + "foo": "bar", + } + ) # Test a get_item returning all items - result = table.get_item(Key={'forum_name': 'key1'}, - ProjectionExpression='nested.level1.id, nested.level2')['Item'] - result.should.equal({'nested': {'level1': {'id': 'id1'}, - 'level2': {'id': 'id2', 'include': 'all'}}}) + result = table.get_item( + Key={"forum_name": "key1"}, + ProjectionExpression="nested.level1.id, nested.level2", + )["Item"] + result.should.equal( + {"nested": {"level1": {"id": "id1"}, "level2": {"id": "id2", "include": "all"}}} + ) # Assert actual data has not been deleted - result = table.get_item(Key={'forum_name': 'key1'})['Item'] - result.should.equal({u'foo': u'bar', - u'forum_name': u'key1', - u'nested': {u'level1': {u'id': u'id1', u'att': u'irrelevant'}, - u'level2': {u'id': u'id2', u'include': u'all'}, - u'level3': {u'id': u'irrelevant'}}}) + result = table.get_item(Key={"forum_name": "key1"})["Item"] + result.should.equal( + { + "foo": "bar", + "forum_name": "key1", + "nested": { + "level1": {"id": "id1", "att": "irrelevant"}, + "level2": {"id": "id2", "include": "all"}, + "level3": {"id": "irrelevant"}, + }, + } + ) @mock_dynamodb2 def test_basic_projection_expressions_using_query(): - dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + dynamodb = boto3.resource("dynamodb", region_name="us-east-1") # Create the DynamoDB table. - dynamodb.create_table(TableName='users', - KeySchema=[{'AttributeName': 'forum_name', 'KeyType': 'HASH'}, - {'AttributeName': 'subject', 'KeyType': 'RANGE'}], - AttributeDefinitions=[{'AttributeName': 'forum_name', 'AttributeType': 'S'}, - {'AttributeName': 'subject', 'AttributeType': 'S'}], - ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5}) - table = dynamodb.Table('users') - table.put_item(Item={'forum_name': 'the-key', 'subject': '123', 'body': 'some test message'}) - table.put_item(Item={'forum_name': 'not-the-key', 'subject': '123', 'body': 'some other test message'}) + dynamodb.create_table( + TableName="users", + KeySchema=[ + {"AttributeName": "forum_name", "KeyType": "HASH"}, + {"AttributeName": "subject", "KeyType": "RANGE"}, + ], + AttributeDefinitions=[ + {"AttributeName": "forum_name", "AttributeType": "S"}, + {"AttributeName": "subject", "AttributeType": "S"}, + ], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + table = dynamodb.Table("users") + table.put_item( + Item={"forum_name": "the-key", "subject": "123", "body": "some test message"} + ) + table.put_item( + Item={ + "forum_name": "not-the-key", + "subject": "123", + "body": "some other test message", + } + ) # Test a query returning all items - result = table.query(KeyConditionExpression=Key('forum_name').eq('the-key'), - ProjectionExpression='body, subject')['Items'][0] + result = table.query( + KeyConditionExpression=Key("forum_name").eq("the-key"), + ProjectionExpression="body, subject", + )["Items"][0] - assert 'body' in result - assert result['body'] == 'some test message' - assert 'subject' in result - assert 'forum_name' not in result + assert "body" in result + assert result["body"] == "some test message" + assert "subject" in result + assert "forum_name" not in result - table.put_item(Item={'forum_name': 'the-key', 'subject': '1234', 'body': 'yet another test message'}) + table.put_item( + Item={ + "forum_name": "the-key", + "subject": "1234", + "body": "yet another test message", + } + ) - items = table.query(KeyConditionExpression=Key('forum_name').eq('the-key'), - ProjectionExpression='body')['Items'] + items = table.query( + KeyConditionExpression=Key("forum_name").eq("the-key"), + ProjectionExpression="body", + )["Items"] - assert 'body' in items[0] - assert 'subject' not in items[0] - assert items[0]['body'] == 'some test message' - assert 'body' in items[1] - assert 'subject' not in items[1] - assert items[1]['body'] == 'yet another test message' + assert "body" in items[0] + assert "subject" not in items[0] + assert items[0]["body"] == "some test message" + assert "body" in items[1] + assert "subject" not in items[1] + assert items[1]["body"] == "yet another test message" # The projection expression should not remove data from storage - items = table.query(KeyConditionExpression=Key('forum_name').eq('the-key'))['Items'] - assert 'subject' in items[0] - assert 'body' in items[1] - assert 'forum_name' in items[1] + items = table.query(KeyConditionExpression=Key("forum_name").eq("the-key"))["Items"] + assert "subject" in items[0] + assert "body" in items[1] + assert "forum_name" in items[1] @mock_dynamodb2 def test_nested_projection_expression_using_query(): - dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + dynamodb = boto3.resource("dynamodb", region_name="us-east-1") # Create the DynamoDB table. - dynamodb.create_table(TableName='users', - KeySchema=[{'AttributeName': 'forum_name', 'KeyType': 'HASH'}], - AttributeDefinitions=[{'AttributeName': 'forum_name', 'AttributeType': 'S'}], - ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5}) - table = dynamodb.Table('users') - table.put_item(Item={'forum_name': 'key1', 'nested': {'level1': {'id': 'id1', 'att': 'irrelevant'}, - 'level2': {'id': 'id2', 'include': 'all'}, - 'level3': {'id': 'irrelevant'}}, - 'foo': 'bar'}) - table.put_item(Item={'forum_name': 'key2', 'nested': {'id': 'id2', 'incode': 'code2'}, 'foo': 'bar'}) + dynamodb.create_table( + TableName="users", + KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + table = dynamodb.Table("users") + table.put_item( + Item={ + "forum_name": "key1", + "nested": { + "level1": {"id": "id1", "att": "irrelevant"}, + "level2": {"id": "id2", "include": "all"}, + "level3": {"id": "irrelevant"}, + }, + "foo": "bar", + } + ) + table.put_item( + Item={ + "forum_name": "key2", + "nested": {"id": "id2", "incode": "code2"}, + "foo": "bar", + } + ) # Test a query returning all items - result = table.query(KeyConditionExpression=Key('forum_name').eq('key1'), - ProjectionExpression="nested.level1.id, nested.level2")['Items'][0] + result = table.query( + KeyConditionExpression=Key("forum_name").eq("key1"), + ProjectionExpression="nested.level1.id, nested.level2", + )["Items"][0] - assert 'nested' in result - result['nested'].should.equal({'level1': {'id': 'id1'}, - 'level2': {'id': 'id2', 'include': 'all'}}) - assert 'foo' not in result + assert "nested" in result + result["nested"].should.equal( + {"level1": {"id": "id1"}, "level2": {"id": "id2", "include": "all"}} + ) + assert "foo" not in result # Assert actual data has not been deleted - result = table.query(KeyConditionExpression=Key('forum_name').eq('key1'))['Items'][0] - result.should.equal({u'foo': u'bar', - u'forum_name': u'key1', - u'nested': {u'level1': {u'id': u'id1', u'att': u'irrelevant'}, - u'level2': {u'id': u'id2', u'include': u'all'}, - u'level3': {u'id': u'irrelevant'}}}) + result = table.query(KeyConditionExpression=Key("forum_name").eq("key1"))["Items"][ + 0 + ] + result.should.equal( + { + "foo": "bar", + "forum_name": "key1", + "nested": { + "level1": {"id": "id1", "att": "irrelevant"}, + "level2": {"id": "id2", "include": "all"}, + "level3": {"id": "irrelevant"}, + }, + } + ) @mock_dynamodb2 def test_basic_projection_expressions_using_scan(): - dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + dynamodb = boto3.resource("dynamodb", region_name="us-east-1") # Create the DynamoDB table. - dynamodb.create_table(TableName='users', - KeySchema=[{'AttributeName': 'forum_name', 'KeyType': 'HASH'}, - {'AttributeName': 'subject', 'KeyType': 'RANGE'}], - AttributeDefinitions=[{'AttributeName': 'forum_name', 'AttributeType': 'S'}, - {'AttributeName': 'subject', 'AttributeType': 'S'}], - ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5}) - table = dynamodb.Table('users') + dynamodb.create_table( + TableName="users", + KeySchema=[ + {"AttributeName": "forum_name", "KeyType": "HASH"}, + {"AttributeName": "subject", "KeyType": "RANGE"}, + ], + AttributeDefinitions=[ + {"AttributeName": "forum_name", "AttributeType": "S"}, + {"AttributeName": "subject", "AttributeType": "S"}, + ], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + table = dynamodb.Table("users") - table.put_item(Item={'forum_name': 'the-key', 'subject': '123', 'body': 'some test message'}) - table.put_item(Item={'forum_name': 'not-the-key', 'subject': '123', 'body': 'some other test message'}) + table.put_item( + Item={"forum_name": "the-key", "subject": "123", "body": "some test message"} + ) + table.put_item( + Item={ + "forum_name": "not-the-key", + "subject": "123", + "body": "some other test message", + } + ) # Test a scan returning all items - results = table.scan(FilterExpression=Key('forum_name').eq('the-key'), - ProjectionExpression='body, subject')['Items'] + results = table.scan( + FilterExpression=Key("forum_name").eq("the-key"), + ProjectionExpression="body, subject", + )["Items"] - results.should.equal([{u'body': u'some test message', u'subject': u'123'}]) + results.should.equal([{"body": "some test message", "subject": "123"}]) - table.put_item(Item={'forum_name': 'the-key', 'subject': '1234', 'body': 'yet another test message'}) + table.put_item( + Item={ + "forum_name": "the-key", + "subject": "1234", + "body": "yet another test message", + } + ) - results = table.scan(FilterExpression=Key('forum_name').eq('the-key'), - ProjectionExpression='body')['Items'] + results = table.scan( + FilterExpression=Key("forum_name").eq("the-key"), ProjectionExpression="body" + )["Items"] - assert {u'body': u'some test message'} in results - assert {u'body': u'yet another test message'} in results + assert {"body": "some test message"} in results + assert {"body": "yet another test message"} in results # The projection expression should not remove data from storage - results = table.query(KeyConditionExpression=Key('forum_name').eq('the-key')) - assert 'subject' in results['Items'][0] - assert 'body' in results['Items'][1] - assert 'forum_name' in results['Items'][1] + results = table.query(KeyConditionExpression=Key("forum_name").eq("the-key")) + assert "subject" in results["Items"][0] + assert "body" in results["Items"][1] + assert "forum_name" in results["Items"][1] @mock_dynamodb2 def test_nested_projection_expression_using_scan(): - dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + dynamodb = boto3.resource("dynamodb", region_name="us-east-1") # Create the DynamoDB table. - dynamodb.create_table(TableName='users', - KeySchema=[{'AttributeName': 'forum_name', 'KeyType': 'HASH'}], - AttributeDefinitions=[{'AttributeName': 'forum_name', 'AttributeType': 'S'}], - ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5}) - table = dynamodb.Table('users') - table.put_item(Item={'forum_name': 'key1', 'nested': {'level1': {'id': 'id1', 'att': 'irrelevant'}, - 'level2': {'id': 'id2', 'include': 'all'}, - 'level3': {'id': 'irrelevant'}}, - 'foo': 'bar'}) - table.put_item(Item={'forum_name': 'key2', 'nested': {'id': 'id2', 'incode': 'code2'}, 'foo': 'bar'}) + dynamodb.create_table( + TableName="users", + KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + table = dynamodb.Table("users") + table.put_item( + Item={ + "forum_name": "key1", + "nested": { + "level1": {"id": "id1", "att": "irrelevant"}, + "level2": {"id": "id2", "include": "all"}, + "level3": {"id": "irrelevant"}, + }, + "foo": "bar", + } + ) + table.put_item( + Item={ + "forum_name": "key2", + "nested": {"id": "id2", "incode": "code2"}, + "foo": "bar", + } + ) # Test a scan - results = table.scan(FilterExpression=Key('forum_name').eq('key1'), - ProjectionExpression='nested.level1.id, nested.level2')['Items'] - results.should.equal([{'nested': {'level1': {'id': 'id1'}, - 'level2': {'include': 'all', 'id': 'id2'}}}]) + results = table.scan( + FilterExpression=Key("forum_name").eq("key1"), + ProjectionExpression="nested.level1.id, nested.level2", + )["Items"] + results.should.equal( + [ + { + "nested": { + "level1": {"id": "id1"}, + "level2": {"include": "all", "id": "id2"}, + } + } + ] + ) # Assert original data is still there - results = table.scan(FilterExpression=Key('forum_name').eq('key1'))['Items'] - results.should.equal([{'forum_name': 'key1', - 'foo': 'bar', - 'nested': {'level1': {'att': 'irrelevant', 'id': 'id1'}, - 'level2': {'include': 'all', 'id': 'id2'}, - 'level3': {'id': 'irrelevant'}}}]) + results = table.scan(FilterExpression=Key("forum_name").eq("key1"))["Items"] + results.should.equal( + [ + { + "forum_name": "key1", + "foo": "bar", + "nested": { + "level1": {"att": "irrelevant", "id": "id1"}, + "level2": {"include": "all", "id": "id2"}, + "level3": {"id": "irrelevant"}, + }, + } + ] + ) @mock_dynamodb2 @@ -832,67 +962,117 @@ def test_basic_projection_expressions_using_query_with_attr_expression_names(): @mock_dynamodb2 def test_nested_projection_expression_using_get_item_with_attr_expression(): - dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + dynamodb = boto3.resource("dynamodb", region_name="us-east-1") # Create the DynamoDB table. - dynamodb.create_table(TableName='users', - KeySchema=[{'AttributeName': 'forum_name', 'KeyType': 'HASH'}], - AttributeDefinitions=[{'AttributeName': 'forum_name', 'AttributeType': 'S'}], - ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5}) - table = dynamodb.Table('users') - table.put_item(Item={'forum_name': 'key1', 'nested': {'level1': {'id': 'id1', 'att': 'irrelevant'}, - 'level2': {'id': 'id2', 'include': 'all'}, - 'level3': {'id': 'irrelevant'}}, - 'foo': 'bar'}) - table.put_item(Item={'forum_name': 'key2', 'nested': {'id': 'id2', 'incode': 'code2'}, 'foo': 'bar'}) + dynamodb.create_table( + TableName="users", + KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + table = dynamodb.Table("users") + table.put_item( + Item={ + "forum_name": "key1", + "nested": { + "level1": {"id": "id1", "att": "irrelevant"}, + "level2": {"id": "id2", "include": "all"}, + "level3": {"id": "irrelevant"}, + }, + "foo": "bar", + } + ) + table.put_item( + Item={ + "forum_name": "key2", + "nested": {"id": "id2", "incode": "code2"}, + "foo": "bar", + } + ) # Test a get_item returning all items - result = table.get_item(Key={'forum_name': 'key1'}, - ProjectionExpression='#nst.level1.id, #nst.#lvl2', - ExpressionAttributeNames={'#nst': 'nested', '#lvl2': 'level2'})['Item'] - result.should.equal({'nested': {'level1': {'id': 'id1'}, - 'level2': {'id': 'id2', 'include': 'all'}}}) + result = table.get_item( + Key={"forum_name": "key1"}, + ProjectionExpression="#nst.level1.id, #nst.#lvl2", + ExpressionAttributeNames={"#nst": "nested", "#lvl2": "level2"}, + )["Item"] + result.should.equal( + {"nested": {"level1": {"id": "id1"}, "level2": {"id": "id2", "include": "all"}}} + ) # Assert actual data has not been deleted - result = table.get_item(Key={'forum_name': 'key1'})['Item'] - result.should.equal({u'foo': u'bar', - u'forum_name': u'key1', - u'nested': {u'level1': {u'id': u'id1', u'att': u'irrelevant'}, - u'level2': {u'id': u'id2', u'include': u'all'}, - u'level3': {u'id': u'irrelevant'}}}) + result = table.get_item(Key={"forum_name": "key1"})["Item"] + result.should.equal( + { + "foo": "bar", + "forum_name": "key1", + "nested": { + "level1": {"id": "id1", "att": "irrelevant"}, + "level2": {"id": "id2", "include": "all"}, + "level3": {"id": "irrelevant"}, + }, + } + ) @mock_dynamodb2 def test_nested_projection_expression_using_query_with_attr_expression_names(): - dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + dynamodb = boto3.resource("dynamodb", region_name="us-east-1") # Create the DynamoDB table. - dynamodb.create_table(TableName='users', - KeySchema=[{'AttributeName': 'forum_name', 'KeyType': 'HASH'}], - AttributeDefinitions=[{'AttributeName': 'forum_name', 'AttributeType': 'S'}], - ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5}) - table = dynamodb.Table('users') - table.put_item(Item={'forum_name': 'key1', 'nested': {'level1': {'id': 'id1', 'att': 'irrelevant'}, - 'level2': {'id': 'id2', 'include': 'all'}, - 'level3': {'id': 'irrelevant'}}, - 'foo': 'bar'}) - table.put_item(Item={'forum_name': 'key2', 'nested': {'id': 'id2', 'incode': 'code2'}, 'foo': 'bar'}) + dynamodb.create_table( + TableName="users", + KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + table = dynamodb.Table("users") + table.put_item( + Item={ + "forum_name": "key1", + "nested": { + "level1": {"id": "id1", "att": "irrelevant"}, + "level2": {"id": "id2", "include": "all"}, + "level3": {"id": "irrelevant"}, + }, + "foo": "bar", + } + ) + table.put_item( + Item={ + "forum_name": "key2", + "nested": {"id": "id2", "incode": "code2"}, + "foo": "bar", + } + ) # Test a query returning all items - result = table.query(KeyConditionExpression=Key('forum_name').eq('key1'), - ProjectionExpression="#nst.level1.id, #nst.#lvl2", - ExpressionAttributeNames={'#nst': 'nested', '#lvl2': 'level2'})['Items'][0] + result = table.query( + KeyConditionExpression=Key("forum_name").eq("key1"), + ProjectionExpression="#nst.level1.id, #nst.#lvl2", + ExpressionAttributeNames={"#nst": "nested", "#lvl2": "level2"}, + )["Items"][0] - assert 'nested' in result - result['nested'].should.equal({'level1': {'id': 'id1'}, - 'level2': {'id': 'id2', 'include': 'all'}}) - assert 'foo' not in result + assert "nested" in result + result["nested"].should.equal( + {"level1": {"id": "id1"}, "level2": {"id": "id2", "include": "all"}} + ) + assert "foo" not in result # Assert actual data has not been deleted - result = table.query(KeyConditionExpression=Key('forum_name').eq('key1'))['Items'][0] - result.should.equal( {u'foo': u'bar', - u'forum_name': u'key1', - u'nested': {u'level1': {u'id': u'id1', u'att': u'irrelevant'}, - u'level2': {u'id': u'id2', u'include': u'all'}, - u'level3': {u'id': u'irrelevant'}}}) + result = table.query(KeyConditionExpression=Key("forum_name").eq("key1"))["Items"][ + 0 + ] + result.should.equal( + { + "foo": "bar", + "forum_name": "key1", + "nested": { + "level1": {"id": "id1", "att": "irrelevant"}, + "level2": {"id": "id2", "include": "all"}, + "level3": {"id": "irrelevant"}, + }, + } + ) @mock_dynamodb2 @@ -958,33 +1138,66 @@ def test_basic_projection_expressions_using_scan_with_attr_expression_names(): @mock_dynamodb2 def test_nested_projection_expression_using_scan_with_attr_expression_names(): - dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + dynamodb = boto3.resource("dynamodb", region_name="us-east-1") # Create the DynamoDB table. - dynamodb.create_table(TableName='users', - KeySchema=[{'AttributeName': 'forum_name', 'KeyType': 'HASH'}], - AttributeDefinitions=[{'AttributeName': 'forum_name', 'AttributeType': 'S'}], - ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5}) - table = dynamodb.Table('users') - table.put_item(Item={'forum_name': 'key1', 'nested': {'level1': {'id': 'id1', 'att': 'irrelevant'}, - 'level2': {'id': 'id2', 'include': 'all'}, - 'level3': {'id': 'irrelevant'}}, - 'foo': 'bar'}) - table.put_item(Item={'forum_name': 'key2', 'nested': {'id': 'id2', 'incode': 'code2'}, 'foo': 'bar'}) + dynamodb.create_table( + TableName="users", + KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + table = dynamodb.Table("users") + table.put_item( + Item={ + "forum_name": "key1", + "nested": { + "level1": {"id": "id1", "att": "irrelevant"}, + "level2": {"id": "id2", "include": "all"}, + "level3": {"id": "irrelevant"}, + }, + "foo": "bar", + } + ) + table.put_item( + Item={ + "forum_name": "key2", + "nested": {"id": "id2", "incode": "code2"}, + "foo": "bar", + } + ) # Test a scan - results = table.scan(FilterExpression=Key('forum_name').eq('key1'), - ProjectionExpression='nested.level1.id, nested.level2', - ExpressionAttributeNames={'#nst': 'nested', '#lvl2': 'level2'})['Items'] - results.should.equal([{'nested': {'level1': {'id': 'id1'}, - 'level2': {'include': 'all', 'id': 'id2'}}}]) + results = table.scan( + FilterExpression=Key("forum_name").eq("key1"), + ProjectionExpression="nested.level1.id, nested.level2", + ExpressionAttributeNames={"#nst": "nested", "#lvl2": "level2"}, + )["Items"] + results.should.equal( + [ + { + "nested": { + "level1": {"id": "id1"}, + "level2": {"include": "all", "id": "id2"}, + } + } + ] + ) # Assert original data is still there - results = table.scan(FilterExpression=Key('forum_name').eq('key1'))['Items'] - results.should.equal([{'forum_name': 'key1', - 'foo': 'bar', - 'nested': {'level1': {'att': 'irrelevant', 'id': 'id1'}, - 'level2': {'include': 'all', 'id': 'id2'}, - 'level3': {'id': 'irrelevant'}}}]) + results = table.scan(FilterExpression=Key("forum_name").eq("key1"))["Items"] + results.should.equal( + [ + { + "forum_name": "key1", + "foo": "bar", + "nested": { + "level1": {"att": "irrelevant", "id": "id1"}, + "level2": {"include": "all", "id": "id2"}, + "level3": {"id": "irrelevant"}, + }, + } + ] + ) @mock_dynamodb2