Skip to content

Commit

Permalink
Update documentation for new expression api.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpinner-lyft committed Aug 25, 2017
1 parent 7bc940c commit be06095
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 147 deletions.
57 changes: 16 additions & 41 deletions docs/batch.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,74 +53,49 @@ Here is an example using an iterator for retrieving items in bulk:
for item in Thread.batch_get(item_keys):
print(item)
.. _filtering:

Query Filters
^^^^^^^^^^^^^

You can query items from your table using a simple syntax, similar to other Python ORMs:
You can query items from your table using a simple syntax:

.. code-block:: python
for item in Thread.query('ForumName', subject__begins_with='mygreatprefix'):
for item in Thread.query('ForumName', Thread.subject.startswith('mygreatprefix')):
print("Query returned item {0}".format(item))
Query filters are translated from an ORM like syntax to DynamoDB API calls, and use
the following syntax: `attribute__operator=value`, where `attribute` is the name of an attribute
and `operator` can be one of the following:
Additionally, you can filter the results before they are returned using condition expressions:

.. code-block:: python
for item in Thread.query('ForumName', Thread.subject == 'Subject', Thread.views > 0):
print("Query returned item {0}".format(item))
* eq
* le
* lt
* ge
* gt
* begins_with
* between
Query filters use the condition expression syntax (see :ref:`conditions`).

.. note::

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


Scan Filters
^^^^^^^^^^^^

Scan filters have the same syntax as Query filters, but support different operations (a consequence of the
DynamoDB API - not PynamoDB). The supported operators are:

* eq
* ne
* le
* lt
* gt
* not_null
* null
* contains
* not_contains
* begins_with
* between

You can even specify multiple filters:
Scan filters have the same syntax as Query filters, but support all condition expressions:

.. code-block:: python
>>> for item in Thread.scan(forum_name__begins_with='Prefix', views__gt=10):
>>> for item in Thread.scan(Thread.forum_name.startswith('Prefix') & (Thread.views > 10)):
print(item)
.. note::

PynamoDB is currently based on deprecated API functionality that does not support multiple operators per key.
`between` can be used in place of `le AND ge`.


Limiting results
^^^^^^^^^^^^^^^^

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

.. code-block:: python
for item in Thread.query('ForumName', subject__begins_with='mygreatprefix', limit=5):
for item in Thread.query('ForumName', Thread.subject.startswith('mygreatprefix'), limit=5):
print("Query returned item {0}".format(item))
126 changes: 45 additions & 81 deletions docs/conditional.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
Conditional Operations
======================

Some DynamoDB operations (UpdateItem, PutItem, DeleteItem) support the inclusion of conditions. The user can supply a list of conditions to be
evaluated by DynamoDB before the operation is performed, as well as specifying whether those conditions are
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>`_
for more details. PynamoDB supports conditionals through keyword arguments, using syntax that is similar to the filter syntax (see :ref:`filtering`).
Multiple conditions may be supplied, and each value provided will be serialized using the serializer defined for that attribute.
Some DynamoDB operations (UpdateItem, PutItem, DeleteItem) support the inclusion of conditions. The user can supply a condition to be
evaluated by DynamoDB before the operation is performed. See the `official documentation <http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.ConditionalUpdate>`_
for more details.

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

Expand All @@ -25,114 +23,80 @@ Suppose that you have defined a `Thread` Model for the examples below.
subject = UnicodeAttribute(range_key=True)
views = NumberAttribute(default=0)
AND vs. OR
^^^^^^^^^^

Specifying that the conditions should be applied with AND or OR is achieved through the use of the `conditional_operator` keyword,
which can be `and` or `or`.
.. _conditions:

Condition Expressions
^^^^^^^^^^^^^^^^^^^^^

PynamoDB supports creating condition expressions from attributes using a simple syntax.
Any value provided will be serialized using the serializer defined for that attribute.
See the `comparison operator and function reference <http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html>`_
for more details.

.. csv-table::
:header: DynamoDB Condition, PynamoDB Syntax, Example

=, ==, Thread.forum_name == 'Some Forum'
<>, !=, Thread.forum_name != 'Some Forum'
<, <, Thread.views < 10
<=, <=, Thread.views <= 10
>, >, Thread.views > 10
>=, >=, Thread.views >= 10
BETWEEN, "between( `lower` , `upper` )", "Thread.views.between(1, 5)"
IN, is_in( `*values` ), "Thread.subject.is_in('Subject', 'Other Subject')"
attribute_exists ( `path` ), exists(), Thread.forum_name.exists()
attribute_not_exists ( `path` ), does_not_exist(), Thread.forum_name.does_not_exist()
"attribute_type ( `path` , `type` )", is_type(), Thread.forum_name.is_type()
"begins_with ( `path` , `substr` )", startswith( `prefix` ), Thread.subject.startswith('Example')
"contains ( `path` , `operand` )", contains( `item` ), Thread.subject.contains('foobar')
size ( `path`), size( `attribute` ), size(Thread.subject) == 10
AND, &, (Thread.views > 1) & (Thread.views < 5)
OR, \|, (Thread.views < 1) | (Thread.views > 5)
NOT, ~, ~Thread.subject.contains('foobar')

If necessary, you can use document paths to access nested list and map attributes:

.. code-block:: python
thread_item = Thread('Existing Forum', 'Example Subject')
from pynamodb.expressions.condition import size
# The item will be saved if the forum name is not null OR the subject contains 'foobar'
thread_item.save(
forum_name__null=False,
forum_subject__contains='foobar',
conditional_operator='or'
)
print(size('foo.bar[0].baz') == 0)
# The item will be saved if the forum name is not null AND the subject contains 'foobar'
thread_item.save(
forum_name__null=False,
forum_subject__contains='foobar',
conditional_operator='and'
)
Conditional Model.save
^^^^^^^^^^^^^^^^^^^^^^

The following conditional operators are supported for `Model.save`:

* eq
* ne
* le
* lt
* ge
* gt
* null
* contains
* not_contains
* begins_with
* in
* between

This example saves a `Thread` item, only if the item exists, and the `forum_name` attribute is not null.
This example saves a `Thread` item, only if the item exists.

.. code-block:: python
thread_item = Thread('Existing Forum', 'Example Subject')
# DynamoDB will only save the item if forum_name exists and is not null
print(thread_item.save(forum_name__null=False)
# DynamoDB will only save the item if forum_name exists
print(thread_item.save(Thread.forum_name.exists())
# You can specify multiple conditions
print(thread_item.save(forum_name__null=False, forum_subject__contains='foobar'))
print(thread_item.save(Thread.forum_name.exists() & Thread.forum_subject.contains('foobar')))
Conditional Model.update
^^^^^^^^^^^^^^^^^^^^^^^^
The following conditional operators are supported for `Model.update`:
* eq
* ne
* le
* lt
* ge
* gt
* null
* contains
* not_contains
* begins_with
* in
* between
This example will update a `Thread` item, if the `forum_name` attribute equals 'Some Forum' *OR* the subject is not null:
This example will update a `Thread` item, if the `views` attribute is less than 5 *OR* greater than 10:
.. code-block:: python
thread_item.update(
conditional_operator='or',
forum_name__eq='Some Forum',
subject__null=False)
)
thread_item.update((Thread.views < 5) | (Thread.views > 10))
Conditional Model.delete
^^^^^^^^^^^^^^^^^^^^^^^^
The following conditional operators are supported for `Model.delete`:

* eq
* ne
* le
* lt
* ge
* gt
* null
* contains
* not_contains
* begins_with
* in
* between

This example will delete the item, only if its `views` attribute is equal to 0.
.. code-block:: python
print(thread_item.delete(views__eq=0))
print(thread_item.delete(Thread.views == 0))
Conditional Operation Failures
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -142,7 +106,7 @@ You can check for conditional operation failures by inspecting the cause of the
.. code-block:: python
try:
thread_item.save(forum_name__null=False)
thread_item.save(Thread.forum_name.exists())
except PutError as e:
if isinstance(e.cause, ClientError):
code = e.cause.response['Error'].get('Code')
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Topics
attributes
local
backup_restore
signals
examples
settings
low_level
Expand Down
2 changes: 1 addition & 1 deletion docs/indexes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,5 @@ range key of the index. Here is an example that queries the index for values of

.. code-block:: python
for item in TestModel.view_index.query('foo', view__gt=0):
for item in TestModel.view_index.query('foo', TestModel.view > 0):
print("Item queried from index: {0}".format(item.view))
12 changes: 6 additions & 6 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ Now, suppose that you want to search the table for users with a last name

::

for user in UserModel.query('Smith', first_name__begins_with='J'):
for user in UserModel.query('Smith', UserModel.first_name.startswith('J')):
print(user.first_name)

You can combine query terms using 'AND' or 'OR':
You can combine query terms:

::

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


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

::

print(UserModel.count('Smith', first_name__begins_with='J'))
print(UserModel.count('Smith', UserModel.first_name.startswith('J'))


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

# raises a ValueError
print(UserModel.count(first_name__eq='John'))
print(UserModel.count(UserModel.first_name == 'John'))

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


Batch Operations
Expand Down
Loading

0 comments on commit be06095

Please sign in to comment.