Skip to content

Commit

Permalink
allows udpate multiple attributes at once
Browse files Browse the repository at this point in the history
  • Loading branch information
yedpodtrzitko committed Nov 8, 2016
1 parent 499d5ac commit 751c9da
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 21 deletions.
9 changes: 9 additions & 0 deletions docs/tutorial.rst
Expand Up @@ -281,3 +281,12 @@ atomically updating the view count of an item:
>>> thread_item.update_item('views', 1, action='add')
Multiple attributes can be updated at once too:

.. code-block:: python
>>> thread_item.update_item({
'views': {'value': 1, 'action': 'add'},
'last_view': {'value' time(), 'action': 'put'},
})
56 changes: 35 additions & 21 deletions pynamodb/models.py
Expand Up @@ -316,7 +316,15 @@ def update_item(self, attribute, value=None, action=None, conditional_operator=N
"""
Updates an item using the UpdateItem operation.
This should be used for updating a single attribute of an item.
This should be used for updating attributes of an item.
In case multiple attributes are updates, the parameters `value` and `action` should be omited
and all the attributes should be passed as the first parameter in the following format
{
attr_name: {'value': 10, 'action': 'ADD'},
next_attr: {'value': True, 'action': 'PUT'},
}
:param attribute: The name of the attribute to be updated
:param value: The new value for the attribute.
Expand All @@ -325,27 +333,33 @@ def update_item(self, attribute, value=None, action=None, conditional_operator=N
"""
self._conditional_operator_check(conditional_operator)
args, save_kwargs = self._get_save_args(null_check=False)
attribute_cls = None
for attr_name, attr_cls in self._get_attributes().items():
if attr_name == attribute:
value = attr_cls.serialize(value)
attribute_cls = attr_cls
break
if save_kwargs.get(pythonic(RANGE_KEY)):
kwargs = {pythonic(RANGE_KEY): save_kwargs.get(pythonic(RANGE_KEY))}
else:
kwargs = {}
if len(expected_values):
kwargs.update(expected=self._build_expected_values(expected_values, UPDATE_FILTER_OPERATOR_MAP))
kwargs[pythonic(ATTR_UPDATES)] = {
attribute_cls.attr_name: {
ACTION: action.upper() if action else None,
}
kwargs = {
pythonic(RETURN_VALUES): ALL_NEW,
pythonic(ATTR_UPDATES): {},
'conditional_operator': conditional_operator,
}
if action is not None and action.upper() != DELETE:
kwargs[pythonic(ATTR_UPDATES)][attribute_cls.attr_name][VALUE] = {ATTR_TYPE_MAP[attribute_cls.attr_type]: value}
kwargs[pythonic(RETURN_VALUES)] = ALL_NEW
kwargs.update(conditional_operator=conditional_operator)

if pythonic(RANGE_KEY) in save_kwargs:
kwargs[pythonic(RANGE_KEY)] = save_kwargs[pythonic(RANGE_KEY)]

if len(expected_values):
kwargs['expected'] = self._build_expected_values(expected_values, UPDATE_FILTER_OPERATOR_MAP)

# if it's a single attribute, convert it to a dict of attrs
if type(attribute) is not dict:
attribute = {attribute: {'value': value, 'action': action}}
elif value or action:
raise ValueError("parameters `value` and `action` can be used only for a single attribute update")

for attr, params in attribute.items():
attribute_cls = self._get_attributes()[attr]
action, value = params['action'] and params['action'].upper(), attribute_cls.serialize(params['value'])
attr_value = {ACTION: action}
if action != DELETE:
attr_value[VALUE] = {ATTR_TYPE_MAP[attribute_cls.attr_type]: value}

kwargs[pythonic(ATTR_UPDATES)][attribute_cls.attr_name] = attr_value

data = self._get_connection().update_item(
*args,
**kwargs
Expand Down
35 changes: 35 additions & 0 deletions pynamodb/tests/test_model.py
Expand Up @@ -1125,6 +1125,41 @@ def test_update_item(self):
}
deep_eq(args, params, _assert=True)

with patch(PATCH_METHOD) as req:
item.update_item({
'is_active': {'value': False, 'action': 'put'},
'views': {'value': 10, 'action': 'add'}
})

args = req.call_args[0][1]
params = {
'TableName': 'SimpleModel',
'ReturnValues': 'ALL_NEW',
'Key': {
'user_name': {
'S': 'foo'
}
},
'AttributeUpdates': {
'is_active': {
'Action': 'PUT',
'Value': {
'BOOL': False,
}
},
'views': {
'Action': 'ADD',
'Value': {
'N': '10',
}
}

},
'ReturnConsumedCapacity': 'TOTAL'
}

deep_eq(args, params, _assert=True)


def test_save(self):
"""
Expand Down

0 comments on commit 751c9da

Please sign in to comment.