File: tutorial-tornado.rst

package info (click to toggle)
python-motor 3.7.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,572 kB
  • sloc: python: 12,252; javascript: 137; makefile: 74; sh: 8
file content (509 lines) | stat: -rw-r--r-- 17,861 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
.. currentmodule:: motor.motor_tornado

Tutorial: Using Motor With Tornado
==================================

.. warning:: Motor will be deprecated on May 14th, 2026, one year after the production release of the PyMongo Async driver. Critical bug fixes will be made until May 14th, 2027.
  We strongly recommend that Motor users migrate to the PyMongo Async driver while Motor is still supported.
  To learn more, see `the migration guide <https://www.mongodb.com/docs/languages/python/pymongo-driver/current/reference/migration/>`_.


.. These setups are redundant because I can't figure out how to make doctest
  run a common setup *before* the setup for the two groups. A "testsetup:: *"
  is the obvious answer, but it's run *after* group-specific setup.

.. testsetup:: before-inserting-2000-docs

  import pymongo
  import motor
  import tornado.web
  from tornado.ioloop import IOLoop
  from tornado import gen

  db = motor.motor_tornado.MotorClient().test_database

.. testsetup:: after-inserting-2000-docs

  import pymongo
  import motor
  import tornado.web
  from tornado.ioloop import IOLoop
  from tornado import gen

  db = motor.motor_tornado.MotorClient().test_database
  sync_db = pymongo.MongoClient().test_database
  sync_db.test_collection.drop()
  sync_db.test_collection.insert_many([{"i": i} for i in range(2000)])

.. testcleanup:: *

  import pymongo

  pymongo.MongoClient().test_database.test_collection.delete_many({})

A guide to using MongoDB and Tornado with Motor.

.. contents::

Tutorial Prerequisites
----------------------
You can learn about MongoDB with the `MongoDB Tutorial`_ before you learn Motor.

Install pip_ and then do::

  $ pip install tornado motor

Once done, the following should run in the Python shell without raising an
exception:

.. doctest::

  >>> import motor.motor_tornado

This tutorial also assumes that a MongoDB instance is running on the
default host and port. Assuming you have `downloaded and installed
<https://mongodb.com/docs/manual/installation/>`_ MongoDB, you
can start it like so:

.. code-block:: bash

  $ mongod

.. _pip: http://www.pip-installer.org/en/latest/installing.html

.. _MongoDB Tutorial: https://mongodb.com/docs/manual/tutorial/getting-started/

Object Hierarchy
----------------
Motor, like PyMongo, represents data with a 4-level object hierarchy:

* :class:`MotorClient` represents a mongod process, or a cluster of them. You
  explicitly create one of these client objects, connect it to a running mongod
  or mongods, and use it for the lifetime of your application.
* :class:`MotorDatabase`: Each mongod has a set of databases (distinct
  sets of data files on disk). You can get a reference to a database from a
  client.
* :class:`MotorCollection`: A database has a set of collections, which
  contain documents; you get a reference to a collection from a database.
* :class:`MotorCursor`: Executing :meth:`~MotorCollection.find` on
  a :class:`MotorCollection` gets a :class:`MotorCursor`, which
  represents the set of documents matching a query.

Creating a Client
-----------------
Creating a client is what establishes a connection to MongoDB and tells your
app what deployment (i.e. cluster) to connect to. You typically create a single
instance of :class:`MotorClient` at the time your application starts up.

.. doctest:: before-inserting-2000-docs

  >>> client = motor.motor_tornado.MotorClient()

This connects to a ``mongod`` listening on the default host and port. You can
specify the host and port like:

.. doctest:: before-inserting-2000-docs

  >>> client = motor.motor_tornado.MotorClient("localhost", 27017)

Motor also supports `connection URIs`_:

.. doctest:: before-inserting-2000-docs

  >>> client = motor.motor_tornado.MotorClient("mongodb://localhost:27017")

Connect to a replica set like:

  >>> client = motor.motor_tornado.MotorClient('mongodb://host1,host2/?replicaSet=my-replicaset-name')

.. _connection URIs: https://mongodb.com/docs/manual/reference/connection-string/

Getting a Database
------------------
A single instance of MongoDB can support multiple independent
`databases <https://www.mongodb.com/docs/manual/core/databases-and-collections/>`_.
From an open client, you can get a reference to a particular database with
dot-notation or bracket-notation:

.. doctest:: before-inserting-2000-docs

  >>> db = client.test_database
  >>> db = client["test_database"]

Creating a reference to a database does no I/O and does not require an
``await`` expression.

Tornado Application Startup Sequence
------------------------------------
Now that we can create a client and get a database, we're ready to start
a Tornado application that uses Motor::

    db = motor.motor_tornado.MotorClient().test_database

    application = tornado.web.Application([
        (r'/', MainHandler)
    ], db=db)

    application.listen(8888)
    tornado.ioloop.IOLoop.current().start()

There are two things to note in this code. First, the ``MotorClient``
constructor doesn't actually connect to the server; the client will
initiate a connection when you attempt the first operation.
Second, passing the database as the ``db`` keyword argument to ``Application``
makes it available to request handlers::

    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            db = self.settings['db']

.. warning::    It is a common mistake to create a new client object for every
    request; **this comes at a dire performance cost**. Create the client
    when your application starts and reuse that one client for the lifetime
    of the process, as shown in these examples.

The Tornado :class:`~tornado.httpserver.HTTPServer` class's :meth:`start`
method is a simple way to fork multiple web servers and use all of your
machine's CPUs. However, you must create your ``MotorClient`` after forking::

    # Create the application before creating a MotorClient.
    application = tornado.web.Application([
        (r'/', MainHandler)
    ])

    server = tornado.httpserver.HTTPServer(application)
    server.bind(8888)

    # Forks one process per CPU.
    server.start(0)

    # Now, in each child process, create a MotorClient.
    application.settings['db'] = MotorClient().test_database
    IOLoop.current().start()

For production-ready, multiple-CPU deployments of Tornado there are better
methods than ``HTTPServer.start()``. See Tornado's guide to
:doc:`tornado:guide/running`.

Getting a Collection
--------------------
A `collection <https://www.mongodb.com/docs/manual/core/databases-and-collections/>`_
is a group of documents stored in MongoDB, and can be thought of as roughly
the equivalent of a table in a relational database. Getting a
collection in Motor works the same as getting a database:

.. doctest:: before-inserting-2000-docs

  >>> collection = db.test_collection
  >>> collection = db["test_collection"]

Just like getting a reference to a database, getting a reference to a
collection does no I/O and doesn't require an ``await`` expression.

Inserting a Document
--------------------
As in PyMongo, Motor represents MongoDB documents with Python dictionaries. To
store a document in MongoDB, call :meth:`~MotorCollection.insert_one` in an
``await`` expression:

.. doctest:: before-inserting-2000-docs

  >>> async def do_insert():
  ...     document = {"key": "value"}
  ...     result = await db.test_collection.insert_one(document)
  ...     print("result %s" % repr(result.inserted_id))
  ...
  >>>
  >>> IOLoop.current().run_sync(do_insert)
  result ObjectId('...')

.. mongodoc:: insert

.. doctest:: before-inserting-2000-docs
  :hide:

  >>> # Clean up from previous insert
  >>> pymongo.MongoClient().test_database.test_collection.delete_many({})
  DeleteResult({'n': 1, 'ok': 1.0}, acknowledged=True)

A typical beginner's mistake with Motor is to insert documents in a loop,
not waiting for each insert to complete before beginning the next::

  >>> for i in range(2000):
  ...     db.test_collection.insert_one({'i': i})

.. Note that the above is NOT a doctest!!

In PyMongo this would insert each document in turn using a single socket, but
Motor attempts to run all the :meth:`insert_one` operations at once. This requires
up to ``max_pool_size`` open sockets connected to MongoDB,
which taxes the client and server. To ensure instead that all inserts run in
sequence, use ``await``:

.. doctest:: before-inserting-2000-docs

  >>> async def do_insert():
  ...     for i in range(2000):
  ...         await db.test_collection.insert_one({"i": i})
  ...
  >>> IOLoop.current().run_sync(do_insert)

.. seealso:: :doc:`examples/bulk`.

.. mongodoc:: insert

.. doctest:: before-inserting-2000-docs
  :hide:

  >>> # Clean up from previous insert
  >>> pymongo.MongoClient().test_database.test_collection.delete_many({})
  DeleteResult({'n': 2000, 'ok': 1.0}, acknowledged=True)

For better performance, insert documents in large batches with
:meth:`~MotorCollection.insert_many`:

.. doctest:: before-inserting-2000-docs

  >>> async def do_insert():
  ...     result = await db.test_collection.insert_many([{"i": i} for i in range(2000)])
  ...     print("inserted %d docs" % (len(result.inserted_ids),))
  ...
  >>> IOLoop.current().run_sync(do_insert)
  inserted 2000 docs

Getting a Single Document With :meth:`~MotorCollection.find_one`
----------------------------------------------------------------
Use :meth:`~MotorCollection.find_one` to get the first document that
matches a query. For example, to get a document where the value for key "i" is
less than 1:

.. doctest:: after-inserting-2000-docs

  >>> async def do_find_one():
  ...     document = await db.test_collection.find_one({"i": {"$lt": 1}})
  ...     pprint.pprint(document)
  ...
  >>> IOLoop.current().run_sync(do_find_one)
  {'_id': ObjectId('...'), 'i': 0}

The result is a dictionary matching the one that we inserted previously.

The returned document contains an ``"_id"``, which was
automatically added on insert.

(We use ``pprint`` here instead of ``print`` to ensure the document's key names
are sorted the same in your output as ours.)

.. mongodoc:: find

Querying for More Than One Document
-----------------------------------
Use :meth:`~MotorCollection.find` to query for a set of documents.
:meth:`~MotorCollection.find` does no I/O and does not require an ``await``
expression. It merely creates an :class:`~MotorCursor` instance. The query is
actually executed on the server when you call :meth:`~MotorCursor.to_list`
or execute an ``async for`` loop.

To find all documents with "i" less than 5:

.. doctest:: after-inserting-2000-docs

  >>> async def do_find():
  ...     cursor = db.test_collection.find({"i": {"$lt": 5}}).sort("i")
  ...     for document in await cursor.to_list(length=100):
  ...         pprint.pprint(document)
  ...
  >>> IOLoop.current().run_sync(do_find)
  {'_id': ObjectId('...'), 'i': 0}
  {'_id': ObjectId('...'), 'i': 1}
  {'_id': ObjectId('...'), 'i': 2}
  {'_id': ObjectId('...'), 'i': 3}
  {'_id': ObjectId('...'), 'i': 4}

A ``length`` argument is required when you call ``to_list`` to prevent Motor
from buffering an unlimited number of documents.

``async for``
~~~~~~~~~~~~~

You can handle one document at a time in an ``async for`` loop:

.. doctest:: after-inserting-2000-docs

  >>> async def do_find():
  ...     c = db.test_collection
  ...     async for document in c.find({"i": {"$lt": 2}}):
  ...         pprint.pprint(document)
  ...
  >>> IOLoop.current().run_sync(do_find)
  {'_id': ObjectId('...'), 'i': 0}
  {'_id': ObjectId('...'), 'i': 1}

You can apply a sort, limit, or skip to a query before you begin iterating:

.. doctest:: after-inserting-2000-docs

  >>> async def do_find():
  ...     cursor = db.test_collection.find({"i": {"$lt": 4}})
  ...     # Modify the query before iterating
  ...     cursor.sort("i", -1).skip(1).limit(2)
  ...     async for document in cursor:
  ...         pprint.pprint(document)
  ...
  >>> IOLoop.current().run_sync(do_find)
  {'_id': ObjectId('...'), 'i': 2}
  {'_id': ObjectId('...'), 'i': 1}

The cursor does not actually retrieve each document from the server
individually; it gets documents efficiently in `large batches`_.

.. _`large batches`: https://mongodb.com/docs/manual/tutorial/iterate-a-cursor/#cursor-batches

Counting Documents
------------------
Use :meth:`~MotorCollection.count_documents` to determine the number of
documents in a collection, or the number of documents that match a query:

.. doctest:: after-inserting-2000-docs

  >>> async def do_count():
  ...     n = await db.test_collection.count_documents({})
  ...     print("%s documents in collection" % n)
  ...     n = await db.test_collection.count_documents({"i": {"$gt": 1000}})
  ...     print("%s documents where i > 1000" % n)
  ...
  >>> IOLoop.current().run_sync(do_count)
  2000 documents in collection
  999 documents where i > 1000

Updating Documents
------------------

:meth:`~MotorCollection.replace_one` changes a document. It requires two
parameters: a *query* that specifies which document to replace, and a
replacement document. The query follows the same syntax as for :meth:`find` or
:meth:`find_one`. To replace a document:

.. doctest:: after-inserting-2000-docs

  >>> async def do_replace():
  ...     coll = db.test_collection
  ...     old_document = await coll.find_one({"i": 50})
  ...     print("found document: %s" % pprint.pformat(old_document))
  ...     _id = old_document["_id"]
  ...     result = await coll.replace_one({"_id": _id}, {"key": "value"})
  ...     print("replaced %s document" % result.modified_count)
  ...     new_document = await coll.find_one({"_id": _id})
  ...     print("document is now %s" % pprint.pformat(new_document))
  ...
  >>> IOLoop.current().run_sync(do_replace)
  found document: {'_id': ObjectId('...'), 'i': 50}
  replaced 1 document
  document is now {'_id': ObjectId('...'), 'key': 'value'}

You can see that :meth:`replace_one` replaced everything in the old document
except its ``_id`` with the new document.

Use :meth:`~MotorCollection.update_one` with MongoDB's modifier operators to
update part of a document and leave the
rest intact. We'll find the document whose "i" is 51 and use the ``$set``
operator to set "key" to "value":

.. doctest:: after-inserting-2000-docs

  >>> async def do_update():
  ...     coll = db.test_collection
  ...     result = await coll.update_one({"i": 51}, {"$set": {"key": "value"}})
  ...     print("updated %s document" % result.modified_count)
  ...     new_document = await coll.find_one({"i": 51})
  ...     print("document is now %s" % pprint.pformat(new_document))
  ...
  >>> IOLoop.current().run_sync(do_update)
  updated 1 document
  document is now {'_id': ObjectId('...'), 'i': 51, 'key': 'value'}

"key" is set to "value" and "i" is still 51.

:meth:`update_one` only affects the first document it finds, you can
update all of them with :meth:`update_many`::

    await coll.update_many({'i': {'$gt': 100}},
                           {'$set': {'key': 'value'}})

.. mongodoc:: update

Removing Documents
------------------

:meth:`~MotorCollection.delete_one` takes a query with the same syntax as
:meth:`~MotorCollection.find`.
:meth:`delete_one` immediately removes the first returned matching document.

.. doctest:: after-inserting-2000-docs

  >>> async def do_delete_one():
  ...     coll = db.test_collection
  ...     n = await coll.count_documents({})
  ...     print("%s documents before calling delete_one()" % n)
  ...     result = await db.test_collection.delete_one({"i": {"$gte": 1000}})
  ...     print("%s documents after" % (await coll.count_documents({})))
  ...
  >>> IOLoop.current().run_sync(do_delete_one)
  2000 documents before calling delete_one()
  1999 documents after

:meth:`~MotorCollection.delete_many` takes a query with the same syntax as
:meth:`~MotorCollection.find`.
:meth:`delete_many` immediately removes all matching documents.

.. doctest:: after-inserting-2000-docs

  >>> async def do_delete_many():
  ...     coll = db.test_collection
  ...     n = await coll.count_documents({})
  ...     print("%s documents before calling delete_many()" % n)
  ...     result = await db.test_collection.delete_many({"i": {"$gte": 1000}})
  ...     print("%s documents after" % (await coll.count_documents({})))
  ...
  >>> IOLoop.current().run_sync(do_delete_many)
  1999 documents before calling delete_many()
  1000 documents after

.. mongodoc:: remove

Commands
--------
All operations on MongoDB are implemented internally as commands. Run them using
the :meth:`~motor.motor_tornado.MotorDatabase.command` method on
:class:`~motor.motor_tornado.MotorDatabase`::

.. doctest:: after-inserting-2000-docs

  >>> from bson import SON
  >>> async def use_distinct_command():
  ...     response = await db.command(SON([("distinct", "test_collection"), ("key", "i")]))
  ...
  >>> IOLoop.current().run_sync(use_distinct_command)

Since the order of command parameters matters, don't use a Python dict to pass
the command's parameters. Instead, make a habit of using :class:`bson.SON`,
from the ``bson`` module included with PyMongo.

Many commands have special helper methods, such as
:meth:`~MotorDatabase.create_collection` or
:meth:`~MotorCollection.aggregate`, but these are just conveniences atop
the basic :meth:`command` method.

.. mongodoc:: commands

Further Reading
---------------
The handful of classes and methods introduced here are sufficient for daily
tasks. The API documentation for :class:`MotorClient`, :class:`MotorDatabase`,
:class:`MotorCollection`, and :class:`MotorCursor` provides a
reference to Motor's complete feature set.

Learning to use the MongoDB driver is just the beginning, of course. For
in-depth instruction in MongoDB itself, see `The MongoDB Manual`_.

.. _The MongoDB Manual: https://mongodb.com/docs/manual/