Skip to content

Commit c64df08

Browse files
authored
Fix attribute lookup from subclassed map attributes. (#325)
1 parent 1ca68f4 commit c64df08

File tree

1 file changed

+16
-3
lines changed

1 file changed

+16
-3
lines changed

pynamodb/attributes.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,20 @@ def __init__(self,
3939
self.attr_name = attr_name
4040

4141
def __set__(self, instance, value):
42-
if instance:
42+
if instance and not self._is_map_attribute_class_object(instance):
4343
attr_name = instance._dynamo_to_python_attrs.get(self.attr_name, self.attr_name)
4444
instance.attribute_values[attr_name] = value
4545

4646
def __get__(self, instance, owner):
47-
if instance:
47+
if instance and not self._is_map_attribute_class_object(instance):
4848
attr_name = instance._dynamo_to_python_attrs.get(self.attr_name, self.attr_name)
4949
return instance.attribute_values.get(attr_name, None)
5050
else:
5151
return self
5252

53+
def _is_map_attribute_class_object(self, instance):
54+
return isinstance(instance, MapAttribute) and getattr(instance, '_class_object', False)
55+
5356
def serialize(self, value):
5457
"""
5558
This method should return a dynamodb compatible value
@@ -120,8 +123,18 @@ def _initialize_attributes(cls):
120123
if item_cls is None:
121124
continue
122125

123-
if issubclass(item_cls, (Attribute, )):
126+
if issubclass(item_cls, Attribute):
124127
instance = getattr(cls, item_name)
128+
if isinstance(instance, MapAttribute):
129+
# Attributes are data descriptors that bind their value to the containing object.
130+
# When subclassing MapAttribute and using them as AttributeContainers on a Model,
131+
# their internal attributes are bound to the instance in the Model class.
132+
# The `_class_object` attribute is used to indicate that the MapAttribute instance
133+
# belongs to a class object and not a class instance, overriding the binding.
134+
# Without this, Model.MapAttribute().attribute would the value and not the object;
135+
# whereas Model.attribute always returns the object.
136+
instance._class_object = True
137+
125138
cls._attributes[item_name] = instance
126139
if instance.attr_name is not None:
127140
cls._dynamo_to_python_attrs[instance.attr_name] = item_name

0 commit comments

Comments
 (0)