commit python-pymongo for openSUSE:Factory
Hello community, here is the log from the commit of package python-pymongo for openSUSE:Factory checked in at 2014-08-27 07:46:44 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pymongo (Old) and /work/SRC/openSUSE:Factory/.python-pymongo.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-pymongo" Changes: -------- --- /work/SRC/openSUSE:Factory/python-pymongo/python-pymongo.changes 2014-07-26 09:42:25.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python-pymongo.new/python-pymongo.changes 2014-08-27 07:47:10.000000000 +0200 @@ -1,0 +2,16 @@ +Tue Aug 26 10:18:16 UTC 2014 - mlin@suse.com + +- Update to version 2.7.2 + * Insert _id in document after applying non-copying SONManipulators + (https://jira.mongodb.org/browse/PYTHON-709) + * Fix exhaust cursor error-handling (https://jira.mongodb.org/browse/PYTHON-736) + * Handle network errors when adding existing credentials to sockets + (https://jira.mongodb.org/browse/PYTHON-732) + * ObjectId.is_valid(None) should be False (https://jira.mongodb.org/browse/PYTHON-712) + * Clarify versionchanged line for bulk insert (https://jira.mongodb.org/browse/PYTHON-738) + * Work around localhost exception issues in add_user when connected to + MongoDB >= 2.7.1 (https://jira.mongodb.org/browse/PYTHON-714) + * Fix Bulk API legacy upsert _id compatibility (https://jira.mongodb.org/browse/PYTHON-705) + * SON.to_dict shouldn't change original data (https://jira.mongodb.org/browse/PYTHON-710) + +------------------------------------------------------------------- Old: ---- pymongo-2.7.1.tar.gz New: ---- pymongo-2.7.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pymongo.spec ++++++ --- /var/tmp/diff_new_pack.1OZ0g9/_old 2014-08-27 07:47:11.000000000 +0200 +++ /var/tmp/diff_new_pack.1OZ0g9/_new 2014-08-27 07:47:11.000000000 +0200 @@ -17,7 +17,7 @@ Name: python-pymongo -Version: 2.7.1 +Version: 2.7.2 Release: 0 Url: http://github.com/mongodb/mongo-python-driver Summary: Python driver for MongoDB ++++++ pymongo-2.7.1.tar.gz -> pymongo-2.7.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/PKG-INFO new/pymongo-2.7.2/PKG-INFO --- old/pymongo-2.7.1/PKG-INFO 2014-05-23 23:49:14.000000000 +0200 +++ new/pymongo-2.7.2/PKG-INFO 2014-07-29 23:46:02.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pymongo -Version: 2.7.1 +Version: 2.7.2 Summary: Python driver for MongoDB http://www.mongodb.org Home-page: http://github.com/mongodb/mongo-python-driver Author: Bernie Hackett diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/bson/objectid.py new/pymongo-2.7.2/bson/objectid.py --- old/pymongo-2.7.1/bson/objectid.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/bson/objectid.py 2014-07-29 23:29:27.000000000 +0200 @@ -140,6 +140,9 @@ .. versionadded:: 2.3 """ + if not oid: + return False + try: ObjectId(oid) return True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/bson/son.py new/pymongo-2.7.2/bson/son.py --- old/pymongo-2.7.1/bson/son.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/bson/son.py 2014-07-29 23:29:27.000000000 +0200 @@ -226,12 +226,12 @@ def transform_value(value): if isinstance(value, list): return [transform_value(v) for v in value] - if isinstance(value, SON): - value = dict(value) - if isinstance(value, dict): - for k, v in value.iteritems(): - value[k] = transform_value(v) - return value + elif isinstance(value, dict): + return dict([ + (k, transform_value(v)) + for k, v in value.iteritems()]) + else: + return value return transform_value(dict(self)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/doc/changelog.rst new/pymongo-2.7.2/doc/changelog.rst --- old/pymongo-2.7.1/doc/changelog.rst 2014-05-23 22:37:25.000000000 +0200 +++ new/pymongo-2.7.2/doc/changelog.rst 2014-07-29 23:29:27.000000000 +0200 @@ -1,6 +1,23 @@ Changelog ========= +Changes in Version 2.7.2 +------------------------ + +Version 2.7.2 includes fixes for upsert reporting in the bulk API for MongoDB +versions previous to 2.6, a regression in how son manipulators are applied in +:meth:`~pymongo.collection.Collection.insert`, a few obscure connection pool +semaphore leaks, and a few other minor issues. See the list of issues resolved +for full details. + +Issues Resolved +............... + +See the `PyMongo 2.7.2 release notes in JIRA`_ for the list of resolved issues +in this release. + +.. _PyMongo 2.7.2 release notes in JIRA: https://jira.mongodb.org/browse/PYTHON/fixforversion/14005 + Changes in Version 2.7.1 ------------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/pymongo/__init__.py new/pymongo-2.7.2/pymongo/__init__.py --- old/pymongo-2.7.1/pymongo/__init__.py 2014-05-23 22:40:53.000000000 +0200 +++ new/pymongo-2.7.2/pymongo/__init__.py 2014-07-29 23:31:05.000000000 +0200 @@ -77,7 +77,7 @@ ALL = 2 """Profile all operations.""" -version_tuple = (2, 7, 1) +version_tuple = (2, 7, 2) def get_version_string(): if isinstance(version_tuple[-1], basestring): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/pymongo/bulk.py new/pymongo-2.7.2/pymongo/bulk.py --- old/pymongo-2.7.1/pymongo/bulk.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/pymongo/bulk.py 2014-07-29 23:29:27.000000000 +0200 @@ -101,13 +101,25 @@ if run.op_type == _INSERT: full_result['nInserted'] += 1 + elif run.op_type == _UPDATE: if "upserted" in result: doc = {u"index": run.index(index), u"_id": result["upserted"]} full_result["upserted"].append(doc) full_result['nUpserted'] += affected + # Versions of MongoDB before 2.6 don't return the _id for an + # upsert if _id is not an ObjectId. + elif result.get("updatedExisting") == False and affected == 1: + op = run.ops[index] + # If _id is in both the update document *and* the query spec + # the update document _id takes precedence. + _id = op['u'].get('_id', op['q'].get('_id')) + doc = {u"index": run.index(index), u"_id": _id} + full_result["upserted"].append(doc) + full_result['nUpserted'] += affected else: full_result['nMatched'] += affected + elif run.op_type == _DELETE: full_result['nRemoved'] += affected diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/pymongo/collection.py new/pymongo-2.7.2/pymongo/collection.py --- old/pymongo-2.7.1/pymongo/collection.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/pymongo/collection.py 2014-07-29 23:29:27.000000000 +0200 @@ -356,7 +356,7 @@ Support for passing `getLastError` options as keyword arguments. .. versionchanged:: 1.1 - Bulk insert works with any iterable + Bulk insert works with an iterable sequence of documents. .. mongodoc:: insert """ @@ -378,11 +378,14 @@ def gen(): db = self.__database for doc in docs: + # Apply user-configured SON manipulators. This order of + # operations is required for backwards compatibility, + # see PYTHON-709. + doc = db._apply_incoming_manipulators(doc, self) if '_id' not in doc: doc['_id'] = ObjectId() - # Apply user-configured SON manipulators. - doc = db._fix_incoming(doc, self) + doc = db._apply_incoming_copying_manipulators(doc, self) ids.append(doc['_id']) yield doc else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/pymongo/cursor.py new/pymongo-2.7.2/pymongo/cursor.py --- old/pymongo-2.7.1/pymongo/cursor.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/pymongo/cursor.py 2014-07-29 23:29:27.000000000 +0200 @@ -22,8 +22,8 @@ from pymongo import helpers, message, read_preferences from pymongo.read_preferences import ReadPreference, secondary_ok_commands from pymongo.errors import (AutoReconnect, - CursorNotFound, - InvalidOperation) + InvalidOperation, + OperationFailure) _QUERY_OPTIONS = { "tailable_cursor": 2, @@ -56,6 +56,15 @@ self.pool.maybe_return_socket(self.sock) self.sock, self.pool = None, None + def error(self): + """Clean up after an error on the managed socket. + """ + if self.sock: + self.sock.close() + + # Return the closed socket to avoid a semaphore leak in the pool. + self.close() + # 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 @@ -914,8 +923,14 @@ # due to a socket timeout. self.__killed = True raise - else: # exhaust cursor - no getMore message - response = client._exhaust_next(self.__exhaust_mgr.sock) + else: + # Exhaust cursor - no getMore message. + try: + response = client._exhaust_next(self.__exhaust_mgr.sock) + except AutoReconnect: + self.__killed = True + self.__exhaust_mgr.error() + raise try: response = helpers._unpack_response(response, self.__id, @@ -923,8 +938,10 @@ self.__tz_aware, self.__uuid_subtype, self.__compile_re) - except CursorNotFound: + except OperationFailure: self.__killed = True + # Make sure exhaust socket is returned immediately, if necessary. + self.__die() # If this is a tailable cursor the error is likely # due to capped collection roll over. Setting # self.__killed to True ensures Cursor.alive will be @@ -936,8 +953,11 @@ # Don't send kill cursors to another server after a "not master" # error. It's completely pointless. self.__killed = True + # Make sure exhaust socket is returned immediately, if necessary. + self.__die() client.disconnect() raise + self.__id = response["cursor_id"] # starting from doesn't get set on getmore's for tailable cursors diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/pymongo/database.py new/pymongo-2.7.2/pymongo/database.py --- old/pymongo-2.7.1/pymongo/database.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/pymongo/database.py 2014-07-29 23:29:27.000000000 +0200 @@ -247,6 +247,16 @@ return Collection(self, name, **opts) + def _apply_incoming_manipulators(self, son, collection): + for manipulator in self.__incoming_manipulators: + son = manipulator.transform_incoming(son, collection) + return son + + def _apply_incoming_copying_manipulators(self, son, collection): + for manipulator in self.__incoming_copying_manipulators: + son = manipulator.transform_incoming(son, collection) + return son + def _fix_incoming(self, son, collection): """Apply manipulators to an incoming SON object before it gets stored. @@ -254,10 +264,8 @@ - `son`: the son object going into the database - `collection`: the collection the son object is being saved in """ - for manipulator in self.__incoming_manipulators: - son = manipulator.transform_incoming(son, collection) - for manipulator in self.__incoming_copying_manipulators: - son = manipulator.transform_incoming(son, collection) + son = self._apply_incoming_manipulators(son, collection) + son = self._apply_incoming_copying_manipulators(son, collection) return son def _fix_outgoing(self, son, collection): @@ -782,17 +790,20 @@ try: uinfo = self.command("usersInfo", name, read_preference=ReadPreference.PRIMARY) + self._create_or_update_user( + (not uinfo["users"]), name, password, read_only, **kwargs) except OperationFailure, exc: # MongoDB >= 2.5.3 requires the use of commands to manage # users. if exc.code in common.COMMAND_NOT_FOUND_CODES: self._legacy_add_user(name, password, read_only, **kwargs) - return - raise - - # Create the user if not found in uinfo, otherwise update one. - self._create_or_update_user( - (not uinfo["users"]), name, password, read_only, **kwargs) + # Unauthorized. MongoDB >= 2.7.1 has a narrow localhost exception, + # and we must add a user before sending commands. + elif exc.code == 13: + self._create_or_update_user( + True, name, password, read_only, **kwargs) + else: + raise def remove_user(self, name): """Remove user `name` from this :class:`Database`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/pymongo/mongo_client.py new/pymongo-2.7.2/pymongo/mongo_client.py --- old/pymongo-2.7.1/pymongo/mongo_client.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/pymongo/mongo_client.py 2014-07-29 23:29:27.000000000 +0200 @@ -673,13 +673,16 @@ 'max_write_batch_size', common.MAX_WRITE_BATCH_SIZE) def __simple_command(self, sock_info, dbname, spec): - """Send a command to the server. + """Send a command to the server. May raise AutoReconnect. """ rqst_id, msg, _ = message.query(0, dbname + '.$cmd', 0, -1, spec) start = time.time() try: sock_info.sock.sendall(msg) response = self.__receive_message_on_socket(1, rqst_id, sock_info) + except socket.error, e: + sock_info.close() + raise AutoReconnect(e) except: sock_info.close() raise @@ -916,7 +919,7 @@ "%s %s" % (host_details, str(why))) try: self.__check_auth(sock_info) - except OperationFailure: + except: connection_pool.maybe_return_socket(sock_info) raise return sock_info @@ -1189,27 +1192,35 @@ sock_info = self.__socket(member) exhaust = kwargs.get('exhaust') try: - try: - if not exhaust and "network_timeout" in kwargs: - sock_info.sock.settimeout(kwargs["network_timeout"]) - response = self.__send_and_receive(message, sock_info) - - if not exhaust: - if "network_timeout" in kwargs: - sock_info.sock.settimeout(self.__net_timeout) + if not exhaust and "network_timeout" in kwargs: + sock_info.sock.settimeout(kwargs["network_timeout"]) + + response = self.__send_and_receive(message, sock_info) - return (None, (response, sock_info, member.pool)) - except (ConnectionFailure, socket.error), e: - self.disconnect() - raise AutoReconnect(str(e)) - finally: if not exhaust: + if "network_timeout" in kwargs: + sock_info.sock.settimeout(self.__net_timeout) + member.pool.maybe_return_socket(sock_info) + return (None, (response, sock_info, member.pool)) + except (ConnectionFailure, socket.error), e: + self.disconnect() + member.pool.maybe_return_socket(sock_info) + raise AutoReconnect(str(e)) + except: + member.pool.maybe_return_socket(sock_info) + raise + def _exhaust_next(self, sock_info): """Used with exhaust cursors to get the next batch off the socket. + + Can raise AutoReconnect. """ - return self.__receive_message_on_socket(1, None, sock_info) + try: + return self.__receive_message_on_socket(1, None, sock_info) + except socket.error, e: + raise AutoReconnect(str(e)) def start_request(self): """Ensure the current thread or greenlet always uses the same socket diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/pymongo/mongo_replica_set_client.py new/pymongo-2.7.2/pymongo/mongo_replica_set_client.py --- old/pymongo-2.7.1/pymongo/mongo_replica_set_client.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/pymongo/mongo_replica_set_client.py 2014-07-29 23:29:27.000000000 +0200 @@ -1711,8 +1711,13 @@ def _exhaust_next(self, sock_info): """Used with exhaust cursors to get the next batch off the socket. + + Can raise AutoReconnect. """ - return self.__recv_msg(1, None, sock_info) + try: + return self.__recv_msg(1, None, sock_info) + except socket.error, e: + raise AutoReconnect(str(e)) def start_request(self): """Ensure the current thread or greenlet always uses the same socket diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/pymongo.egg-info/PKG-INFO new/pymongo-2.7.2/pymongo.egg-info/PKG-INFO --- old/pymongo-2.7.1/pymongo.egg-info/PKG-INFO 2014-05-23 23:49:14.000000000 +0200 +++ new/pymongo-2.7.2/pymongo.egg-info/PKG-INFO 2014-07-29 23:46:02.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pymongo -Version: 2.7.1 +Version: 2.7.2 Summary: Python driver for MongoDB http://www.mongodb.org Home-page: http://github.com/mongodb/mongo-python-driver Author: Bernie Hackett diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/setup.py new/pymongo-2.7.2/setup.py --- old/pymongo-2.7.1/setup.py 2014-05-23 22:40:36.000000000 +0200 +++ new/pymongo-2.7.2/setup.py 2014-07-29 23:31:13.000000000 +0200 @@ -33,7 +33,7 @@ from distutils.errors import DistutilsPlatformError, DistutilsExecError from distutils.core import Extension -version = "2.7.1" +version = "2.7.2" f = open("README.rst") try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/test/test_bulk.py new/pymongo-2.7.2/test/test_bulk.py --- old/pymongo-2.7.1/test/test_bulk.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/test/test_bulk.py 2014-07-29 23:29:27.000000000 +0200 @@ -600,6 +600,28 @@ self.assertEqual(1, self.coll.find({'x': 1}).count()) + def test_client_generated_upsert_id(self): + batch = self.coll.initialize_ordered_bulk_op() + batch.find({'_id': 0}).upsert().update_one({'$set': {'a': 0}}) + batch.find({'a': 1}).upsert().replace_one({'_id': 1}) + if not version.at_least(self.coll.database.connection, (2, 6, 0)): + # This case is only possible in MongoDB versions before 2.6. + batch.find({'_id': 3}).upsert().replace_one({'_id': 2}) + else: + # This is just here to make the counts right in all cases. + batch.find({'_id': 2}).upsert().replace_one({'_id': 2}) + result = batch.execute() + self.assertEqualResponse( + {'nMatched': 0, + 'nModified': 0, + 'nUpserted': 3, + 'nInserted': 0, + 'nRemoved': 0, + 'upserted': [{'index': 0, '_id': 0}, + {'index': 1, '_id': 1}, + {'index': 2, '_id': 2}]}, + result) + def test_single_ordered_batch(self): batch = self.coll.initialize_ordered_bulk_op() batch.insert({'a': 1}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/test/test_client.py new/pymongo-2.7.2/test/test_client.py --- old/pymongo-2.7.1/test/test_client.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/test/test_client.py 2014-07-29 23:29:27.000000000 +0200 @@ -34,7 +34,7 @@ from pymongo.mongo_client import MongoClient from pymongo.database import Database from pymongo.pool import SocketInfo -from pymongo import thread_util, common +from pymongo import auth, thread_util, common from pymongo.errors import (AutoReconnect, ConfigurationError, ConnectionFailure, @@ -52,9 +52,11 @@ server_started_with_auth, TestRequestMixin, _TestLazyConnectMixin, + _TestExhaustCursorMixin, lazy_client_trial, NTHREADS, - get_pool) + get_pool, + one) def get_client(*args, **kwargs): @@ -999,6 +1001,42 @@ client = get_client(_connect=False) client.pymongo_test.test.remove(w=0) + def test_auth_network_error(self): + # Make sure there's no semaphore leak if we get a network error + # when authenticating a new socket with cached credentials. + auth_client = get_client() + if not server_started_with_auth(auth_client): + raise SkipTest('Authentication is not enabled on server') + + auth_client.admin.add_user('admin', 'password') + auth_client.admin.authenticate('admin', 'password') + try: + # Get a client with one socket so we detect if it's leaked. + c = get_client(max_pool_size=1, waitQueueTimeoutMS=1) + + # Simulate an authenticate() call on a different socket. + credentials = auth._build_credentials_tuple( + 'MONGODB-CR', 'admin', + unicode('admin'), unicode('password'), + {}) + + c._cache_credentials('test', credentials, connect=False) + + # Cause a network error on the actual socket. + pool = get_pool(c) + socket_info = one(pool.sockets) + socket_info.sock.close() + + # In __check_auth, the client authenticates its socket with the + # new credential, but gets a socket.error. Should be reraised as + # AutoReconnect. + self.assertRaises(AutoReconnect, c.test.collection.find_one) + + # No semaphore leak, the pool is allowed to make a new socket. + c.test.collection.find_one() + finally: + remove_all_users(auth_client.admin) + class TestClientLazyConnect(unittest.TestCase, _TestLazyConnectMixin): def _get_client(self, **kwargs): @@ -1110,5 +1148,10 @@ c.db.collection.find_one() +class TestExhaustCursor(_TestExhaustCursorMixin, unittest.TestCase): + def _get_client(self, **kwargs): + return get_client(**kwargs) + + if __name__ == "__main__": unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/test/test_database.py new/pymongo-2.7.2/test/test_database.py --- old/pymongo-2.7.1/test/test_database.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/test/test_database.py 2014-07-29 23:29:27.000000000 +0200 @@ -45,6 +45,7 @@ OperationFailure) from pymongo.son_manipulator import (AutoReference, NamespaceInjector, + SONManipulator, ObjectIdShuffler) from test import version from test.utils import (catch_warnings, get_command_line, @@ -996,6 +997,29 @@ "maxTimeAlwaysTimeOut", mode="off") + def test_object_to_dict_transformer(self): + # PYTHON-709: Some users rely on their custom SONManipulators to run + # before any other checks, so they can insert non-dict objects and + # have them dictified before the _id is inserted or any other + # processing. + class Thing(object): + def __init__(self, value): + self.value = value + + class ThingTransformer(SONManipulator): + def transform_incoming(self, thing, collection): + return {'value': thing.value} + + db = self.client.foo + db.add_son_manipulator(ThingTransformer()) + t = Thing('value') + + db.test.remove() + db.test.insert([t]) + out = db.test.find_one() + self.assertEqual('value', out.get('value')) + + if __name__ == "__main__": unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/test/test_objectid.py new/pymongo-2.7.2/test/test_objectid.py --- old/pymongo-2.7.1/test/test_objectid.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/test/test_objectid.py 2014-07-29 23:29:27.000000000 +0200 @@ -181,6 +181,7 @@ self.assertEqual(oid_1_9, oid_1_10) def test_is_valid(self): + self.assertFalse(ObjectId.is_valid(None)) self.assertFalse(ObjectId.is_valid(4)) self.assertFalse(ObjectId.is_valid(175.0)) self.assertFalse(ObjectId.is_valid({"test": 4})) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/test/test_read_preferences.py new/pymongo-2.7.2/test/test_read_preferences.py --- old/pymongo-2.7.1/test/test_read_preferences.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/test/test_read_preferences.py 2014-07-29 23:29:27.000000000 +0200 @@ -309,11 +309,11 @@ # Distinct self._test_fn(True, lambda: self.c.pymongo_test.command( - 'distinct', 'test', key={'a': 1})) + 'distinct', 'test', key='a')) self._test_fn(True, lambda: self.c.pymongo_test.command( - 'distinct', 'test', key={'a': 1}, query={'a': 1})) + 'distinct', 'test', key='a', query={'a': 1})) self._test_fn(True, lambda: self.c.pymongo_test.command(SON([ - ('distinct', 'test'), ('key', {'a': 1}), ('query', {'a': 1})]))) + ('distinct', 'test'), ('key', 'a'), ('query', {'a': 1})]))) # Geo stuff. Make sure a 2d index is created and replicated self.c.pymongo_test.system.indexes.insert({ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/test/test_replica_set_client.py new/pymongo-2.7.2/test/test_replica_set_client.py --- old/pymongo-2.7.1/test/test_replica_set_client.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/test/test_replica_set_client.py 2014-07-29 23:29:27.000000000 +0200 @@ -45,13 +45,14 @@ ConnectionFailure, InvalidName, OperationFailure, InvalidOperation) +from pymongo import auth from test import version, port, pair from test.pymongo_mocks import MockReplicaSetClient from test.utils import ( delay, assertReadFrom, assertReadFromAll, read_from_which_host, remove_all_users, assertRaisesExactly, TestRequestMixin, one, server_started_with_auth, pools_from_rs_client, get_pool, - _TestLazyConnectMixin) + _TestLazyConnectMixin, _TestExhaustCursorMixin) class TestReplicaSetClientAgainstStandalone(unittest.TestCase): @@ -1126,6 +1127,42 @@ self.assertFalse(client.alive()) + def test_auth_network_error(self): + # Make sure there's no semaphore leak if we get a network error + # when authenticating a new socket with cached credentials. + auth_client = self._get_client() + if not server_started_with_auth(auth_client): + raise SkipTest('Authentication is not enabled on server') + + auth_client.admin.add_user('admin', 'password') + auth_client.admin.authenticate('admin', 'password') + try: + # Get a client with one socket so we detect if it's leaked. + c = self._get_client(max_pool_size=1, waitQueueTimeoutMS=1) + + # Simulate an authenticate() call on a different socket. + credentials = auth._build_credentials_tuple( + 'MONGODB-CR', 'admin', + unicode('admin'), unicode('password'), + {}) + + c._cache_credentials('test', credentials, connect=False) + + # Cause a network error on the actual socket. + pool = get_pool(c) + socket_info = one(pool.sockets) + socket_info.sock.close() + + # In __check_auth, the client authenticates its socket with the + # new credential, but gets a socket.error. Should be reraised as + # AutoReconnect. + self.assertRaises(AutoReconnect, c.test.collection.find_one) + + # No semaphore leak, the pool is allowed to make a new socket. + c.test.collection.find_one() + finally: + remove_all_users(auth_client.admin) + class TestReplicaSetWireVersion(unittest.TestCase): def test_wire_version(self): @@ -1237,5 +1274,12 @@ self.assertEqual(c.max_write_batch_size, 2) +class TestReplicaSetClientExhaustCursor( + _TestExhaustCursorMixin, + TestReplicaSetClientBase): + + # Base class implements _get_client already. + pass + if __name__ == "__main__": unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/test/test_son.py new/pymongo-2.7.2/test/test_son.py --- old/pymongo-2.7.1/test/test_son.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/test/test_son.py 2014-07-29 23:29:27.000000000 +0200 @@ -67,6 +67,15 @@ ('mike', 'awesome'), ('hello', 'world')))) + # Embedded SON. + d4 = SON([('blah', {'foo': SON()})]) + self.assertEqual(d4, {'blah': {'foo': {}}}) + self.assertEqual(d4, {'blah': {'foo': SON()}}) + self.assertNotEqual(d4, {'blah': {'foo': []}}) + + # Original data unaffected. + self.assertEqual(SON, d4['blah']['foo'].__class__) + def test_to_dict(self): a1 = SON() b2 = SON([("blah", SON())]) @@ -81,6 +90,9 @@ self.assertEqual(dict, c3.to_dict()["blah"][0].__class__) self.assertEqual(dict, d4.to_dict()["blah"]["foo"].__class__) + # Original data unaffected. + self.assertEqual(SON, d4['blah']['foo'].__class__) + def test_pickle(self): simple_son = SON([]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-2.7.1/test/utils.py new/pymongo-2.7.2/test/utils.py --- old/pymongo-2.7.1/test/utils.py 2014-05-23 22:28:24.000000000 +0200 +++ new/pymongo-2.7.2/test/utils.py 2014-07-29 23:29:27.000000000 +0200 @@ -15,14 +15,18 @@ """Utilities for testing pymongo """ +import gc import os import struct import sys import threading +import time from nose.plugins.skip import SkipTest + +from bson.son import SON from pymongo import MongoClient, MongoReplicaSetClient -from pymongo.errors import AutoReconnect +from pymongo.errors import AutoReconnect, ConnectionFailure, OperationFailure from pymongo.pool import NO_REQUEST, NO_SOCKET_YET, SocketInfo from test import host, port, version @@ -453,7 +457,7 @@ # Make concurrency bugs more likely to manifest. interval = None if not sys.platform.startswith('java'): - if sys.version_info >= (3, 2): + if hasattr(sys, 'getswitchinterval'): interval = sys.getswitchinterval() sys.setswitchinterval(1e-6) else: @@ -472,7 +476,7 @@ finally: if not sys.platform.startswith('java'): - if sys.version_info >= (3, 2): + if hasattr(sys, 'setswitchinterval'): sys.setswitchinterval(interval) else: sys.setcheckinterval(interval) @@ -586,6 +590,121 @@ c.max_message_size) +class _TestExhaustCursorMixin(object): + """Test that clients properly handle errors from exhaust cursors. + + Inherit from this class and from unittest.TestCase, and override + _get_client(self, **kwargs). + """ + def test_exhaust_query_server_error(self): + # When doing an exhaust query, the socket stays checked out on success + # but must be checked in on error to avoid semaphore leaks. + client = self._get_client(max_pool_size=1) + if is_mongos(client): + raise SkipTest("Can't use exhaust cursors with mongos") + if not version.at_least(client, (2, 2, 0)): + raise SkipTest("mongod < 2.2.0 closes exhaust socket on error") + + collection = client.pymongo_test.test + pool = get_pool(client) + + sock_info = one(pool.sockets) + # 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) + self.assertRaises(OperationFailure, cursor.next) + self.assertFalse(sock_info.closed) + + # The semaphore was decremented despite the error. + self.assertTrue(pool._socket_semaphore.acquire(blocking=False)) + + def test_exhaust_getmore_server_error(self): + # When doing a getmore on an exhaust cursor, the socket stays checked + # out on success but must be checked in on error to avoid semaphore + # leaks. + client = self._get_client(max_pool_size=1) + if is_mongos(client): + raise SkipTest("Can't use exhaust cursors with mongos") + + # A separate client that doesn't affect the test client's pool. + client2 = self._get_client() + + collection = client.pymongo_test.test + collection.remove() + + # Enough data to ensure it streams down for a few milliseconds. + long_str = 'a' * (256 * 1024) + collection.insert([{'a': long_str} for _ in range(200)]) + + pool = get_pool(client) + pool._check_interval_seconds = None # Never check. + sock_info = one(pool.sockets) + + cursor = collection.find(exhaust=True) + + # Initial query succeeds. + cursor.next() + + # Cause a server error on getmore. + client2.pymongo_test.test.drop() + self.assertRaises(OperationFailure, list, cursor) + + # Make sure the socket is still valid + self.assertEqual(0, collection.count()) + + def test_exhaust_query_network_error(self): + # When doing an exhaust query, the socket stays checked out on success + # but must be checked in on error to avoid semaphore leaks. + client = self._get_client(max_pool_size=1) + if is_mongos(client): + raise SkipTest("Can't use exhaust cursors with mongos") + + collection = client.pymongo_test.test + pool = get_pool(client) + pool._check_interval_seconds = None # Never check. + + # Cause a network error. + sock_info = one(pool.sockets) + sock_info.sock.close() + cursor = collection.find(exhaust=True) + self.assertRaises(ConnectionFailure, cursor.next) + self.assertTrue(sock_info.closed) + + # The semaphore was decremented despite the error. + self.assertTrue(pool._socket_semaphore.acquire(blocking=False)) + + def test_exhaust_getmore_network_error(self): + # When doing a getmore on an exhaust cursor, the socket stays checked + # out on success but must be checked in on error to avoid semaphore + # leaks. + client = self._get_client(max_pool_size=1) + if is_mongos(client): + raise SkipTest("Can't use exhaust cursors with mongos") + + collection = client.pymongo_test.test + collection.remove() + collection.insert([{} for _ in range(200)]) # More than one batch. + pool = get_pool(client) + pool._check_interval_seconds = None # Never check. + + cursor = collection.find(exhaust=True) + + # Initial query succeeds. + cursor.next() + + # Cause a network error. + sock_info = cursor._Cursor__exhaust_mgr.sock + sock_info.sock.close() + + # A getmore fails. + self.assertRaises(ConnectionFailure, list, cursor) + self.assertTrue(sock_info.closed) + + # The semaphore was decremented despite the error. + self.assertTrue(pool._socket_semaphore.acquire(blocking=False)) + + # Backport of WarningMessage from python 2.6, with fixed syntax for python 2.4. class WarningMessage(object): -- To unsubscribe, e-mail: opensuse-commit+unsubscribe@opensuse.org For additional commands, e-mail: opensuse-commit+help@opensuse.org
participants (1)
-
root@hilbert.suse.de