Skip to content

Commit d773b63

Browse files
Zack Hsijpinner-lyft
Zack Hsi
authored andcommittedApr 24, 2018
Make get_attributes public (#452)
Being able to dynamically introspect Model classes is valuable for tasks such as validation. This commit converts the underscore-prefixed private method `_get_attributes` into a public method.
1 parent ae4bb6c commit d773b63

File tree

2 files changed

+42
-31
lines changed

2 files changed

+42
-31
lines changed
 

‎pynamodb/attributes.py

+20-9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from base64 import b64encode, b64decode
88
from copy import deepcopy
99
from datetime import datetime
10+
import warnings
1011
from dateutil.parser import parse
1112
from dateutil.tz import tzutc
1213
from inspect import getargspec
@@ -226,6 +227,16 @@ def _get_attributes(cls):
226227
"""
227228
Returns the attributes of this class as a mapping from `python_attr_name` => `attribute`.
228229
230+
:rtype: dict[str, Attribute]
231+
"""
232+
warnings.warn("`Model._get_attributes` is deprecated in favor of `Model.get_attributes` now")
233+
return cls.get_attributes()
234+
235+
@classmethod
236+
def get_attributes(cls):
237+
"""
238+
Returns the attributes of this class as a mapping from `python_attr_name` => `attribute`.
239+
229240
:rtype: dict[str, Attribute]
230241
"""
231242
return cls._attributes
@@ -243,7 +254,7 @@ def _set_defaults(self):
243254
"""
244255
Sets and fields that provide a default value
245256
"""
246-
for name, attr in self._get_attributes().items():
257+
for name, attr in self.get_attributes().items():
247258
default = attr.default
248259
if callable(default):
249260
value = default()
@@ -257,7 +268,7 @@ def _set_attributes(self, **attributes):
257268
Sets the attributes for this object
258269
"""
259270
for attr_name, attr_value in six.iteritems(attributes):
260-
if attr_name not in self._get_attributes():
271+
if attr_name not in self.get_attributes():
261272
raise ValueError("Attribute {0} specified does not exist".format(attr_name))
262273
setattr(self, attr_name, attr_value)
263274

@@ -636,7 +647,7 @@ def __init__(self, **attributes):
636647
# cannot raise a ValueError (if this assumption is wrong, calling `_make_attribute` removes them)
637648
# - the names of all attributes in self.attribute_kwargs match attributes defined on the class
638649
if self.attribute_kwargs and (
639-
attributes or self.is_raw() or all(arg in self._get_attributes() for arg in self.attribute_kwargs)):
650+
attributes or self.is_raw() or all(arg in self.get_attributes() for arg in self.attribute_kwargs)):
640651
self._set_attributes(**self.attribute_kwargs)
641652

642653
def _is_attribute_container(self):
@@ -655,7 +666,7 @@ def _make_attribute(self):
655666
del self.attribute_kwargs
656667
del self.attribute_values
657668
Attribute.__init__(self, **kwargs)
658-
for name, attr in self._get_attributes().items():
669+
for name, attr in self.get_attributes().items():
659670
# Set a local attribute with the same name that shadows the class attribute.
660671
# Because attr is a data descriptor and the attribute already exists on the class,
661672
# we have to store the local copy directly into __dict__ to prevent calling attr.__set__.
@@ -667,7 +678,7 @@ def _update_attribute_paths(self, path_segment):
667678
# WARNING! This function is only intended to be called from the AttributeContainerMeta metaclass.
668679
if self._is_attribute_container():
669680
raise AssertionError("MapAttribute._update_attribute_paths called before MapAttribute._make_attribute")
670-
for name in self._get_attributes().keys():
681+
for name in self.get_attributes().keys():
671682
local_attr = self.__dict__[name]
672683
local_attr.attr_path.insert(0, path_segment)
673684
if isinstance(local_attr, MapAttribute):
@@ -754,7 +765,7 @@ def is_correctly_typed(self, key, attr):
754765
return True # TODO: check that the actual type of `value` meets requirements of `attr`
755766

756767
def validate(self):
757-
return all(self.is_correctly_typed(k, v) for k, v in six.iteritems(self._get_attributes()))
768+
return all(self.is_correctly_typed(k, v) for k, v in six.iteritems(self.get_attributes()))
758769

759770
def serialize(self, values):
760771
rval = {}
@@ -771,7 +782,7 @@ def serialize(self, values):
771782
attr_key = _get_key_for_serialize(v)
772783

773784
# If this is a subclassed MapAttribute, there may be an alternate attr name
774-
attr = self._get_attributes().get(k)
785+
attr = self.get_attributes().get(k)
775786
attr_name = attr.attr_name if attr else k
776787

777788
serialized = attr_class.serialize(v)
@@ -824,13 +835,13 @@ def _should_skip(self, value):
824835
@classmethod
825836
def _get_serialize_class(cls, key, value):
826837
if not cls.is_raw():
827-
return cls._get_attributes().get(key)
838+
return cls.get_attributes().get(key)
828839
return _get_class_for_serialize(value)
829840

830841
@classmethod
831842
def _get_deserialize_class(cls, key, value):
832843
if not cls.is_raw():
833-
return cls._get_attributes().get(key)
844+
return cls.get_attributes().get(key)
834845
return _get_class_for_deserialize(value)
835846

836847

‎pynamodb/models.py

+22-22
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ def __init__(self, hash_key=None, range_key=None, **attributes):
244244

245245
@classmethod
246246
def has_map_or_list_attributes(cls):
247-
for attr_value in cls._get_attributes().values():
247+
for attr_value in cls.get_attributes().values():
248248
if isinstance(attr_value, MapAttribute) or isinstance(attr_value, ListAttribute):
249249
return True
250250
return False
@@ -356,7 +356,7 @@ def update_item(self, attribute, value=None, action=None, condition=None, condit
356356
self._conditional_operator_check(conditional_operator)
357357
args, save_kwargs = self._get_save_args(null_check=False)
358358
attribute_cls = None
359-
for attr_name, attr_cls in self._get_attributes().items():
359+
for attr_name, attr_cls in self.get_attributes().items():
360360
if attr_name == attribute:
361361
attribute_cls = attr_cls
362362
break
@@ -387,7 +387,7 @@ def update_item(self, attribute, value=None, action=None, condition=None, condit
387387

388388
for name, value in data.get(ATTRIBUTES).items():
389389
attr_name = self._dynamo_to_python_attr(name)
390-
attr = self._get_attributes().get(attr_name)
390+
attr = self.get_attributes().get(attr_name)
391391
if attr:
392392
setattr(self, attr_name, attr.deserialize(attr.get_value(value)))
393393
return data
@@ -423,7 +423,7 @@ def update(self, attributes=None, actions=None, condition=None, conditional_oper
423423
if expected_values:
424424
kwargs['expected'] = self._build_expected_values(expected_values, UPDATE_FILTER_OPERATOR_MAP)
425425

426-
attrs = self._get_attributes()
426+
attrs = self.get_attributes()
427427
attributes = attributes or {}
428428
for attr, params in attributes.items():
429429
attribute_cls = attrs[attr]
@@ -439,7 +439,7 @@ def update(self, attributes=None, actions=None, condition=None, conditional_oper
439439
data = self._get_connection().update_item(*args, **kwargs)
440440
for name, value in data[ATTRIBUTES].items():
441441
attr_name = self._dynamo_to_python_attr(name)
442-
attr = self._get_attributes().get(attr_name)
442+
attr = self.get_attributes().get(attr_name)
443443
if attr:
444444
setattr(self, attr_name, attr.deserialize(attr.get_value(value)))
445445
return data
@@ -511,19 +511,19 @@ def from_raw_data(cls, data):
511511
hash_key_type = cls._get_meta_data().get_attribute_type(hash_keyname)
512512
hash_key = mutable_data.pop(hash_keyname).get(hash_key_type)
513513

514-
hash_key_attr = cls._get_attributes().get(cls._dynamo_to_python_attr(hash_keyname))
514+
hash_key_attr = cls.get_attributes().get(cls._dynamo_to_python_attr(hash_keyname))
515515

516516
hash_key = hash_key_attr.deserialize(hash_key)
517517
args = (hash_key,)
518518
kwargs = {}
519519
if range_keyname:
520-
range_key_attr = cls._get_attributes().get(cls._dynamo_to_python_attr(range_keyname))
520+
range_key_attr = cls.get_attributes().get(cls._dynamo_to_python_attr(range_keyname))
521521
range_key_type = cls._get_meta_data().get_attribute_type(range_keyname)
522522
range_key = mutable_data.pop(range_keyname).get(range_key_type)
523523
kwargs['range_key'] = range_key_attr.deserialize(range_key)
524524
for name, value in mutable_data.items():
525525
attr_name = cls._dynamo_to_python_attr(name)
526-
attr = cls._get_attributes().get(attr_name, None)
526+
attr = cls.get_attributes().get(attr_name, None)
527527
if attr:
528528
kwargs[attr_name] = attr.deserialize(attr.get_value(value))
529529
return cls(*args, **kwargs)
@@ -557,12 +557,12 @@ def count(cls,
557557
if index_name:
558558
hash_key = cls._index_classes[index_name]._hash_key_attribute().serialize(hash_key)
559559
key_attribute_classes = cls._index_classes[index_name]._get_attributes()
560-
non_key_attribute_classes = cls._get_attributes()
560+
non_key_attribute_classes = cls.get_attributes()
561561
else:
562562
hash_key = cls._serialize_keys(hash_key)[0]
563-
non_key_attribute_classes = dict(cls._get_attributes())
564-
key_attribute_classes = dict(cls._get_attributes())
565-
for name, attr in cls._get_attributes().items():
563+
non_key_attribute_classes = dict(cls.get_attributes())
564+
key_attribute_classes = dict(cls.get_attributes())
565+
for name, attr in cls.get_attributes().items():
566566
if attr.is_range_key or attr.is_hash_key:
567567
key_attribute_classes[name] = attr
568568
else:
@@ -636,12 +636,12 @@ def query(cls,
636636
if index_name:
637637
hash_key = cls._index_classes[index_name]._hash_key_attribute().serialize(hash_key)
638638
key_attribute_classes = cls._index_classes[index_name]._get_attributes()
639-
non_key_attribute_classes = cls._get_attributes()
639+
non_key_attribute_classes = cls.get_attributes()
640640
else:
641641
hash_key = cls._serialize_keys(hash_key)[0]
642642
non_key_attribute_classes = {}
643643
key_attribute_classes = {}
644-
for name, attr in cls._get_attributes().items():
644+
for name, attr in cls.get_attributes().items():
645645
if attr.is_range_key or attr.is_hash_key:
646646
key_attribute_classes[name] = attr
647647
else:
@@ -729,7 +729,7 @@ def rate_limited_scan(cls,
729729
key_filter, scan_filter = cls._build_filters(
730730
SCAN_OPERATOR_MAP,
731731
non_key_operator_map=SCAN_OPERATOR_MAP,
732-
key_attribute_classes=cls._get_attributes(),
732+
key_attribute_classes=cls.get_attributes(),
733733
filters=filters
734734
)
735735
key_filter.update(scan_filter)
@@ -786,7 +786,7 @@ def scan(cls,
786786
key_filter, scan_filter = cls._build_filters(
787787
SCAN_OPERATOR_MAP,
788788
non_key_operator_map=SCAN_OPERATOR_MAP,
789-
key_attribute_classes=cls._get_attributes(),
789+
key_attribute_classes=cls.get_attributes(),
790790
filters=filters
791791
)
792792
key_filter.update(scan_filter)
@@ -950,7 +950,7 @@ def _build_expected_values(cls, expected_values, operator_map=None):
950950
:param expected_values: A list of expected values
951951
"""
952952
expected_values_result = {}
953-
attributes = cls._get_attributes()
953+
attributes = cls.get_attributes()
954954
filters = {}
955955
for attr_name, attr_value in expected_values.items():
956956
attr_cond = VALUE
@@ -1109,7 +1109,7 @@ def _get_schema(cls):
11091109
pythonic(ATTR_DEFINITIONS): [],
11101110
pythonic(KEY_SCHEMA): []
11111111
}
1112-
for attr_name, attr_cls in cls._get_attributes().items():
1112+
for attr_name, attr_cls in cls.get_attributes().items():
11131113
if attr_cls.is_hash_key or attr_cls.is_range_key:
11141114
schema[pythonic(ATTR_DEFINITIONS)].append({
11151115
pythonic(ATTR_NAME): attr_cls.attr_name,
@@ -1202,7 +1202,7 @@ def _range_key_attribute(cls):
12021202
"""
12031203
Returns the attribute class for the hash key
12041204
"""
1205-
attributes = cls._get_attributes()
1205+
attributes = cls.get_attributes()
12061206
range_keyname = cls._get_meta_data().range_keyname
12071207
if range_keyname:
12081208
attr = attributes[cls._dynamo_to_python_attr(range_keyname)]
@@ -1215,7 +1215,7 @@ def _hash_key_attribute(cls):
12151215
"""
12161216
Returns the attribute class for the hash key
12171217
"""
1218-
attributes = cls._get_attributes()
1218+
attributes = cls.get_attributes()
12191219
hash_keyname = cls._get_meta_data().hash_keyname
12201220
return attributes[cls._dynamo_to_python_attr(hash_keyname)]
12211221

@@ -1301,7 +1301,7 @@ def _deserialize(self, attrs):
13011301
13021302
:param attrs: A dictionary of attributes to update this item with.
13031303
"""
1304-
for name, attr in self._get_attributes().items():
1304+
for name, attr in self.get_attributes().items():
13051305
value = attrs.get(attr.attr_name, None)
13061306
if value is not None:
13071307
value = value.get(ATTR_TYPE_MAP[attr.attr_type], None)
@@ -1318,7 +1318,7 @@ def _serialize(self, attr_map=False, null_check=True):
13181318
"""
13191319
attributes = pythonic(ATTRIBUTES)
13201320
attrs = {attributes: {}}
1321-
for name, attr in self._get_attributes().items():
1321+
for name, attr in self.get_attributes().items():
13221322
value = getattr(self, name)
13231323
if isinstance(value, MapAttribute):
13241324
if not value.validate():

0 commit comments

Comments
 (0)
Please sign in to comment.