PYTHON-820 - API changes for find/find_one to comply with CRUD spec.

- Changed parameter names (default values and behaviors remain the same):
  - spec (spec_or_id in find_one) -> filter
  - fields -> projection
  - partial -> allow_partial_results

- The "timeout" option is renamed to "no_cursor_timeout" with its default
  changed to False.

- The tailable, await_data, and exhaust options will be replaced with a
  cursor_type option. Valid values:
  - cursor.NON_TAILABLE
  - cursor.TAILABLE
  - cursor.TAILABLE_AWAIT
  - cursor.EXHAUST

- The following options are added:
  - oplog_replay (bool - default False) - only valid with tailable cursors
    against the oplog.
  - modifiers (document - default None) - A dict of query modifiers. See
    http://docs.mongodb.org/manual/reference/operator/query-modifier/#modifiers for
    options.

- The following options are removed, replaced by the "modifiers" option:
  - max_scan
  - snapshot

- The as_class option is removed. Use Collection.with_options instead.
This commit is contained in:
behackett 2015-01-19 22:10:56 -08:00
parent 71e083d81b
commit c07e543e58
14 changed files with 240 additions and 251 deletions

View File

@ -11,6 +11,11 @@
.. autodata:: pymongo.GEOSPHERE
.. autodata:: pymongo.HASHED
.. autodata:: pymongo.TEXT
.. autodata:: pymongo.cursor.NON_TAILABLE
.. autodata:: pymongo.cursor.TAILABLE
.. autodata:: pymongo.cursor.TAILABLE_AWAIT
.. autodata:: pymongo.cursor.EXHAUST
.. autoclass:: pymongo.collection.Collection(database, name[, create=False[, **kwargs]]])
@ -35,8 +40,8 @@
.. automethod:: initialize_unordered_bulk_op
.. automethod:: initialize_ordered_bulk_op
.. automethod:: drop
.. automethod:: find([spec=None[, fields=None[, skip=0[, limit=0[, timeout=True[, snapshot=False[, tailable=False[, sort=None[, max_scan=None[, as_class=None[, await_data=False[, partial=False[, manipulate=True[, exhaust=False]]]]]]]]]]]]]])
.. automethod:: find_one([spec_or_id=None[, *args[, **kwargs]]])
.. automethod:: find([filter=None[, projection=None[, skip=0[, limit=0[, no_cursor_timeout=False[, cursor_type=NON_TAILABLE[, sort=None[, allow_partial_results=False[, oplog_replay=False[, modifiers=None[, manipulate=True]]]]]]]]]]])
.. automethod:: find_one([filter_or_id=None[, *args[, **kwargs]]])
.. automethod:: parallel_scan
.. automethod:: count
.. automethod:: create_index

View File

@ -4,7 +4,7 @@
.. automodule:: pymongo.cursor
:synopsis: Tools for iterating over MongoDB query results
.. autoclass:: pymongo.cursor.Cursor(collection, spec=None, fields=None, skip=0, limit=0, timeout=True, snapshot=False, tailable=False, sort=None, max_scan=None, as_class=None, await_data=False, partial=False, manipulate=True, exhaust=False)
.. autoclass:: pymongo.cursor.Cursor(collection, filter=None, projection=None, skip=0, limit=0, no_cursor_timeout=False, cursor_type=NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, modifiers=None, manipulate=True)
:members:
.. describe:: c[index]

View File

@ -99,7 +99,7 @@ raised when attempting to iterate the cursor.
How do I change the timeout value for cursors?
----------------------------------------------
MongoDB doesn't support custom timeouts for cursors, but cursor
timeouts can be turned off entirely. Pass ``timeout=False`` to
timeouts can be turned off entirely. Pass ``no_cursor_timeout=True`` to
:meth:`~pymongo.collection.Collection.find`.
How can I store :mod:`decimal.Decimal` instances?
@ -273,5 +273,5 @@ out documents with values outside of the range supported by
Another option, assuming you don't need the datetime field, is to filter out
just that field::
>>> cur = coll.find({}, fields={'dt': False})
>>> cur = coll.find({}, projection={'dt': False})

View File

@ -248,7 +248,7 @@ class GridFS(object):
name for name in self.__files.distinct("filename")
if name is not None]
def find_one(self, spec_or_id=None, *args, **kwargs):
def find_one(self, filter=None, *args, **kwargs):
"""Get a single file from gridfs.
All arguments to :meth:`find` are also valid arguments for
@ -259,18 +259,18 @@ class GridFS(object):
file = fs.find_one({"filename": "lisa.txt"})
:Parameters:
- `spec_or_id` (optional): a dictionary specifying
the query to be performing OR any other type to be used as
- `filter` (optional): a dictionary specifying
the query to be performing OR any other type to be used as
the value for a query for ``"_id"`` in the file collection.
- `*args` (optional): any additional positional arguments are
the same as the arguments to :meth:`find`.
- `**kwargs` (optional): any additional keyword arguments
are the same as the arguments to :meth:`find`.
"""
if spec_or_id is not None and not isinstance(spec_or_id, dict):
spec_or_id = {"_id": spec_or_id}
if filter is not None and not isinstance(filter, dict):
filter = {"_id": filter}
for f in self.find(spec_or_id, *args, **kwargs):
for f in self.find(filter, *args, **kwargs):
return f
return None
@ -282,12 +282,14 @@ class GridFS(object):
arbitrary queries on the files collection. Can be combined
with other modifiers for additional control. For example::
for grid_out in fs.find({"filename": "lisa.txt"}, timeout=False):
for grid_out in fs.find({"filename": "lisa.txt"},
no_cursor_timeout=True):
data = grid_out.read()
would iterate through all versions of "lisa.txt" stored in GridFS.
Note that setting timeout to False may be important to prevent the
cursor from timing out during long multi-file processing work.
Note that setting no_cursor_timeout to True may be important to
prevent the cursor from timing out during long multi-file processing
work.
As another example, the call::
@ -301,23 +303,21 @@ class GridFS(object):
in :class:`~pymongo.collection.Collection`.
:Parameters:
- `spec` (optional): a SON object specifying elements which
- `filter` (optional): a SON object specifying elements which
must be present for a document to be included in the
result set
- `skip` (optional): the number of files to omit (from
the start of the result set) when returning the results
- `limit` (optional): the maximum number of results to
return
- `timeout` (optional): if True (the default), any returned
cursor is closed by the server after 10 minutes of
inactivity. If set to False, the returned cursor will never
- `no_cursor_timeout` (optional): if False (the default), any
returned cursor is closed by the server after 10 minutes of
inactivity. If set to True, the returned cursor will never
time out on the server. Care should be taken to ensure that
cursors with timeout turned off are properly closed.
cursors with no_cursor_timeout turned on are properly closed.
- `sort` (optional): a list of (key, direction) pairs
specifying the sort order for this query. See
:meth:`~pymongo.cursor.Cursor.sort` for details.
- `max_scan` (optional): limit the number of file documents
examined when performing the query
Raises :class:`TypeError` if any of the arguments are of
improper type. Returns an instance of

View File

@ -594,8 +594,8 @@ class GridOutCursor(Cursor):
"""A cursor / iterator for returning GridOut objects as the result
of an arbitrary query against the GridFS files collection.
"""
def __init__(self, collection, spec=None, skip=0, limit=0,
timeout=True, sort=None, max_scan=None):
def __init__(self, collection, filter=None, skip=0, limit=0,
no_cursor_timeout=False, sort=None):
"""Create a new cursor, similar to the normal
:class:`~pymongo.cursor.Cursor`.
@ -610,8 +610,8 @@ class GridOutCursor(Cursor):
self.__root_collection = collection
super(GridOutCursor, self).__init__(
collection.files, spec, skip=skip, limit=limit, timeout=timeout,
sort=sort, max_scan=max_scan)
collection.files, filter, skip=skip, limit=limit,
no_cursor_timeout=no_cursor_timeout, sort=sort)
def next(self):
"""Get next GridOut object from cursor.

View File

@ -732,7 +732,7 @@ class Collection(common.BaseObject):
concern, uuid_representation,
int(not multi)), safe)
def find_one(self, spec_or_id=None, *args, **kwargs):
def find_one(self, filter=None, *args, **kwargs):
"""Get a single document from the database.
All arguments to :meth:`find` are also valid arguments for
@ -745,7 +745,7 @@ class Collection(common.BaseObject):
:Parameters:
- `spec_or_id` (optional): a dictionary specifying
- `filter` (optional): a dictionary specifying
the query to be performed OR any other type to be used as
the value for a query for ``"_id"``.
@ -760,12 +760,12 @@ class Collection(common.BaseObject):
>>> find_one(max_time_ms=100)
"""
if (spec_or_id is not None and not
isinstance(spec_or_id, collections.Mapping)):
spec_or_id = {"_id": spec_or_id}
if (filter is not None and not
isinstance(filter, collections.Mapping)):
filter = {"_id": filter}
max_time_ms = kwargs.pop("max_time_ms", None)
cursor = self.find(spec_or_id,
cursor = self.find(filter,
*args, **kwargs).max_time_ms(max_time_ms)
for result in cursor.limit(-1):
@ -775,15 +775,15 @@ class Collection(common.BaseObject):
def find(self, *args, **kwargs):
"""Query the database.
The `spec` argument is a prototype document that all results
The `filter` argument is a prototype document that all results
must match. For example:
>>> db.test.find({"hello": "world"})
only matches documents that have a key "hello" with value
"world". Matches can have other keys *in addition* to
"hello". The `fields` argument is used to specify a subset of
fields that should be included in the result documents. By
"hello". The `projection` argument is used to specify a subset
of fields that should be included in the result documents. By
limiting results to a certain subset of fields you can cut
down on network traffic and decoding time.
@ -795,78 +795,86 @@ class Collection(common.BaseObject):
this :class:`Collection`.
:Parameters:
- `spec` (optional): a SON object specifying elements which
- `filter` (optional): a SON object specifying elements which
must be present for a document to be included in the
result set
- `fields` (optional): a list of field names that should be
- `projection` (optional): a list of field names that should be
returned in the result set or a dict specifying the fields
to include or exclude. If `fields` is a list "_id" will
to include or exclude. If `projection` is a list "_id" will
always be returned. Use a dict to exclude fields from
the result (e.g. fields={'_id': False}).
the result (e.g. projection={'_id': False}).
- `skip` (optional): the number of documents to omit (from
the start of the result set) when returning the results
- `limit` (optional): the maximum number of results to
return
- `timeout` (optional): if True (the default), any returned
cursor is closed by the server after 10 minutes of
inactivity. If set to False, the returned cursor will never
- `no_cursor_timeout` (optional): if False (the default), any
returned cursor is closed by the server after 10 minutes of
inactivity. If set to True, the returned cursor will never
time out on the server. Care should be taken to ensure that
cursors with timeout turned off are properly closed.
- `snapshot` (optional): if True, snapshot mode will be used
for this query. Snapshot mode assures no duplicates are
returned, or objects missed, which were present at both
the start and end of the query's execution. For details,
see the `snapshot documentation
<http://dochub.mongodb.org/core/snapshot>`_.
- `tailable` (optional): the result of this find call will
be a tailable cursor - tailable cursors aren't closed when
the last data is retrieved but are kept open and the
cursors location marks the final document's position. if
more data is received iteration of the cursor will
continue from the last document received. For details, see
the `tailable cursor documentation
<http://www.mongodb.org/display/DOCS/Tailable+Cursors>`_.
cursors with no_cursor_timeout turned on are properly closed.
- `cursor_type` (optional): the type of cursor to return. The valid
options are:
- :data:`~pymongo.cursor.NON_TAILABLE` - the result of this find
call will return a standard cursor over the result set.
- :data:`~pymongo.cursor.TAILABLE` - the result of this find call
will be a tailable cursor - tailable cursors are only for use
with capped collections. They are not closed when the last data
is retrieved but are kept open and the cursor location marks the
final document position. If more data is received iteration of
the cursor will continue from the last document received. For
details, see the `tailable cursor documentation
<http://www.mongodb.org/display/DOCS/Tailable+Cursors>`_.
- :data:`~pymongo.cursor.TAILABLE_AWAIT` - the result of this find
call will be a tailable cursor with the await flag set. The
server will wait for a few seconds after returning the full
result set so that it can capture and return additional data
added during the query.
- :data:`~pymongo.cursor.EXHAUST` - the result of this find call
will be an exhaust cursor. MongoDB will stream batched results to
the client without waiting for the client to request each batch,
reducing latency. See notes on compatibility below.
- `sort` (optional): a list of (key, direction) pairs
specifying the sort order for this query. See
:meth:`~pymongo.cursor.Cursor.sort` for details.
- `max_scan` (optional): limit the number of documents
examined when performing the query
- `as_class` (optional): class to use for documents in the
query result (default is
:attr:`~pymongo.mongo_client.MongoClient.document_class`)
- `await_data` (optional): if True, the server will block for
some extra time before returning, waiting for more data to
return. Ignored if `tailable` is False.
- `partial` (optional): if True, mongos will return partial
results if some shards are down instead of returning an error.
- `allow_partial_results` (optional): if True, mongos will return
partial results if some shards are down instead of returning an
error.
- `oplog_replay` (optional): If True, set the oplogReplay query
flag.
- `modifiers` (optional): A dict specifying the MongoDB query
modifiers that should be used for this query.
- `manipulate`: (optional): If True (the default), apply any
outgoing SON manipulators before returning.
- `exhaust` (optional): If ``True`` create an "exhaust" cursor.
MongoDB will stream batched results to the client without waiting
for the client to request each batch, reducing latency.
.. note:: There are a number of caveats to using the `exhaust`
parameter:
.. note:: There are a number of caveats to using
:data:`~pymongo.cursor.EXHAUST` as cursor_type:
1. The `exhaust` and `limit` options are incompatible and can
not be used together.
1. The `limit` option can not be used with an exhaust cursor.
2. The `exhaust` option is not supported by mongos and can not be
2. Exhaust cursors are not supported by mongos and can not be
used with a sharded cluster.
3. A :class:`~pymongo.cursor.Cursor` instance created with the
`exhaust` option requires an exclusive :class:`~socket.socket`
connection to MongoDB. If the :class:`~pymongo.cursor.Cursor` is
discarded without being completely iterated the underlying
:class:`~socket.socket` connection will be closed and discarded
without being returned to the connection pool.
cursor.EXHAUST cursor_type requires an exclusive
:class:`~socket.socket` connection to MongoDB. If the
:class:`~pymongo.cursor.Cursor` is discarded without being
completely iterated the underlying :class:`~socket.socket`
connection will be closed and discarded without being returned to
the connection pool.
.. note:: The `manipulate` parameter may default to False in a future
release.
.. versionchanged:: 3.0
Removed the `network_timeout`, `read_preference`, `tag_sets`, and
`secondary_acceptable_latency_ms` parameters.
Changed the parameter names `spec`, `fields`, `timeout`, and
`partial` to `filter`, `projection`, `no_cursor_timeout`, and
`allow_partial_results` respectively.
Added the `cursor_type`, `oplog_replay`, and `modifiers` options.
Removed the `network_timeout`, `read_preference`, `tag_sets`,
`secondary_acceptable_latency_ms`, `max_scan`, `snapshot`,
`tailable`, `await_data`, `exhaust`, and `as_class` parameters.
Removed `compile_re` option: PyMongo now always represents BSON
regular expressions as :class:`~bson.regex.Regex` objects. Use
:meth:`~bson.regex.Regex.try_compile` to attempt to convert from a
@ -1291,8 +1299,9 @@ class Collection(common.BaseObject):
else:
raw = self.__database.get_collection(
"system.indexes",
codec_options=CodecOptions(as_class=SON),
read_preference=ReadPreference.PRIMARY).find(
{"ns": self.__full_name}, {"ns": 0}, as_class=SON)
{"ns": self.__full_name}, {"ns": 0})
info = {}
for index in raw:
index["key"] = index["key"].items()
@ -1696,7 +1705,7 @@ class Collection(common.BaseObject):
- `remove`: remove rather than updating (default ``False``)
- `new`: return updated rather than original object
(default ``False``)
- `fields`: see second argument to :meth:`find` (default all)
- `fields`: see `projection` argument to :meth:`find` (default all)
- `manipulate`: (optional): If ``True``, apply any outgoing SON
manipulators before returning. Ignored when `full_response`
is set to True. Defaults to ``False``.

View File

@ -26,7 +26,6 @@ from bson.py3compat import (iteritems,
string_type)
from bson.son import SON
from pymongo import helpers, message
from pymongo.codec_options import CodecOptions
from pymongo.read_preferences import ReadPreference
from pymongo.errors import (AutoReconnect,
InvalidOperation,
@ -43,6 +42,34 @@ _QUERY_OPTIONS = {
"partial": 128}
NON_TAILABLE = 0
"""The standard cursor type."""
TAILABLE = _QUERY_OPTIONS["tailable_cursor"]
"""The tailable cursor type.
Tailable cursors are only for use with capped collections. They are not closed
when the last data is retrieved but are kept open and the cursor location marks
the final document position. If more data is received iteration of the cursor
will continue from the last document received.
"""
TAILABLE_AWAIT = TAILABLE | _QUERY_OPTIONS["await_data"]
"""A tailable cursor with the await option set.
Creates a tailable cursor that will wait for a few seconds after returning the
full result set so that it can capture and return additional data added during
the query.
"""
EXHAUST = _QUERY_OPTIONS["exhaust"]
"""An exhaust cursor.
MongoDB will stream batched results to the client without waiting for the
client to request each batch, reducing latency.
"""
# This has to be an old style class due to
# http://bugs.jython.org/issue1057
class _SocketManager:
@ -65,17 +92,14 @@ class _SocketManager:
self.sock, self.pool = None, None
# TODO might be cool to be able to do find().include("foo") or
# find().exclude(["bar", "baz"]) or find().slice("a", 1, 2) as an
# alternative to the fields specifier.
class Cursor(object):
"""A cursor / iterator over Mongo query results.
"""
def __init__(self, collection, spec=None, fields=None, skip=0, limit=0,
timeout=True, snapshot=False, tailable=False, sort=None,
max_scan=None, as_class=None, await_data=False, partial=False,
manipulate=True, exhaust=False):
def __init__(self, collection, filter=None, projection=None, skip=0,
limit=0, no_cursor_timeout=False, cursor_type=NON_TAILABLE,
sort=None, allow_partial_results=False, oplog_replay=False,
modifiers=None, manipulate=True):
"""Create a new cursor.
Should not be called directly by application developers - see
@ -85,61 +109,62 @@ class Cursor(object):
"""
self.__id = None
spec = filter
if spec is None:
spec = {}
if not isinstance(spec, Mapping):
raise TypeError("spec must be a mapping type.")
raise TypeError("filter must be a mapping type.")
if not isinstance(skip, int):
raise TypeError("skip must be an instance of int")
if not isinstance(limit, int):
raise TypeError("limit must be an instance of int")
if not isinstance(timeout, bool):
raise TypeError("timeout must be an instance of bool")
if not isinstance(snapshot, bool):
raise TypeError("snapshot must be an instance of bool")
if not isinstance(tailable, bool):
raise TypeError("tailable must be an instance of bool")
if not isinstance(await_data, bool):
raise TypeError("await_data must be an instance of bool")
if not isinstance(partial, bool):
raise TypeError("partial must be an instance of bool")
if not isinstance(exhaust, bool):
raise TypeError("exhaust must be an instance of bool")
if not isinstance(no_cursor_timeout, bool):
raise TypeError("no_cursor_timeout must be an instance of bool")
if cursor_type not in (NON_TAILABLE, TAILABLE,
TAILABLE_AWAIT, EXHAUST):
raise ValueError("not a valid value for cursor_type")
if not isinstance(allow_partial_results, bool):
raise TypeError("allow_partial_results "
"must be an instance of bool")
if not isinstance(oplog_replay, bool):
raise TypeError("oplog_replay must be an instance of bool")
if modifiers is not None and not isinstance(modifiers, Mapping):
raise TypeError("modifiers must be a mapping type.")
if fields is not None:
if not fields:
fields = {"_id": 1}
if not isinstance(fields, Mapping):
fields = helpers._fields_list_to_dict(fields)
# XXX: Continue to support passing as_class to find() and find_one()?
if as_class is None:
self.__codec_options = collection.codec_options
else:
cco = collection.codec_options
self.__codec_options = CodecOptions(as_class,
cco.tz_aware,
cco.uuid_representation)
if projection is not None:
if not projection:
projection = {"_id": 1}
if not isinstance(projection, Mapping):
projection = helpers._fields_list_to_dict(projection)
self.__collection = collection
self.__spec = spec
self.__fields = fields
self.__projection = projection
self.__skip = skip
self.__limit = limit
self.__max_time_ms = None
self.__batch_size = 0
self.__modifiers = modifiers and modifiers.copy() or {}
self.__ordering = sort and helpers._index_document(sort) or None
self.__max_scan = None
self.__explain = False
self.__hint = None
self.__comment = None
self.__max_time_ms = None
self.__max = None
self.__min = None
self.__manipulate = manipulate
# Exhaust cursor support
if self.__collection.database.connection.is_mongos and exhaust:
raise InvalidOperation('Exhaust cursors are '
'not supported by mongos')
if limit and exhaust:
raise InvalidOperation("Can't use limit and exhaust together.")
self.__exhaust = exhaust
self.__exhaust = False
self.__exhaust_mgr = None
if cursor_type == EXHAUST:
if self.__collection.database.connection.is_mongos:
raise InvalidOperation('Exhaust cursors are '
'not supported by mongos')
if limit:
raise InvalidOperation("Can't use limit and exhaust together.")
self.__exhaust = True
# This is ugly. People want to be able to do cursor[5:5] and
# get an empty result set (old behavior was an
@ -149,34 +174,23 @@ class Cursor(object):
# it anytime we change __limit.
self.__empty = False
self.__snapshot = snapshot
self.__ordering = sort and helpers._index_document(sort) or None
self.__max_scan = max_scan
self.__explain = False
self.__hint = None
self.__comment = None
self.__manipulate = manipulate
self.__data = deque()
self.__address = None
self.__retrieved = 0
self.__killed = False
self.__codec_options = collection.codec_options
self.__read_preference = collection.read_preference
self.__query_flags = 0
if tailable:
self.__query_flags |= _QUERY_OPTIONS["tailable_cursor"]
if self.__read_preference.mode != ReadPreference.PRIMARY.mode:
self.__query_flags = cursor_type
if self.__read_preference != ReadPreference.PRIMARY:
self.__query_flags |= _QUERY_OPTIONS["slave_okay"]
if not timeout:
if no_cursor_timeout:
self.__query_flags |= _QUERY_OPTIONS["no_timeout"]
if tailable and await_data:
self.__query_flags |= _QUERY_OPTIONS["await_data"]
if exhaust:
self.__query_flags |= _QUERY_OPTIONS["exhaust"]
if partial:
if allow_partial_results:
self.__query_flags |= _QUERY_OPTIONS["partial"]
if oplog_replay:
self.__query_flags |= _QUERY_OPTIONS["oplog_replay"]
@property
def collection(self):
@ -224,10 +238,11 @@ class Cursor(object):
def _clone(self, deepcopy=True):
clone = self._clone_base()
values_to_clone = ("spec", "fields", "skip", "limit", "max_time_ms",
"comment", "max", "min", "snapshot", "ordering",
"explain", "hint", "batch_size", "max_scan",
"manipulate", "query_flags", "codec_options")
values_to_clone = ("spec", "projection", "skip", "limit",
"max_time_ms", "comment", "max", "min",
"ordering", "explain", "hint", "batch_size",
"max_scan", "manipulate", "query_flags",
"modifiers")
data = dict((k, v) for k, v in iteritems(self.__dict__)
if k.startswith('_Cursor__') and k[9:] in values_to_clone)
if deepcopy:
@ -266,7 +281,7 @@ class Cursor(object):
def __query_spec(self):
"""Get the spec to use for a query.
"""
operators = {}
operators = self.__modifiers.copy()
if self.__ordering:
operators["$orderby"] = self.__ordering
if self.__explain:
@ -275,8 +290,6 @@ class Cursor(object):
operators["$hint"] = self.__hint
if self.__comment:
operators["$comment"] = self.__comment
if self.__snapshot:
operators["$snapshot"] = True
if self.__max_scan:
operators["$maxScan"] = self.__max_scan
if self.__max_time_ms is not None:
@ -898,7 +911,7 @@ class Cursor(object):
message.query(self.__query_flags,
self.__collection.full_name,
self.__skip, ntoreturn,
self.__query_spec(), self.__fields,
self.__query_spec(), self.__projection,
self.__codec_options.uuid_representation))
if not self.__id:
self.__killed = True

View File

@ -1048,4 +1048,4 @@ class SystemJS(object):
def list(self):
"""Get a list of the names of the functions stored in this database."""
return [x["_id"] for x in self._db.system.js.find(fields=["_id"])]
return [x["_id"] for x in self._db.system.js.find(projection=["_id"])]

View File

@ -29,9 +29,9 @@ sys.path[0:0] = [""]
from bson.py3compat import thread, u
from bson.son import SON
from bson.tz_util import utc
from pymongo.mongo_client import MongoClient
from pymongo.database import Database
from pymongo import auth, message
from pymongo.cursor import EXHAUST
from pymongo.database import Database
from pymongo.codec_options import CodecOptions
from pymongo.errors import (AutoReconnect,
ConfigurationError,
@ -41,6 +41,7 @@ from pymongo.errors import (AutoReconnect,
CursorNotFound,
NetworkTimeout,
InvalidURI)
from pymongo.mongo_client import MongoClient
from pymongo.read_preferences import ReadPreference
from pymongo.server_selectors import (any_server_selector,
writable_server_selector)
@ -477,7 +478,6 @@ class TestClient(IntegrationTest):
self.assertEqual(SON, c.document_class)
self.assertTrue(isinstance(db.test.find_one(), SON))
self.assertFalse(isinstance(db.test.find_one(as_class=dict), SON))
# document_class is read-only in PyMongo 3.0.
with self.assertRaises(AttributeError):
@ -764,7 +764,7 @@ class TestClient(IntegrationTest):
# Cause a network error.
sock_info = one(pool.sockets)
sock_info.sock.close()
cursor = collection.find(exhaust=True)
cursor = collection.find(cursor_type=EXHAUST)
with self.assertRaises(ConnectionFailure):
next(cursor)
@ -856,7 +856,7 @@ class TestExhaustCursor(IntegrationTest):
# This will cause OperationFailure in all mongo versions since
# the value for $orderby must be a document.
cursor = collection.find(
SON([('$query', {}), ('$orderby', True)]), exhaust=True)
SON([('$query', {}), ('$orderby', True)]), cursor_type=EXHAUST)
self.assertRaises(OperationFailure, cursor.next)
self.assertFalse(sock_info.closed)
@ -880,7 +880,7 @@ class TestExhaustCursor(IntegrationTest):
pool._check_interval_seconds = None # Never check.
sock_info = one(pool.sockets)
cursor = collection.find(exhaust=True)
cursor = collection.find(cursor_type=EXHAUST)
# Initial query succeeds.
cursor.next()
@ -905,7 +905,7 @@ class TestExhaustCursor(IntegrationTest):
sock_info = one(pool.sockets)
sock_info.sock.close()
cursor = collection.find(exhaust=True)
cursor = collection.find(cursor_type=EXHAUST)
self.assertRaises(ConnectionFailure, cursor.next)
self.assertTrue(sock_info.closed)
@ -923,7 +923,7 @@ class TestExhaustCursor(IntegrationTest):
pool = get_pool(client)
pool._check_interval_seconds = None # Never check.
cursor = collection.find(exhaust=True)
cursor = collection.find(cursor_type=EXHAUST)
# Initial query succeeds.
cursor.next()

View File

@ -37,6 +37,7 @@ from pymongo import MongoClient
from pymongo.codec_options import CodecOptions
from pymongo.collection import Collection
from pymongo.command_cursor import CommandCursor
from pymongo.cursor import EXHAUST
from pymongo.errors import (DocumentTooLarge,
DuplicateKeyError,
InvalidDocument,
@ -606,17 +607,20 @@ class TestCollection(IntegrationTest):
self.assertEqual(doc["_id"], id)
self.assertTrue(isinstance(id, ObjectId))
doc_class = None
doc_class = dict
# Work around http://bugs.jython.org/issue1728
if (sys.platform.startswith('java') and
sys.version_info[:3] >= (2, 5, 2)):
doc_class = SON
db = self.client.get_database(
db.name, codec_options=CodecOptions(as_class=doc_class))
def remove_insert_find_one(doc):
db.test.remove({})
db.test.insert(doc)
# SON equality is order sensitive.
return db.test.find_one(as_class=doc_class) == doc.to_dict()
return db.test.find_one() == doc.to_dict()
qcheck.check_unittest(self, remove_insert_find_one,
qcheck.gen_mongo_dict(3))
@ -693,10 +697,10 @@ class TestCollection(IntegrationTest):
self.assertEqual([1, 2, 3], db.test.find_one()["x"])
if client_context.version.at_least(1, 5, 1):
self.assertEqual([2, 3],
db.test.find_one(fields={"x": {"$slice":
-2}})["x"])
self.assertTrue("x" not in db.test.find_one(fields={"x": 0}))
self.assertTrue("mike" in db.test.find_one(fields={"x": 0}))
db.test.find_one(
projection={"x": {"$slice": -2}})["x"])
self.assertTrue("x" not in db.test.find_one(projection={"x": 0}))
self.assertTrue("mike" in db.test.find_one(projection={"x": 0}))
def test_find_w_regex(self):
db = self.db
@ -1515,16 +1519,6 @@ class TestCollection(IntegrationTest):
self.assertRaises(OperationFailure, db.foo.rename, "test")
db.foo.rename("test", dropTarget=True)
# doesn't really test functionality, just that the option is set correctly
def test_snapshot(self):
db = self.db
self.assertRaises(TypeError, db.test.find, snapshot=5)
list(db.test.find(snapshot=True))
self.assertRaises(OperationFailure, list,
db.test.find(snapshot=True).sort("foo", 1))
def test_find_one(self):
db = self.db
db.drop_collection("test")
@ -1538,9 +1532,9 @@ class TestCollection(IntegrationTest):
self.assertEqual(db.test.find_one({"hello": "world"}),
db.test.find_one())
self.assertTrue("hello" in db.test.find_one(fields=["hello"]))
self.assertTrue("hello" not in db.test.find_one(fields=["foo"]))
self.assertEqual(["_id"], list(db.test.find_one(fields=[])))
self.assertTrue("hello" in db.test.find_one(projection=["hello"]))
self.assertTrue("hello" not in db.test.find_one(projection=["foo"]))
self.assertEqual(["_id"], list(db.test.find_one(projection=[])))
self.assertEqual(None, db.test.find_one({"hello": "foo"}))
self.assertEqual(None, db.test.find_one(ObjectId()))
@ -1615,20 +1609,19 @@ class TestCollection(IntegrationTest):
# TODO doesn't actually test functionality, just that it doesn't blow up
def test_cursor_timeout(self):
list(self.db.test.find(timeout=False))
list(self.db.test.find(timeout=True))
list(self.db.test.find(no_cursor_timeout=True))
list(self.db.test.find(no_cursor_timeout=False))
def test_exhaust(self):
if is_mongos(self.db.connection):
self.assertRaises(InvalidOperation,
self.db.test.find, exhaust=True)
self.db.test.find, cursor_type=EXHAUST)
return
self.assertRaises(TypeError, self.db.test.find, exhaust=5)
# Limit is incompatible with exhaust.
self.assertRaises(InvalidOperation,
self.db.test.find, exhaust=True, limit=5)
cur = self.db.test.find(exhaust=True)
self.db.test.find, cursor_type=EXHAUST, limit=5)
cur = self.db.test.find(cursor_type=EXHAUST)
self.assertRaises(InvalidOperation, cur.limit, 5)
cur = self.db.test.find(limit=5)
self.assertRaises(InvalidOperation, cur.add_option, 64)
@ -1644,7 +1637,7 @@ class TestCollection(IntegrationTest):
socks = get_pool(client).sockets
# Make sure the socket is returned after exhaustion.
cur = client[self.db.name].test.find(exhaust=True)
cur = client[self.db.name].test.find(cursor_type=EXHAUST)
next(cur)
self.assertEqual(0, len(socks))
for doc in cur:
@ -1652,14 +1645,14 @@ class TestCollection(IntegrationTest):
self.assertEqual(1, len(socks))
# Same as previous but don't call next()
for doc in client[self.db.name].test.find(exhaust=True):
for doc in client[self.db.name].test.find(cursor_type=EXHAUST):
pass
self.assertEqual(1, len(socks))
# If the Cursor instance is discarded before being
# completely iterated we have to close and
# discard the socket.
cur = client[self.db.name].test.find(exhaust=True)
cur = client[self.db.name].test.find(cursor_type=EXHAUST)
next(cur)
self.assertEqual(0, len(socks))
if sys.platform.startswith('java') or 'PyPy' in sys.version:
@ -1963,26 +1956,6 @@ class TestCollection(IntegrationTest):
c.insert(id_only, check_keys=False)
self.assertEqual(id_only, c.find_one())
def test_as_class(self):
c = self.db.test
c.drop()
c.insert({"x": 1})
doc = next(c.find())
self.assertTrue(isinstance(doc, dict))
doc = next(c.find())
self.assertFalse(isinstance(doc, SON))
doc = next(c.find(as_class=SON))
self.assertTrue(isinstance(doc, SON))
self.assertTrue(isinstance(c.find_one(), dict))
self.assertFalse(isinstance(c.find_one(), SON))
self.assertTrue(isinstance(c.find_one(as_class=SON), SON))
self.assertEqual(1, c.find_one(as_class=SON)["x"])
doc = next(c.find(as_class=SON))
self.assertEqual(1, doc["x"])
def test_find_and_modify(self):
c = self.db.test
c.drop()

View File

@ -30,6 +30,7 @@ from pymongo import (MongoClient,
ALL,
OFF)
from pymongo.command_cursor import CommandCursor
from pymongo.cursor import TAILABLE, TAILABLE_AWAIT, EXHAUST
from pymongo.cursor_manager import CursorManager
from pymongo.errors import (InvalidOperation,
OperationFailure,
@ -67,18 +68,17 @@ class TestCursorNoConnect(unittest.TestCase):
cursor = self.db.test.find()
self.assertEqual(0, cursor._Cursor__query_flags)
cursor.add_option(2)
cursor2 = self.db.test.find(tailable=True)
cursor2 = self.db.test.find(cursor_type=TAILABLE)
self.assertEqual(2, cursor2._Cursor__query_flags)
self.assertEqual(cursor._Cursor__query_flags,
cursor2._Cursor__query_flags)
cursor.add_option(32)
cursor2 = self.db.test.find(tailable=True, await_data=True)
cursor2 = self.db.test.find(cursor_type=TAILABLE_AWAIT)
self.assertEqual(34, cursor2._Cursor__query_flags)
self.assertEqual(cursor._Cursor__query_flags,
cursor2._Cursor__query_flags)
cursor.add_option(128)
cursor2 = self.db.test.find(tailable=True,
await_data=True).add_option(128)
cursor2 = self.db.test.find(cursor_type=TAILABLE_AWAIT).add_option(128)
self.assertEqual(162, cursor2._Cursor__query_flags)
self.assertEqual(cursor._Cursor__query_flags,
cursor2._Cursor__query_flags)
@ -88,12 +88,12 @@ class TestCursorNoConnect(unittest.TestCase):
self.assertEqual(162, cursor._Cursor__query_flags)
cursor.remove_option(128)
cursor2 = self.db.test.find(tailable=True, await_data=True)
cursor2 = self.db.test.find(cursor_type=TAILABLE_AWAIT)
self.assertEqual(34, cursor2._Cursor__query_flags)
self.assertEqual(cursor._Cursor__query_flags,
cursor2._Cursor__query_flags)
cursor.remove_option(32)
cursor2 = self.db.test.find(tailable=True)
cursor2 = self.db.test.find(cursor_type=TAILABLE)
self.assertEqual(2, cursor2._Cursor__query_flags)
self.assertEqual(cursor._Cursor__query_flags,
cursor2._Cursor__query_flags)
@ -103,7 +103,7 @@ class TestCursorNoConnect(unittest.TestCase):
self.assertEqual(2, cursor._Cursor__query_flags)
# Timeout
cursor = self.db.test.find(timeout=False)
cursor = self.db.test.find(no_cursor_timeout=True)
self.assertEqual(16, cursor._Cursor__query_flags)
cursor2 = self.db.test.find().add_option(16)
self.assertEqual(cursor._Cursor__query_flags,
@ -112,7 +112,7 @@ class TestCursorNoConnect(unittest.TestCase):
self.assertEqual(0, cursor._Cursor__query_flags)
# Tailable / Await data
cursor = self.db.test.find(tailable=True, await_data=True)
cursor = self.db.test.find(cursor_type=TAILABLE_AWAIT)
self.assertEqual(34, cursor._Cursor__query_flags)
cursor2 = self.db.test.find().add_option(34)
self.assertEqual(cursor._Cursor__query_flags,
@ -121,7 +121,7 @@ class TestCursorNoConnect(unittest.TestCase):
self.assertEqual(2, cursor._Cursor__query_flags)
# Exhaust - which mongos doesn't support
cursor = self.db.test.find(exhaust=True)
cursor = self.db.test.find(cursor_type=EXHAUST)
self.assertEqual(64, cursor._Cursor__query_flags)
cursor2 = self.db.test.find().add_option(64)
self.assertEqual(cursor._Cursor__query_flags,
@ -132,7 +132,7 @@ class TestCursorNoConnect(unittest.TestCase):
self.assertFalse(cursor._Cursor__exhaust)
# Partial
cursor = self.db.test.find(partial=True)
cursor = self.db.test.find(allow_partial_results=True)
self.assertEqual(128, cursor._Cursor__query_flags)
cursor2 = self.db.test.find().add_option(128)
self.assertEqual(cursor._Cursor__query_flags,
@ -735,26 +735,14 @@ class TestCursor(IntegrationTest):
self.assertNotEqual(cursor, cursor.clone())
class MyClass(dict):
pass
cursor = self.db.test.find(as_class=MyClass)
for e in cursor:
self.assertEqual(type(MyClass()), type(e))
cursor = self.db.test.find(as_class=MyClass)
self.assertEqual(type(MyClass()), type(cursor[0]))
# Just test attributes
cursor = self.db.test.find({"x": re.compile("^hello.*")},
skip=1,
timeout=False,
snapshot=True,
tailable=True,
as_class=MyClass,
await_data=True,
partial=True,
no_cursor_timeout=True,
cursor_type=TAILABLE_AWAIT,
allow_partial_results=True,
manipulate=False,
fields={'_id': False}).limit(2)
projection={'_id': False}).limit(2)
cursor.min([('a', 1)]).max([('b', 3)])
cursor.add_option(128)
cursor.comment('hi!')
@ -762,7 +750,6 @@ class TestCursor(IntegrationTest):
cursor2 = cursor.clone()
self.assertEqual(cursor._Cursor__skip, cursor2._Cursor__skip)
self.assertEqual(cursor._Cursor__limit, cursor2._Cursor__limit)
self.assertEqual(cursor._Cursor__snapshot, cursor2._Cursor__snapshot)
self.assertEqual(type(cursor._Cursor__codec_options),
type(cursor2._Cursor__codec_options))
self.assertEqual(cursor._Cursor__manipulate,
@ -778,17 +765,17 @@ class TestCursor(IntegrationTest):
# Shallow copies can so can mutate
cursor2 = copy.copy(cursor)
cursor2._Cursor__fields['cursor2'] = False
self.assertTrue('cursor2' in cursor._Cursor__fields)
cursor2._Cursor__projection['cursor2'] = False
self.assertTrue('cursor2' in cursor._Cursor__projection)
# Deepcopies and shouldn't mutate
cursor3 = copy.deepcopy(cursor)
cursor3._Cursor__fields['cursor3'] = False
self.assertFalse('cursor3' in cursor._Cursor__fields)
cursor3._Cursor__projection['cursor3'] = False
self.assertFalse('cursor3' in cursor._Cursor__projection)
cursor4 = cursor.clone()
cursor4._Cursor__fields['cursor4'] = False
self.assertFalse('cursor4' in cursor._Cursor__fields)
cursor4._Cursor__projection['cursor4'] = False
self.assertFalse('cursor4' in cursor._Cursor__projection)
# Test memo when deepcopying queries
query = {"hello": "world"}
@ -957,7 +944,7 @@ class TestCursor(IntegrationTest):
db.create_collection("test", capped=True, size=1000, max=3)
try:
cursor = db.test.find(tailable=True)
cursor = db.test.find(cursor_type=TAILABLE)
db.test.insert({"x": 1})
count = 0
@ -1027,7 +1014,7 @@ class TestCursor(IntegrationTest):
self.db.test.insert({})
self.assertEqual(100, len(list(self.db.test.find())))
self.assertEqual(50, len(list(self.db.test.find(max_scan=50))))
self.assertEqual(50, len(list(self.db.test.find().max_scan(50))))
self.assertEqual(50, len(list(self.db.test.find()
.max_scan(90).max_scan(50))))

View File

@ -594,7 +594,9 @@ class TestDatabase(IntegrationTest):
db.test.insert(SON([("hello", "world"),
("_id", 5)]))
cursor = db.test.find(as_class=SON)
db = self.client.get_database(
"pymongo_test", codec_options=CodecOptions(as_class=SON))
cursor = db.test.find()
for x in cursor:
for (k, v) in x.items():
self.assertEqual(k, "_id")
@ -627,10 +629,11 @@ class TestDatabase(IntegrationTest):
db.test.remove({})
db.test.insert({"_id": 4, "foo": "bar"})
db = self.client.get_database(
"pymongo_test", codec_options=CodecOptions(as_class=SON))
self.assertEqual(SON([("foo", "bar")]),
db.dereference(DBRef("test", 4),
fields={"_id": False},
as_class=SON))
projection={"_id": False}))
@client_context.require_no_auth
def test_eval(self):

View File

@ -75,9 +75,7 @@ class TestGridFileNoConnect(unittest.TestCase):
def test_grid_out_cursor_options(self):
self.assertRaises(TypeError, GridOutCursor.__init__, self.db.fs, {},
tailable=True)
self.assertRaises(TypeError, GridOutCursor.__init__, self.db.fs, {},
fields={"filename": 1})
projection={"filename": 1})
cursor = GridOutCursor(self.db.fs, {})
cursor_clone = cursor.clone()

View File

@ -394,7 +394,8 @@ class TestGridfs(IntegrationTest):
self.fs.put(b"test2++", filename="two")
self.assertEqual(3, self.fs.find({"filename":"two"}).count())
self.assertEqual(4, self.fs.find().count())
cursor = self.fs.find(timeout=False).sort("uploadDate", -1).skip(1).limit(2)
cursor = self.fs.find(
no_cursor_timeout=False).sort("uploadDate", -1).skip(1).limit(2)
gout = next(cursor)
self.assertEqual(b"test1", gout.read())
cursor.rewind()