Improve connect/disconnect
- document disconnect + sample of usage - add more test cases to prevent github issues regressions
This commit is contained in:
		| @@ -5,7 +5,7 @@ Changelog | |||||||
| Development | Development | ||||||
| =========== | =========== | ||||||
| - expose `mongoengine.connection.disconnect` and `mongoengine.connection.disconnect_all` | - expose `mongoengine.connection.disconnect` and `mongoengine.connection.disconnect_all` | ||||||
| - POTENTIAL BREAKING CHANGE: Fixes in connect/disconnect methods | - POTENTIAL BREAKING CHANGE: Fixes in connect/disconnect methods #565 #566 | ||||||
|     - calling `connect` 2 times with the same alias and different parameter will raise an error (should call disconnect first) |     - calling `connect` 2 times with the same alias and different parameter will raise an error (should call disconnect first) | ||||||
|     - disconnect now clears `mongoengine.connection._connection_settings` |     - disconnect now clears `mongoengine.connection._connection_settings` | ||||||
|     - disconnect now clears the cached attribute `Document._collection` |     - disconnect now clears the cached attribute `Document._collection` | ||||||
|   | |||||||
| @@ -4,9 +4,11 @@ | |||||||
| Connecting to MongoDB | Connecting to MongoDB | ||||||
| ===================== | ===================== | ||||||
|  |  | ||||||
| To connect to a running instance of :program:`mongod`, use the | Connections in MongoEngine are registered globally and are identified with aliases. | ||||||
| :func:`~mongoengine.connect` function. The first argument is the name of the | If no `alias` is provided during the connection, it will use "default" as alias. | ||||||
| database to connect to:: |  | ||||||
|  | To connect to a running instance of :program:`mongod`, use the :func:`~mongoengine.connect` | ||||||
|  | function. The first argument is the name of the database to connect to:: | ||||||
|  |  | ||||||
|     from mongoengine import connect |     from mongoengine import connect | ||||||
|     connect('project1') |     connect('project1') | ||||||
| @@ -42,6 +44,9 @@ the :attr:`host` to | |||||||
|     will establish connection to ``production`` database using |     will establish connection to ``production`` database using | ||||||
|     ``admin`` username and ``qwerty`` password. |     ``admin`` username and ``qwerty`` password. | ||||||
|  |  | ||||||
|  | .. note:: Calling :func:`~mongoengine.connect` without argument will establish | ||||||
|  |     a connection to the "test" database by default | ||||||
|  |  | ||||||
| Replica Sets | Replica Sets | ||||||
| ============ | ============ | ||||||
|  |  | ||||||
| @@ -71,6 +76,8 @@ is used. | |||||||
| In the background this uses :func:`~mongoengine.register_connection` to | In the background this uses :func:`~mongoengine.register_connection` to | ||||||
| store the data and you can register all aliases up front if required. | store the data and you can register all aliases up front if required. | ||||||
|  |  | ||||||
|  | Documents defined in different database | ||||||
|  | --------------------------------------- | ||||||
| Individual documents can also support multiple databases by providing a | Individual documents can also support multiple databases by providing a | ||||||
| `db_alias` in their meta data. This allows :class:`~pymongo.dbref.DBRef` | `db_alias` in their meta data. This allows :class:`~pymongo.dbref.DBRef` | ||||||
| objects to point across databases and collections. Below is an example schema, | objects to point across databases and collections. Below is an example schema, | ||||||
| @@ -93,6 +100,33 @@ using 3 different databases to store data:: | |||||||
|             meta = {'db_alias': 'users-books-db'} |             meta = {'db_alias': 'users-books-db'} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Disconnecting an existing connection | ||||||
|  | ------------------------------------ | ||||||
|  | The function :func:`~mongoengine.disconnect` can be used to | ||||||
|  | disconnect a particular connection. This can be used to change a | ||||||
|  | connection globally:: | ||||||
|  |  | ||||||
|  |         from mongoengine import connect, disconnect | ||||||
|  |         connect('a_db', alias='db1') | ||||||
|  |  | ||||||
|  |         class User(Document): | ||||||
|  |             name = StringField() | ||||||
|  |             meta = {'db_alias': 'db1'} | ||||||
|  |  | ||||||
|  |         disconnect(alias='db1') | ||||||
|  |  | ||||||
|  |         connect('another_db', alias='db1') | ||||||
|  |  | ||||||
|  | .. note:: Calling :func:`~mongoengine.disconnect` without argument | ||||||
|  |     will disconnect the "default" connection | ||||||
|  |  | ||||||
|  | .. note:: Since connections gets registered globally, it is important | ||||||
|  |     to use the `disconnect` function from MongoEngine and not the | ||||||
|  |     `disconnect()` method of an existing connection (pymongo.MongoClient) | ||||||
|  |  | ||||||
|  | .. note:: :class:`~mongoengine.Document` are caching the pymongo collection. | ||||||
|  |     using `disconnect` ensures that it gets cleaned as well | ||||||
|  |  | ||||||
| Context Managers | Context Managers | ||||||
| ================ | ================ | ||||||
| Sometimes you may want to switch the database or collection to query against. | Sometimes you may want to switch the database or collection to query against. | ||||||
| @@ -119,7 +153,7 @@ access to the same User document across databases:: | |||||||
|  |  | ||||||
| Switch Collection | Switch Collection | ||||||
| ----------------- | ----------------- | ||||||
| The :class:`~mongoengine.context_managers.switch_collection` context manager | The :func:`~mongoengine.context_managers.switch_collection` context manager | ||||||
| allows you to change the collection for a given class allowing quick and easy | allows you to change the collection for a given class allowing quick and easy | ||||||
| access to the same Group document across collection:: | access to the same Group document across collection:: | ||||||
|  |  | ||||||
|   | |||||||
| @@ -318,6 +318,9 @@ def connect(db=None, alias=DEFAULT_CONNECTION_NAME, **kwargs): | |||||||
|     Multiple databases are supported by using aliases. Provide a separate |     Multiple databases are supported by using aliases. Provide a separate | ||||||
|     `alias` to connect to a different instance of :program:`mongod`. |     `alias` to connect to a different instance of :program:`mongod`. | ||||||
|  |  | ||||||
|  |     In order to replace a connection identified by a given alias, you'll | ||||||
|  |     need to call ``disconnect`` first | ||||||
|  |  | ||||||
|     See the docstring for `register_connection` for more details about all |     See the docstring for `register_connection` for more details about all | ||||||
|     supported kwargs. |     supported kwargs. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -795,13 +795,13 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): | |||||||
|         .. versionchanged:: 0.10.7 |         .. versionchanged:: 0.10.7 | ||||||
|             :class:`OperationError` exception raised if no collection available |             :class:`OperationError` exception raised if no collection available | ||||||
|         """ |         """ | ||||||
|         col_name = cls._get_collection_name() |         coll_name = cls._get_collection_name() | ||||||
|         if not col_name: |         if not coll_name: | ||||||
|             raise OperationError('Document %s has no collection defined ' |             raise OperationError('Document %s has no collection defined ' | ||||||
|                                  '(is it abstract ?)' % cls) |                                  '(is it abstract ?)' % cls) | ||||||
|         cls._collection = None |         cls._collection = None | ||||||
|         db = cls._get_db() |         db = cls._get_db() | ||||||
|         db.drop_collection(col_name) |         db.drop_collection(coll_name) | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def create_index(cls, keys, background=False, **kwargs): |     def create_index(cls, keys, background=False, **kwargs): | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| import datetime | import datetime | ||||||
|  |  | ||||||
|  | from pymongo import MongoClient | ||||||
| from pymongo.errors import OperationFailure | from pymongo.errors import OperationFailure | ||||||
|  |  | ||||||
| try: | try: | ||||||
| @@ -238,12 +240,25 @@ class ConnectionTest(unittest.TestCase): | |||||||
|         conn = get_connection('testdb6') |         conn = get_connection('testdb6') | ||||||
|         self.assertIsInstance(conn, mongomock.MongoClient) |         self.assertIsInstance(conn, mongomock.MongoClient) | ||||||
|  |  | ||||||
|     def test_disconnect(self): |     def test_disconnect_cleans_globals(self): | ||||||
|         """Ensure that the disconnect() method works properly""" |         """Ensure that the disconnect() method cleans the globals objects""" | ||||||
|         connections = mongoengine.connection._connections |         connections = mongoengine.connection._connections | ||||||
|         dbs = mongoengine.connection._dbs |         dbs = mongoengine.connection._dbs | ||||||
|         connection_settings = mongoengine.connection._connection_settings |         connection_settings = mongoengine.connection._connection_settings | ||||||
|  |  | ||||||
|  |         connect('mongoenginetest') | ||||||
|  |  | ||||||
|  |         self.assertEqual(len(connections), 1) | ||||||
|  |         self.assertEqual(len(dbs), 1) | ||||||
|  |         self.assertEqual(len(connection_settings), 1) | ||||||
|  |  | ||||||
|  |         disconnect() | ||||||
|  |         self.assertEqual(len(connections), 0) | ||||||
|  |         self.assertEqual(len(dbs), 0) | ||||||
|  |         self.assertEqual(len(connection_settings), 0) | ||||||
|  |  | ||||||
|  |     def test_disconnect_cleans_cached_collection_attribute_in_document(self): | ||||||
|  |         """Ensure that the disconnect() method works properly""" | ||||||
|         conn1 = connect('mongoenginetest') |         conn1 = connect('mongoenginetest') | ||||||
|  |  | ||||||
|         class History(Document): |         class History(Document): | ||||||
| @@ -252,29 +267,50 @@ class ConnectionTest(unittest.TestCase): | |||||||
|         self.assertIsNone(History._collection) |         self.assertIsNone(History._collection) | ||||||
|  |  | ||||||
|         History.drop_collection() |         History.drop_collection() | ||||||
|  |  | ||||||
|         History.objects.first()     # will trigger the caching of _collection attribute |         History.objects.first()     # will trigger the caching of _collection attribute | ||||||
|  |  | ||||||
|         self.assertIsNotNone(History._collection) |         self.assertIsNotNone(History._collection) | ||||||
|  |  | ||||||
|         self.assertEqual(len(connections), 1) |  | ||||||
|         self.assertEqual(len(dbs), 1) |  | ||||||
|         self.assertEqual(len(connection_settings), 1) |  | ||||||
|  |  | ||||||
|         disconnect() |         disconnect() | ||||||
|  |  | ||||||
|         self.assertIsNone(History._collection) |         self.assertIsNone(History._collection) | ||||||
|  |  | ||||||
|         self.assertEqual(len(connections), 0) |  | ||||||
|         self.assertEqual(len(dbs), 0) |  | ||||||
|         self.assertEqual(len(connection_settings), 0) |  | ||||||
|  |  | ||||||
|         with self.assertRaises(MongoEngineConnectionError) as ctx_err: |         with self.assertRaises(MongoEngineConnectionError) as ctx_err: | ||||||
|             History.objects.first() |             History.objects.first() | ||||||
|         self.assertEqual("You have not defined a default connection", str(ctx_err.exception)) |         self.assertEqual("You have not defined a default connection", str(ctx_err.exception)) | ||||||
|  |  | ||||||
|         conn2 = connect('mongoenginetest') |     def test_connect_disconnect_works_on_same_document(self): | ||||||
|         History.objects.first()     # Make sure its back on track |         """Ensure that the connect/disconnect works properly with a single Document""" | ||||||
|         self.assertTrue(conn1 is not conn2) |         db1 = 'db1' | ||||||
|  |         db2 = 'db2' | ||||||
|  |  | ||||||
|  |         # Ensure freshness of the 2 databases through pymongo | ||||||
|  |         client = MongoClient('localhost', 27017) | ||||||
|  |         client.drop_database(db1) | ||||||
|  |         client.drop_database(db2) | ||||||
|  |  | ||||||
|  |         # Save in db1 | ||||||
|  |         connect(db1) | ||||||
|  |  | ||||||
|  |         class User(Document): | ||||||
|  |             name = StringField(required=True) | ||||||
|  |  | ||||||
|  |         user1 = User(name='John is in db1').save() | ||||||
|  |         disconnect() | ||||||
|  |  | ||||||
|  |         # Make sure save doesnt work at this stage | ||||||
|  |         with self.assertRaises(MongoEngineConnectionError): | ||||||
|  |             User(name='Wont work').save() | ||||||
|  |  | ||||||
|  |         # Save in db2 | ||||||
|  |         connect(db2) | ||||||
|  |         user2 = User(name='Bob is in db2').save() | ||||||
|  |         disconnect() | ||||||
|  |  | ||||||
|  |         db1_users = list(client[db1].user.find()) | ||||||
|  |         self.assertEqual(db1_users, [{'_id': user1.id, 'name': 'John is in db1'}]) | ||||||
|  |         db2_users = list(client[db2].user.find()) | ||||||
|  |         self.assertEqual(db2_users, [{'_id': user2.id, 'name': 'Bob is in db2'}]) | ||||||
|  |  | ||||||
|     def test_disconnect_silently_pass_if_alias_does_not_exist(self): |     def test_disconnect_silently_pass_if_alias_does_not_exist(self): | ||||||
|         connections = mongoengine.connection._connections |         connections = mongoengine.connection._connections | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user