Compare commits

...

3 Commits

Author SHA1 Message Date
Stefan Wojcik
fbf32c6907 clean up field unit tests 2017-03-01 23:41:02 -05:00
Ephraim Berkovitch
398964945a Document.objects.create should raise NotUniqueError upon saving duplicate primary key (#1485) 2017-02-27 09:42:44 -05:00
Stefan Wójcik
5f43c032f2 revamp the "connecting" user guide and test more ways of connecting to a replica set (#1490) 2017-02-26 21:29:06 -05:00
7 changed files with 659 additions and 714 deletions

1
.gitignore vendored
View File

@@ -17,3 +17,4 @@ tests/test_bugfix.py
htmlcov/ htmlcov/
venv venv
venv3 venv3
scratchpad

View File

@@ -42,13 +42,18 @@ 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.
ReplicaSets Replica Sets
=========== ============
MongoEngine supports MongoEngine supports connecting to replica sets::
:class:`~pymongo.mongo_replica_set_client.MongoReplicaSetClient`. To use them,
please use an URI style connection and provide the ``replicaSet`` name from mongoengine import connect
in the connection kwargs.
# Regular connect
connect('dbname', replicaset='rs-name')
# MongoDB URI-style connect
connect(host='mongodb://localhost/dbname?replicaSet=rs-name')
Read preferences are supported through the connection or via individual Read preferences are supported through the connection or via individual
queries by passing the read_preference :: queries by passing the read_preference ::
@@ -59,76 +64,74 @@ queries by passing the read_preference ::
Multiple Databases Multiple Databases
================== ==================
Multiple database support was added in MongoEngine 0.6. To use multiple To use multiple databases you can use :func:`~mongoengine.connect` and provide
databases you can use :func:`~mongoengine.connect` and provide an `alias` name an `alias` name for the connection - if no `alias` is provided then "default"
for the connection - if no `alias` is provided then "default" is used. 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.
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` objects `db_alias` in their meta data. This allows :class:`~pymongo.dbref.DBRef`
to point across databases and collections. Below is an example schema, using objects to point across databases and collections. Below is an example schema,
3 different databases to store data:: using 3 different databases to store data::
class User(Document): class User(Document):
name = StringField() name = StringField()
meta = {"db_alias": "user-db"} meta = {'db_alias': 'user-db'}
class Book(Document): class Book(Document):
name = StringField() name = StringField()
meta = {"db_alias": "book-db"} meta = {'db_alias': 'book-db'}
class AuthorBooks(Document): class AuthorBooks(Document):
author = ReferenceField(User) author = ReferenceField(User)
book = ReferenceField(Book) book = ReferenceField(Book)
meta = {"db_alias": "users-books-db"} meta = {'db_alias': 'users-books-db'}
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.
for a class.
For example, archiving older data into a separate database for performance For example, archiving older data into a separate database for performance
reasons or writing functions that dynamically choose collections to write reasons or writing functions that dynamically choose collections to write
document to. a document to.
Switch Database Switch Database
--------------- ---------------
The :class:`~mongoengine.context_managers.switch_db` context manager allows The :class:`~mongoengine.context_managers.switch_db` context manager allows
you to change the database alias for a given class allowing quick and easy you to change the database alias for a given class allowing quick and easy
access the same User document across databases:: access to the same User document across databases::
from mongoengine.context_managers import switch_db from mongoengine.context_managers import switch_db
class User(Document): class User(Document):
name = StringField() name = StringField()
meta = {"db_alias": "user-db"} meta = {'db_alias': 'user-db'}
with switch_db(User, 'archive-user-db') as User: with switch_db(User, 'archive-user-db') as User:
User(name="Ross").save() # Saves the 'archive-user-db' User(name='Ross').save() # Saves the 'archive-user-db'
Switch Collection Switch Collection
----------------- -----------------
The :class:`~mongoengine.context_managers.switch_collection` context manager The :class:`~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 the same Group document across collection:: access to the same Group document across collection::
from mongoengine.context_managers import switch_collection from mongoengine.context_managers import switch_collection
class Group(Document): class Group(Document):
name = StringField() name = StringField()
Group(name="test").save() # Saves in the default db Group(name='test').save() # Saves in the default db
with switch_collection(Group, 'group2000') as Group: with switch_collection(Group, 'group2000') as Group:
Group(name="hello Group 2000 collection!").save() # Saves in group2000 collection Group(name='hello Group 2000 collection!').save() # Saves in group2000 collection
.. note:: Make sure any aliases have been registered with .. note:: Make sure any aliases have been registered with

View File

@@ -2,13 +2,13 @@
Installing MongoEngine Installing MongoEngine
====================== ======================
To use MongoEngine, you will need to download `MongoDB <http://mongodb.org/>`_ To use MongoEngine, you will need to download `MongoDB <http://mongodb.com/>`_
and ensure it is running in an accessible location. You will also need and ensure it is running in an accessible location. You will also need
`PyMongo <http://api.mongodb.org/python>`_ to use MongoEngine, but if you `PyMongo <http://api.mongodb.org/python>`_ to use MongoEngine, but if you
install MongoEngine using setuptools, then the dependencies will be handled for install MongoEngine using setuptools, then the dependencies will be handled for
you. you.
MongoEngine is available on PyPI, so to use it you can use :program:`pip`: MongoEngine is available on PyPI, so you can use :program:`pip`:
.. code-block:: console .. code-block:: console

View File

@@ -286,7 +286,7 @@ class BaseQuerySet(object):
.. versionadded:: 0.4 .. versionadded:: 0.4
""" """
return self._document(**kwargs).save() return self._document(**kwargs).save(force_insert=True)
def first(self): def first(self):
"""Retrieve the first object matching the query.""" """Retrieve the first object matching the query."""

View File

@@ -412,7 +412,6 @@ class IndexesTest(unittest.TestCase):
User.ensure_indexes() User.ensure_indexes()
info = User.objects._collection.index_information() info = User.objects._collection.index_information()
self.assertEqual(sorted(info.keys()), ['_cls_1_user_guid_1', '_id_']) self.assertEqual(sorted(info.keys()), ['_cls_1_user_guid_1', '_id_'])
User.drop_collection()
def test_embedded_document_index(self): def test_embedded_document_index(self):
"""Tests settings an index on an embedded document """Tests settings an index on an embedded document
@@ -434,7 +433,6 @@ class IndexesTest(unittest.TestCase):
info = BlogPost.objects._collection.index_information() info = BlogPost.objects._collection.index_information()
self.assertEqual(sorted(info.keys()), ['_id_', 'date.yr_-1']) self.assertEqual(sorted(info.keys()), ['_id_', 'date.yr_-1'])
BlogPost.drop_collection()
def test_list_embedded_document_index(self): def test_list_embedded_document_index(self):
"""Ensure list embedded documents can be indexed """Ensure list embedded documents can be indexed
@@ -461,7 +459,6 @@ class IndexesTest(unittest.TestCase):
post1 = BlogPost(title="Embedded Indexes tests in place", post1 = BlogPost(title="Embedded Indexes tests in place",
tags=[Tag(name="about"), Tag(name="time")]) tags=[Tag(name="about"), Tag(name="time")])
post1.save() post1.save()
BlogPost.drop_collection()
def test_recursive_embedded_objects_dont_break_indexes(self): def test_recursive_embedded_objects_dont_break_indexes(self):
@@ -622,8 +619,6 @@ class IndexesTest(unittest.TestCase):
post3 = BlogPost(title='test3', date=Date(year=2010), slug='test') post3 = BlogPost(title='test3', date=Date(year=2010), slug='test')
self.assertRaises(OperationError, post3.save) self.assertRaises(OperationError, post3.save)
BlogPost.drop_collection()
def test_unique_embedded_document(self): def test_unique_embedded_document(self):
"""Ensure that uniqueness constraints are applied to fields on embedded documents. """Ensure that uniqueness constraints are applied to fields on embedded documents.
""" """
@@ -651,8 +646,6 @@ class IndexesTest(unittest.TestCase):
sub=SubDocument(year=2010, slug='test')) sub=SubDocument(year=2010, slug='test'))
self.assertRaises(NotUniqueError, post3.save) self.assertRaises(NotUniqueError, post3.save)
BlogPost.drop_collection()
def test_unique_embedded_document_in_list(self): def test_unique_embedded_document_in_list(self):
""" """
Ensure that the uniqueness constraints are applied to fields in Ensure that the uniqueness constraints are applied to fields in
@@ -683,8 +676,6 @@ class IndexesTest(unittest.TestCase):
self.assertRaises(NotUniqueError, post2.save) self.assertRaises(NotUniqueError, post2.save)
BlogPost.drop_collection()
def test_unique_with_embedded_document_and_embedded_unique(self): def test_unique_with_embedded_document_and_embedded_unique(self):
"""Ensure that uniqueness constraints are applied to fields on """Ensure that uniqueness constraints are applied to fields on
embedded documents. And work with unique_with as well. embedded documents. And work with unique_with as well.
@@ -718,8 +709,6 @@ class IndexesTest(unittest.TestCase):
sub=SubDocument(year=2009, slug='test-1')) sub=SubDocument(year=2009, slug='test-1'))
self.assertRaises(NotUniqueError, post3.save) self.assertRaises(NotUniqueError, post3.save)
BlogPost.drop_collection()
def test_ttl_indexes(self): def test_ttl_indexes(self):
class Log(Document): class Log(Document):
@@ -759,13 +748,11 @@ class IndexesTest(unittest.TestCase):
raise AssertionError("We saved a dupe!") raise AssertionError("We saved a dupe!")
except NotUniqueError: except NotUniqueError:
pass pass
Customer.drop_collection()
def test_unique_and_primary(self): def test_unique_and_primary(self):
"""If you set a field as primary, then unexpected behaviour can occur. """If you set a field as primary, then unexpected behaviour can occur.
You won't create a duplicate but you will update an existing document. You won't create a duplicate but you will update an existing document.
""" """
class User(Document): class User(Document):
name = StringField(primary_key=True, unique=True) name = StringField(primary_key=True, unique=True)
password = StringField() password = StringField()
@@ -781,8 +768,23 @@ class IndexesTest(unittest.TestCase):
self.assertEqual(User.objects.count(), 1) self.assertEqual(User.objects.count(), 1)
self.assertEqual(User.objects.get().password, 'secret2') self.assertEqual(User.objects.get().password, 'secret2')
def test_unique_and_primary_create(self):
"""Create a new record with a duplicate primary key
throws an exception
"""
class User(Document):
name = StringField(primary_key=True)
password = StringField()
User.drop_collection() User.drop_collection()
User.objects.create(name='huangz', password='secret')
with self.assertRaises(NotUniqueError):
User.objects.create(name='huangz', password='secret2')
self.assertEqual(User.objects.count(), 1)
self.assertEqual(User.objects.get().password, 'secret')
def test_index_with_pk(self): def test_index_with_pk(self):
"""Ensure you can use `pk` as part of a query""" """Ensure you can use `pk` as part of a query"""

File diff suppressed because it is too large Load Diff

View File

@@ -198,19 +198,6 @@ class ConnectionTest(unittest.TestCase):
self.assertTrue(isinstance(db, pymongo.database.Database)) self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'test') self.assertEqual(db.name, 'test')
def test_connect_uri_with_replicaset(self):
"""Ensure connect() works when specifying a replicaSet."""
if IS_PYMONGO_3:
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'test')
else:
# PyMongo < v3.x raises an exception:
# "localhost:27017 is not a member of replica set local-rs"
with self.assertRaises(MongoEngineConnectionError):
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
def test_uri_without_credentials_doesnt_override_conn_settings(self): def test_uri_without_credentials_doesnt_override_conn_settings(self):
"""Ensure connect() uses the username & password params if the URI """Ensure connect() uses the username & password params if the URI
doesn't explicitly specify them. doesn't explicitly specify them.
@@ -332,6 +319,38 @@ class ConnectionTest(unittest.TestCase):
self.assertEqual(dict(conn1.write_concern), {'w': 1, 'j': True}) self.assertEqual(dict(conn1.write_concern), {'w': 1, 'j': True})
self.assertEqual(dict(conn2.write_concern), {'w': 1, 'j': True}) self.assertEqual(dict(conn2.write_concern), {'w': 1, 'j': True})
def test_connect_with_replicaset_via_uri(self):
"""Ensure connect() works when specifying a replicaSet via the
MongoDB URI.
"""
if IS_PYMONGO_3:
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'test')
else:
# PyMongo < v3.x raises an exception:
# "localhost:27017 is not a member of replica set local-rs"
with self.assertRaises(MongoEngineConnectionError):
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
def test_connect_with_replicaset_via_kwargs(self):
"""Ensure connect() works when specifying a replicaSet via the
connection kwargs
"""
if IS_PYMONGO_3:
c = connect(replicaset='local-rs')
self.assertEqual(c._MongoClient__options.replica_set_name,
'local-rs')
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'test')
else:
# PyMongo < v3.x raises an exception:
# "localhost:27017 is not a member of replica set local-rs"
with self.assertRaises(MongoEngineConnectionError):
c = connect(replicaset='local-rs')
def test_datetime(self): def test_datetime(self):
connect('mongoenginetest', tz_aware=True) connect('mongoenginetest', tz_aware=True)
d = datetime.datetime(2010, 5, 5, tzinfo=utc) d = datetime.datetime(2010, 5, 5, tzinfo=utc)