Repository: pynamodb/PynamoDB

Issue:
Deserialize hash key while loading from json
While trying to load a table from a json dump, https://github.com/jlafon/PynamoDB/blob/devel/pynamodb/models.py#L764 does not deserialize the hash key. Hence, we run into an exception if the hash key is anything other than a UnicodeAttribute. 

For example, the following will lead to an exception.

```
from pynamodb.models import Model
from pynamodb.attributes import (
    UnicodeAttribute, NumberAttribute
)

class Thread(Model):
    class Meta:
        table_name = 'Thread'

    forum_name = NumberAttribute(hash_key=True)
    subject = UnicodeAttribute(range_key=True)
    views = NumberAttribute(default=0)

# After the table has been populated

Thread.dump("thread_backup.json")
Thread.load("thread_backup.json")
```


Discussion:

Patch:
diff --git a/pynamodb/models.py b/pynamodb/models.py
index cacb817..19ca4f9 100644
--- a/pynamodb/models.py
+++ b/pynamodb/models.py
@@ -894,6 +894,9 @@ class Model(AttributeContainer):
         Reconstructs a model object from JSON.
         """
         hash_key, attrs = data
+        hash_keyname = cls._get_meta_data().hash_keyname
+        hash_key_attr = cls._get_attributes().get(cls._dynamo_to_python_attr(hash_keyname))
+        hash_key = hash_key_attr.deserialize(hash_key)
         range_key = attrs.pop('range_key', None)
         attributes = attrs.pop(pythonic(ATTRIBUTES))
         if range_key is not None:


Test patch:
diff --git a/pynamodb/tests/data.py b/pynamodb/tests/data.py
index 3a76e22..5f624cd 100644
--- a/pynamodb/tests/data.py
+++ b/pynamodb/tests/data.py
@@ -466,7 +466,7 @@ COMPLEX_MODEL_ITEM_DATA = {
                     'N': '31'
                 },
                 'is_dude': {
-                    'N': '1'
+                    'BOOL': True
                 }
             }
         }
@@ -826,6 +826,33 @@ SERIALIZED_TABLE_DATA = [
     ]
 ]
 
+COMPLEX_MODEL_SERIALIZED_TABLE_DATA = [
+    [
+        "123",
+        {
+            "attributes": {
+                'person': {  # TODO(jpinner) fix deserialize lookup to use attr_name
+                    'M': {
+                        'firstName': {
+                            'S': 'Justin'
+                        },
+                        'lname': {
+                            'S': 'Phillips'
+                        },
+                        'age': {
+                            'N': '31'
+                        },
+                        'is_dude': {
+                            'N': '1'
+                        }
+                    }
+
+                }
+            }
+        }
+    ]
+]
+
 OFFICE_EMPLOYEE_MODEL_TABLE_DATA = {
     "Table": {
         "AttributeDefinitions": [
diff --git a/pynamodb/tests/test_model.py b/pynamodb/tests/test_model.py
index 9870dbd..0f15b96 100644
--- a/pynamodb/tests/test_model.py
+++ b/pynamodb/tests/test_model.py
@@ -36,7 +36,7 @@ from pynamodb.tests.data import (
     BATCH_GET_ITEMS, SIMPLE_BATCH_GET_ITEMS, COMPLEX_TABLE_DATA,
     COMPLEX_ITEM_DATA, INDEX_TABLE_DATA, LOCAL_INDEX_TABLE_DATA, DOG_TABLE_DATA,
     CUSTOM_ATTR_NAME_INDEX_TABLE_DATA, CUSTOM_ATTR_NAME_ITEM_DATA,
-    BINARY_ATTR_DATA, SERIALIZED_TABLE_DATA, OFFICE_EMPLOYEE_MODEL_TABLE_DATA,
+    BINARY_ATTR_DATA, SERIALIZED_TABLE_DATA, OFFICE_EMPLOYEE_MODEL_TABLE_DATA, COMPLEX_MODEL_SERIALIZED_TABLE_DATA,
     GET_OFFICE_EMPLOYEE_ITEM_DATA, GET_OFFICE_EMPLOYEE_ITEM_DATA_WITH_NULL,
     GROCERY_LIST_MODEL_TABLE_DATA, GET_GROCERY_LIST_ITEM_DATA,
     GET_OFFICE_ITEM_DATA, OFFICE_MODEL_TABLE_DATA, COMPLEX_MODEL_TABLE_DATA, COMPLEX_MODEL_ITEM_DATA,
@@ -3732,6 +3732,20 @@ class ModelTestCase(TestCase):
         }
         self.assert_dict_lists_equal(req.call_args[0][1]['RequestItems']['UserModel'], args['UserModel'])
 
+    def test_loads_complex_model(self):
+        with patch(PATCH_METHOD) as req:
+            req.return_value = {}
+            ComplexModel.loads(json.dumps(COMPLEX_MODEL_SERIALIZED_TABLE_DATA))
+
+        args = {
+            'ComplexModel': [
+                {
+                    'PutRequest': COMPLEX_MODEL_ITEM_DATA
+                }
+            ]
+        }
+        self.assert_dict_lists_equal(req.call_args[0][1]['RequestItems']['ComplexModel'], args['ComplexModel'])
+
     def _get_office_employee(self):
         justin = Person(
             fname='Justin',