Skip to content

Commit 3560228

Browse files
authored
Introduce AttributeContainerMeta (#326)
1 parent 9e01729 commit 3560228

File tree

3 files changed

+24
-12
lines changed

3 files changed

+24
-12
lines changed

pynamodb/attributes.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
PynamoDB attributes
33
"""
44
import six
5+
from six import add_metaclass
56
import json
67
from base64 import b64encode, b64decode
78
from datetime import datetime
@@ -118,11 +119,13 @@ def _serialize(self, value):
118119
return {ATTR_TYPE_MAP[self.attribute.attr_type]: self.attribute.serialize(value)}
119120

120121

121-
class AttributeContainer(object):
122+
class AttributeContainerMeta(type):
122123

123-
_dynamo_to_python_attrs = None
124+
def __init__(cls, name, bases, attrs):
125+
super(AttributeContainerMeta, cls).__init__(name, bases, attrs)
126+
AttributeContainerMeta._initialize_attributes(cls)
124127

125-
@classmethod
128+
@staticmethod
126129
def _initialize_attributes(cls):
127130
"""
128131
Initialize attributes on the class.
@@ -157,16 +160,17 @@ def _initialize_attributes(cls):
157160
else:
158161
instance.attr_name = item_name
159162

163+
164+
@add_metaclass(AttributeContainerMeta)
165+
class AttributeContainer(object):
166+
160167
@classmethod
161168
def _get_attributes(cls):
162169
"""
163170
Returns the attributes of this class as a mapping from `python_attr_name` => `attribute`.
164171
165172
:rtype: dict[str, Attribute]
166173
"""
167-
if '_attributes' not in cls.__dict__:
168-
# Each subclass of AttributeContainer needs its own attributes map.
169-
cls._initialize_attributes()
170174
return cls._attributes
171175

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

183185
def _set_defaults(self):
@@ -473,6 +475,13 @@ def deserialize(self, value):
473475
return None
474476

475477

478+
class MapAttributeMeta(AttributeContainerMeta):
479+
"""
480+
This is only here for backwards compatibility: i.e. so type(MapAttribute) == MapAttributeMeta
481+
"""
482+
483+
484+
@add_metaclass(MapAttributeMeta)
476485
class MapAttribute(AttributeContainer, Attribute):
477486
attr_type = MAP
478487

@@ -482,7 +491,6 @@ def __init__(self, hash_key=False, range_key=False, null=None, default=None, att
482491
null=null,
483492
default=default,
484493
attr_name=attr_name)
485-
self._get_attributes() # Ensure attributes are always inited
486494
self.attribute_values = {}
487495
self._set_defaults()
488496
self._set_attributes(**attrs)

pynamodb/models.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from six import add_metaclass
1212
from pynamodb.exceptions import DoesNotExist, TableDoesNotExist, TableError
1313
from pynamodb.throttle import NoThrottle
14-
from pynamodb.attributes import Attribute, AttributeContainer, MapAttribute, ListAttribute
14+
from pynamodb.attributes import Attribute, AttributeContainer, AttributeContainerMeta, MapAttribute, ListAttribute
1515
from pynamodb.connection.base import MetaTable
1616
from pynamodb.connection.table import TableConnection
1717
from pynamodb.connection.util import pythonic
@@ -153,14 +153,15 @@ def __iter__(self):
153153
return iter(self.results)
154154

155155

156-
class MetaModel(type):
156+
class MetaModel(AttributeContainerMeta):
157157
"""
158158
Model meta class
159159
160160
This class is just here so that index queries have nice syntax.
161161
Model.index.query()
162162
"""
163163
def __init__(cls, name, bases, attrs):
164+
super(MetaModel, cls).__init__(name, bases, attrs)
164165
if isinstance(attrs, dict):
165166
for attr_name, attr_obj in attrs.items():
166167
if attr_name == META_CLASS_NAME:

pynamodb/tests/test_attributes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from pynamodb.attributes import (
2020
BinarySetAttribute, BinaryAttribute, NumberSetAttribute, NumberAttribute,
2121
UnicodeAttribute, UnicodeSetAttribute, UTCDateTimeAttribute, BooleanAttribute, LegacyBooleanAttribute,
22-
MapAttribute, ListAttribute, Attribute,
22+
MapAttribute, MapAttributeMeta, ListAttribute, Attribute,
2323
JSONAttribute, DEFAULT_ENCODING, NUMBER, STRING, STRING_SET, NUMBER_SET, BINARY_SET,
2424
BINARY, MAP, LIST, BOOLEAN, _get_value_for_deserialize)
2525

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

719+
def test_metaclass(self):
720+
assert type(MapAttribute) == MapAttributeMeta
721+
719722

720723
class TestValueDeserialize:
721724
def test__get_value_for_deserialize(self):

0 commit comments

Comments
 (0)