Skip to content

Commit

Permalink
Key attribute_values off the field name instead of attr_name (#292)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnliu authored and garrettheel committed Jun 9, 2017
1 parent 2d4f8a0 commit 19eeb5a
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 18 deletions.
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ env:
- AWS_SECRET_ACCESS_KEY=fake_key AWS_ACCESS_KEY_ID=fake_id

before_install:
- pip install six==1.8.0
- pip install six==1.9.0

install:
- pip install -r requirements-dev.txt
Expand All @@ -20,4 +20,5 @@ script:
- py.test --cov-report term-missing --cov=pynamodb pynamodb/tests/

after_success:
- coveralls
- coveralls

13 changes: 10 additions & 3 deletions pynamodb/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ def __init__(self,

def __set__(self, instance, value):
if instance:
instance.attribute_values[self.attr_name] = value
attr_name = instance._dynamo_to_python_attrs.get(self.attr_name, self.attr_name)
instance.attribute_values[attr_name] = value

def __get__(self, instance, owner):
if instance:
return instance.attribute_values.get(self.attr_name, None)
attr_name = instance._dynamo_to_python_attrs.get(self.attr_name, self.attr_name)
return instance.attribute_values.get(attr_name, None)
else:
return self

Expand Down Expand Up @@ -451,7 +453,12 @@ def __getitem__(self, item):
return self.attribute_values[item]

def __getattr__(self, attr):
return self.attribute_values[attr]
# Should only be called for non-subclassed, otherwise we would go through
# the descriptor instead.
try:
return self.attribute_values[attr]
except KeyError:
raise AttributeError("'{0}' has no attribute '{1}'".format(self.__class__.__name__, attr))

def __set__(self, instance, value):
if isinstance(value, collections.Mapping):
Expand Down
1 change: 0 additions & 1 deletion pynamodb/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,6 @@ def _conditional_operator_check(cls, conditional_operator):
if conditional_operator is not None and cls.has_map_or_list_attributes():
raise NotImplementedError('Map and List attribute do not support conditional_operator yet')


@classmethod
def batch_get(cls, items, consistent_read=None, attributes_to_get=None):
"""
Expand Down
42 changes: 31 additions & 11 deletions pynamodb/tests/test_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,18 +668,38 @@ class CustomMapAttribute(MapAttribute):
}
assert serialized_datetime == expected_serialized_value

def test_serialize_datetime(self):
class CustomMapAttribute(MapAttribute):
date_attr = UTCDateTimeAttribute()
def test_complex_map_accessors(self):
class NestedThing(MapAttribute):
double_nested = MapAttribute()
double_nested_renamed = MapAttribute(attr_name='something_else')

cm = CustomMapAttribute(date_attr=datetime(2017, 1, 1))
serialized_datetime = cm.serialize(cm)
expected_serialized_value = {
'date_attr': {
'S': u'2017-01-01T00:00:00.000000+0000'
}
}
assert serialized_datetime == expected_serialized_value
class ThingModel(Model):
nested = NestedThing()

t = ThingModel(nested=NestedThing(
double_nested={'hello': 'world'},
double_nested_renamed={'foo': 'bar'})
)

assert t.nested.double_nested.as_dict() == {'hello': 'world'}
assert t.nested.double_nested_renamed.as_dict() == {'foo': 'bar'}
assert t.nested.double_nested.hello == 'world'
assert t.nested.double_nested_renamed.foo == 'bar'
assert t.nested['double_nested'].as_dict() == {'hello': 'world'}
assert t.nested['double_nested_renamed'].as_dict() == {'foo': 'bar'}
assert t.nested['double_nested']['hello'] == 'world'
assert t.nested['double_nested_renamed']['foo'] == 'bar'

with pytest.raises(AttributeError):
bad = t.nested.double_nested.bad
with pytest.raises(AttributeError):
bad = t.nested.bad
with pytest.raises(AttributeError):
bad = t.nested.something_else
with pytest.raises(KeyError):
bad = t.nested.double_nested['bad']
with pytest.raises(KeyError):
bad = t.nested['something_else']


class TestValueDeserialize:
Expand Down
29 changes: 28 additions & 1 deletion pynamodb/tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import six
from botocore.client import ClientError
from botocore.vendored import requests
import pytest

from pynamodb.compat import CompatTestCase as TestCase
from pynamodb.tests.deep_eq import deep_eq
Expand Down Expand Up @@ -3178,7 +3179,33 @@ def test_model_with_maps_with_nulls_retrieve_from_db(self):
GET_OFFICE_EMPLOYEE_ITEM_DATA_WITH_NULL.get(ITEM).get('person').get(
MAP_SHORT).get('firstName').get(STRING_SHORT))
self.assertIsNone(item.person.age)
self.assertIsNone(item.person.is_dude)
self.assertIsNone(item.person.is_male)

def test_model_with_maps_with_pythonic_attributes(self):
fake_db = self.database_mocker(
OfficeEmployee,
OFFICE_EMPLOYEE_MODEL_TABLE_DATA,
GET_OFFICE_EMPLOYEE_ITEM_DATA,
'office_employee_id',
'N',
'123'
)

with patch(PATCH_METHOD, new=fake_db) as req:
req.return_value = GET_OFFICE_EMPLOYEE_ITEM_DATA
item = OfficeEmployee.get(123)
self.assertEqual(
item.person.fname,
GET_OFFICE_EMPLOYEE_ITEM_DATA
.get(ITEM)
.get('person')
.get(MAP_SHORT)
.get('firstName')
.get(STRING_SHORT)
)
assert item.person.is_male
with pytest.raises(AttributeError):
item.person.is_dude

def test_model_with_list_retrieve_from_db(self):
fake_db = self.database_mocker(GroceryList, GROCERY_LIST_MODEL_TABLE_DATA,
Expand Down

0 comments on commit 19eeb5a

Please sign in to comment.