Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a20d40618f | ||
|
|
b4af8ec751 | ||
|
|
f4fa39c70e | ||
|
|
7b7165f5d8 | ||
|
|
13897db6d3 | ||
|
|
c4afdb7198 | ||
|
|
0284975f3f | ||
|
|
269e3d1303 | ||
|
|
8c81f7ece9 | ||
|
|
f6e0593774 | ||
|
|
3d80e549cb | ||
|
|
0372e07eb0 | ||
|
|
00221e3410 | ||
|
|
31d7f70e27 | ||
|
|
04e8b83d45 | ||
|
|
e87bf71f20 | ||
|
|
2dd70c8d62 | ||
|
|
a3886702a3 | ||
|
|
713af133a0 | ||
|
|
057ffffbf2 | ||
|
|
a81d6d124b | ||
|
|
23f07fde5e | ||
|
|
b42b760393 | ||
|
|
bf6f4c48c0 | ||
|
|
6133f04841 | ||
|
|
3c18f79ea4 | ||
|
|
2af8342fea | ||
|
|
fc3db7942d | ||
|
|
164e2b2678 | ||
|
|
b7b28390df | ||
|
|
a6e996d921 | ||
|
|
07e666345d | ||
|
|
007f10d29d | ||
|
|
f9284d20ca | ||
|
|
9050869781 | ||
|
|
54975de0f3 | ||
|
|
a7aead5138 | ||
|
|
6868f66f24 | ||
|
|
04497aec36 | ||
|
|
aa9d596930 | ||
|
|
f96e68cd11 | ||
|
|
013227323d | ||
|
|
0a1ba7c434 | ||
|
|
cceef33fef | ||
|
|
ed8174fe36 | ||
|
|
3c8906494f | ||
|
|
6e745e9882 | ||
|
|
fb4e9c3772 | ||
|
|
11024deaae |
@@ -25,7 +25,8 @@ install:
|
|||||||
- sudo apt-get install python-dev python3-dev libopenjpeg-dev zlib1g-dev libjpeg-turbo8-dev
|
- sudo apt-get install python-dev python3-dev libopenjpeg-dev zlib1g-dev libjpeg-turbo8-dev
|
||||||
libtiff4-dev libjpeg8-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev
|
libtiff4-dev libjpeg8-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev
|
||||||
python-tk
|
python-tk
|
||||||
- travis_retry pip install tox>=1.9 coveralls
|
# virtualenv>=14.0.0 has dropped Python 3.2 support
|
||||||
|
- travis_retry pip install "virtualenv<14.0.0" "tox>=1.9" coveralls
|
||||||
- travis_retry tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -e test
|
- travis_retry tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -e test
|
||||||
script:
|
script:
|
||||||
- tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- --with-coverage
|
- tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- --with-coverage
|
||||||
|
|||||||
4
AUTHORS
4
AUTHORS
@@ -230,3 +230,7 @@ that much better:
|
|||||||
* Amit Lichtenberg (https://github.com/amitlicht)
|
* Amit Lichtenberg (https://github.com/amitlicht)
|
||||||
* Lars Butler (https://github.com/larsbutler)
|
* Lars Butler (https://github.com/larsbutler)
|
||||||
* George Macon (https://github.com/gmacon)
|
* George Macon (https://github.com/gmacon)
|
||||||
|
* Ashley Whetter (https://github.com/AWhetter)
|
||||||
|
* Paul-Armand Verhaegen (https://github.com/paularmand)
|
||||||
|
* Steven Rossiter (https://github.com/BeardedSteve)
|
||||||
|
* Luo Peng (https://github.com/RussellLuo)
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ Optional Dependencies
|
|||||||
|
|
||||||
Examples
|
Examples
|
||||||
========
|
========
|
||||||
Some simple examples of what MongoEngine code looks like::
|
Some simple examples of what MongoEngine code looks like:
|
||||||
|
|
||||||
|
.. code :: python
|
||||||
|
|
||||||
class BlogPost(Document):
|
class BlogPost(Document):
|
||||||
title = StringField(required=True, max_length=200)
|
title = StringField(required=True, max_length=200)
|
||||||
|
|||||||
@@ -2,13 +2,33 @@
|
|||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
Changes in 0.10.6
|
||||||
|
=================
|
||||||
|
- Add support for mocking MongoEngine based on mongomock. #1151
|
||||||
|
- Fixed not being able to run tests on Windows. #1153
|
||||||
|
- Allow creation of sparse compound indexes. #1114
|
||||||
|
|
||||||
|
Changes in 0.10.5
|
||||||
|
=================
|
||||||
|
- Fix for reloading of strict with special fields. #1156
|
||||||
|
|
||||||
|
Changes in 0.10.4
|
||||||
|
=================
|
||||||
|
- SaveConditionError is now importable from the top level package. #1165
|
||||||
|
- upsert_one method added. #1157
|
||||||
|
|
||||||
|
Changes in 0.10.3
|
||||||
|
=================
|
||||||
|
- Fix `read_preference` (it had chaining issues with PyMongo 2.x and it didn't work at all with PyMongo 3.x) #1042
|
||||||
|
|
||||||
Changes in 0.10.2
|
Changes in 0.10.2
|
||||||
=================
|
=================
|
||||||
- Allow shard key to point to a field in an embedded document. #551
|
- Allow shard key to point to a field in an embedded document. #551
|
||||||
- Allow arbirary metadata in fields. #1129
|
- Allow arbirary metadata in fields. #1129
|
||||||
|
- ReferenceFields now support abstract document types. #837
|
||||||
|
|
||||||
Changes in 0.10.1
|
Changes in 0.10.1
|
||||||
=======================
|
=================
|
||||||
- Fix infinite recursion with CASCADE delete rules under specific conditions. #1046
|
- Fix infinite recursion with CASCADE delete rules under specific conditions. #1046
|
||||||
- Fix CachedReferenceField bug when loading cached docs as DBRef but failing to save them. #1047
|
- Fix CachedReferenceField bug when loading cached docs as DBRef but failing to save them. #1047
|
||||||
- Fix ignored chained options #842
|
- Fix ignored chained options #842
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ class Post(Document):
|
|||||||
tags = ListField(StringField(max_length=30))
|
tags = ListField(StringField(max_length=30))
|
||||||
comments = ListField(EmbeddedDocumentField(Comment))
|
comments = ListField(EmbeddedDocumentField(Comment))
|
||||||
|
|
||||||
|
# bugfix
|
||||||
|
meta = {'allow_inheritance': True}
|
||||||
|
|
||||||
|
|
||||||
class TextPost(Post):
|
class TextPost(Post):
|
||||||
content = StringField()
|
content = StringField()
|
||||||
|
|
||||||
@@ -45,7 +49,8 @@ print 'ALL POSTS'
|
|||||||
print
|
print
|
||||||
for post in Post.objects:
|
for post in Post.objects:
|
||||||
print post.title
|
print post.title
|
||||||
print '=' * post.title.count()
|
#print '=' * post.title.count()
|
||||||
|
print "=" * 20
|
||||||
|
|
||||||
if isinstance(post, TextPost):
|
if isinstance(post, TextPost):
|
||||||
print post.content
|
print post.content
|
||||||
|
|||||||
@@ -13,3 +13,4 @@ User Guide
|
|||||||
gridfs
|
gridfs
|
||||||
signals
|
signals
|
||||||
text-indexes
|
text-indexes
|
||||||
|
mongomock
|
||||||
|
|||||||
21
docs/guide/mongomock.rst
Normal file
21
docs/guide/mongomock.rst
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
==============================
|
||||||
|
Use mongomock for testing
|
||||||
|
==============================
|
||||||
|
|
||||||
|
`mongomock <https://github.com/vmalloc/mongomock/>`_ is a package to do just
|
||||||
|
what the name implies, mocking a mongo database.
|
||||||
|
|
||||||
|
To use with mongoengine, simply specify mongomock when connecting with
|
||||||
|
mongoengine:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
connect('mongoenginetest', host='mongomock://localhost')
|
||||||
|
conn = get_connection()
|
||||||
|
|
||||||
|
or with an alias:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
connect('mongoenginetest', host='mongomock://localhost', alias='testdb')
|
||||||
|
conn = get_connection('testdb')
|
||||||
@@ -14,7 +14,7 @@ import errors
|
|||||||
__all__ = (list(document.__all__) + fields.__all__ + connection.__all__ +
|
__all__ = (list(document.__all__) + fields.__all__ + connection.__all__ +
|
||||||
list(queryset.__all__) + signals.__all__ + list(errors.__all__))
|
list(queryset.__all__) + signals.__all__ + list(errors.__all__))
|
||||||
|
|
||||||
VERSION = (0, 10, 1)
|
VERSION = (0, 10, 6)
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
|
|||||||
@@ -839,10 +839,6 @@ class BaseDocument(object):
|
|||||||
|
|
||||||
if index_list:
|
if index_list:
|
||||||
spec['fields'] = index_list
|
spec['fields'] = index_list
|
||||||
if spec.get('sparse', False) and len(spec['fields']) > 1:
|
|
||||||
raise ValueError(
|
|
||||||
'Sparse indexes can only have one field in them. '
|
|
||||||
'See https://jira.mongodb.org/browse/SERVER-2193')
|
|
||||||
|
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,11 @@ def register_connection(alias, name=None, host=None, port=None,
|
|||||||
:param username: username to authenticate with
|
:param username: username to authenticate with
|
||||||
:param password: password to authenticate with
|
:param password: password to authenticate with
|
||||||
:param authentication_source: database to authenticate against
|
:param authentication_source: database to authenticate against
|
||||||
|
:param is_mock: explicitly use mongomock for this connection
|
||||||
|
(can also be done by using `mongomock://` as db host prefix)
|
||||||
:param kwargs: allow ad-hoc parameters to be passed into the pymongo driver
|
:param kwargs: allow ad-hoc parameters to be passed into the pymongo driver
|
||||||
|
|
||||||
|
.. versionchanged:: 0.10.6 - added mongomock support
|
||||||
"""
|
"""
|
||||||
global _connection_settings
|
global _connection_settings
|
||||||
|
|
||||||
@@ -54,8 +57,13 @@ def register_connection(alias, name=None, host=None, port=None,
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Handle uri style connections
|
# Handle uri style connections
|
||||||
if "://" in conn_settings['host']:
|
conn_host = conn_settings['host']
|
||||||
uri_dict = uri_parser.parse_uri(conn_settings['host'])
|
if conn_host.startswith('mongomock://'):
|
||||||
|
conn_settings['is_mock'] = True
|
||||||
|
# `mongomock://` is not a valid url prefix and must be replaced by `mongodb://`
|
||||||
|
conn_settings['host'] = conn_host.replace('mongomock://', 'mongodb://', 1)
|
||||||
|
elif '://' in conn_host:
|
||||||
|
uri_dict = uri_parser.parse_uri(conn_host)
|
||||||
conn_settings.update({
|
conn_settings.update({
|
||||||
'name': uri_dict.get('database') or name,
|
'name': uri_dict.get('database') or name,
|
||||||
'username': uri_dict.get('username'),
|
'username': uri_dict.get('username'),
|
||||||
@@ -106,7 +114,19 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
|
|||||||
conn_settings.pop('password', None)
|
conn_settings.pop('password', None)
|
||||||
conn_settings.pop('authentication_source', None)
|
conn_settings.pop('authentication_source', None)
|
||||||
|
|
||||||
|
is_mock = conn_settings.pop('is_mock', None)
|
||||||
|
if is_mock:
|
||||||
|
# Use MongoClient from mongomock
|
||||||
|
try:
|
||||||
|
import mongomock
|
||||||
|
except ImportError:
|
||||||
|
raise RuntimeError('You need mongomock installed '
|
||||||
|
'to mock MongoEngine.')
|
||||||
|
connection_class = mongomock.MongoClient
|
||||||
|
else:
|
||||||
|
# Use MongoClient from pymongo
|
||||||
connection_class = MongoClient
|
connection_class = MongoClient
|
||||||
|
|
||||||
if 'replicaSet' in conn_settings:
|
if 'replicaSet' in conn_settings:
|
||||||
# Discard port since it can't be used on MongoReplicaSetClient
|
# Discard port since it can't be used on MongoReplicaSetClient
|
||||||
conn_settings.pop('port', None)
|
conn_settings.pop('port', None)
|
||||||
@@ -126,6 +146,7 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
|
|||||||
connection_settings.pop('name', None)
|
connection_settings.pop('name', None)
|
||||||
connection_settings.pop('username', None)
|
connection_settings.pop('username', None)
|
||||||
connection_settings.pop('password', None)
|
connection_settings.pop('password', None)
|
||||||
|
connection_settings.pop('authentication_source', None)
|
||||||
if conn_settings == connection_settings and _connections.get(db_alias, None):
|
if conn_settings == connection_settings and _connections.get(db_alias, None):
|
||||||
connection = _connections[db_alias]
|
connection = _connections[db_alias]
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ class Document(BaseDocument):
|
|||||||
Returns True if the document has been updated or False if the document
|
Returns True if the document has been updated or False if the document
|
||||||
in the database doesn't match the query.
|
in the database doesn't match the query.
|
||||||
|
|
||||||
.. note:: All unsaved changes that has been made to the document are
|
.. note:: All unsaved changes that have been made to the document are
|
||||||
rejected if the method returns True.
|
rejected if the method returns True.
|
||||||
|
|
||||||
:param query: the update will be performed only if the document in the
|
:param query: the update will be performed only if the document in the
|
||||||
@@ -407,7 +407,7 @@ class Document(BaseDocument):
|
|||||||
|
|
||||||
def cascade_save(self, *args, **kwargs):
|
def cascade_save(self, *args, **kwargs):
|
||||||
"""Recursively saves any references /
|
"""Recursively saves any references /
|
||||||
generic references on an objects"""
|
generic references on the document"""
|
||||||
_refs = kwargs.get('_refs', []) or []
|
_refs = kwargs.get('_refs', []) or []
|
||||||
|
|
||||||
ReferenceField = _import_class('ReferenceField')
|
ReferenceField = _import_class('ReferenceField')
|
||||||
@@ -604,6 +604,11 @@ class Document(BaseDocument):
|
|||||||
if not fields or field in fields:
|
if not fields or field in fields:
|
||||||
try:
|
try:
|
||||||
setattr(self, field, self._reload(field, obj[field]))
|
setattr(self, field, self._reload(field, obj[field]))
|
||||||
|
except (KeyError, AttributeError):
|
||||||
|
try:
|
||||||
|
# If field is a special field, e.g. items is stored as _reserved_items,
|
||||||
|
# an KeyError is thrown. So try to retrieve the field from _data
|
||||||
|
setattr(self, field, self._reload(field, obj._data.get(field)))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# If field is removed from the database while the object
|
# If field is removed from the database while the object
|
||||||
# is in memory, a reload would cause a KeyError
|
# is in memory, a reload would cause a KeyError
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from mongoengine.python_support import txt_type
|
|||||||
__all__ = ('NotRegistered', 'InvalidDocumentError', 'LookUpError',
|
__all__ = ('NotRegistered', 'InvalidDocumentError', 'LookUpError',
|
||||||
'DoesNotExist', 'MultipleObjectsReturned', 'InvalidQueryError',
|
'DoesNotExist', 'MultipleObjectsReturned', 'InvalidQueryError',
|
||||||
'OperationError', 'NotUniqueError', 'FieldDoesNotExist',
|
'OperationError', 'NotUniqueError', 'FieldDoesNotExist',
|
||||||
'ValidationError')
|
'ValidationError', 'SaveConditionError')
|
||||||
|
|
||||||
|
|
||||||
class NotRegistered(Exception):
|
class NotRegistered(Exception):
|
||||||
|
|||||||
@@ -895,6 +895,10 @@ class ReferenceField(BaseField):
|
|||||||
or as the :class:`~pymongo.objectid.ObjectId`.id .
|
or as the :class:`~pymongo.objectid.ObjectId`.id .
|
||||||
:param reverse_delete_rule: Determines what to do when the referring
|
:param reverse_delete_rule: Determines what to do when the referring
|
||||||
object is deleted
|
object is deleted
|
||||||
|
|
||||||
|
.. note ::
|
||||||
|
A reference to an abstract document type is always stored as a
|
||||||
|
:class:`~pymongo.dbref.DBRef`, regardless of the value of `dbref`.
|
||||||
"""
|
"""
|
||||||
if not isinstance(document_type, basestring):
|
if not isinstance(document_type, basestring):
|
||||||
if not issubclass(document_type, (Document, basestring)):
|
if not issubclass(document_type, (Document, basestring)):
|
||||||
@@ -927,9 +931,14 @@ class ReferenceField(BaseField):
|
|||||||
self._auto_dereference = instance._fields[self.name]._auto_dereference
|
self._auto_dereference = instance._fields[self.name]._auto_dereference
|
||||||
# Dereference DBRefs
|
# Dereference DBRefs
|
||||||
if self._auto_dereference and isinstance(value, DBRef):
|
if self._auto_dereference and isinstance(value, DBRef):
|
||||||
value = self.document_type._get_db().dereference(value)
|
if hasattr(value, 'cls'):
|
||||||
|
# Dereference using the class type specified in the reference
|
||||||
|
cls = get_document(value.cls)
|
||||||
|
else:
|
||||||
|
cls = self.document_type
|
||||||
|
value = cls._get_db().dereference(value)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
instance._data[self.name] = self.document_type._from_son(value)
|
instance._data[self.name] = cls._from_son(value)
|
||||||
|
|
||||||
return super(ReferenceField, self).__get__(instance, owner)
|
return super(ReferenceField, self).__get__(instance, owner)
|
||||||
|
|
||||||
@@ -939,21 +948,29 @@ class ReferenceField(BaseField):
|
|||||||
return document.id
|
return document.id
|
||||||
return document
|
return document
|
||||||
|
|
||||||
id_field_name = self.document_type._meta['id_field']
|
|
||||||
id_field = self.document_type._fields[id_field_name]
|
|
||||||
|
|
||||||
if isinstance(document, Document):
|
if isinstance(document, Document):
|
||||||
# We need the id from the saved object to create the DBRef
|
# We need the id from the saved object to create the DBRef
|
||||||
id_ = document.pk
|
id_ = document.pk
|
||||||
if id_ is None:
|
if id_ is None:
|
||||||
self.error('You can only reference documents once they have'
|
self.error('You can only reference documents once they have'
|
||||||
' been saved to the database')
|
' been saved to the database')
|
||||||
|
|
||||||
|
# Use the attributes from the document instance, so that they
|
||||||
|
# override the attributes of this field's document type
|
||||||
|
cls = document
|
||||||
else:
|
else:
|
||||||
id_ = document
|
id_ = document
|
||||||
|
cls = self.document_type
|
||||||
|
|
||||||
|
id_field_name = cls._meta['id_field']
|
||||||
|
id_field = cls._fields[id_field_name]
|
||||||
|
|
||||||
id_ = id_field.to_mongo(id_)
|
id_ = id_field.to_mongo(id_)
|
||||||
if self.dbref:
|
if self.document_type._meta.get('abstract'):
|
||||||
collection = self.document_type._get_collection_name()
|
collection = cls._get_collection_name()
|
||||||
|
return DBRef(collection, id_, cls=cls._class_name)
|
||||||
|
elif self.dbref:
|
||||||
|
collection = cls._get_collection_name()
|
||||||
return DBRef(collection, id_)
|
return DBRef(collection, id_)
|
||||||
|
|
||||||
return id_
|
return id_
|
||||||
@@ -982,6 +999,14 @@ class ReferenceField(BaseField):
|
|||||||
self.error('You can only reference documents once they have been '
|
self.error('You can only reference documents once they have been '
|
||||||
'saved to the database')
|
'saved to the database')
|
||||||
|
|
||||||
|
if self.document_type._meta.get('abstract') and \
|
||||||
|
not isinstance(value, self.document_type):
|
||||||
|
self.error('%s is not an instance of abstract reference'
|
||||||
|
' type %s' % (value._class_name,
|
||||||
|
self.document_type._class_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def lookup_member(self, member_name):
|
def lookup_member(self, member_name):
|
||||||
return self.document_type._fields.get(member_name)
|
return self.document_type._fields.get(member_name)
|
||||||
|
|
||||||
|
|||||||
@@ -471,6 +471,32 @@ class BaseQuerySet(object):
|
|||||||
raise OperationError(message)
|
raise OperationError(message)
|
||||||
raise OperationError(u'Update failed (%s)' % unicode(err))
|
raise OperationError(u'Update failed (%s)' % unicode(err))
|
||||||
|
|
||||||
|
|
||||||
|
def upsert_one(self, write_concern=None, **update):
|
||||||
|
"""Overwrite or add the first document matched by the query.
|
||||||
|
|
||||||
|
:param write_concern: Extra keyword arguments are passed down which
|
||||||
|
will be used as options for the resultant
|
||||||
|
``getLastError`` command. For example,
|
||||||
|
``save(..., write_concern={w: 2, fsync: True}, ...)`` will
|
||||||
|
wait until at least two servers have recorded the write and
|
||||||
|
will force an fsync on the primary server.
|
||||||
|
:param update: Django-style update keyword arguments
|
||||||
|
|
||||||
|
:returns the new or overwritten document
|
||||||
|
|
||||||
|
.. versionadded:: 0.10.2
|
||||||
|
"""
|
||||||
|
|
||||||
|
atomic_update = self.update(multi=False, upsert=True, write_concern=write_concern,
|
||||||
|
full_result=True,**update)
|
||||||
|
|
||||||
|
if atomic_update['updatedExisting']:
|
||||||
|
document = self.get()
|
||||||
|
else:
|
||||||
|
document = self._document.objects.with_id(atomic_update['upserted'])
|
||||||
|
return document
|
||||||
|
|
||||||
def update_one(self, upsert=False, write_concern=None, **update):
|
def update_one(self, upsert=False, write_concern=None, **update):
|
||||||
"""Perform an atomic update on the fields of the first document
|
"""Perform an atomic update on the fields of the first document
|
||||||
matched by the query.
|
matched by the query.
|
||||||
@@ -930,6 +956,7 @@ class BaseQuerySet(object):
|
|||||||
validate_read_preference('read_preference', read_preference)
|
validate_read_preference('read_preference', read_preference)
|
||||||
queryset = self.clone()
|
queryset = self.clone()
|
||||||
queryset._read_preference = read_preference
|
queryset._read_preference = read_preference
|
||||||
|
queryset._cursor_obj = None # we need to re-create the cursor object whenever we apply read_preference
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def scalar(self, *fields):
|
def scalar(self, *fields):
|
||||||
@@ -1443,6 +1470,14 @@ class BaseQuerySet(object):
|
|||||||
def _cursor(self):
|
def _cursor(self):
|
||||||
if self._cursor_obj is None:
|
if self._cursor_obj is None:
|
||||||
|
|
||||||
|
# In PyMongo 3+, we define the read preference on a collection
|
||||||
|
# level, not a cursor level. Thus, we need to get a cloned
|
||||||
|
# collection object using `with_options` first.
|
||||||
|
if IS_PYMONGO_3 and self._read_preference is not None:
|
||||||
|
self._cursor_obj = self._collection\
|
||||||
|
.with_options(read_preference=self._read_preference)\
|
||||||
|
.find(self._query, **self._cursor_args)
|
||||||
|
else:
|
||||||
self._cursor_obj = self._collection.find(self._query,
|
self._cursor_obj = self._collection.find(self._query,
|
||||||
**self._cursor_args)
|
**self._cursor_args)
|
||||||
# Apply where clauses to cursor
|
# Apply where clauses to cursor
|
||||||
|
|||||||
@@ -863,6 +863,20 @@ class IndexesTest(unittest.TestCase):
|
|||||||
self.assertTrue([('provider_ids.foo', 1)] in info)
|
self.assertTrue([('provider_ids.foo', 1)] in info)
|
||||||
self.assertTrue([('provider_ids.bar', 1)] in info)
|
self.assertTrue([('provider_ids.bar', 1)] in info)
|
||||||
|
|
||||||
|
def test_sparse_compound_indexes(self):
|
||||||
|
|
||||||
|
class MyDoc(Document):
|
||||||
|
provider_ids = DictField()
|
||||||
|
meta = {
|
||||||
|
"indexes": [{'fields': ("provider_ids.foo", "provider_ids.bar"),
|
||||||
|
'sparse': True}],
|
||||||
|
}
|
||||||
|
|
||||||
|
info = MyDoc.objects._collection.index_information()
|
||||||
|
self.assertEqual([('provider_ids.foo', 1), ('provider_ids.bar', 1)],
|
||||||
|
info['provider_ids.foo_1_provider_ids.bar_1']['key'])
|
||||||
|
self.assertTrue(info['provider_ids.foo_1_provider_ids.bar_1']['sparse'])
|
||||||
|
|
||||||
def test_text_indexes(self):
|
def test_text_indexes(self):
|
||||||
|
|
||||||
class Book(Document):
|
class Book(Document):
|
||||||
|
|||||||
@@ -571,6 +571,28 @@ class InstanceTest(unittest.TestCase):
|
|||||||
except Exception:
|
except Exception:
|
||||||
self.assertFalse("Threw wrong exception")
|
self.assertFalse("Threw wrong exception")
|
||||||
|
|
||||||
|
def test_reload_of_non_strict_with_special_field_name(self):
|
||||||
|
"""Ensures reloading works for documents with meta strict == False
|
||||||
|
"""
|
||||||
|
class Post(Document):
|
||||||
|
meta = {
|
||||||
|
'strict': False
|
||||||
|
}
|
||||||
|
title = StringField()
|
||||||
|
items = ListField()
|
||||||
|
|
||||||
|
Post.drop_collection()
|
||||||
|
|
||||||
|
Post._get_collection().insert({
|
||||||
|
"title": "Items eclipse",
|
||||||
|
"items": ["more lorem", "even more ipsum"]
|
||||||
|
})
|
||||||
|
|
||||||
|
post = Post.objects.first()
|
||||||
|
post.reload()
|
||||||
|
self.assertEqual(post.title, "Items eclipse")
|
||||||
|
self.assertEqual(post.items, ["more lorem", "even more ipsum"])
|
||||||
|
|
||||||
def test_dictionary_access(self):
|
def test_dictionary_access(self):
|
||||||
"""Ensure that dictionary-style field access works properly.
|
"""Ensure that dictionary-style field access works properly.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -2281,6 +2281,81 @@ class FieldTest(unittest.TestCase):
|
|||||||
Member.drop_collection()
|
Member.drop_collection()
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
|
def test_reference_class_with_abstract_parent(self):
|
||||||
|
"""Ensure that a class with an abstract parent can be referenced.
|
||||||
|
"""
|
||||||
|
class Sibling(Document):
|
||||||
|
name = StringField()
|
||||||
|
meta = {"abstract": True}
|
||||||
|
|
||||||
|
class Sister(Sibling):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Brother(Sibling):
|
||||||
|
sibling = ReferenceField(Sibling)
|
||||||
|
|
||||||
|
Sister.drop_collection()
|
||||||
|
Brother.drop_collection()
|
||||||
|
|
||||||
|
sister = Sister(name="Alice")
|
||||||
|
sister.save()
|
||||||
|
brother = Brother(name="Bob", sibling=sister)
|
||||||
|
brother.save()
|
||||||
|
|
||||||
|
self.assertEquals(Brother.objects[0].sibling.name, sister.name)
|
||||||
|
|
||||||
|
Sister.drop_collection()
|
||||||
|
Brother.drop_collection()
|
||||||
|
|
||||||
|
def test_reference_abstract_class(self):
|
||||||
|
"""Ensure that an abstract class instance cannot be used in the
|
||||||
|
reference of that abstract class.
|
||||||
|
"""
|
||||||
|
class Sibling(Document):
|
||||||
|
name = StringField()
|
||||||
|
meta = {"abstract": True}
|
||||||
|
|
||||||
|
class Sister(Sibling):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Brother(Sibling):
|
||||||
|
sibling = ReferenceField(Sibling)
|
||||||
|
|
||||||
|
Sister.drop_collection()
|
||||||
|
Brother.drop_collection()
|
||||||
|
|
||||||
|
sister = Sibling(name="Alice")
|
||||||
|
brother = Brother(name="Bob", sibling=sister)
|
||||||
|
self.assertRaises(ValidationError, brother.save)
|
||||||
|
|
||||||
|
Sister.drop_collection()
|
||||||
|
Brother.drop_collection()
|
||||||
|
|
||||||
|
def test_abstract_reference_base_type(self):
|
||||||
|
"""Ensure that an an abstract reference fails validation when given a
|
||||||
|
Document that does not inherit from the abstract type.
|
||||||
|
"""
|
||||||
|
class Sibling(Document):
|
||||||
|
name = StringField()
|
||||||
|
meta = {"abstract": True}
|
||||||
|
|
||||||
|
class Brother(Sibling):
|
||||||
|
sibling = ReferenceField(Sibling)
|
||||||
|
|
||||||
|
class Mother(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
Brother.drop_collection()
|
||||||
|
Mother.drop_collection()
|
||||||
|
|
||||||
|
mother = Mother(name="Carol")
|
||||||
|
mother.save()
|
||||||
|
brother = Brother(name="Bob", sibling=mother)
|
||||||
|
self.assertRaises(ValidationError, brother.save)
|
||||||
|
|
||||||
|
Brother.drop_collection()
|
||||||
|
Mother.drop_collection()
|
||||||
|
|
||||||
def test_generic_reference(self):
|
def test_generic_reference(self):
|
||||||
"""Ensure that a GenericReferenceField properly dereferences items.
|
"""Ensure that a GenericReferenceField properly dereferences items.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -680,12 +680,21 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
def test_upsert_one(self):
|
def test_upsert_one(self):
|
||||||
self.Person.drop_collection()
|
self.Person.drop_collection()
|
||||||
|
|
||||||
self.Person.objects(name="Bob", age=30).update_one(upsert=True)
|
bob = self.Person.objects(name="Bob", age=30).upsert_one()
|
||||||
|
|
||||||
bob = self.Person.objects.first()
|
|
||||||
self.assertEqual("Bob", bob.name)
|
self.assertEqual("Bob", bob.name)
|
||||||
self.assertEqual(30, bob.age)
|
self.assertEqual(30, bob.age)
|
||||||
|
|
||||||
|
bob.name = "Bobby"
|
||||||
|
bob.save()
|
||||||
|
|
||||||
|
bobby = self.Person.objects(name="Bobby", age=30).upsert_one()
|
||||||
|
|
||||||
|
self.assertEqual("Bobby", bobby.name)
|
||||||
|
self.assertEqual(30, bobby.age)
|
||||||
|
self.assertEqual(bob.id, bobby.id)
|
||||||
|
|
||||||
|
|
||||||
def test_set_on_insert(self):
|
def test_set_on_insert(self):
|
||||||
self.Person.drop_collection()
|
self.Person.drop_collection()
|
||||||
|
|
||||||
@@ -4165,7 +4174,11 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_read_preference(self):
|
def test_read_preference(self):
|
||||||
class Bar(Document):
|
class Bar(Document):
|
||||||
pass
|
txt = StringField()
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
'indexes': [ 'txt' ]
|
||||||
|
}
|
||||||
|
|
||||||
Bar.drop_collection()
|
Bar.drop_collection()
|
||||||
bars = list(Bar.objects(read_preference=ReadPreference.PRIMARY))
|
bars = list(Bar.objects(read_preference=ReadPreference.PRIMARY))
|
||||||
@@ -4177,9 +4190,51 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
error_class = TypeError
|
error_class = TypeError
|
||||||
self.assertRaises(error_class, Bar.objects, read_preference='Primary')
|
self.assertRaises(error_class, Bar.objects, read_preference='Primary')
|
||||||
|
|
||||||
|
# read_preference as a kwarg
|
||||||
bars = Bar.objects(read_preference=ReadPreference.SECONDARY_PREFERRED)
|
bars = Bar.objects(read_preference=ReadPreference.SECONDARY_PREFERRED)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(bars._cursor._Cursor__read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
|
# read_preference as a query set method
|
||||||
|
bars = Bar.objects.read_preference(ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(
|
||||||
|
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(bars._cursor._Cursor__read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
|
# read_preference after skip
|
||||||
|
bars = Bar.objects.skip(1) \
|
||||||
|
.read_preference(ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(
|
||||||
|
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(bars._cursor._Cursor__read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
|
# read_preference after limit
|
||||||
|
bars = Bar.objects.limit(1) \
|
||||||
|
.read_preference(ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(
|
||||||
|
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(bars._cursor._Cursor__read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
|
# read_preference after order_by
|
||||||
|
bars = Bar.objects.order_by('txt') \
|
||||||
|
.read_preference(ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(
|
||||||
|
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(bars._cursor._Cursor__read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
|
# read_preference after hint
|
||||||
|
bars = Bar.objects.hint([('txt', 1)]) \
|
||||||
|
.read_preference(ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(
|
||||||
|
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
self.assertEqual(bars._cursor._Cursor__read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
def test_json_simple(self):
|
def test_json_simple(self):
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ try:
|
|||||||
import unittest2 as unittest
|
import unittest2 as unittest
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import unittest
|
import unittest
|
||||||
|
from nose.plugins.skip import SkipTest
|
||||||
|
|
||||||
import pymongo
|
import pymongo
|
||||||
from bson.tz_util import utc
|
from bson.tz_util import utc
|
||||||
@@ -51,6 +52,42 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
conn = get_connection('testdb')
|
conn = get_connection('testdb')
|
||||||
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
|
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
|
||||||
|
|
||||||
|
def test_connect_in_mocking(self):
|
||||||
|
"""Ensure that the connect() method works properly in mocking.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import mongomock
|
||||||
|
except ImportError:
|
||||||
|
raise SkipTest('you need mongomock installed to run this testcase')
|
||||||
|
|
||||||
|
connect('mongoenginetest', host='mongomock://localhost')
|
||||||
|
conn = get_connection()
|
||||||
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
|
connect('mongoenginetest2', host='mongomock://localhost', alias='testdb2')
|
||||||
|
conn = get_connection('testdb2')
|
||||||
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
|
connect('mongoenginetest3', host='mongodb://localhost', is_mock=True, alias='testdb3')
|
||||||
|
conn = get_connection('testdb3')
|
||||||
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
|
connect('mongoenginetest4', is_mock=True, alias='testdb4')
|
||||||
|
conn = get_connection('testdb4')
|
||||||
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
|
connect(host='mongodb://localhost:27017/mongoenginetest5', is_mock=True, alias='testdb5')
|
||||||
|
conn = get_connection('testdb5')
|
||||||
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
|
connect(host='mongomock://localhost:27017/mongoenginetest6', alias='testdb6')
|
||||||
|
conn = get_connection('testdb6')
|
||||||
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
|
connect(host='mongomock://localhost:27017/mongoenginetest7', is_mock=True, alias='testdb7')
|
||||||
|
conn = get_connection('testdb7')
|
||||||
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
def test_disconnect(self):
|
def test_disconnect(self):
|
||||||
"""Ensure that the disconnect() method works properly
|
"""Ensure that the disconnect() method works properly
|
||||||
"""
|
"""
|
||||||
@@ -151,7 +188,7 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
self.assertRaises(ConnectionError, get_db, 'test1')
|
self.assertRaises(ConnectionError, get_db, 'test1')
|
||||||
|
|
||||||
# Authentication succeeds with "authSource"
|
# Authentication succeeds with "authSource"
|
||||||
test_conn2 = connect(
|
connect(
|
||||||
'mongoenginetest', alias='test2',
|
'mongoenginetest', alias='test2',
|
||||||
host=('mongodb://username2:password@localhost/'
|
host=('mongodb://username2:password@localhost/'
|
||||||
'mongoenginetest?authSource=admin')
|
'mongoenginetest?authSource=admin')
|
||||||
|
|||||||
3
tox.ini
3
tox.ini
@@ -12,3 +12,6 @@ deps =
|
|||||||
mg28: PyMongo>=2.8,<3.0
|
mg28: PyMongo>=2.8,<3.0
|
||||||
mg30: PyMongo>=3.0
|
mg30: PyMongo>=3.0
|
||||||
mgdev: https://github.com/mongodb/mongo-python-driver/tarball/master
|
mgdev: https://github.com/mongodb/mongo-python-driver/tarball/master
|
||||||
|
setenv =
|
||||||
|
PYTHON_EGG_CACHE = {envdir}/python-eggs
|
||||||
|
passenv = windir
|
||||||
|
|||||||
Reference in New Issue
Block a user