Skip to content

Commit e006c25

Browse files
authored
Merge pull request MongoEngine#2732 from bagerard/davidlatwe-alternative_conection
[Clone] Allow specifying alternative connection_class
2 parents 0ae8704 + 585532d commit e006c25

File tree

5 files changed

+103
-56
lines changed

5 files changed

+103
-56
lines changed

docs/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ Changelog
77
Development
88
===========
99
- (Fill this out as you fix issues and develop your features).
10+
- Added `mongo_client_class` optional parameter to connect() to allow to use an alternative mongo client than pymongo.MongoClient.
11+
Typically to support mock mongo libraries like mongomock, montydb, mongita #2729
12+
- BREAKING CHANGE: connecting MongoEngine with mongomock should now use the new `mongo_client_class`
13+
For more info, check https://docs.mongoengine.org/guide/mongomock.html
1014

1115
Changes in 0.26.0
1216
=================

docs/guide/mongomock.rst

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
1-
==============================
1+
=========================
22
Use mongomock for testing
3-
==============================
3+
=========================
44

5-
`mongomock <https://github.com/vmalloc/mongomock/>`_ is a package to do just
6-
what the name implies, mocking a mongo database.
5+
Although we recommend running your tests against a regular MongoDB server, it is sometimes useful to plug
6+
MongoEngine to alternative implementations (mongomock, montydb, mongita, etc).
7+
8+
`mongomock <https://github.com/vmalloc/mongomock/>`_ is historically the one suggested for MongoEngine and is
9+
a package to do just what the name implies, mocking a mongo database.
710

811
To use with mongoengine, simply specify mongomock when connecting with
912
mongoengine:
1013

1114
.. code-block:: python
1215
13-
connect('mongoenginetest', host='mongomock://localhost')
16+
import mongomock
17+
18+
connect('mongoenginetest', host='mongodb://localhost', mongo_client_class=mongomock.MongoClient)
1419
conn = get_connection()
1520
1621
or with an alias:
1722

1823
.. code-block:: python
1924
20-
connect('mongoenginetest', host='mongomock://localhost', alias='testdb')
25+
connect('mongoenginetest', host='mongodb://localhost', mongo_client_class=mongomock.MongoClient, alias='testdb')
2126
conn = get_connection('testdb')
2227
2328
Example of test file:
@@ -34,7 +39,7 @@ Example of test file:
3439
3540
@classmethod
3641
def setUpClass(cls):
37-
connect('mongoenginetest', host='mongomock://localhost')
42+
connect('mongoenginetest', host='mongodb://localhost', mongo_client_class=mongomock.MongoClient)
3843
3944
@classmethod
4045
def tearDownClass(cls):

mongoengine/connection.py

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,9 @@ def _get_connection_settings(
7474
:param authentication_mechanism: database authentication mechanisms.
7575
By default, use SCRAM-SHA-1 with MongoDB 3.0 and later,
7676
MONGODB-CR (MongoDB Challenge Response protocol) for older servers.
77-
:param is_mock: explicitly use mongomock for this connection
78-
(can also be done by using `mongomock: // ` as db host prefix)
77+
:param mongo_client_class: using alternative connection client other than
78+
pymongo.MongoClient, e.g. mongomock, montydb, that provides pymongo alike
79+
interface but not necessarily for connecting to a real mongo instance.
7980
:param kwargs: ad-hoc parameters to be passed into the pymongo driver,
8081
for example maxpoolsize, tz_aware, etc. See the documentation
8182
for pymongo's `MongoClient` for a full list.
@@ -102,22 +103,17 @@ def _get_connection_settings(
102103
resolved_hosts = []
103104
for entity in conn_host:
104105

105-
# Handle Mongomock
106-
if entity.startswith("mongomock://"):
107-
conn_settings["is_mock"] = True
108-
# `mongomock://` is not a valid url prefix and must be replaced by `mongodb://`
109-
new_entity = entity.replace("mongomock://", "mongodb://", 1)
110-
resolved_hosts.append(new_entity)
111-
112-
uri_dict = uri_parser.parse_uri(new_entity)
113-
114-
database = uri_dict.get("database")
115-
if database:
116-
conn_settings["name"] = database
106+
# Reject old mongomock integration
107+
# To be removed in a few versions after 0.27.0
108+
if entity.startswith("mongomock://") or kwargs.get("is_mock"):
109+
raise Exception(
110+
"Use of mongomock:// URI or 'is_mock' were removed in favor of 'mongo_client_class=mongomock.MongoClient'. "
111+
"Check the CHANGELOG for more info"
112+
)
117113

118114
# Handle URI style connections, only updating connection params which
119115
# were explicitly specified in the URI.
120-
elif "://" in entity:
116+
if "://" in entity:
121117
uri_dict = uri_parser.parse_uri(entity)
122118
resolved_hosts.append(entity)
123119

@@ -219,8 +215,9 @@ def register_connection(
219215
:param authentication_mechanism: database authentication mechanisms.
220216
By default, use SCRAM-SHA-1 with MongoDB 3.0 and later,
221217
MONGODB-CR (MongoDB Challenge Response protocol) for older servers.
222-
:param is_mock: explicitly use mongomock for this connection
223-
(can also be done by using `mongomock: // ` as db host prefix)
218+
:param mongo_client_class: using alternative connection client other than
219+
pymongo.MongoClient, e.g. mongomock, montydb, that provides pymongo alike
220+
interface but not necessarily for connecting to a real mongo instance.
224221
:param kwargs: ad-hoc parameters to be passed into the pymongo driver,
225222
for example maxpoolsize, tz_aware, etc. See the documentation
226223
for pymongo's `MongoClient` for a full list.
@@ -326,35 +323,30 @@ def _clean_settings(settings_dict):
326323
conn_settings = _clean_settings(raw_conn_settings)
327324

328325
# Determine if we should use PyMongo's or mongomock's MongoClient.
329-
is_mock = conn_settings.pop("is_mock", False)
330-
if is_mock:
331-
try:
332-
import mongomock
333-
except ImportError:
334-
raise RuntimeError("You need mongomock installed to mock MongoEngine.")
335-
connection_class = mongomock.MongoClient
326+
if "mongo_client_class" in conn_settings:
327+
mongo_client_class = conn_settings.pop("mongo_client_class")
336328
else:
337-
connection_class = MongoClient
329+
mongo_client_class = MongoClient
338330

339331
# Re-use existing connection if one is suitable.
340332
existing_connection = _find_existing_connection(raw_conn_settings)
341333
if existing_connection:
342334
connection = existing_connection
343335
else:
344336
connection = _create_connection(
345-
alias=alias, connection_class=connection_class, **conn_settings
337+
alias=alias, mongo_client_class=mongo_client_class, **conn_settings
346338
)
347339
_connections[alias] = connection
348340
return _connections[alias]
349341

350342

351-
def _create_connection(alias, connection_class, **connection_settings):
343+
def _create_connection(alias, mongo_client_class, **connection_settings):
352344
"""
353345
Create the new connection for this alias. Raise
354346
ConnectionFailure if it can't be established.
355347
"""
356348
try:
357-
return connection_class(**connection_settings)
349+
return mongo_client_class(**connection_settings)
358350
except Exception as e:
359351
raise ConnectionFailure(f"Cannot connect to database {alias} :\n{e}")
360352

tests/test_connection.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,21 @@ def test_connect(self):
5353
connect("mongoenginetest")
5454

5555
conn = get_connection()
56-
assert isinstance(conn, pymongo.mongo_client.MongoClient)
56+
assert isinstance(conn, pymongo.MongoClient)
5757

5858
db = get_db()
5959
assert isinstance(db, pymongo.database.Database)
6060
assert db.name == "mongoenginetest"
6161

6262
connect("mongoenginetest2", alias="testdb")
6363
conn = get_connection("testdb")
64-
assert isinstance(conn, pymongo.mongo_client.MongoClient)
64+
assert isinstance(conn, pymongo.MongoClient)
65+
66+
connect(
67+
"mongoenginetest2", alias="testdb3", mongo_client_class=pymongo.MongoClient
68+
)
69+
conn = get_connection("testdb")
70+
assert isinstance(conn, pymongo.MongoClient)
6571

6672
def test_connect_disconnect_works_properly(self):
6773
class History1(Document):

tests/test_connection_mongomock.py

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,45 +32,76 @@ def tearDown(self):
3232
mongoengine.connection._connections = {}
3333
mongoengine.connection._dbs = {}
3434

35+
@require_mongomock
36+
def test_connect_raise_if_mongomock_uri_provided(self):
37+
with pytest.raises(
38+
Exception, match="Use of mongomock:// URI or 'is_mock' were removed"
39+
):
40+
connect("test", host="mongomock://localhost")
41+
42+
@require_mongomock
43+
def test_connect_raise_if_is_mock_provided(self):
44+
with pytest.raises(
45+
Exception, match="Use of mongomock:// URI or 'is_mock' were removed"
46+
):
47+
connect("test", host="mongodb://localhost", is_mock=True)
48+
3549
@require_mongomock
3650
def test_connect_in_mocking(self):
3751
"""Ensure that the connect() method works properly in mocking."""
38-
connect("mongoenginetest", host="mongomock://localhost")
52+
connect(
53+
"mongoenginetest",
54+
host="mongodb://localhost",
55+
mongo_client_class=mongomock.MongoClient,
56+
)
3957
conn = get_connection()
4058
assert isinstance(conn, mongomock.MongoClient)
4159

42-
connect("mongoenginetest2", host="mongomock://localhost", alias="testdb2")
60+
connect(
61+
"mongoenginetest2",
62+
host="mongodb://localhost",
63+
mongo_client_class=mongomock.MongoClient,
64+
alias="testdb2",
65+
)
4366
conn = get_connection("testdb2")
4467
assert isinstance(conn, mongomock.MongoClient)
4568

4669
connect(
4770
"mongoenginetest3",
4871
host="mongodb://localhost",
49-
is_mock=True,
72+
mongo_client_class=mongomock.MongoClient,
5073
alias="testdb3",
5174
)
5275
conn = get_connection("testdb3")
5376
assert isinstance(conn, mongomock.MongoClient)
5477

55-
connect("mongoenginetest4", is_mock=True, alias="testdb4")
78+
connect(
79+
"mongoenginetest4",
80+
mongo_client_class=mongomock.MongoClient,
81+
alias="testdb4",
82+
)
5683
conn = get_connection("testdb4")
5784
assert isinstance(conn, mongomock.MongoClient)
5885

5986
connect(
6087
host="mongodb://localhost:27017/mongoenginetest5",
61-
is_mock=True,
88+
mongo_client_class=mongomock.MongoClient,
6289
alias="testdb5",
6390
)
6491
conn = get_connection("testdb5")
6592
assert isinstance(conn, mongomock.MongoClient)
6693

67-
connect(host="mongomock://localhost:27017/mongoenginetest6", alias="testdb6")
94+
connect(
95+
host="mongodb://localhost:27017/mongoenginetest6",
96+
mongo_client_class=mongomock.MongoClient,
97+
alias="testdb6",
98+
)
6899
conn = get_connection("testdb6")
69100
assert isinstance(conn, mongomock.MongoClient)
70101

71102
connect(
72-
host="mongomock://localhost:27017/mongoenginetest7",
73-
is_mock=True,
103+
host="mongodb://localhost:27017/mongoenginetest7",
104+
mongo_client_class=mongomock.MongoClient,
74105
alias="testdb7",
75106
)
76107
conn = get_connection("testdb7")
@@ -84,7 +115,10 @@ def test_default_database_with_mocking(self):
84115
class SomeDocument(Document):
85116
pass
86117

87-
conn = connect(host="mongomock://localhost:27017/mongoenginetest")
118+
conn = connect(
119+
host="mongodb://localhost:27017/mongoenginetest",
120+
mongo_client_class=mongomock.MongoClient,
121+
)
88122
some_document = SomeDocument()
89123
# database won't exist until we save a document
90124
some_document.save()
@@ -96,7 +130,10 @@ class SomeDocument(Document):
96130
def test_basic_queries_against_mongomock(self):
97131
disconnect_all()
98132

99-
connect(host="mongomock://localhost:27017/mongoenginetest")
133+
connect(
134+
host="mongodb://localhost:27017/mongoenginetest",
135+
mongo_client_class=mongomock.MongoClient,
136+
)
100137

101138
class Person(Document):
102139
name = StringField()
@@ -129,35 +166,38 @@ def test_connect_with_host_list(self):
129166
130167
Uses mongomock to test w/o needing multiple mongod/mongos processes
131168
"""
132-
connect(host=["mongomock://localhost"])
169+
connect(host=["mongodb://localhost"], mongo_client_class=mongomock.MongoClient)
133170
conn = get_connection()
134171
assert isinstance(conn, mongomock.MongoClient)
135172

136-
connect(host=["mongodb://localhost"], is_mock=True, alias="testdb2")
137-
conn = get_connection("testdb2")
138-
assert isinstance(conn, mongomock.MongoClient)
139-
140-
connect(host=["localhost"], is_mock=True, alias="testdb3")
173+
connect(
174+
host=["localhost"],
175+
mongo_client_class=mongomock.MongoClient,
176+
alias="testdb3",
177+
)
141178
conn = get_connection("testdb3")
142179
assert isinstance(conn, mongomock.MongoClient)
143180

144181
connect(
145-
host=["mongomock://localhost:27017", "mongomock://localhost:27018"],
182+
host=["mongodb://localhost:27017", "mongodb://localhost:27018"],
146183
alias="testdb4",
184+
mongo_client_class=mongomock.MongoClient,
147185
)
148186
conn = get_connection("testdb4")
149187
assert isinstance(conn, mongomock.MongoClient)
150188

151189
connect(
152190
host=["mongodb://localhost:27017", "mongodb://localhost:27018"],
153-
is_mock=True,
191+
mongo_client_class=mongomock.MongoClient,
154192
alias="testdb5",
155193
)
156194
conn = get_connection("testdb5")
157195
assert isinstance(conn, mongomock.MongoClient)
158196

159197
connect(
160-
host=["localhost:27017", "localhost:27018"], is_mock=True, alias="testdb6"
198+
host=["localhost:27017", "localhost:27018"],
199+
mongo_client_class=mongomock.MongoClient,
200+
alias="testdb6",
161201
)
162202
conn = get_connection("testdb6")
163203
assert isinstance(conn, mongomock.MongoClient)

0 commit comments

Comments
 (0)