From d69f76d3800546ff90ae024f64147979ed6f6667 Mon Sep 17 00:00:00 2001 From: Bernie Hackett Date: Wed, 4 Mar 2015 17:37:28 -0800 Subject: [PATCH] Finalize option locations and exports. - Move ReturnDocument to pymongo.collection. - Change ReturnDocument.Before to ReturnDocument.BEFORE - Change ReturnDocument.After to ReturnDocument.AFTER - Add pymongo.cursor.CursorType. - Move pymongo.cursor.NON_TAILABLE and friends to attributes of CursorType. - read_preferences.ReadPreference is once again an "enum". - Fix docs for read_preferences.ReadPreference. - Rename pymongo.options to pymongo.operations. - Export CursorType, ReturnDocument, WriteConcern, and public classes from pymongo.opertions through pymongo/__init__.py - Fix up a number of documentation issues in the process. --- doc/api/pymongo/collection.rst | 13 ++-- doc/api/pymongo/cursor.rst | 13 +++- doc/api/pymongo/index.rst | 2 +- doc/api/pymongo/operations.rst | 6 ++ doc/api/pymongo/options.rst | 6 -- doc/api/pymongo/read_preferences.rst | 8 ++- pymongo/__init__.py | 9 +++ pymongo/collection.py | 99 ++++++++++++++++----------- pymongo/cursor.py | 50 +++++++------- pymongo/{options.py => operations.py} | 14 +--- pymongo/read_preferences.py | 85 +++++++++++------------ pymongo/results.py | 9 ++- test/test_bulk.py | 2 +- test/test_client.py | 13 ++-- test/test_collection.py | 30 ++++---- test/test_cursor.py | 21 +++--- test/test_read_preferences.py | 6 +- 17 files changed, 214 insertions(+), 172 deletions(-) create mode 100644 doc/api/pymongo/operations.rst delete mode 100644 doc/api/pymongo/options.rst rename pymongo/{options.py => operations.py} (92%) diff --git a/doc/api/pymongo/collection.rst b/doc/api/pymongo/collection.rst index 88341e91d..8a5e409af 100644 --- a/doc/api/pymongo/collection.rst +++ b/doc/api/pymongo/collection.rst @@ -11,10 +11,13 @@ .. 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.ReturnDocument + + .. autoattribute:: BEFORE + :annotation: + .. autoattribute:: AFTER + :annotation: .. autoclass:: pymongo.collection.Collection(database, name, create=False, **kwargs) @@ -41,7 +44,7 @@ .. automethod:: delete_one .. automethod:: delete_many .. automethod:: aggregate - .. 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(filter=None, projection=None, skip=0, limit=0, no_cursor_timeout=False, cursor_type=CursorType.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:: find_one_and_delete .. automethod:: find_one_and_replace(filter, replacement, projection=None, sort=None, return_document=ReturnDocument.Before, **kwargs) diff --git a/doc/api/pymongo/cursor.rst b/doc/api/pymongo/cursor.rst index e5da66a5a..42624d252 100644 --- a/doc/api/pymongo/cursor.rst +++ b/doc/api/pymongo/cursor.rst @@ -4,7 +4,18 @@ .. automodule:: pymongo.cursor :synopsis: Tools for iterating over MongoDB query results - .. 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) + .. autoclass:: pymongo.cursor.CursorType + + .. autoattribute:: NON_TAILABLE + :annotation: + .. autoattribute:: TAILABLE + :annotation: + .. autoattribute:: TAILABLE_AWAIT + :annotation: + .. autoattribute:: EXHAUST + :annotation: + + .. autoclass:: pymongo.cursor.Cursor(collection, filter=None, projection=None, skip=0, limit=0, no_cursor_timeout=False, cursor_type=CursorType.NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, modifiers=None, manipulate=True) :members: .. describe:: c[index] diff --git a/doc/api/pymongo/index.rst b/doc/api/pymongo/index.rst index dde01d5ce..da45d8a00 100644 --- a/doc/api/pymongo/index.rst +++ b/doc/api/pymongo/index.rst @@ -40,7 +40,7 @@ Sub-modules: message mongo_client mongo_replica_set_client - options + operations pool read_preferences results diff --git a/doc/api/pymongo/operations.rst b/doc/api/pymongo/operations.rst new file mode 100644 index 000000000..e4ab1b9a8 --- /dev/null +++ b/doc/api/pymongo/operations.rst @@ -0,0 +1,6 @@ +:mod:`operations` -- Operation class definitions +================================================ + +.. automodule:: pymongo.operations + :synopsis: Operation class definitions + :members: diff --git a/doc/api/pymongo/options.rst b/doc/api/pymongo/options.rst deleted file mode 100644 index c757f7635..000000000 --- a/doc/api/pymongo/options.rst +++ /dev/null @@ -1,6 +0,0 @@ -:mod:`options` -- Option class definitions -========================================== - -.. automodule:: pymongo.options - :synopsis: Option class definitions - :members: diff --git a/doc/api/pymongo/read_preferences.rst b/doc/api/pymongo/read_preferences.rst index 7fb4d2bae..25b6e3db8 100644 --- a/doc/api/pymongo/read_preferences.rst +++ b/doc/api/pymongo/read_preferences.rst @@ -15,5 +15,11 @@ .. autoclass:: pymongo.read_preferences.Nearest :inherited-members: - .. autodata:: ReadPreference + .. autoclass:: ReadPreference + + .. autoattribute:: PRIMARY + .. autoattribute:: PRIMARY_PREFERRED + .. autoattribute:: SECONDARY + .. autoattribute:: SECONDARY_PREFERRED + .. autoattribute:: NEAREST diff --git a/pymongo/__init__.py b/pymongo/__init__.py index 8eb4f3b8e..3b3c5c83b 100644 --- a/pymongo/__init__.py +++ b/pymongo/__init__.py @@ -80,11 +80,20 @@ def get_version_string(): version = get_version_string() """Current version of PyMongo.""" +from pymongo.collection import ReturnDocument from pymongo.common import (MIN_SUPPORTED_WIRE_VERSION, MAX_SUPPORTED_WIRE_VERSION) +from pymongo.cursor import CursorType from pymongo.mongo_client import MongoClient from pymongo.mongo_replica_set_client import MongoReplicaSetClient +from pymongo.operations import (InsertOne, + DeleteOne, + DeleteMany, + UpdateOne, + UpdateMany, + ReplaceOne) from pymongo.read_preferences import ReadPreference +from pymongo.write_concern import WriteConcern def has_c(): """Is the C extension installed?""" diff --git a/pymongo/collection.py b/pymongo/collection.py index ee10cd067..4f89d8a85 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -35,7 +35,7 @@ from pymongo.cursor import Cursor from pymongo.errors import ConfigurationError, InvalidName, OperationFailure from pymongo.helpers import _check_write_command_response, _command from pymongo.message import _INSERT, _UPDATE, _DELETE -from pymongo.options import ReturnDocument, _WriteOp +from pymongo.operations import _WriteOp from pymongo.read_preferences import ReadPreference from pymongo.results import (BulkWriteResult, DeleteResult, @@ -44,7 +44,6 @@ from pymongo.results import (BulkWriteResult, UpdateResult) from pymongo.write_concern import WriteConcern - try: from collections import OrderedDict _ORDERED_TYPES = (SON, OrderedDict) @@ -54,6 +53,19 @@ except ImportError: _NO_OBJ_ERROR = "No matching object found" +class ReturnDocument(object): + """An enum used with + :meth:`~pymongo.collection.Collection.find_one_and_replace` and + :meth:`~pymongo.collection.Collection.find_one_and_update`. + """ + BEFORE = False + """Return the original document before it was updated/replaced, or + ``None`` if no document matches the query. + """ + AFTER = True + """Return the updated/replaced or inserted document.""" + + def _gen_index_name(keys): """Generate an index name from the set of fields it is over. """ @@ -287,8 +299,13 @@ class Collection(common.BaseObject): def bulk_write(self, requests, ordered=True): """Send a batch of write operations to the server. - Write operations are passed as a list using the write operation classes - from the :mod:`~pymongo.options` module:: + Requests are passed as a list of write operation instances ( + :class:`~pymongo.operations.InsertOne`, + :class:`~pymongo.operations.UpdateOne`, + :class:`~pymongo.operations.UpdateMany`, + :class:`~pymongo.operations.ReplaceOne`, + :class:`~pymongo.operations.DeleteOne`, or + :class:`~pymongo.operations.DeleteMany`). >>> for doc in db.test.find({}): ... print(doc) @@ -297,7 +314,7 @@ class Collection(common.BaseObject): {u'x': 1, u'_id': ObjectId('54f62e60fba5226811f634f0')} >>> # DeleteMany, UpdateOne, and UpdateMany are also available. ... - >>> from pymongo.options import InsertOne, DeleteOne, ReplaceOne + >>> from pymongo import InsertOne, DeleteOne, ReplaceOne >>> requests = [InsertOne({'y': 1}), DeleteOne({'x': 1}), ... ReplaceOne({'w': 1}, {'z': 1}, upsert=True)] >>> result = db.test.bulk_write(requests) @@ -797,27 +814,27 @@ class Collection(common.BaseObject): time out on the server. Care should be taken to ensure that cursors with no_cursor_timeout turned on are properly closed. - `cursor_type` (optional): the type of cursor to return. The valid - options are: + options are defined by :class:`~pymongo.cursor.CursorType`: - - :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 + - :attr:`~pymongo.cursor.CursorType.NON_TAILABLE` - the result of + this find call will return a standard cursor over the result set. + - :attr:`~pymongo.cursor.CursorType.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 `_. - - :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 + - :attr:`~pymongo.cursor.CursorType.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. + - :attr:`~pymongo.cursor.CursorType.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 @@ -833,7 +850,7 @@ class Collection(common.BaseObject): apply any outgoing SON manipulators before returning. .. note:: There are a number of caveats to using - :data:`~pymongo.cursor.EXHAUST` as cursor_type: + :attr:`~pymongo.cursor.CursorType.EXHAUST` as cursor_type: 1. The `limit` option can not be used with an exhaust cursor. @@ -841,8 +858,8 @@ class Collection(common.BaseObject): used with a sharded cluster. 3. A :class:`~pymongo.cursor.Cursor` instance created with the - cursor.EXHAUST cursor_type requires an exclusive - :class:`~socket.socket` connection to MongoDB. If the + :attr:`~pymongo.cursor.CursorType.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 @@ -1654,12 +1671,12 @@ class Collection(common.BaseObject): return res.get("results") def __find_and_modify(self, filter, projection, sort, upsert=None, - return_document=ReturnDocument.Before, **kwargs): + return_document=ReturnDocument.BEFORE, **kwargs): """Internal findAndModify helper.""" common.validate_is_mapping("filter", filter) if not isinstance(return_document, bool): raise ValueError("return_document must be " - "ReturnDocument.Before or ReturnDocument.After") + "ReturnDocument.BEFORE or ReturnDocument.AFTER") cmd = SON([("findAndModify", self.__name), ("query", filter), ("new", return_document)]) @@ -1726,13 +1743,13 @@ class Collection(common.BaseObject): def find_one_and_replace(self, filter, replacement, projection=None, sort=None, upsert=False, - return_document=ReturnDocument.Before, **kwargs): + return_document=ReturnDocument.BEFORE, **kwargs): """Finds a single document and replaces it, returning either the original or the replaced document. - The `find_one_and_replace` method differs from `find_one_and_update` - by replacing the document matched by *filter*, rather than modifying - the existing document. + The :meth:`find_one_and_replace` method differs from + :meth:`find_one_and_update` by replacing the document matched by + *filter*, rather than modifying the existing document. >>> for doc in db.test.find({}): ... print(doc) @@ -1763,10 +1780,10 @@ class Collection(common.BaseObject): - `upsert` (optional): When ``True``, inserts a new document if no document matches the query. Defaults to ``False``. - `return_document`: If - :attr:`~pymongo.options.ReturnDocument.Before` (the default), + :attr:`ReturnDocument.BEFORE` (the default), returns the original document before it was replaced, or ``None`` if no document matches. If - :attr:`~pymongo.options.ReturnDocument.After`, returns the replaced + :attr:`ReturnDocument.AFTER`, returns the replaced or inserted document. - `**kwargs` (optional): additional command arguments can be passed as keyword arguments (for example maxTimeMS can be used with @@ -1781,7 +1798,7 @@ class Collection(common.BaseObject): def find_one_and_update(self, filter, update, projection=None, sort=None, upsert=False, - return_document=ReturnDocument.Before, **kwargs): + return_document=ReturnDocument.BEFORE, **kwargs): """Finds a single document and updates it, returning either the original or the updated document. @@ -1789,11 +1806,11 @@ class Collection(common.BaseObject): ... {'_id': 665}, {'$inc': {'count': 1}, '$set': {'done': True}}) {u'_id': 665, u'done': False, u'count': 25}} - By default `find_one_and_update` returns the original version of the - document before the update was applied. To return the updated version - of the document instead, use the *return_document* option. + By default :meth:`find_one_and_update` returns the original version of + the document before the update was applied. To return the updated + version of the document instead, use the *return_document* option. - >>> from pymongo.options import ReturnDocument + >>> from pymongo import ReturnDocument >>> db.example.find_one_and_update( ... {'_id': 'userid'}, ... {'$inc': {'seq': 1}}, @@ -1849,10 +1866,10 @@ class Collection(common.BaseObject): - `upsert` (optional): When ``True``, inserts a new document if no document matches the query. Defaults to ``False``. - `return_document`: If - :attr:`~pymongo.options.ReturnDocument.Before` (the default), + :attr:`ReturnDocument.BEFORE` (the default), returns the original document before it was updated, or ``None`` if no document matches. If - :attr:`~pymongo.options.ReturnDocument.After`, returns the updated + :attr:`ReturnDocument.AFTER`, returns the updated or inserted document. - `**kwargs` (optional): additional command arguments can be passed as keyword arguments (for example maxTimeMS can be used with diff --git a/pymongo/cursor.py b/pymongo/cursor.py index 41ad4c4a4..872e1683b 100644 --- a/pymongo/cursor.py +++ b/pymongo/cursor.py @@ -43,32 +43,33 @@ _QUERY_OPTIONS = { "partial": 128} -NON_TAILABLE = 0 -"""The standard cursor type.""" +class CursorType(object): + NON_TAILABLE = 0 + """The standard cursor type.""" -TAILABLE = _QUERY_OPTIONS["tailable_cursor"] -"""The tailable 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 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. + 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. -""" + 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. + 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. -""" + 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 @@ -98,7 +99,8 @@ class Cursor(object): """ def __init__(self, collection, filter=None, projection=None, skip=0, - limit=0, no_cursor_timeout=False, cursor_type=NON_TAILABLE, + limit=0, no_cursor_timeout=False, + cursor_type=CursorType.NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, modifiers=None, manipulate=True): """Create a new cursor. @@ -120,8 +122,8 @@ class Cursor(object): if not isinstance(limit, int): raise TypeError("limit must be an instance of int") validate_boolean("no_cursor_timeout", no_cursor_timeout) - if cursor_type not in (NON_TAILABLE, TAILABLE, - TAILABLE_AWAIT, EXHAUST): + if cursor_type not in (CursorType.NON_TAILABLE, CursorType.TAILABLE, + CursorType.TAILABLE_AWAIT, CursorType.EXHAUST): raise ValueError("not a valid value for cursor_type") validate_boolean("allow_partial_results", allow_partial_results) validate_boolean("oplog_replay", oplog_replay) @@ -153,7 +155,7 @@ class Cursor(object): # Exhaust cursor support self.__exhaust = False self.__exhaust_mgr = None - if cursor_type == EXHAUST: + if cursor_type == CursorType.EXHAUST: if self.__collection.database.client.is_mongos: raise InvalidOperation('Exhaust cursors are ' 'not supported by mongos') diff --git a/pymongo/options.py b/pymongo/operations.py similarity index 92% rename from pymongo/options.py rename to pymongo/operations.py index 97885abad..437334334 100644 --- a/pymongo/options.py +++ b/pymongo/operations.py @@ -12,22 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Option class definitions.""" +"""Operation class definitions.""" from pymongo.common import validate_boolean, validate_is_mapping -class ReturnDocument(object): - """An enum used with - :meth:`~pymongo.collection.Collection.find_one_and_replace` and - :meth:`~pymongo.collection.Collection.find_one_and_update`. - """ - Before = False - """Return the original document before it was updated/replaced, or - ``None`` if no document matches the query. - """ - After = True - """Return the updated/replaced or inserted document.""" - class _WriteOp(object): """Private base class for all write operations.""" diff --git a/pymongo/read_preferences.py b/pymongo/read_preferences.py index 8fb6553b8..356321beb 100644 --- a/pymongo/read_preferences.py +++ b/pymongo/read_preferences.py @@ -14,7 +14,7 @@ """Utilities for choosing which member of a replica set to read from.""" -from collections import Mapping, namedtuple +from collections import Mapping from pymongo.errors import ConfigurationError from pymongo.server_selectors import (member_with_tags_server_selector, @@ -278,66 +278,61 @@ _MODES = ( ) -ReadPreference = namedtuple("ReadPreference", _MODES)( - Primary(), PrimaryPreferred(), Secondary(), SecondaryPreferred(), Nearest()) -"""An enum that defines the read preference modes supported by PyMongo. +class ReadPreference(object): + """An enum that defines the read preference modes supported by PyMongo. -See :doc:`/examples/high_availability` for code examples. + See :doc:`/examples/high_availability` for code examples. -A read preference is used in three cases: + A read preference is used in three cases: -:class:`~pymongo.mongo_client.MongoClient` connected to a single mongod: + :class:`~pymongo.mongo_client.MongoClient` connected to a single mongod: -- ``PRIMARY``: Queries are allowed if the server is standalone or a replica - set primary. -- All other modes allow queries to standalone servers, to a replica set - primary, or to replica set secondaries. + - ``PRIMARY``: Queries are allowed if the server is standalone or a replica + set primary. + - All other modes allow queries to standalone servers, to a replica set + primary, or to replica set secondaries. -:class:`~pymongo.mongo_client.MongoClient` initialized with the -``replicaSet`` option: + :class:`~pymongo.mongo_client.MongoClient` initialized with the + ``replicaSet`` option: -- ``PRIMARY``: Read from the primary. This is the default, and provides the - strongest consistency. If no primary is available, raise - :class:`~pymongo.errors.AutoReconnect`. + - ``PRIMARY``: Read from the primary. This is the default, and provides the + strongest consistency. If no primary is available, raise + :class:`~pymongo.errors.AutoReconnect`. -- ``PRIMARY_PREFERRED``: Read from the primary if available, or if there is - none, read from a secondary matching your choice of ``tag_sets`` and - ``local_threshold_ms``. + - ``PRIMARY_PREFERRED``: Read from the primary if available, or if there is + none, read from a secondary. -- ``SECONDARY``: Read from a secondary matching your choice of ``tag_sets`` and - ``local_threshold_ms``. If no matching secondary is available, - raise :class:`~pymongo.errors.AutoReconnect`. + - ``SECONDARY``: Read from a secondary. If no secondary is available, + raise :class:`~pymongo.errors.AutoReconnect`. -- ``SECONDARY_PREFERRED``: Read from a secondary matching your choice of - ``tag_sets`` and ``local_threshold_ms`` if available, otherwise - from primary (regardless of the primary's tags and local threshold). + - ``SECONDARY_PREFERRED``: Read from a secondary if available, otherwise + from the primary. -- ``NEAREST``: Read from any member matching your choice of ``tag_sets`` and - ``local_threshold_ms``. + - ``NEAREST``: Read from any member. -:class:`~pymongo.mongo_client.MongoClient` connected to a mongos, with a -sharded cluster of replica sets: + :class:`~pymongo.mongo_client.MongoClient` connected to a mongos, with a + sharded cluster of replica sets: -- ``PRIMARY``: Read from the primary of the shard, or raise - :class:`~pymongo.errors.OperationFailure` if there is none. - This is the default. + - ``PRIMARY``: Read from the primary of the shard, or raise + :class:`~pymongo.errors.OperationFailure` if there is none. + This is the default. -- ``PRIMARY_PREFERRED``: Read from the primary of the shard, or if there is - none, read from a secondary matching your choice of ``tag_sets``. + - ``PRIMARY_PREFERRED``: Read from the primary of the shard, or if there is + none, read from a secondary of the shard. -- ``SECONDARY``: Read from a secondary matching your choice of ``tag_sets``, - or raise :class:`~pymongo.errors.OperationFailure` if there is none. + - ``SECONDARY``: Read from a secondary of the shard, or raise + :class:`~pymongo.errors.OperationFailure` if there is none. -- ``SECONDARY_PREFERRED``: Read from a secondary matching your choice of - ``tag_sets``, otherwise from primary. + - ``SECONDARY_PREFERRED``: Read from a secondary of the shard if available, + otherwise from the shard primary. -- ``NEAREST``: Read from any member matching your choice of ``tag_sets``. - -.. note:: ``local_threshold_ms`` is ignored when talking to a - replica set *through* a mongos. The equivalent is the - `localThreshold `_ - command line option. -""" + - ``NEAREST``: Read from any shard member. + """ + PRIMARY = Primary() + PRIMARY_PREFERRED = PrimaryPreferred() + SECONDARY = Secondary() + SECONDARY_PREFERRED = SecondaryPreferred() + NEAREST = Nearest() def read_pref_mode_from_name(name): diff --git a/pymongo/results.py b/pymongo/results.py index 3b0746ce9..ac41156bc 100644 --- a/pymongo/results.py +++ b/pymongo/results.py @@ -38,7 +38,8 @@ class _WriteResult(object): class InsertOneResult(_WriteResult): - """The return type for :meth:`Collection.insert_one`.""" + """The return type for :meth:`~pymongo.collection.Collection.insert_one`. + """ __slots__ = ("__inserted_id", "__acknowledged") @@ -75,8 +76,10 @@ class InsertManyResult(_WriteResult): class UpdateResult(_WriteResult): - """The return type for :meth:`~pymongo.collection.Collection.update_one` - and :meth:`~pymongo.collection.Collection.update_many`""" + """The return type for :meth:`~pymongo.collection.Collection.update_one`, + :meth:`~pymongo.collection.Collection.update_many`, and + :meth:`~pymongo.collection.Collection.replace_one`. + """ __slots__ = ("__raw_result", "__acknowledged") diff --git a/test/test_bulk.py b/test/test_bulk.py index db1009341..c32925cff 100644 --- a/test/test_bulk.py +++ b/test/test_bulk.py @@ -22,7 +22,7 @@ from bson import InvalidDocument, SON from bson.objectid import ObjectId from bson.py3compat import string_type from pymongo import MongoClient -from pymongo.options import * +from pymongo.operations import * from pymongo.common import partition_node from pymongo.errors import (BulkWriteError, ConfigurationError, diff --git a/test/test_client.py b/test/test_client.py index 94736dcd6..58d91419a 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -32,7 +32,7 @@ from bson.py3compat import thread, u from bson.son import SON from bson.tz_util import utc from pymongo import auth, message -from pymongo.cursor import EXHAUST +from pymongo.cursor import CursorType from pymongo.database import Database from pymongo.errors import (AutoReconnect, ConfigurationError, @@ -756,7 +756,7 @@ class TestClient(IntegrationTest): # Cause a network error. sock_info = one(pool.sockets) sock_info.sock.close() - cursor = collection.find(cursor_type=EXHAUST) + cursor = collection.find(cursor_type=CursorType.EXHAUST) with self.assertRaises(ConnectionFailure): next(cursor) @@ -848,7 +848,8 @@ 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)]), cursor_type=EXHAUST) + SON([('$query', {}), ('$orderby', True)]), + cursor_type=CursorType.EXHAUST) self.assertRaises(OperationFailure, cursor.next) self.assertFalse(sock_info.closed) @@ -871,7 +872,7 @@ class TestExhaustCursor(IntegrationTest): pool._check_interval_seconds = None # Never check. sock_info = one(pool.sockets) - cursor = collection.find(cursor_type=EXHAUST) + cursor = collection.find(cursor_type=CursorType.EXHAUST) # Initial query succeeds. cursor.next() @@ -907,7 +908,7 @@ class TestExhaustCursor(IntegrationTest): sock_info = one(pool.sockets) sock_info.sock.close() - cursor = collection.find(cursor_type=EXHAUST) + cursor = collection.find(cursor_type=CursorType.EXHAUST) self.assertRaises(ConnectionFailure, cursor.next) self.assertTrue(sock_info.closed) @@ -925,7 +926,7 @@ class TestExhaustCursor(IntegrationTest): pool = get_pool(client) pool._check_interval_seconds = None # Never check. - cursor = collection.find(cursor_type=EXHAUST) + cursor = collection.find(cursor_type=CursorType.EXHAUST) # Initial query succeeds. cursor.next() diff --git a/test/test_collection.py b/test/test_collection.py index 21170f415..54e93c223 100644 --- a/test/test_collection.py +++ b/test/test_collection.py @@ -32,15 +32,14 @@ from bson.son import SON from pymongo import (ASCENDING, DESCENDING, GEO2D, GEOHAYSTACK, GEOSPHERE, HASHED, TEXT) from pymongo import MongoClient -from pymongo.collection import Collection +from pymongo.collection import Collection, ReturnDocument from pymongo.command_cursor import CommandCursor -from pymongo.cursor import EXHAUST +from pymongo.cursor import CursorType from pymongo.errors import (DuplicateKeyError, InvalidDocument, InvalidName, InvalidOperation, OperationFailure) -from pymongo.options import ReturnDocument from pymongo.read_preferences import ReadPreference from pymongo.results import (InsertOneResult, InsertManyResult, @@ -1374,13 +1373,16 @@ class TestCollection(IntegrationTest): def test_exhaust(self): if is_mongos(self.db.client): self.assertRaises(InvalidOperation, - self.db.test.find, cursor_type=EXHAUST) + self.db.test.find, + cursor_type=CursorType.EXHAUST) return # Limit is incompatible with exhaust. self.assertRaises(InvalidOperation, - self.db.test.find, cursor_type=EXHAUST, limit=5) - cur = self.db.test.find(cursor_type=EXHAUST) + self.db.test.find, + cursor_type=CursorType.EXHAUST, + limit=5) + cur = self.db.test.find(cursor_type=CursorType.EXHAUST) self.assertRaises(InvalidOperation, cur.limit, 5) cur = self.db.test.find(limit=5) self.assertRaises(InvalidOperation, cur.add_option, 64) @@ -1396,7 +1398,7 @@ class TestCollection(IntegrationTest): socks = get_pool(client).sockets # Make sure the socket is returned after exhaustion. - cur = client[self.db.name].test.find(cursor_type=EXHAUST) + cur = client[self.db.name].test.find(cursor_type=CursorType.EXHAUST) next(cur) self.assertEqual(0, len(socks)) for _ in cur: @@ -1404,14 +1406,14 @@ class TestCollection(IntegrationTest): self.assertEqual(1, len(socks)) # Same as previous but don't call next() - for _ in client[self.db.name].test.find(cursor_type=EXHAUST): + for _ in client[self.db.name].test.find(cursor_type=CursorType.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(cursor_type=EXHAUST) + cur = client[self.db.name].test.find(cursor_type=CursorType.EXHAUST) next(cur) self.assertEqual(0, len(socks)) if sys.platform.startswith('java') or 'PyPy' in sys.version: @@ -1606,7 +1608,7 @@ class TestCollection(IntegrationTest): self.assertEqual({'_id': 1, 'i': 3}, c.find_one_and_update( {'_id': 1}, {'$inc': {'i': 1}}, - return_document=ReturnDocument.After)) + return_document=ReturnDocument.AFTER)) self.assertEqual({'_id': 1, 'i': 3}, c.find_one_and_delete({'_id': 1})) @@ -1617,23 +1619,23 @@ class TestCollection(IntegrationTest): self.assertEqual({'_id': 1, 'i': 1}, c.find_one_and_update( {'_id': 1}, {'$inc': {'i': 1}}, - return_document=ReturnDocument.After, + return_document=ReturnDocument.AFTER, upsert=True)) self.assertEqual({'_id': 1, 'i': 2}, c.find_one_and_update( {'_id': 1}, {'$inc': {'i': 1}}, - return_document=ReturnDocument.After)) + return_document=ReturnDocument.AFTER)) self.assertEqual({'_id': 1, 'i': 3}, c.find_one_and_replace( {'_id': 1}, {'i': 3, 'j': 1}, projection=['i'], - return_document=ReturnDocument.After)) + return_document=ReturnDocument.AFTER)) self.assertEqual({'i': 4}, c.find_one_and_update( {'_id': 1}, {'$inc': {'i': 1}}, projection={'i': 1, '_id': 0}, - return_document=ReturnDocument.After)) + return_document=ReturnDocument.AFTER)) c.drop() for j in range(5): diff --git a/test/test_cursor.py b/test/test_cursor.py index 0fa17654e..e2758c976 100644 --- a/test/test_cursor.py +++ b/test/test_cursor.py @@ -30,7 +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 import CursorType from pymongo.cursor_manager import CursorManager from pymongo.errors import (InvalidOperation, OperationFailure, @@ -68,17 +68,18 @@ 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(cursor_type=TAILABLE) + cursor2 = self.db.test.find(cursor_type=CursorType.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(cursor_type=TAILABLE_AWAIT) + cursor2 = self.db.test.find(cursor_type=CursorType.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(cursor_type=TAILABLE_AWAIT).add_option(128) + cursor2 = self.db.test.find( + cursor_type=CursorType.TAILABLE_AWAIT).add_option(128) self.assertEqual(162, cursor2._Cursor__query_flags) self.assertEqual(cursor._Cursor__query_flags, cursor2._Cursor__query_flags) @@ -88,12 +89,12 @@ class TestCursorNoConnect(unittest.TestCase): self.assertEqual(162, cursor._Cursor__query_flags) cursor.remove_option(128) - cursor2 = self.db.test.find(cursor_type=TAILABLE_AWAIT) + cursor2 = self.db.test.find(cursor_type=CursorType.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(cursor_type=TAILABLE) + cursor2 = self.db.test.find(cursor_type=CursorType.TAILABLE) self.assertEqual(2, cursor2._Cursor__query_flags) self.assertEqual(cursor._Cursor__query_flags, cursor2._Cursor__query_flags) @@ -112,7 +113,7 @@ class TestCursorNoConnect(unittest.TestCase): self.assertEqual(0, cursor._Cursor__query_flags) # Tailable / Await data - cursor = self.db.test.find(cursor_type=TAILABLE_AWAIT) + cursor = self.db.test.find(cursor_type=CursorType.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 +122,7 @@ class TestCursorNoConnect(unittest.TestCase): self.assertEqual(2, cursor._Cursor__query_flags) # Exhaust - which mongos doesn't support - cursor = self.db.test.find(cursor_type=EXHAUST) + cursor = self.db.test.find(cursor_type=CursorType.EXHAUST) self.assertEqual(64, cursor._Cursor__query_flags) cursor2 = self.db.test.find().add_option(64) self.assertEqual(cursor._Cursor__query_flags, @@ -723,7 +724,7 @@ class TestCursor(IntegrationTest): cursor = self.db.test.find({"x": re.compile("^hello.*")}, skip=1, no_cursor_timeout=True, - cursor_type=TAILABLE_AWAIT, + cursor_type=CursorType.TAILABLE_AWAIT, allow_partial_results=True, manipulate=False, projection={'_id': False}).limit(2) @@ -916,7 +917,7 @@ class TestCursor(IntegrationTest): db.drop_collection("test") db.create_collection("test", capped=True, size=1000, max=3) self.addCleanup(db.drop_collection, "test") - cursor = db.test.find(cursor_type=TAILABLE) + cursor = db.test.find(cursor_type=CursorType.TAILABLE) db.test.insert_one({"x": 1}) count = 0 diff --git a/test/test_read_preferences.py b/test/test_read_preferences.py index 9a4ba6a4a..bd7b598b5 100644 --- a/test/test_read_preferences.py +++ b/test/test_read_preferences.py @@ -91,7 +91,11 @@ class TestReadPreferencesBase(TestReplicaSetClientBase): class TestReadPreferences(TestReadPreferencesBase): def test_mode_validation(self): - for mode in ReadPreference: + for mode in (ReadPreference.PRIMARY, + ReadPreference.PRIMARY_PREFERRED, + ReadPreference.SECONDARY, + ReadPreference.SECONDARY_PREFERRED, + ReadPreference.NEAREST): self.assertEqual( mode, rs_client(read_preference=mode).read_preference)