Skip to content

Commit 415f652

Browse files
authored
Update documentation for condition expressions. (#338)
1 parent 7bc940c commit 415f652

File tree

6 files changed

+84
-147
lines changed

6 files changed

+84
-147
lines changed

docs/batch.rst

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -53,74 +53,49 @@ Here is an example using an iterator for retrieving items in bulk:
5353
for item in Thread.batch_get(item_keys):
5454
print(item)
5555
56-
.. _filtering:
57-
5856
Query Filters
5957
^^^^^^^^^^^^^
6058

61-
You can query items from your table using a simple syntax, similar to other Python ORMs:
59+
You can query items from your table using a simple syntax:
6260

6361
.. code-block:: python
6462
65-
for item in Thread.query('ForumName', subject__begins_with='mygreatprefix'):
63+
for item in Thread.query('ForumName', Thread.subject.startswith('mygreatprefix')):
6664
print("Query returned item {0}".format(item))
6765
68-
Query filters are translated from an ORM like syntax to DynamoDB API calls, and use
69-
the following syntax: `attribute__operator=value`, where `attribute` is the name of an attribute
70-
and `operator` can be one of the following:
66+
Additionally, you can filter the results before they are returned using condition expressions:
67+
68+
.. code-block:: python
69+
70+
for item in Thread.query('ForumName', Thread.subject == 'Subject', Thread.views > 0):
71+
print("Query returned item {0}".format(item))
72+
73+
7174
72-
* eq
73-
* le
74-
* lt
75-
* ge
76-
* gt
77-
* begins_with
78-
* between
75+
Query filters use the condition expression syntax (see :ref:`conditions`).
7976

8077
.. note::
8178

82-
DynamoDB does not allow multiple operators against range keys. `PynamoDB` is also currently
83-
based on deprecated API functionality that does not support multiple operators against any
84-
key, even if not part of the primary key. `between` can be used in place of `le AND ge`.
79+
DynamoDB only allows the following conditions on range keys: `==`, `<`, `<=`, `>`, `>=`, `between`, and `startswith`.
80+
DynamoDB does not allow multiple conditions using range keys.
8581

8682

8783
Scan Filters
8884
^^^^^^^^^^^^
8985

90-
Scan filters have the same syntax as Query filters, but support different operations (a consequence of the
91-
DynamoDB API - not PynamoDB). The supported operators are:
92-
93-
* eq
94-
* ne
95-
* le
96-
* lt
97-
* gt
98-
* not_null
99-
* null
100-
* contains
101-
* not_contains
102-
* begins_with
103-
* between
104-
105-
You can even specify multiple filters:
86+
Scan filters have the same syntax as Query filters, but support all condition expressions:
10687

10788
.. code-block:: python
10889
109-
>>> for item in Thread.scan(forum_name__begins_with='Prefix', views__gt=10):
90+
>>> for item in Thread.scan(Thread.forum_name.startswith('Prefix') & (Thread.views > 10)):
11091
print(item)
11192
112-
.. note::
113-
114-
PynamoDB is currently based on deprecated API functionality that does not support multiple operators per key.
115-
`between` can be used in place of `le AND ge`.
116-
117-
11893
Limiting results
11994
^^^^^^^^^^^^^^^^
12095

12196
Both `Scan` and `Query` results can be limited to a maximum number of items using the `limit` argument.
12297

12398
.. code-block:: python
12499
125-
for item in Thread.query('ForumName', subject__begins_with='mygreatprefix', limit=5):
100+
for item in Thread.query('ForumName', Thread.subject.startswith('mygreatprefix'), limit=5):
126101
print("Query returned item {0}".format(item))

docs/conditional.rst

Lines changed: 45 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
Conditional Operations
22
======================
33

4-
Some DynamoDB operations (UpdateItem, PutItem, DeleteItem) support the inclusion of conditions. The user can supply a list of conditions to be
5-
evaluated by DynamoDB before the operation is performed, as well as specifying whether those conditions are
6-
applied with logical OR (at least one must be true) or logical AND (all must be true). See the `official documentation <http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#ConditionalExpressions>`_
7-
for more details. PynamoDB supports conditionals through keyword arguments, using syntax that is similar to the filter syntax (see :ref:`filtering`).
8-
Multiple conditions may be supplied, and each value provided will be serialized using the serializer defined for that attribute.
4+
Some DynamoDB operations (UpdateItem, PutItem, DeleteItem) support the inclusion of conditions. The user can supply a condition to be
5+
evaluated by DynamoDB before the operation is performed. See the `official documentation <http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.ConditionalUpdate>`_
6+
for more details.
97

108
Suppose that you have defined a `Thread` Model for the examples below.
119

@@ -25,114 +23,80 @@ Suppose that you have defined a `Thread` Model for the examples below.
2523
subject = UnicodeAttribute(range_key=True)
2624
views = NumberAttribute(default=0)
2725
28-
29-
AND vs. OR
30-
^^^^^^^^^^
31-
32-
Specifying that the conditions should be applied with AND or OR is achieved through the use of the `conditional_operator` keyword,
33-
which can be `and` or `or`.
26+
.. _conditions:
27+
28+
Condition Expressions
29+
^^^^^^^^^^^^^^^^^^^^^
30+
31+
PynamoDB supports creating condition expressions from attributes using a mix of built-in operators and method calls.
32+
Any value provided will be serialized using the serializer defined for that attribute.
33+
See the `comparison operator and function reference <http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html>`_
34+
for more details.
35+
36+
.. csv-table::
37+
:header: DynamoDB Condition, PynamoDB Syntax, Example
38+
39+
=, ==, Thread.forum_name == 'Some Forum'
40+
<>, !=, Thread.forum_name != 'Some Forum'
41+
<, <, Thread.views < 10
42+
<=, <=, Thread.views <= 10
43+
>, >, Thread.views > 10
44+
>=, >=, Thread.views >= 10
45+
BETWEEN, "between( `lower` , `upper` )", "Thread.views.between(1, 5)"
46+
IN, is_in( `*values` ), "Thread.subject.is_in('Subject', 'Other Subject')"
47+
attribute_exists ( `path` ), exists(), Thread.forum_name.exists()
48+
attribute_not_exists ( `path` ), does_not_exist(), Thread.forum_name.does_not_exist()
49+
"attribute_type ( `path` , `type` )", is_type(), Thread.forum_name.is_type()
50+
"begins_with ( `path` , `substr` )", startswith( `prefix` ), Thread.subject.startswith('Example')
51+
"contains ( `path` , `operand` )", contains( `item` ), Thread.subject.contains('foobar')
52+
size ( `path`), size( `attribute` ), size(Thread.subject) == 10
53+
AND, &, (Thread.views > 1) & (Thread.views < 5)
54+
OR, \|, (Thread.views < 1) | (Thread.views > 5)
55+
NOT, ~, ~Thread.subject.contains('foobar')
56+
57+
If necessary, you can use document paths to access nested list and map attributes:
3458

3559
.. code-block:: python
3660
37-
thread_item = Thread('Existing Forum', 'Example Subject')
61+
from pynamodb.expressions.condition import size
3862
39-
# The item will be saved if the forum name is not null OR the subject contains 'foobar'
40-
thread_item.save(
41-
forum_name__null=False,
42-
forum_subject__contains='foobar',
43-
conditional_operator='or'
44-
)
63+
print(size('foo.bar[0].baz') == 0)
4564
46-
# The item will be saved if the forum name is not null AND the subject contains 'foobar'
47-
thread_item.save(
48-
forum_name__null=False,
49-
forum_subject__contains='foobar',
50-
conditional_operator='and'
51-
)
5265
5366
Conditional Model.save
5467
^^^^^^^^^^^^^^^^^^^^^^
5568

56-
The following conditional operators are supported for `Model.save`:
57-
58-
* eq
59-
* ne
60-
* le
61-
* lt
62-
* ge
63-
* gt
64-
* null
65-
* contains
66-
* not_contains
67-
* begins_with
68-
* in
69-
* between
70-
71-
This example saves a `Thread` item, only if the item exists, and the `forum_name` attribute is not null.
69+
This example saves a `Thread` item, only if the item exists.
7270

7371
.. code-block:: python
7472
7573
thread_item = Thread('Existing Forum', 'Example Subject')
7674
77-
# DynamoDB will only save the item if forum_name exists and is not null
78-
print(thread_item.save(forum_name__null=False)
75+
# DynamoDB will only save the item if forum_name exists
76+
print(thread_item.save(Thread.forum_name.exists())
7977
8078
# You can specify multiple conditions
81-
print(thread_item.save(forum_name__null=False, forum_subject__contains='foobar'))
79+
print(thread_item.save(Thread.forum_name.exists() & Thread.forum_subject.contains('foobar')))
8280
8381
8482
Conditional Model.update
8583
^^^^^^^^^^^^^^^^^^^^^^^^
8684
87-
The following conditional operators are supported for `Model.update`:
88-
89-
* eq
90-
* ne
91-
* le
92-
* lt
93-
* ge
94-
* gt
95-
* null
96-
* contains
97-
* not_contains
98-
* begins_with
99-
* in
100-
* between
101-
102-
This example will update a `Thread` item, if the `forum_name` attribute equals 'Some Forum' *OR* the subject is not null:
85+
This example will update a `Thread` item, if the `views` attribute is less than 5 *OR* greater than 10:
10386
10487
.. code-block:: python
10588
106-
thread_item.update(
107-
conditional_operator='or',
108-
forum_name__eq='Some Forum',
109-
subject__null=False)
110-
)
89+
thread_item.update((Thread.views < 5) | (Thread.views > 10))
11190
11291
11392
Conditional Model.delete
11493
^^^^^^^^^^^^^^^^^^^^^^^^
11594
116-
The following conditional operators are supported for `Model.delete`:
117-
118-
* eq
119-
* ne
120-
* le
121-
* lt
122-
* ge
123-
* gt
124-
* null
125-
* contains
126-
* not_contains
127-
* begins_with
128-
* in
129-
* between
130-
13195
This example will delete the item, only if its `views` attribute is equal to 0.
13296
13397
.. code-block:: python
13498
135-
print(thread_item.delete(views__eq=0))
99+
print(thread_item.delete(Thread.views == 0))
136100
137101
Conditional Operation Failures
138102
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -142,7 +106,7 @@ You can check for conditional operation failures by inspecting the cause of the
142106
.. code-block:: python
143107
144108
try:
145-
thread_item.save(forum_name__null=False)
109+
thread_item.save(Thread.forum_name.exists())
146110
except PutError as e:
147111
if isinstance(e.cause, ClientError):
148112
code = e.cause.response['Error'].get('Code')

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Topics
3434
attributes
3535
local
3636
backup_restore
37+
signals
3738
examples
3839
settings
3940
low_level

docs/indexes.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,5 +118,5 @@ range key of the index. Here is an example that queries the index for values of
118118

119119
.. code-block:: python
120120
121-
for item in TestModel.view_index.query('foo', view__gt=0):
121+
for item in TestModel.view_index.query('foo', TestModel.view > 0):
122122
print("Item queried from index: {0}".format(item.view))

docs/quickstart.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,14 @@ Now, suppose that you want to search the table for users with a last name
8888

8989
::
9090

91-
for user in UserModel.query('Smith', first_name__begins_with='J'):
91+
for user in UserModel.query('Smith', UserModel.first_name.startswith('J')):
9292
print(user.first_name)
9393

94-
You can combine query terms using 'AND' or 'OR':
94+
You can combine query terms:
9595

9696
::
9797

98-
for user in UserModel.query('Smith', first_name__begins_with='J', email__contains='domain.com', conditional_operator='OR'):
98+
for user in UserModel.query('Smith', UserModel.first_name.startswith('J') | UserModel.email.contains('domain.com')):
9999
print(user)
100100

101101

@@ -106,7 +106,7 @@ You can retrieve the count for queries by using the `count` method:
106106

107107
::
108108

109-
print(UserModel.count('Smith', first_name__begins_with='J'))
109+
print(UserModel.count('Smith', UserModel.first_name.startswith('J'))
110110

111111

112112
Counts also work for indexes:
@@ -129,10 +129,10 @@ this argument can be `None`, filters must not be used when `hash_key` is `None`:
129129
::
130130

131131
# raises a ValueError
132-
print(UserModel.count(first_name__eq='John'))
132+
print(UserModel.count(UserModel.first_name == 'John'))
133133

134134
# returns count of only the matching users
135-
print(UserModel.count('my_hash_key', first_name__eq='John'))
135+
print(UserModel.count('my_hash_key', UserModel.first_name == 'John'))
136136

137137

138138
Batch Operations

0 commit comments

Comments
 (0)