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:
parent
71e083d81b
commit
c07e543e58
@ -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
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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})
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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``.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"])]
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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))))
|
||||
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user