Skip to content

Commit

Permalink
Introduce AttributeContainerMeta (#326)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpinner-lyft committed Aug 14, 2017
1 parent 9e01729 commit 3560228
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 12 deletions.
26 changes: 17 additions & 9 deletions pynamodb/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
PynamoDB attributes
"""
import six
from six import add_metaclass
import json
from base64 import b64encode, b64decode
from datetime import datetime
Expand Down Expand Up @@ -118,11 +119,13 @@ def _serialize(self, value):
return {ATTR_TYPE_MAP[self.attribute.attr_type]: self.attribute.serialize(value)}


class AttributeContainer(object):
class AttributeContainerMeta(type):

_dynamo_to_python_attrs = None
def __init__(cls, name, bases, attrs):
super(AttributeContainerMeta, cls).__init__(name, bases, attrs)
AttributeContainerMeta._initialize_attributes(cls)

@classmethod
@staticmethod
def _initialize_attributes(cls):
"""
Initialize attributes on the class.
Expand Down Expand Up @@ -157,16 +160,17 @@ def _initialize_attributes(cls):
else:
instance.attr_name = item_name


@add_metaclass(AttributeContainerMeta)
class AttributeContainer(object):

@classmethod
def _get_attributes(cls):
"""
Returns the attributes of this class as a mapping from `python_attr_name` => `attribute`.
:rtype: dict[str, Attribute]
"""
if '_attributes' not in cls.__dict__:
# Each subclass of AttributeContainer needs its own attributes map.
cls._initialize_attributes()
return cls._attributes

@classmethod
Expand All @@ -176,8 +180,6 @@ def _dynamo_to_python_attr(cls, dynamo_key):
This covers cases where an attribute name has been overridden via "attr_name".
"""
if cls._attributes is None:
cls._initialize_attributes()
return cls._dynamo_to_python_attrs.get(dynamo_key, dynamo_key)

def _set_defaults(self):
Expand Down Expand Up @@ -473,6 +475,13 @@ def deserialize(self, value):
return None


class MapAttributeMeta(AttributeContainerMeta):
"""
This is only here for backwards compatibility: i.e. so type(MapAttribute) == MapAttributeMeta
"""


@add_metaclass(MapAttributeMeta)
class MapAttribute(AttributeContainer, Attribute):
attr_type = MAP

Expand All @@ -482,7 +491,6 @@ def __init__(self, hash_key=False, range_key=False, null=None, default=None, att
null=null,
default=default,
attr_name=attr_name)
self._get_attributes() # Ensure attributes are always inited
self.attribute_values = {}
self._set_defaults()
self._set_attributes(**attrs)
Expand Down
5 changes: 3 additions & 2 deletions pynamodb/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from six import add_metaclass
from pynamodb.exceptions import DoesNotExist, TableDoesNotExist, TableError
from pynamodb.throttle import NoThrottle
from pynamodb.attributes import Attribute, AttributeContainer, MapAttribute, ListAttribute
from pynamodb.attributes import Attribute, AttributeContainer, AttributeContainerMeta, MapAttribute, ListAttribute
from pynamodb.connection.base import MetaTable
from pynamodb.connection.table import TableConnection
from pynamodb.connection.util import pythonic
Expand Down Expand Up @@ -153,14 +153,15 @@ def __iter__(self):
return iter(self.results)


class MetaModel(type):
class MetaModel(AttributeContainerMeta):
"""
Model meta class
This class is just here so that index queries have nice syntax.
Model.index.query()
"""
def __init__(cls, name, bases, attrs):
super(MetaModel, cls).__init__(name, bases, attrs)
if isinstance(attrs, dict):
for attr_name, attr_obj in attrs.items():
if attr_name == META_CLASS_NAME:
Expand Down
5 changes: 4 additions & 1 deletion pynamodb/tests/test_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from pynamodb.attributes import (
BinarySetAttribute, BinaryAttribute, NumberSetAttribute, NumberAttribute,
UnicodeAttribute, UnicodeSetAttribute, UTCDateTimeAttribute, BooleanAttribute, LegacyBooleanAttribute,
MapAttribute, ListAttribute, Attribute,
MapAttribute, MapAttributeMeta, ListAttribute, Attribute,
JSONAttribute, DEFAULT_ENCODING, NUMBER, STRING, STRING_SET, NUMBER_SET, BINARY_SET,
BINARY, MAP, LIST, BOOLEAN, _get_value_for_deserialize)

Expand Down Expand Up @@ -716,6 +716,9 @@ class ThingModel(Model):
with pytest.raises(KeyError):
bad = t.nested['something_else']

def test_metaclass(self):
assert type(MapAttribute) == MapAttributeMeta


class TestValueDeserialize:
def test__get_value_for_deserialize(self):
Expand Down

0 comments on commit 3560228

Please sign in to comment.