New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multiple attrs update #194
Changes from all commits
cc4b9ea
ae39b87
d1f272e
495f340
70fe12e
2d89666
d0eb3c8
2018285
6b7cf22
c56c8da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,8 @@ | |
import six | ||
import copy | ||
import logging | ||
import warnings | ||
|
||
from six import with_metaclass | ||
from pynamodb.exceptions import DoesNotExist, TableDoesNotExist, TableError | ||
from pynamodb.throttle import NoThrottle | ||
|
@@ -323,6 +325,8 @@ def update_item(self, attribute, value=None, action=None, conditional_operator=N | |
:param action: The action to take if this item already exists. | ||
See: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html#DDB-UpdateItem-request-AttributeUpdate | ||
""" | ||
warnings.warn("`Model.update_item` is deprecated in favour of `Model.update` now") | ||
|
||
self._conditional_operator_check(conditional_operator) | ||
args, save_kwargs = self._get_save_args(null_check=False) | ||
attribute_cls = None | ||
|
@@ -357,6 +361,52 @@ def update_item(self, attribute, value=None, action=None, conditional_operator=N | |
setattr(self, name, attr.deserialize(value.get(ATTR_TYPE_MAP[attr.attr_type]))) | ||
return data | ||
|
||
|
||
def update(self, attributes, conditional_operator=None, **expected_values): | ||
""" | ||
Updates an item using the UpdateItem operation. | ||
|
||
:param attributes: A dictionary of attributes to update in the following format | ||
{ | ||
attr_name: {'value': 10, 'action': 'ADD'}, | ||
next_attr: {'value': True, 'action': 'PUT'}, | ||
} | ||
""" | ||
if not isinstance(attributes, dict): | ||
raise TypeError("the value of `attributes` is expected to be a dictionary") | ||
|
||
self._conditional_operator_check(conditional_operator) | ||
args, save_kwargs = self._get_save_args(null_check=False) | ||
kwargs = { | ||
pythonic(RETURN_VALUES): ALL_NEW, | ||
pythonic(ATTR_UPDATES): {}, | ||
'conditional_operator': conditional_operator, | ||
} | ||
|
||
if pythonic(RANGE_KEY) in save_kwargs: | ||
kwargs[pythonic(RANGE_KEY)] = save_kwargs[pythonic(RANGE_KEY)] | ||
|
||
if expected_values: | ||
kwargs['expected'] = self._build_expected_values(expected_values, UPDATE_FILTER_OPERATOR_MAP) | ||
|
||
attrs = self._get_attributes() | ||
for attr, params in attributes.items(): | ||
attribute_cls = attrs[attr] | ||
action = params['action'] and params['action'].upper() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. won't this return a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, either >>> a = {'none': False, 'one': 'yes'}
>>> a['none'] and a['none'].upper()
False
>>> a['one'] and a['one'].upper()
'YES' |
||
attr_values = {ACTION: action} | ||
if action != DELETE: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we add a test for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||
attr_values[VALUE] = self._serialize_value(attribute_cls, params['value']) | ||
|
||
kwargs[pythonic(ATTR_UPDATES)][attribute_cls.attr_name] = attr_values | ||
|
||
data = self._get_connection().update_item(*args, **kwargs) | ||
self._throttle.add_record(data.get(CONSUMED_CAPACITY)) | ||
for name, value in data[ATTRIBUTES].items(): | ||
attr = self._get_attributes().get(name) | ||
if attr: | ||
setattr(self, name, attr.deserialize(value.get(ATTR_TYPE_MAP[attr.attr_type]))) | ||
return data | ||
|
||
def save(self, conditional_operator=None, **expected_values): | ||
""" | ||
Save this object to dynamodb | ||
|
@@ -1189,7 +1239,7 @@ def _deserialize(self, attrs): | |
|
||
def _serialize(self, attr_map=False, null_check=True): | ||
""" | ||
Serializes a value for use with DynamoDB | ||
Serializes all model attributes for use with DynamoDB | ||
|
||
:param attr_map: If True, then attributes are returned | ||
:param null_check: If True, then attributes are checked for null | ||
|
@@ -1198,33 +1248,48 @@ def _serialize(self, attr_map=False, null_check=True): | |
attrs = {attributes: {}} | ||
for name, attr in self._get_attributes().aliased_attrs(): | ||
value = getattr(self, name) | ||
if value is None: | ||
if attr.null: | ||
continue | ||
elif null_check: | ||
raise ValueError("Attribute '{0}' cannot be None".format(attr.attr_name)) | ||
if isinstance(value, MapAttribute): | ||
if not value.validate(): | ||
raise ValueError("Attribute '{0}' is not correctly typed".format(attr.attr_name)) | ||
value = value.get_values() | ||
serialized = attr.serialize(value) | ||
if serialized is None: | ||
|
||
serialized = self._serialize_value(attr, value, null_check) | ||
if NULL in serialized: | ||
continue | ||
|
||
if attr_map: | ||
attrs[attributes][attr.attr_name] = { | ||
ATTR_TYPE_MAP[attr.attr_type]: serialized | ||
} | ||
attrs[attributes][attr.attr_name] = serialized | ||
else: | ||
if attr.is_hash_key: | ||
attrs[HASH] = serialized | ||
attrs[HASH] = serialized[ATTR_TYPE_MAP[attr.attr_type]] | ||
elif attr.is_range_key: | ||
attrs[RANGE] = serialized | ||
attrs[RANGE] = serialized[ATTR_TYPE_MAP[attr.attr_type]] | ||
else: | ||
attrs[attributes][attr.attr_name] = { | ||
ATTR_TYPE_MAP[attr.attr_type]: serialized | ||
} | ||
attrs[attributes][attr.attr_name] = serialized | ||
|
||
return attrs | ||
|
||
@classmethod | ||
def _serialize_value(cls, attr, value, null_check=True): | ||
""" | ||
Serializes a value for use with DynamoDB | ||
|
||
:param attr: an instance of `Attribute` for serialization | ||
:param value: a value to be serialized | ||
:param null_check: If True, then attributes are checked for null | ||
""" | ||
if value is None: | ||
serialized = None | ||
else: | ||
serialized = attr.serialize(value) | ||
|
||
if serialized is None: | ||
if not attr.null and null_check: | ||
raise ValueError("Attribute '{0}' cannot be None".format(attr.attr_name)) | ||
return {NULL: True} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we just set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, this is a fix for #195 |
||
|
||
return {ATTR_TYPE_MAP[attr.attr_type]: serialized} | ||
|
||
@classmethod | ||
def _serialize_keys(cls, hash_key, range_key=None): | ||
""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it be better to take kwargs that map from the model_field_name to the new value? i see we've already got
**expected_values
filling that role though... maybe we could make that a keyword argumentThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would be good to do now since this is new...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @danielhochman
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jmphilli you might need to pass
action
for the attributes too. That would mean a bunch of small dictionaries likeattribute_name={'value': 1, 'action': 'add'}
.