Skip to content

Commit e1850d8

Browse files
committed
PYTHON-988 - Deprecate cursor managers and kill_cursors
1 parent 057429c commit e1850d8

File tree

6 files changed

+145
-112
lines changed

6 files changed

+145
-112
lines changed

pymongo/cursor_manager.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,39 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
"""A manager to handle when cursors are killed after they are closed.
15+
"""DEPRECATED - A manager to handle when cursors are killed after they are
16+
closed.
1617
1718
New cursor managers should be defined as subclasses of CursorManager and can be
1819
installed on a client by calling
1920
:meth:`~pymongo.mongo_client.MongoClient.set_cursor_manager`.
2021
22+
.. versionchanged:: 3.3
23+
Deprecated, for real this time.
24+
2125
.. versionchanged:: 3.0
2226
Undeprecated. :meth:`~pymongo.cursor_manager.CursorManager.close` now
2327
requires an `address` argument. The ``BatchCursorManager`` class is removed.
2428
"""
2529

30+
import warnings
2631
import weakref
2732
from bson.py3compat import integer_types
2833

2934

3035
class CursorManager(object):
31-
"""The cursor manager base class."""
36+
"""DEPRECATED - The cursor manager base class."""
3237

3338
def __init__(self, client):
3439
"""Instantiate the manager.
3540
3641
:Parameters:
3742
- `client`: a MongoClient
3843
"""
44+
warnings.warn(
45+
"Cursor managers are deprecated.",
46+
DeprecationWarning,
47+
stacklevel=2)
3948
self.__client = weakref.ref(client)
4049

4150
def close(self, cursor_id, address):

pymongo/mongo_client.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
import contextlib
3535
import datetime
3636
import threading
37+
import warnings
3738
import weakref
39+
3840
from collections import defaultdict
3941

4042
from bson.codec_options import DEFAULT_CODEC_OPTIONS
@@ -358,7 +360,7 @@ def __init__(
358360

359361
self.__default_database_name = dbase
360362
self.__lock = threading.Lock()
361-
self.__cursor_manager = CursorManager(self)
363+
self.__cursor_manager = None
362364
self.__kill_cursors_queue = []
363365

364366
self._event_listeners = options.pool_options.event_listeners
@@ -712,7 +714,7 @@ def close(self):
712714
self._topology.close()
713715

714716
def set_cursor_manager(self, manager_class):
715-
"""Set this client's cursor manager.
717+
"""DEPRECATED - Set this client's cursor manager.
716718
717719
Raises :class:`TypeError` if `manager_class` is not a subclass of
718720
:class:`~pymongo.cursor_manager.CursorManager`. A cursor manager
@@ -723,9 +725,16 @@ def set_cursor_manager(self, manager_class):
723725
:Parameters:
724726
- `manager_class`: cursor manager to use
725727
728+
.. versionchanged:: 3.3
729+
Deprecated, for real this time.
730+
726731
.. versionchanged:: 3.0
727732
Undeprecated.
728733
"""
734+
warnings.warn(
735+
"set_cursor_manager is Deprecated",
736+
DeprecationWarning,
737+
stacklevel=2)
729738
manager = manager_class(self)
730739
if not isinstance(manager, CursorManager):
731740
raise TypeError("manager_class must be a subclass of "
@@ -920,12 +929,17 @@ def __getitem__(self, name):
920929
return database.Database(self, name)
921930

922931
def close_cursor(self, cursor_id, address=None):
923-
"""Close a single database cursor.
932+
"""Send a kill cursors message soon with the given id.
924933
925934
Raises :class:`TypeError` if `cursor_id` is not an instance of
926935
``(int, long)``. What closing the cursor actually means
927936
depends on this client's cursor manager.
928937
938+
This method may be called from a :class:`~pymongo.cursor.Cursor`
939+
destructor during garbage collection, so it isn't safe to take a
940+
lock or do network I/O. Instead, we schedule the cursor to be closed
941+
soon on a background thread.
942+
929943
:Parameters:
930944
- `cursor_id`: id of cursor to close
931945
- `address` (optional): (host, port) pair of the cursor's server.
@@ -938,30 +952,36 @@ def close_cursor(self, cursor_id, address=None):
938952
if not isinstance(cursor_id, integer_types):
939953
raise TypeError("cursor_id must be an instance of (int, long)")
940954

941-
self.__cursor_manager.close(cursor_id, address)
955+
if self.__cursor_manager is not None:
956+
self.__cursor_manager.close(cursor_id, address)
957+
else:
958+
self.__kill_cursors_queue.append((address, [cursor_id]))
942959

943960
def kill_cursors(self, cursor_ids, address=None):
944-
"""Send a kill cursors message soon with the given ids.
961+
"""DEPRECATED - Send a kill cursors message soon with the given ids.
945962
946963
Raises :class:`TypeError` if `cursor_ids` is not an instance of
947964
``list``.
948965
949-
This method may be called from a :class:`~pymongo.cursor.Cursor`
950-
destructor during garbage collection, so it isn't safe to take a
951-
lock or do network I/O. Instead, we schedule the cursor to be closed
952-
soon on a background thread.
953-
954966
:Parameters:
955967
- `cursor_ids`: list of cursor ids to kill
956968
- `address` (optional): (host, port) pair of the cursor's server.
957969
If it is not provided, the client attempts to close the cursor on
958970
the primary or standalone, or a mongos server.
959971
972+
.. versionchanged:: 3.3
973+
Deprecated.
974+
960975
.. versionchanged:: 3.0
961976
Now accepts an `address` argument. Schedules the cursors to be
962977
closed on a background thread instead of sending the message
963978
immediately.
964979
"""
980+
warnings.warn(
981+
"kill_cursors is deprecated.",
982+
DeprecationWarning,
983+
stacklevel=2)
984+
965985
if not isinstance(cursor_ids, list):
966986
raise TypeError("cursor_ids must be a list")
967987

test/test_client.py

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import sys
2323
import time
2424
import traceback
25-
import warnings
2625

2726
sys.path[0:0] = [""]
2827

@@ -39,10 +38,8 @@
3938
ConnectionFailure,
4039
InvalidName,
4140
OperationFailure,
42-
CursorNotFound,
4341
NetworkTimeout,
4442
InvalidURI)
45-
from pymongo.message import _CursorAddress
4643
from pymongo.mongo_client import MongoClient
4744
from pymongo.pool import SocketInfo
4845
from pymongo.read_preferences import ReadPreference
@@ -807,68 +804,6 @@ def test_operation_failure(self):
807804
new_sock_info = next(iter(pool.sockets))
808805
self.assertEqual(old_sock_info, new_sock_info)
809806

810-
def test_kill_cursors_with_cursoraddress(self):
811-
if (client_context.is_mongos
812-
and not client_context.version.at_least(2, 4, 7)):
813-
# Old mongos sends incorrectly formatted error response when
814-
# cursor isn't found, see SERVER-9738.
815-
raise SkipTest("Can't test kill_cursors against old mongos")
816-
817-
self.collection = self.client.pymongo_test.test
818-
self.collection.drop()
819-
820-
self.collection.insert_many([{'_id': i} for i in range(200)])
821-
cursor = self.collection.find().batch_size(1)
822-
next(cursor)
823-
self.client.kill_cursors(
824-
[cursor.cursor_id],
825-
_CursorAddress(self.client.address, self.collection.full_name))
826-
827-
# Prevent killcursors from reaching the server while a getmore is in
828-
# progress -- the server logs "Assertion: 16089:Cannot kill active
829-
# cursor."
830-
time.sleep(2)
831-
832-
def raises_cursor_not_found():
833-
try:
834-
next(cursor)
835-
return False
836-
except CursorNotFound:
837-
return True
838-
839-
wait_until(raises_cursor_not_found, 'close cursor')
840-
841-
def test_kill_cursors_with_tuple(self):
842-
if (client_context.is_mongos
843-
and not client_context.version.at_least(2, 4, 7)):
844-
# Old mongos sends incorrectly formatted error response when
845-
# cursor isn't found, see SERVER-9738.
846-
raise SkipTest("Can't test kill_cursors against old mongos")
847-
848-
self.collection = self.client.pymongo_test.test
849-
self.collection.drop()
850-
851-
self.collection.insert_many([{'_id': i} for i in range(200)])
852-
cursor = self.collection.find().batch_size(1)
853-
next(cursor)
854-
self.client.kill_cursors(
855-
[cursor.cursor_id],
856-
self.client.address)
857-
858-
# Prevent killcursors from reaching the server while a getmore is in
859-
# progress -- the server logs "Assertion: 16089:Cannot kill active
860-
# cursor."
861-
time.sleep(2)
862-
863-
def raises_cursor_not_found():
864-
try:
865-
next(cursor)
866-
return False
867-
except CursorNotFound:
868-
return True
869-
870-
wait_until(raises_cursor_not_found, 'close cursor')
871-
872807
def test_lazy_connect_w0(self):
873808
# Ensure that connect-on-demand works when the first operation is
874809
# an unacknowledged write. This exercises _writable_max_wire_version().

test/test_cursor.py

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@
3030
DESCENDING,
3131
ALL,
3232
OFF)
33-
from pymongo.command_cursor import CommandCursor
3433
from pymongo.cursor import CursorType
35-
from pymongo.cursor_manager import CursorManager
3634
from pymongo.errors import (InvalidOperation,
3735
OperationFailure,
3836
ExecutionTimeout)
@@ -1217,38 +1215,6 @@ def test_comment(self):
12171215
next(cursor)
12181216
self.assertRaises(InvalidOperation, cursor.comment, 'hello')
12191217

1220-
def test_cursor_transfer(self):
1221-
1222-
# This is just a test, don't try this at home...
1223-
1224-
client = client_context.rs_or_standalone_client
1225-
db = client.pymongo_test
1226-
1227-
db.test.delete_many({})
1228-
db.test.insert_many([{'_id': i} for i in range(200)])
1229-
1230-
class CManager(CursorManager):
1231-
def __init__(self, client):
1232-
super(CManager, self).__init__(client)
1233-
1234-
def close(self, dummy, dummy2):
1235-
# Do absolutely nothing...
1236-
pass
1237-
1238-
client.set_cursor_manager(CManager)
1239-
self.addCleanup(client.set_cursor_manager, CursorManager)
1240-
docs = []
1241-
cursor = db.test.find().batch_size(10)
1242-
docs.append(next(cursor))
1243-
cursor.close()
1244-
docs.extend(cursor)
1245-
self.assertEqual(len(docs), 10)
1246-
cmd_cursor = {'id': cursor.cursor_id, 'firstBatch': []}
1247-
ccursor = CommandCursor(cursor.collection, cmd_cursor,
1248-
cursor.address, retrieved=cursor.retrieved)
1249-
docs.extend(ccursor)
1250-
self.assertEqual(len(docs), 200)
1251-
12521218
def test_modifiers(self):
12531219
cur = self.db.test.find()
12541220
self.assertTrue('$query' not in cur._Cursor__query_spec())

test/test_cursor_manager.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
"""Test the cursor_manager module."""
1616

1717
import sys
18+
import warnings
1819

1920
sys.path[0:0] = [""]
2021

22+
from pymongo.command_cursor import CommandCursor
2123
from pymongo.cursor_manager import CursorManager
2224
from pymongo.errors import CursorNotFound
2325
from pymongo.message import _CursorAddress
@@ -34,6 +36,10 @@ class TestCursorManager(IntegrationTest):
3436
@classmethod
3537
def setUpClass(cls):
3638
super(TestCursorManager, cls).setUpClass()
39+
cls.warn_context = warnings.catch_warnings()
40+
cls.warn_context.__enter__()
41+
warnings.simplefilter("ignore", DeprecationWarning)
42+
3743
cls.collection = cls.db.test
3844
cls.collection.drop()
3945

@@ -42,6 +48,8 @@ def setUpClass(cls):
4248

4349
@classmethod
4450
def tearDownClass(cls):
51+
cls.warn_context.__exit__()
52+
cls.warn_context = None
4553
cls.collection.drop()
4654

4755
def test_cursor_manager_validation(self):
@@ -89,6 +97,37 @@ def raises_cursor_not_found():
8997
wait_until(raises_cursor_not_found, 'close cursor')
9098
self.assertTrue(self.close_was_called)
9199

100+
def test_cursor_transfer(self):
101+
102+
# This is just a test, don't try this at home...
103+
104+
client = rs_or_single_client()
105+
db = client.pymongo_test
106+
107+
db.test.delete_many({})
108+
db.test.insert_many([{'_id': i} for i in range(200)])
109+
110+
class CManager(CursorManager):
111+
def __init__(self, client):
112+
super(CManager, self).__init__(client)
113+
114+
def close(self, dummy, dummy2):
115+
# Do absolutely nothing...
116+
pass
117+
118+
client.set_cursor_manager(CManager)
119+
docs = []
120+
cursor = db.test.find().batch_size(10)
121+
docs.append(next(cursor))
122+
cursor.close()
123+
docs.extend(cursor)
124+
self.assertEqual(len(docs), 10)
125+
cmd_cursor = {'id': cursor.cursor_id, 'firstBatch': []}
126+
ccursor = CommandCursor(cursor.collection, cmd_cursor,
127+
cursor.address, retrieved=cursor.retrieved)
128+
docs.extend(ccursor)
129+
self.assertEqual(len(docs), 200)
130+
92131

93132
if __name__ == "__main__":
94133
unittest.main()

0 commit comments

Comments
 (0)