Skip to content

Commit 4fc015c

Browse files
authored
Add documentation for update expressions. (#371)
1 parent 5d95d49 commit 4fc015c

File tree

9 files changed

+89
-46
lines changed

9 files changed

+89
-46
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Topics
3030
tutorial
3131
indexes
3232
batch
33+
updates
3334
conditional
3435
attributes
3536
local

docs/release_notes.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Release Notes
22
=============
33

44
v3.2.0rc2
5-
-------
5+
---------
66

77
:date 2017-10-09
88

@@ -16,7 +16,7 @@ Contributors to this release:
1616
* @jpinner-lyft
1717

1818
v3.2.0rc1
19-
-------
19+
---------
2020

2121
:date: 2017-09-22
2222

docs/tutorial.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ atomically updating the view count of an item + updating the value of the last p
284284
Thread.last_post_datetime.set(datetime.now()),
285285
])
286286
287+
Update actions use the update expression syntax (see :ref:`updates`).
287288

288289
.. deprecated:: 2.0
289290

docs/updates.rst

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
Update Operations
2+
=================
3+
4+
The UpdateItem DynamoDB operations allows you to create or modify attributes of an item using an update expression.
5+
See the `official documentation <http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html>`_
6+
for more details.
7+
8+
Suppose that you have defined a `Thread` Model for the examples below.
9+
10+
.. code-block:: python
11+
12+
from pynamodb.models import Model
13+
from pynamodb.attributes import (
14+
ListAttribute, UnicodeAttribute, UnicodeSetAttribute, NumberAttribute
15+
)
16+
17+
18+
class Thread(Model):
19+
class Meta:
20+
table_name = 'Thread'
21+
22+
forum_name = UnicodeAttribute(hash_key=True)
23+
subjects = UnicodeSetAttribute(default={})
24+
views = NumberAttribute(default=0)
25+
notes = ListAttribute(default=[])
26+
27+
28+
.. _updates:
29+
30+
Update Expressions
31+
^^^^^^^^^^^^^^^^^^
32+
33+
PynamoDB supports creating update expressions from attributes using a mix of built-in operators and method calls.
34+
Any value provided will be serialized using the serializer defined for that attribute.
35+
36+
.. csv-table::
37+
:header: DynamoDB Action / Operator, PynamoDB Syntax, Example
38+
39+
SET, set( `value` ), Thread.views.set(10)
40+
REMOVE, remove(), Thread.subjects.remove()
41+
ADD, add( `value` ), "Thread.subjects.add({'A New Subject', 'Another New Subject'})"
42+
DELETE, delete( `value` ), Thread.subjects.delete({'An Old Subject'})
43+
`attr_or_value_1` \+ `attr_or_value_2`, `attr_or_value_1` \+ `attr_or_value_2`, Thread.views + 5
44+
`attr_or_value_1` \- `attr_or_value_2`, `attr_or_value_1` \- `attr_or_value_2`, 5 - Thread.views
45+
"list_append( `attr` , `value` )", append( `value` ), Thread.notes.append(['my last note'])
46+
"list_append( `value` , `attr` )", prepend( `value` ), Thread.notes.prepend(['my first note'])
47+
"if_not_exists( `attr`, `value` )", `attr` | `value`, Thread.forum_name | 'Default Forum Name'

examples/model.py

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -167,37 +167,32 @@ class Meta:
167167

168168
# DynamoDB will update the item, by adding 1 to the views attribute,
169169
# if the forum_name attribute equals 'Some Forum' or the subject attribute exists
170-
print(thread_item.update_item(
171-
'views',
172-
1,
173-
action='add',
174-
condition=((Thread.forum_name == 'Some Forum') | Thread.subject.exists())
170+
print(thread_item.update(
171+
actions=[
172+
Thread.views.add(1)
173+
],
174+
condition=(
175+
(Thread.forum_name == 'Some Forum') | Thread.subject.exists()
176+
)
175177
))
176178

177179
# DynamoDB will atomically update the attributes `replies` (increase value by 1),
178180
# and `last_post_datetime` (set value to the current datetime)
179-
print(thread_item.update({
180-
'replies': {
181-
'action': 'add',
182-
'value': 1,
183-
},
184-
'last_post_datetime': {
185-
'action': 'put',
186-
'value': datetime.now(),
187-
},
188-
}))
181+
print(thread_item.update(actions=[
182+
Thread.replies.add(1),
183+
Thread.last_post_datetime.set(datetime.now()),
184+
]))
189185

190186
# DynamoDB will delete the item, only if the views attribute is equal to one
191187
try:
192188
print(thread_item.delete(Thread.views == 1))
193189
except:
194190
pass
195191

196-
# Delete an item's attribute
197-
print(thread_item.update_item(
198-
'tags',
199-
action='delete'
200-
))
192+
# Remove an item's attribute
193+
print(thread_item.update(actions=[
194+
Thread.tags.remove()
195+
]))
201196

202197
# Backup/restore example
203198
# Print the size of the table

pynamodb/attributes.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,15 +159,15 @@ def prepend(self, other):
159159
def set(self, value):
160160
return Path(self).set(value)
161161

162-
def update(self, subset):
163-
return Path(self).update(subset)
164-
165-
def difference_update(self, subset):
166-
return Path(self).difference_update(subset)
167-
168162
def remove(self):
169163
return Path(self).remove()
170164

165+
def add(self, value):
166+
return Path(self).add(value)
167+
168+
def delete(self, value):
169+
return Path(self).delete(value)
170+
171171

172172
class AttributeContainerMeta(type):
173173

pynamodb/connection/base.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -886,12 +886,11 @@ def update_item(self,
886886
attr_type = self.get_attribute_type(table_name, key, value)
887887
value = {attr_type: value}
888888
if action == DELETE:
889-
action = path.remove() if attr_type is None else path.difference_update(value)
889+
action = path.remove() if attr_type is None else path.delete(value)
890890
elif action == PUT:
891891
action = path.set(value)
892892
else:
893-
# right now update() returns an AddAction
894-
action = path.update(value)
893+
action = path.add(value)
895894
update_expression.add_action(action)
896895
operation_kwargs[UPDATE_EXPRESSION] = update_expression.serialize(name_placeholders, expression_attribute_values)
897896

pynamodb/expressions/operand.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -271,18 +271,18 @@ def set(self, value):
271271
# Returns an update action that sets this attribute to the given value
272272
return SetAction(self, self._to_operand(value))
273273

274-
def update(self, subset):
275-
# Returns an update action that adds the subset to this set attribute
276-
return AddAction(self, self._to_operand(subset))
277-
278-
def difference_update(self, subset):
279-
# Returns an update action that deletes the subset from this set attribute
280-
return DeleteAction(self, self._to_operand(subset))
281-
282274
def remove(self):
283275
# Returns an update action that removes this attribute from the item
284276
return RemoveAction(self)
285277

278+
def add(self, value):
279+
# Returns an update action that appends the given value to a set or mathematically adds it to a number
280+
return AddAction(self, self._to_operand(value))
281+
282+
def delete(self, value):
283+
# Returns an update action that removes the given value from a set attribute
284+
return DeleteAction(self, self._to_operand(value))
285+
286286
def exists(self):
287287
return Exists(self)
288288

pynamodb/tests/test_expressions.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -467,15 +467,15 @@ def test_remove_action(self):
467467
assert expression_attribute_values == {}
468468

469469
def test_add_action(self):
470-
action = Path('foo').update(0)
470+
action = Path('foo').add(0)
471471
placeholder_names, expression_attribute_values = {}, {}
472472
expression = action.serialize(placeholder_names, expression_attribute_values)
473473
assert expression == "#0 :0"
474474
assert placeholder_names == {'foo': '#0'}
475475
assert expression_attribute_values == {':0': {'N': '0'}}
476476

477477
def test_add_action_set(self):
478-
action = NumberSetAttribute(attr_name='foo').update({'NS': ['0']})
478+
action = NumberSetAttribute(attr_name='foo').add({'NS': ['0']})
479479
placeholder_names, expression_attribute_values = {}, {}
480480
expression = action.serialize(placeholder_names, expression_attribute_values)
481481
assert expression == "#0 :0"
@@ -484,10 +484,10 @@ def test_add_action_set(self):
484484

485485
def test_add_action_list(self):
486486
with self.assertRaises(ValueError):
487-
Path('foo').update({'L': [{'N': '0'}]})
487+
Path('foo').add({'L': [{'N': '0'}]})
488488

489489
def test_delete_action(self):
490-
action = NumberSetAttribute(attr_name='foo').difference_update({'NS': ['0']})
490+
action = NumberSetAttribute(attr_name='foo').delete({'NS': ['0']})
491491
placeholder_names, expression_attribute_values = {}, {}
492492
expression = action.serialize(placeholder_names, expression_attribute_values)
493493
assert expression == "#0 :0"
@@ -496,14 +496,14 @@ def test_delete_action(self):
496496

497497
def test_delete_action_non_set(self):
498498
with self.assertRaises(ValueError):
499-
Path('foo').difference_update({'N': '0'})
499+
Path('foo').delete({'N': '0'})
500500

501501
def test_update(self):
502502
update = Update(
503503
self.attribute.set({'S': 'bar'}),
504504
self.attribute.remove(),
505-
self.attribute.update({'N': '0'}),
506-
self.attribute.difference_update({'NS': ['0']})
505+
self.attribute.add({'N': '0'}),
506+
self.attribute.delete({'NS': ['0']})
507507
)
508508
placeholder_names, expression_attribute_values = {}, {}
509509
expression = update.serialize(placeholder_names, expression_attribute_values)

0 commit comments

Comments
 (0)