Improve connect/disconnect

- document disconnect + sample of usage
- add more test cases to prevent github issues regressions
This commit is contained in:
Bastien Gérard 2019-04-24 22:44:07 +02:00
parent d1467c2f73
commit b1e28d02f7
5 changed files with 95 additions and 22 deletions

View File

@ -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`

View File

@ -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::

View File

@ -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.

View File

@ -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):

View File

@ -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