Skip to content

Commit 19eeb5a

Browse files
johnliugarrettheel
authored andcommitted
Key attribute_values off the field name instead of attr_name (#292)
1 parent 2d4f8a0 commit 19eeb5a

File tree

5 files changed

+72
-18
lines changed

5 files changed

+72
-18
lines changed

.travis.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ env:
1111
- AWS_SECRET_ACCESS_KEY=fake_key AWS_ACCESS_KEY_ID=fake_id
1212

1313
before_install:
14-
- pip install six==1.8.0
14+
- pip install six==1.9.0
1515

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

2222
after_success:
23-
- coveralls
23+
- coveralls
24+

pynamodb/attributes.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@ def __init__(self,
4040

4141
def __set__(self, instance, value):
4242
if instance:
43-
instance.attribute_values[self.attr_name] = value
43+
attr_name = instance._dynamo_to_python_attrs.get(self.attr_name, self.attr_name)
44+
instance.attribute_values[attr_name] = value
4445

4546
def __get__(self, instance, owner):
4647
if instance:
47-
return instance.attribute_values.get(self.attr_name, None)
48+
attr_name = instance._dynamo_to_python_attrs.get(self.attr_name, self.attr_name)
49+
return instance.attribute_values.get(attr_name, None)
4850
else:
4951
return self
5052

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

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

456463
def __set__(self, instance, value):
457464
if isinstance(value, collections.Mapping):

pynamodb/models.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,6 @@ def _conditional_operator_check(cls, conditional_operator):
245245
if conditional_operator is not None and cls.has_map_or_list_attributes():
246246
raise NotImplementedError('Map and List attribute do not support conditional_operator yet')
247247

248-
249248
@classmethod
250249
def batch_get(cls, items, consistent_read=None, attributes_to_get=None):
251250
"""

pynamodb/tests/test_attributes.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -668,18 +668,38 @@ class CustomMapAttribute(MapAttribute):
668668
}
669669
assert serialized_datetime == expected_serialized_value
670670

671-
def test_serialize_datetime(self):
672-
class CustomMapAttribute(MapAttribute):
673-
date_attr = UTCDateTimeAttribute()
671+
def test_complex_map_accessors(self):
672+
class NestedThing(MapAttribute):
673+
double_nested = MapAttribute()
674+
double_nested_renamed = MapAttribute(attr_name='something_else')
674675

675-
cm = CustomMapAttribute(date_attr=datetime(2017, 1, 1))
676-
serialized_datetime = cm.serialize(cm)
677-
expected_serialized_value = {
678-
'date_attr': {
679-
'S': u'2017-01-01T00:00:00.000000+0000'
680-
}
681-
}
682-
assert serialized_datetime == expected_serialized_value
676+
class ThingModel(Model):
677+
nested = NestedThing()
678+
679+
t = ThingModel(nested=NestedThing(
680+
double_nested={'hello': 'world'},
681+
double_nested_renamed={'foo': 'bar'})
682+
)
683+
684+
assert t.nested.double_nested.as_dict() == {'hello': 'world'}
685+
assert t.nested.double_nested_renamed.as_dict() == {'foo': 'bar'}
686+
assert t.nested.double_nested.hello == 'world'
687+
assert t.nested.double_nested_renamed.foo == 'bar'
688+
assert t.nested['double_nested'].as_dict() == {'hello': 'world'}
689+
assert t.nested['double_nested_renamed'].as_dict() == {'foo': 'bar'}
690+
assert t.nested['double_nested']['hello'] == 'world'
691+
assert t.nested['double_nested_renamed']['foo'] == 'bar'
692+
693+
with pytest.raises(AttributeError):
694+
bad = t.nested.double_nested.bad
695+
with pytest.raises(AttributeError):
696+
bad = t.nested.bad
697+
with pytest.raises(AttributeError):
698+
bad = t.nested.something_else
699+
with pytest.raises(KeyError):
700+
bad = t.nested.double_nested['bad']
701+
with pytest.raises(KeyError):
702+
bad = t.nested['something_else']
683703

684704

685705
class TestValueDeserialize:

pynamodb/tests/test_model.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import six
1111
from botocore.client import ClientError
1212
from botocore.vendored import requests
13+
import pytest
1314

1415
from pynamodb.compat import CompatTestCase as TestCase
1516
from pynamodb.tests.deep_eq import deep_eq
@@ -3178,7 +3179,33 @@ def test_model_with_maps_with_nulls_retrieve_from_db(self):
31783179
GET_OFFICE_EMPLOYEE_ITEM_DATA_WITH_NULL.get(ITEM).get('person').get(
31793180
MAP_SHORT).get('firstName').get(STRING_SHORT))
31803181
self.assertIsNone(item.person.age)
3181-
self.assertIsNone(item.person.is_dude)
3182+
self.assertIsNone(item.person.is_male)
3183+
3184+
def test_model_with_maps_with_pythonic_attributes(self):
3185+
fake_db = self.database_mocker(
3186+
OfficeEmployee,
3187+
OFFICE_EMPLOYEE_MODEL_TABLE_DATA,
3188+
GET_OFFICE_EMPLOYEE_ITEM_DATA,
3189+
'office_employee_id',
3190+
'N',
3191+
'123'
3192+
)
3193+
3194+
with patch(PATCH_METHOD, new=fake_db) as req:
3195+
req.return_value = GET_OFFICE_EMPLOYEE_ITEM_DATA
3196+
item = OfficeEmployee.get(123)
3197+
self.assertEqual(
3198+
item.person.fname,
3199+
GET_OFFICE_EMPLOYEE_ITEM_DATA
3200+
.get(ITEM)
3201+
.get('person')
3202+
.get(MAP_SHORT)
3203+
.get('firstName')
3204+
.get(STRING_SHORT)
3205+
)
3206+
assert item.person.is_male
3207+
with pytest.raises(AttributeError):
3208+
item.person.is_dude
31823209

31833210
def test_model_with_list_retrieve_from_db(self):
31843211
fake_db = self.database_mocker(GroceryList, GROCERY_LIST_MODEL_TABLE_DATA,

0 commit comments

Comments
 (0)