Skip to content

Commit 50fa160

Browse files
jmphillidanielhochman
authored andcommitted
Add support for native map and list attributes (#175)
1 parent e2d8299 commit 50fa160

File tree

10 files changed

+1531
-123
lines changed

10 files changed

+1531
-123
lines changed

docs/attributes.rst

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ Custom Attributes
22
==========================
33

44
Attributes in PynamoDB are classes that are serialized to and from DynamoDB attributes. PynamoDB provides attribute classes
5-
for all of the basic DynamoDB data types, as defined in the `DynamoDB documentation <http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DataModel.html>`_.
5+
for all DynamoDB data types, as defined in the `DynamoDB documentation <http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DataModel.html>`_.
66
Higher level attribute types (internally stored as a DynamoDB data types) can be defined with PynamoDB. Two such types
7-
are included with PynamoDB for convenience: `JSONAttribute` and `UnicodeDatetimeAttribute`.
7+
are included with PynamoDB for convenience: ``JSONAttribute`` and ``UnicodeDatetimeAttribute``.
88

99
Attribute Methods
1010
-----------------
1111

12-
All `Attribute` classes must define two methods, `serialize` and `deserialize`. The `serialize` method takes a Python
13-
value and converts it into a format that can be stored into DynamoDB. The `deserialize` method takes a raw DynamoDB value
14-
and converts it back into its value in Python. Additionally, a class attribute called `attr_type` is required for PynamoDB
15-
to know which DynamoDB data type the attribute is stored as.
12+
All ``Attribute`` classes must define three methods, ``serialize``, ``deserialize`` and ``get_value``. The ``serialize`` method takes a Python
13+
value and converts it into a format that can be stored into DynamoDB. The ``get_value`` method reads the serialized value out of the DynamoDB record.
14+
This raw value is then passed to the ``deserialize`` method. The ``deserialize`` method then converts it back into its value in Python.
15+
Additionally, a class attribute called ``attr_type`` is required for PynamoDB to know which DynamoDB data type the attribute is stored as.
16+
The ``get_value`` method is provided to help when migrating from one attribute type to another, specifically with the ``BooleanAttribute`` type.
17+
If you're writing your own attribute and the ``attr_type`` has not changed you can simply use the base ``Attribute`` implementation of ``get_value``.
1618

1719

1820
Writing your own attribute
@@ -45,8 +47,8 @@ Custom Attribute Example
4547
------------------------
4648

4749
The example below shows how to write a custom attribute that will pickle a customized class. The attribute itself is stored
48-
in DynamoDB as a binary attribute. The `pickle` module is used to serialize and deserialize the attribute. In this example,
49-
it is not necessary to define `attr_type` because the `PickleAttribute` class is inheriting from `BinaryAttribute` which has
50+
in DynamoDB as a binary attribute. The ``pickle`` module is used to serialize and deserialize the attribute. In this example,
51+
it is not necessary to define ``attr_type`` because the ``PickleAttribute`` class is inheriting from ``BinaryAttribute`` which has
5052
already defined it.
5153

5254
.. code-block:: python
@@ -107,4 +109,40 @@ Now we can use our custom attribute to round trip any object that can be pickled
107109
108110
>>>instance = CustomAttributeModel.get('red')
109111
>>>print(instance.obj)
110-
<Color: red>
112+
<Color: red>
113+
114+
115+
List Attributes
116+
---------------
117+
118+
DynamoDB list attributes are simply lists of other attributes. DynamoDB asserts no requirements about the types embedded within the list.
119+
DynamoDB is perfectly content with a list of ``UnicodeAttribute`` and ``NumberAttributes`` mixed together. Pynamo can provide type safety if it is required.
120+
When defining your model use the ``of=`` kwarg and pass in a class. Pynamo will check that all items in the list are of the type you require.
121+
122+
.. code-block:: python
123+
124+
from pynamodb.attributes import ListAttribute, NumberAttribute, UnicodeAttribute
125+
126+
class Office(Model):
127+
class Meta:
128+
table_name = 'OfficeModel'
129+
office_id = NumberAttribute(hash_key=True)
130+
employees = ListAttribute(of=UnicodeAttribute)
131+
132+
133+
Map Attributes
134+
--------------
135+
136+
DynamoDB map attributes are objects embedded inside of top level models. See the examples `here <https://github.com/jlafon/PynamoDB/tree/devel/examples/office_model.py>`_.
137+
When implementing your own MapAttribute you can simply extend ``MapAttribute`` and ignore writing serialization code.
138+
These attributes can then be used inside of Model classes just like any other attribute.
139+
140+
.. code-block:: python
141+
142+
from pynamodb.attributes import MapAttribute, UnicodeAttribute
143+
144+
class CarInfoMap(MapAttribute):
145+
make = UnicodeAttribute(null=False)
146+
model = UnicodeAttribute(null=True)
147+
148+

examples/office_model.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from pynamodb.attributes import ListAttribute, MapAttribute, NumberAttribute, UnicodeAttribute
2+
from pynamodb.models import Model
3+
4+
5+
class Location(MapAttribute):
6+
7+
lat = NumberAttribute(attr_name='latitude')
8+
lng = NumberAttribute(attr_name='longitude')
9+
name = UnicodeAttribute()
10+
11+
12+
class Person(MapAttribute):
13+
14+
fname = UnicodeAttribute(attr_name='firstName')
15+
lname = UnicodeAttribute()
16+
age = NumberAttribute()
17+
18+
19+
class OfficeEmployeeMap(MapAttribute):
20+
21+
office_employee_id = NumberAttribute()
22+
person = Person()
23+
office_location = Location()
24+
25+
26+
class Office(Model):
27+
class Meta:
28+
table_name = 'OfficeModel'
29+
host = "http://localhost:8000"
30+
31+
office_id = NumberAttribute(hash_key=True)
32+
address = Location()
33+
employees = ListAttribute(of=OfficeEmployeeMap)

pynamodb/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
"""
88
__author__ = 'Jharrod LaFon'
99
__license__ = 'MIT'
10-
__version__ = '1.6.0'
10+
__version__ = '2.0.0'

pynamodb/attribute_dict.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import collections
2+
3+
4+
class AttributeDict(collections.MutableMapping):
5+
"""
6+
A dictionary that stores attributes by two keys
7+
"""
8+
def __init__(self, *args, **kwargs):
9+
self._values = {}
10+
self._alt_values = {}
11+
self.update(dict(*args, **kwargs))
12+
13+
def __getitem__(self, key):
14+
if key in self._alt_values:
15+
return self._alt_values[key]
16+
return self._values[key]
17+
18+
def __setitem__(self, key, value):
19+
if value.attr_name is not None:
20+
self._values[value.attr_name] = value
21+
self._alt_values[key] = value
22+
23+
def __delitem__(self, key):
24+
del self._values[key]
25+
26+
def __iter__(self):
27+
return iter(self._alt_values)
28+
29+
def __len__(self):
30+
return len(self._values)
31+
32+
def aliased_attrs(self):
33+
return self._alt_values.items()

0 commit comments

Comments
 (0)