Skip to content

Commit 842a8e1

Browse files
authored
Better handling of MapAttribute iteration and item assignment. (#367)
1 parent 6f77cb3 commit 842a8e1

File tree

3 files changed

+44
-1
lines changed

3 files changed

+44
-1
lines changed

pynamodb/attributes.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ def get_value(self, value):
8282
serialized_dynamo_type = ATTR_TYPE_MAP[self.attr_type]
8383
return value.get(serialized_dynamo_type)
8484

85+
def __iter__(self):
86+
# Because we define __getitem__ below for condition expression support
87+
raise TypeError("'{0}' object is not iterable".format(self.__class__.__name__))
88+
8589
# Condition Expression Support
8690
def __eq__(self, other):
8791
if other is None or isinstance(other, Attribute): # handle object identity comparison
@@ -664,7 +668,9 @@ def _update_attribute_paths(self, path_segment):
664668
local_attr._update_attribute_paths(path_segment)
665669

666670
def __iter__(self):
667-
return iter(self.attribute_values)
671+
if self._is_attribute_container():
672+
return iter(self.attribute_values)
673+
return super(MapAttribute, self).__iter__()
668674

669675
def __getitem__(self, item):
670676
if self._is_attribute_container():
@@ -679,6 +685,16 @@ def __getitem__(self, item):
679685
else:
680686
raise AttributeError("'{0}' has no attribute '{1}'".format(self.__class__.__name__, item))
681687

688+
def __setitem__(self, item, value):
689+
if not self._is_attribute_container():
690+
raise TypeError("'{0}' object does not support item assignment".format(self.__class__.__name__))
691+
if self.is_raw():
692+
self.attribute_values[item] = value
693+
elif item in self._attributes:
694+
setattr(self, item, value)
695+
else:
696+
raise AttributeError("'{0}' has no attribute '{1}'".format(self.__class__.__name__, item))
697+
682698
def __getattr__(self, attr):
683699
# This should only be called for "raw" (i.e. non-subclassed) MapAttribute instances.
684700
# MapAttribute subclasses should access attributes via the Attribute descriptors.

pynamodb/expressions/operand.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,10 @@ def __init__(self, attribute_or_path):
241241
def path(self):
242242
return self.values[0]
243243

244+
def __iter__(self):
245+
# Because we define __getitem__ Path is considered an iterable
246+
raise TypeError("'{0}' object is not iterable".format(self.__class__.__name__))
247+
244248
def __getitem__(self, item):
245249
# The __getitem__ call returns a new Path instance without any attribute set.
246250
# This is intended since the nested element is not the same attribute as ``self``.

pynamodb/tests/test_attributes.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,17 @@ def test_raw_set_attr(self):
606606
assert item.map_attr['num'] == 3
607607
assert item.map_attr['nested']['nestedfoo'] == 'nestedbar'
608608

609+
def test_raw_set_item(self):
610+
item = AttributeTestModel()
611+
item.map_attr = {}
612+
item.map_attr['foo'] = 'bar'
613+
item.map_attr['num'] = 3
614+
item.map_attr['nested'] = {'nestedfoo': 'nestedbar'}
615+
616+
assert item.map_attr['foo'] == 'bar'
617+
assert item.map_attr['num'] == 3
618+
assert item.map_attr['nested']['nestedfoo'] == 'nestedbar'
619+
609620
def test_raw_map_from_dict(self):
610621
item = AttributeTestModel(
611622
map_attr={
@@ -633,6 +644,18 @@ def test_raw_map_access(self):
633644
for k, v in six.iteritems(raw):
634645
assert attr[k] == v
635646

647+
def test_raw_map_iter(self):
648+
raw = {
649+
"foo": "bar",
650+
"num": 3,
651+
"nested": {
652+
"nestedfoo": "nestedbar"
653+
}
654+
}
655+
attr = MapAttribute(**raw)
656+
657+
assert list(iter(raw)) == list(iter(attr))
658+
636659
def test_raw_map_json_serialize(self):
637660
raw = {
638661
"foo": "bar",

0 commit comments

Comments
 (0)