Compare commits

..

73 Commits

Author SHA1 Message Date
Bastien Gérard
88c4aa2d87 Merge pull request #2014 from MongoEngine/draft_v017
Bump version 0.17.0
2019-03-11 22:02:41 +01:00
Erdenezul Batmunkh
fb8c0d8fe3 Merge branch 'master' into draft_v017 2019-03-10 22:22:29 +08:00
erdenezul
1a863725d1 Merge pull request #2011 from bagerard/fix_batch_size_qs_clone
Fix queryset batch_size that wasn't copied to cloned queryset
2019-03-10 21:22:46 +08:00
Erdenezul Batmunkh
7b4245c91c Bump version 0.17.0 2019-03-10 21:16:58 +08:00
Bastien Gérard
9bd0d6b99d update changelog 2019-03-04 23:05:22 +01:00
Bastien Gérard
b640c766db Fix queryset batch_size that wasn't copied to cloned queryset 2019-03-04 23:01:12 +01:00
erdenezul
50ffa8014e Merge pull request #2008 from bagerard/refactor_deprecated_pymongo_test
refactored deprecated pymongo methods in tests
2019-03-03 08:06:48 +08:00
Bastien Gérard
7ef688b256 Added a test for push in DictField (relates to #1679) 2019-03-02 22:05:23 +01:00
Bastien Gérard
b4fe0b35e4 Merge branch 'master' of github.com:MongoEngine/mongoengine into refactor_deprecated_pymongo_test 2019-03-02 21:48:41 +01:00
erdenezul
a2cbbdf819 Merge pull request #2009 from bagerard/mongodb_34
Add support for Mongodb 34 (tests/travis)
2019-03-02 08:24:59 +08:00
Bastien Gérard
35b7efe3f4 refactored deprecated pymongo methods in tests
- remove/count/add_user/insert
- added pymongo_support
2019-03-02 00:28:38 +01:00
Bastien Gérard
7cea2a768f Fix recent flaky test for python 3.6 2019-02-26 22:57:45 +01:00
Bastien Gérard
7247b9b68e additional fixes to support Mongo3.4 2019-02-26 21:38:23 +01:00
Bastien Gérard
dca837b843 Add suport for Mongo 3.4 (travis, fix tests) 2019-02-26 21:38:23 +01:00
Bastien Gérard
c60c2ee8d0 fix minor styling issue in tests 2019-02-25 22:33:36 +01:00
Bastien Gérard
3cdb5b5db2 fix poor assert's in tests 2019-02-25 22:29:44 +01:00
Bastien Gérard
b9cc8a4ca9 refactor more field tests into submodules #1983 2019-02-24 10:24:12 +01:00
Bastien Gérard
28606e9985 refactor fields tests (float, int, lazyref, long, url) #1983 2019-02-24 10:24:12 +01:00
Bastien Gérard
5bbe782812 fix deprecated call to pymongo save() in tests 2019-02-23 22:37:32 +01:00
erdenezul
d65861cdf7 Merge pull request #2004 from bagerard/fix_iteritems_itervalues_23_compat
refactored iteritems/itervalues to improve 2/3 compat
2019-02-23 22:46:21 +08:00
Bastien Gérard
c8df3fd2a7 fix conflict 2019-02-21 21:27:08 +01:00
Bastien Gérard
6cfe6652a3 Merge pull request #2001 from bagerard/separate_date_fields_tests_into_submodules
Separate fields tests into separate modules (date/datetime/complexdt)
2019-02-19 20:57:15 +01:00
erdenezul
6b711da69d Merge pull request #1998 from bagerard/Fix_update_full_result_documentation
Fix documentation of Queryset.update regarding full_result #1995
2019-02-19 17:16:42 +08:00
erdenezul
595cb99b2d Merge pull request #2005 from bagerard/add_deprecation_warn_embeddedDoc_save
Add DeprecationWarning for EmbeddedDocument.save & .reload
2019-02-19 17:13:57 +08:00
Bastien Gérard
f0a3445250 minor fix for import order 2019-02-18 22:22:20 +01:00
Bastien Gérard
6d353dae1e refactored iteritems/itervalues to improve 2/3 compat #2003 2019-02-18 22:13:05 +01:00
Bastien Gérard
57a38282a9 Add DeprecationWarning for EmbeddedDocument.save & .reload - those will be removed soon 2019-02-18 22:03:03 +01:00
erdenezul
db47604865 Merge pull request #1999 from bagerard/fix_documentation_dynamicdoc_underscore_field_undeclared
Improve DynamicDocument documentation regarding fields with underscore
2019-02-18 09:33:39 +08:00
erdenezul
2a121fe202 Merge branch 'master' into Fix_update_full_result_documentation 2019-02-18 09:31:00 +08:00
erdenezul
36baff0d7f Merge pull request #2002 from bagerard/freyr_binaryfield_query_value_type
Fix Binaryfield query operator
2019-02-18 09:27:01 +08:00
Bastien Gérard
201f3008b1 Fix for py3 in binary field test 2019-02-17 22:56:56 +01:00
Bastien Gérard
f4873fee18 add additional test for #1976 2019-02-17 22:50:42 +01:00
Bastien Gérard
e02261be6d add test coverage for #1557 2019-02-17 22:22:29 +01:00
Bastien Gérard
2919e6765c separate test on binary field (#1983) 2019-02-17 21:45:05 +01:00
Bastien Gérard
b8fc4d0079 Merge branch 'master' of github.com:MongoEngine/mongoengine into freyr_binaryfield_query_value_type 2019-02-17 21:38:24 +01:00
Bastien Gérard
4a46f5f095 Separate fields tests into separate modules (date/datetime/complexdatetime)
relates to #1983
2019-02-17 21:32:46 +01:00
Bastien Gérard
3484ceabb8 Fix documentation regarding limitation with fields with underscore in their name in DynamicDocument 2019-02-17 09:05:27 +01:00
Bastien Gérard
cab659dce6 Fix documentation of Queryset.update regarding full_result #1995 2019-02-16 21:54:05 +01:00
Bastien Gérard
a657f29439 Merge pull request #1996 from hrmon/patch-1
Fix indentation in LazyReferenceField ref doc
2019-02-16 20:31:19 +01:00
Hamidreza
4c054bf316 Fix indentation in LazyReferenceField ref doc 2019-02-13 21:27:54 +03:30
erdenezul
dc7922c38b Merge pull request #1976 from 3ddi/eddie/revert_hidden_fields
Bring back _cls and _id fields just as they return in pymongo
2019-02-13 14:25:12 +08:00
Eddie Linder
c6c68abfcc Update changelog and add another ut case 2019-01-11 19:23:47 +02:00
Bastien Gérard
6aacb0c898 Merge pull request #1982 from gbroques/add-long-field-to-docs
Add missing LongField to Fields list in Documentation
2018-12-29 23:28:02 +01:00
G Roques
e7000db491 Add LongField to Fields list 2018-12-29 13:08:39 -06:00
Bastien Gérard
fce994ea7f Merge pull request #1975 from bagerard/dictfield-key-with-dollar-char__clone
PR CLONE - Dictfield key with dollar char  clone
2018-12-27 23:02:00 +01:00
erdenezul
6c6446765e Merge pull request #1980 from bagerard/update_all_doc
minor update to .all() doc to make it clear that it return a queryset
2018-12-26 11:50:11 +08:00
Bastien Gérard
69a99c70c6 minor update to .all() doc to make it clear that it does not return a list but a queryset 2018-12-24 23:51:30 +01:00
Eddie Linder
56d9f7a8af Fix ut to pass constantly 2018-12-20 01:14:23 +02:00
Eddie Linder
363aefe399 Get rid of _get_as_pymongo wrapper altogether as we return raw output 2018-12-20 01:09:28 +02:00
Eddie Linder
7fd4f792ba Don't hide _cls field when using as_pymongo 2018-12-20 01:02:42 +02:00
Eddie Linder
6fbdde63d8 Fix id in test_only_after_count ut 2018-12-20 00:57:02 +02:00
Eddie Linder
b04dc90cdf Bring back _cls and _id fields just as they return in pymongo 2018-12-20 00:39:32 +02:00
Bastien Gérard
b525c91bd3 remove tags file that shouldnt be added to situkangsayur s commit 2018-12-19 20:33:37 +01:00
Hendri Karisma
a32c893078 in the mid DictField key can accept dollar sign 2018-12-19 20:24:28 +01:00
Bastien Gérard
2c6a744848 Merge pull request #1853 from bagerard/fix_baselist_marked_changed_bug
Fix BaseList/Dict bug with marked_as_changed getting called when operation fails
2018-12-19 20:16:57 +01:00
Bastien Gérard
4492874d08 Merge branch 'master' of github.com:MongoEngine/mongoengine into fix_baselist_marked_changed_bug 2018-12-15 20:36:42 +01:00
erdenezul
d3a592e5bf Merge pull request #1913 from bagerard/1453_mapfield_list_reference_field
Fix complex combinations of DictField(ListField(ReferenceField))
2018-12-13 09:16:19 +08:00
erdenezul
cab21b1b21 Merge pull request #1969 from buggyspace/master
Fix .only() working improperly after using .count() of the same instance of QuerySet
2018-12-12 19:25:31 +08:00
Tom Floyer
1319e422ea Updated docs/changelog.rst 2018-12-08 22:38:04 +03:00
Tom Floyer
c88ea40b57 Updated AUTHORS 2018-12-08 22:32:46 +03:00
Tom Floyer
3194a37fcb Reset cursor object after .count()
This change fixes incorrect result of .only() method of QuerySet
instance after using .count().
2018-12-08 22:16:57 +03:00
Tom Floyer
72ebaa52e9 Ensure that QuerySet.only() works correctly after QuerySet.count()
This test checks if .only() method of QuerySet instance works after
using .count() method.
2018-12-08 22:10:10 +03:00
erdenezul
0e00695fc7 Merge pull request #1968 from amir-almusawi/doc-typo
fixed small typographical error
2018-12-08 12:23:18 +08:00
amir-almusawi
48a691e722 fixed small typographical error 2018-12-07 22:08:05 -06:00
erdenezul
cf54d6d6f8 Merge pull request #1967 from erdenezul/bump_version_16_3
bump version 0.16.3
2018-12-06 21:56:31 +08:00
Erdenezul Batmunkh
a03fe234d0 bump version 0.16.3 2018-12-06 16:22:00 +08:00
erdenezul
d88d40cc08 Merge pull request #1966 from tjhall13/1965-fix-position-op-with-nested-list-in-embedded-document
Fix bug #1965 of $position and $push operators do not work with nested list
2018-12-06 14:02:46 +08:00
Trevor Hall
d3b4af116e Add fix to changelog.rst #1965 2018-12-05 23:35:42 -06:00
Trevor Hall
352b23331b Fix bug #1965 of $position and $push operators do not work with list in an EmbeddedDocument. Set key value to joined parts excluding the index at the end. Added test case 2018-12-05 20:18:48 -06:00
Bastien Gérard
0fa6610fdb Fix complex combinations of DictField(ListField(ReferenceField)) (#1453) 2018-10-16 21:08:59 +02:00
Bastien Gérard
7ddbea697e fix CI that fails due to pypi + override BaseDict.get as it was missing 2018-08-30 14:33:57 +02:00
Bastien Gérard
d72daf5f39 Fix BaseList bug with marked_as_changed getting called on error
+ Fix BaseDict as well since it suffers from the same problem
2018-08-27 22:34:11 +02:00
Freyr
54d8c64ad5 Fixes query operations on BinaryFields
Overrides prepare_query_value in BinaryField to wrap the query value in
bson.binary.Binary. This was already done in the to_mongo call when
saving a document, but query operations required the user to convert the
value manually.

Fixes https://github.com/MongoEngine/mongoengine/issues/1127
2017-05-25 10:46:16 -04:00
62 changed files with 4235 additions and 3225 deletions

View File

@@ -19,8 +19,14 @@ elif [ "$MONGODB" = "3.2" ]; then
sudo apt-get update
sudo apt-get install mongodb-org-server=3.2.20
# service should be started automatically
elif [ "$MONGODB" = "3.4" ]; then
sudo apt-key adv --keyserver keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6
echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list
sudo apt-get update
sudo apt-get install mongodb-org-server=3.4.17
# service should be started automatically
else
echo "Invalid MongoDB version, expected 2.6, 3.0, or 3.2"
echo "Invalid MongoDB version, expected 2.6, 3.0, 3.2 or 3.4."
exit 1
fi;

View File

@@ -6,6 +6,7 @@
# v3.5, v3.6, PyPy, and PyMongo v3.x.
# * MongoDB v3.0 & v3.2 are tested against Python v2.7, v3.5 & v3.6
# and Pymongo v3.5 & v3.x
# * MongoDB v3.4 is tested against v3.6 and Pymongo v3.x
# Reminder: Update README.rst if you change MongoDB versions we test.
language: python
@@ -26,16 +27,14 @@ matrix:
include:
- python: 2.7
env: MONGODB=3.0 PYMONGO=3.5
- python: 2.7
env: MONGODB=3.2 PYMONGO=3.x
- python: 3.5
env: MONGODB=3.0 PYMONGO=3.5
- python: 3.5
env: MONGODB=3.2 PYMONGO=3.x
- python: 3.6
env: MONGODB=3.0 PYMONGO=3.5
- python: 3.6
env: MONGODB=3.2 PYMONGO=3.x
- python: 3.6
env: MONGODB=3.4 PYMONGO=3.x
before_install:
- bash .install_mongodb_on_travis.sh

View File

@@ -247,3 +247,5 @@ that much better:
* Erdenezul Batmunkh (https://github.com/erdenezul)
* Andy Yankovsky (https://github.com/werat)
* Bastien Gérard (https://github.com/bagerard)
* Trevor Hall (https://github.com/tjhall13)
* Gleb Voropaev (https://github.com/buggyspace)

View File

@@ -26,7 +26,7 @@ an `API reference <https://mongoengine-odm.readthedocs.io/apireference.html>`_.
Supported MongoDB Versions
==========================
MongoEngine is currently tested against MongoDB v2.6, v3.0 and v3.2. Future
MongoEngine is currently tested against MongoDB v2.6, v3.0, v3.2 and v3.4. Future
versions should be supported as well, but aren't actively tested at the moment.
Make sure to open an issue or submit a pull request if you experience any
problems with MongoDB v3.4+.
@@ -36,7 +36,7 @@ Installation
We recommend the use of `virtualenv <https://virtualenv.pypa.io/>`_ and of
`pip <https://pip.pypa.io/>`_. You can then use ``pip install -U mongoengine``.
You may also have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
and thus you can use ``easy_install -U mongoengine``. Another option is
and thus you can use ``easy_install -U mongoengine``. Another option is
`pipenv <https://docs.pipenv.org/>`_. You can then use ``pipenv install mongoengine``
to both create the virtual environment and install the package. Otherwise, you can
download the source from `GitHub <http://github.com/MongoEngine/mongoengine>`_ and

View File

@@ -6,6 +6,21 @@ Development
===========
- (Fill this out as you fix issues and develop your features).
Changes in 0.17.0
=================
- Fix .only() working improperly after using .count() of the same instance of QuerySet
- Fix batch_size that was not copied when cloning a queryset object #2011
- POTENTIAL BREAKING CHANGE: All result fields are now passed, including internal fields (_cls, _id) when using `QuerySet.as_pymongo` #1976
- Document a BREAKING CHANGE introduced in 0.15.3 and not reported at that time (#1995)
- Fix InvalidStringData error when using modify on a BinaryField #1127
- DEPRECATION: `EmbeddedDocument.save` & `.reload` are marked as deprecated and will be removed in a next version of mongoengine #1552
- Fix test suite and CI to support MongoDB 3.4 #1445
=================
Changes in 0.16.3
=================
- Fix $push with $position operator not working with lists in embedded document #1965
=================
Changes in 0.16.2
=================
@@ -57,6 +72,7 @@ Changes in 0.16.0
Changes in 0.15.3
=================
- BREAKING CHANGES: `Queryset.update/update_one` methods now returns an UpdateResult when `full_result=True` is provided and no longer a dict (relates to #1491)
- Subfield resolve error in generic_emdedded_document query #1651 #1652
- use each modifier only with $position #1673 #1675
- Improve LazyReferenceField and GenericLazyReferenceField with nested fields #1704

View File

@@ -85,6 +85,7 @@ are as follows:
* :class:`~mongoengine.fields.ImageField`
* :class:`~mongoengine.fields.IntField`
* :class:`~mongoengine.fields.ListField`
* :class:`~mongoengine.fields.LongField`
* :class:`~mongoengine.fields.MapField`
* :class:`~mongoengine.fields.ObjectIdField`
* :class:`~mongoengine.fields.ReferenceField`
@@ -155,7 +156,7 @@ arguments can be set on all fields:
An iterable (e.g. list, tuple or set) of choices to which the value of this
field should be limited.
Can be either be a nested tuples of value (stored in mongo) and a
Can either be nested tuples of value (stored in mongo) and a
human readable key ::
SIZE = (('S', 'Small'),

View File

@@ -23,7 +23,7 @@ __all__ = (list(document.__all__) + list(fields.__all__) +
list(signals.__all__) + list(errors.__all__))
VERSION = (0, 16, 2)
VERSION = (0, 17, 0)
def get_version():

View File

@@ -2,11 +2,30 @@ import weakref
from bson import DBRef
import six
from six import iteritems
from mongoengine.common import _import_class
from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
__all__ = ('BaseDict', 'BaseList', 'EmbeddedDocumentList', 'LazyReference')
__all__ = ('BaseDict', 'StrictDict', 'BaseList', 'EmbeddedDocumentList', 'LazyReference')
def mark_as_changed_wrapper(parent_method):
"""Decorators that ensures _mark_as_changed method gets called"""
def wrapper(self, *args, **kwargs):
result = parent_method(self, *args, **kwargs) # Can't use super() in the decorator
self._mark_as_changed()
return result
return wrapper
def mark_key_as_changed_wrapper(parent_method):
"""Decorators that ensures _mark_as_changed method gets called with the key argument"""
def wrapper(self, key, *args, **kwargs):
result = parent_method(self, key, *args, **kwargs) # Can't use super() in the decorator
self._mark_as_changed(key)
return result
return wrapper
class BaseDict(dict):
@@ -24,7 +43,14 @@ class BaseDict(dict):
self._name = name
super(BaseDict, self).__init__(dict_items)
def __getitem__(self, key, *args, **kwargs):
def get(self, key, default=None):
# get does not use __getitem__ by default so we must override it as well
try:
return self.__getitem__(key)
except KeyError:
return default
def __getitem__(self, key):
value = super(BaseDict, self).__getitem__(key)
EmbeddedDocument = _import_class('EmbeddedDocument')
@@ -40,22 +66,6 @@ class BaseDict(dict):
value._instance = self._instance
return value
def __setitem__(self, key, value, *args, **kwargs):
self._mark_as_changed(key)
return super(BaseDict, self).__setitem__(key, value)
def __delete__(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).__delete__(*args, **kwargs)
def __delitem__(self, key, *args, **kwargs):
self._mark_as_changed(key)
return super(BaseDict, self).__delitem__(key)
def __delattr__(self, key, *args, **kwargs):
self._mark_as_changed(key)
return super(BaseDict, self).__delattr__(key)
def __getstate__(self):
self.instance = None
self._dereferenced = False
@@ -65,25 +75,14 @@ class BaseDict(dict):
self = state
return self
def clear(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).clear()
def pop(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).pop(*args, **kwargs)
def popitem(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).popitem()
def setdefault(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).setdefault(*args, **kwargs)
def update(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).update(*args, **kwargs)
__setitem__ = mark_key_as_changed_wrapper(dict.__setitem__)
__delattr__ = mark_key_as_changed_wrapper(dict.__delattr__)
__delitem__ = mark_key_as_changed_wrapper(dict.__delitem__)
pop = mark_as_changed_wrapper(dict.pop)
clear = mark_as_changed_wrapper(dict.clear)
update = mark_as_changed_wrapper(dict.update)
popitem = mark_as_changed_wrapper(dict.popitem)
setdefault = mark_as_changed_wrapper(dict.setdefault)
def _mark_as_changed(self, key=None):
if hasattr(self._instance, '_mark_as_changed'):
@@ -108,17 +107,24 @@ class BaseList(list):
self._name = name
super(BaseList, self).__init__(list_items)
def __getitem__(self, key, *args, **kwargs):
def __getitem__(self, key):
value = super(BaseList, self).__getitem__(key)
if isinstance(key, slice):
# When receiving a slice operator, we don't convert the structure and bind
# to parent's instance. This is buggy for now but would require more work to be handled properly
return value
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(value, EmbeddedDocument) and value._instance is None:
value._instance = self._instance
elif isinstance(value, dict) and not isinstance(value, BaseDict):
# Replace dict by BaseDict
value = BaseDict(value, None, '%s.%s' % (self._name, key))
super(BaseList, self).__setitem__(key, value)
value._instance = self._instance
elif isinstance(value, list) and not isinstance(value, BaseList):
# Replace list by BaseList
value = BaseList(value, None, '%s.%s' % (self._name, key))
super(BaseList, self).__setitem__(key, value)
value._instance = self._instance
@@ -128,25 +134,6 @@ class BaseList(list):
for v in super(BaseList, self).__iter__():
yield v
def __setitem__(self, key, value, *args, **kwargs):
if isinstance(key, slice):
self._mark_as_changed()
else:
self._mark_as_changed(key)
return super(BaseList, self).__setitem__(key, value)
def __delitem__(self, key):
self._mark_as_changed()
return super(BaseList, self).__delitem__(key)
def __setslice__(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).__setslice__(*args, **kwargs)
def __delslice__(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).__delslice__(*args, **kwargs)
def __getstate__(self):
self.instance = None
self._dereferenced = False
@@ -156,41 +143,40 @@ class BaseList(list):
self = state
return self
def __iadd__(self, other):
self._mark_as_changed()
return super(BaseList, self).__iadd__(other)
def __setitem__(self, key, value):
changed_key = key
if isinstance(key, slice):
# In case of slice, we don't bother to identify the exact elements being updated
# instead, we simply marks the whole list as changed
changed_key = None
def __imul__(self, other):
self._mark_as_changed()
return super(BaseList, self).__imul__(other)
result = super(BaseList, self).__setitem__(key, value)
self._mark_as_changed(changed_key)
return result
def append(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).append(*args, **kwargs)
append = mark_as_changed_wrapper(list.append)
extend = mark_as_changed_wrapper(list.extend)
insert = mark_as_changed_wrapper(list.insert)
pop = mark_as_changed_wrapper(list.pop)
remove = mark_as_changed_wrapper(list.remove)
reverse = mark_as_changed_wrapper(list.reverse)
sort = mark_as_changed_wrapper(list.sort)
__delitem__ = mark_as_changed_wrapper(list.__delitem__)
__iadd__ = mark_as_changed_wrapper(list.__iadd__)
__imul__ = mark_as_changed_wrapper(list.__imul__)
def extend(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).extend(*args, **kwargs)
if six.PY2:
# Under py3 __setslice__, __delslice__ and __getslice__
# are replaced by __setitem__, __delitem__ and __getitem__ with a slice as parameter
# so we mimic this under python 2
def __setslice__(self, i, j, sequence):
return self.__setitem__(slice(i, j), sequence)
def insert(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).insert(*args, **kwargs)
def __delslice__(self, i, j):
return self.__delitem__(slice(i, j))
def pop(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).pop(*args, **kwargs)
def remove(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).remove(*args, **kwargs)
def reverse(self):
self._mark_as_changed()
return super(BaseList, self).reverse()
def sort(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).sort(*args, **kwargs)
def __getslice__(self, i, j):
return self.__getitem__(slice(i, j))
def _mark_as_changed(self, key=None):
if hasattr(self._instance, '_mark_as_changed'):
@@ -204,6 +190,10 @@ class BaseList(list):
class EmbeddedDocumentList(BaseList):
def __init__(self, list_items, instance, name):
super(EmbeddedDocumentList, self).__init__(list_items, instance, name)
self._instance = instance
@classmethod
def __match_all(cls, embedded_doc, kwargs):
"""Return True if a given embedded doc matches all the filter
@@ -222,10 +212,6 @@ class EmbeddedDocumentList(BaseList):
return embedded_docs
return [doc for doc in embedded_docs if cls.__match_all(doc, kwargs)]
def __init__(self, list_items, instance, name):
super(EmbeddedDocumentList, self).__init__(list_items, instance, name)
self._instance = instance
def filter(self, **kwargs):
"""
Filters the list by only including embedded documents with the
@@ -378,7 +364,7 @@ class StrictDict(object):
_classes = {}
def __init__(self, **kwargs):
for k, v in kwargs.iteritems():
for k, v in iteritems(kwargs):
setattr(self, k, v)
def __getitem__(self, key):
@@ -426,7 +412,7 @@ class StrictDict(object):
return (key for key in self.__slots__ if hasattr(self, key))
def __len__(self):
return len(list(self.iteritems()))
return len(list(iteritems(self)))
def __eq__(self, other):
return self.items() == other.items()

View File

@@ -2,11 +2,10 @@ import copy
import numbers
from functools import partial
from bson import ObjectId, json_util
from bson.dbref import DBRef
from bson.son import SON
from bson import DBRef, ObjectId, SON, json_util
import pymongo
import six
from six import iteritems
from mongoengine import signals
from mongoengine.base.common import get_document
@@ -85,7 +84,7 @@ class BaseDocument(object):
self._dynamic_fields = SON()
# Assign default values to instance
for key, field in self._fields.iteritems():
for key, field in iteritems(self._fields):
if self._db_field_map.get(key, key) in __only_fields:
continue
value = getattr(self, key, None)
@@ -97,14 +96,14 @@ class BaseDocument(object):
# Set passed values after initialisation
if self._dynamic:
dynamic_data = {}
for key, value in values.iteritems():
for key, value in iteritems(values):
if key in self._fields or key == '_id':
setattr(self, key, value)
else:
dynamic_data[key] = value
else:
FileField = _import_class('FileField')
for key, value in values.iteritems():
for key, value in iteritems(values):
key = self._reverse_db_field_map.get(key, key)
if key in self._fields or key in ('id', 'pk', '_cls'):
if __auto_convert and value is not None:
@@ -120,7 +119,7 @@ class BaseDocument(object):
if self._dynamic:
self._dynamic_lock = False
for key, value in dynamic_data.iteritems():
for key, value in iteritems(dynamic_data):
setattr(self, key, value)
# Flag initialised
@@ -515,7 +514,7 @@ class BaseDocument(object):
if not hasattr(data, 'items'):
iterator = enumerate(data)
else:
iterator = data.iteritems()
iterator = iteritems(data)
for index_or_key, value in iterator:
item_key = '%s%s.' % (base_key, index_or_key)
@@ -680,7 +679,7 @@ class BaseDocument(object):
# Convert SON to a data dict, making sure each key is a string and
# corresponds to the right db field.
data = {}
for key, value in son.iteritems():
for key, value in iteritems(son):
key = str(key)
key = cls._db_field_map.get(key, key)
data[key] = value
@@ -696,7 +695,7 @@ class BaseDocument(object):
if not _auto_dereference:
fields = copy.deepcopy(fields)
for field_name, field in fields.iteritems():
for field_name, field in iteritems(fields):
field._auto_dereference = _auto_dereference
if field.db_field in data:
value = data[field.db_field]
@@ -717,7 +716,7 @@ class BaseDocument(object):
# In STRICT documents, remove any keys that aren't in cls._fields
if cls.STRICT:
data = {k: v for k, v in data.iteritems() if k in cls._fields}
data = {k: v for k, v in iteritems(data) if k in cls._fields}
obj = cls(__auto_convert=False, _created=created, __only_fields=only_fields, **data)
obj._changed_fields = changed_fields

View File

@@ -5,6 +5,7 @@ import weakref
from bson import DBRef, ObjectId, SON
import pymongo
import six
from six import iteritems
from mongoengine.base.common import UPDATE_OPERATORS
from mongoengine.base.datastructures import (BaseDict, BaseList,
@@ -382,11 +383,11 @@ class ComplexBaseField(BaseField):
if self.field:
value_dict = {
key: self.field._to_mongo_safe_call(item, use_db_field, fields)
for key, item in value.iteritems()
for key, item in iteritems(value)
}
else:
value_dict = {}
for k, v in value.iteritems():
for k, v in iteritems(value):
if isinstance(v, Document):
# We need the id from the saved object to create the DBRef
if v.pk is None:
@@ -423,7 +424,7 @@ class ComplexBaseField(BaseField):
errors = {}
if self.field:
if hasattr(value, 'iteritems') or hasattr(value, 'items'):
sequence = value.iteritems()
sequence = iteritems(value)
else:
sequence = enumerate(value)
for k, v in sequence:

View File

@@ -1,6 +1,7 @@
import warnings
import six
from six import iteritems, itervalues
from mongoengine.base.common import _document_registry
from mongoengine.base.fields import BaseField, ComplexBaseField, ObjectIdField
@@ -62,7 +63,7 @@ class DocumentMetaclass(type):
# Standard object mixin - merge in any Fields
if not hasattr(base, '_meta'):
base_fields = {}
for attr_name, attr_value in base.__dict__.iteritems():
for attr_name, attr_value in iteritems(base.__dict__):
if not isinstance(attr_value, BaseField):
continue
attr_value.name = attr_name
@@ -74,7 +75,7 @@ class DocumentMetaclass(type):
# Discover any document fields
field_names = {}
for attr_name, attr_value in attrs.iteritems():
for attr_name, attr_value in iteritems(attrs):
if not isinstance(attr_value, BaseField):
continue
attr_value.name = attr_name
@@ -103,7 +104,7 @@ class DocumentMetaclass(type):
attrs['_fields_ordered'] = tuple(i[1] for i in sorted(
(v.creation_counter, v.name)
for v in doc_fields.itervalues()))
for v in itervalues(doc_fields)))
#
# Set document hierarchy
@@ -173,7 +174,7 @@ class DocumentMetaclass(type):
f.__dict__.update({'im_self': getattr(f, '__self__')})
# Handle delete rules
for field in new_class._fields.itervalues():
for field in itervalues(new_class._fields):
f = field
if f.owner_document is None:
f.owner_document = new_class
@@ -375,7 +376,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
new_class.objects = QuerySetManager()
# Validate the fields and set primary key if needed
for field_name, field in new_class._fields.iteritems():
for field_name, field in iteritems(new_class._fields):
if field.primary_key:
# Ensure only one primary key is set
current_pk = new_class._meta.get('id_field')
@@ -438,7 +439,7 @@ class MetaDict(dict):
_merge_options = ('indexes',)
def merge(self, new_options):
for k, v in new_options.iteritems():
for k, v in iteritems(new_options):
if k in self._merge_options:
self[k] = self.get(k, []) + v
else:

View File

@@ -1,10 +1,10 @@
from pymongo import MongoClient, ReadPreference, uri_parser
import six
from mongoengine.python_support import IS_PYMONGO_3
from mongoengine.pymongo_support import IS_PYMONGO_3
__all__ = ['MongoEngineConnectionError', 'connect', 'register_connection',
'DEFAULT_CONNECTION_NAME']
'DEFAULT_CONNECTION_NAME', 'get_db']
DEFAULT_CONNECTION_NAME = 'default'

View File

@@ -1,8 +1,11 @@
from contextlib import contextmanager
from pymongo.write_concern import WriteConcern
from six import iteritems
from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.pymongo_support import count_documents
__all__ = ('switch_db', 'switch_collection', 'no_dereference',
'no_sub_classes', 'query_counter', 'set_write_concern')
@@ -112,7 +115,7 @@ class no_dereference(object):
GenericReferenceField = _import_class('GenericReferenceField')
ComplexBaseField = _import_class('ComplexBaseField')
self.deref_fields = [k for k, v in self.cls._fields.iteritems()
self.deref_fields = [k for k, v in iteritems(self.cls._fields)
if isinstance(v, (ReferenceField,
GenericReferenceField,
ComplexBaseField))]
@@ -235,7 +238,7 @@ class query_counter(object):
and substracting the queries issued by this context. In fact everytime this is called, 1 query is
issued so we need to balance that
"""
count = self.db.system.profile.find(self._ignored_query).count() - self._ctx_query_counter
count = count_documents(self.db.system.profile, self._ignored_query) - self._ctx_query_counter
self._ctx_query_counter += 1 # Account for the query we just issued to gather the information
return count

View File

@@ -1,5 +1,6 @@
from bson import DBRef, SON
import six
from six import iteritems
from mongoengine.base import (BaseDict, BaseList, EmbeddedDocumentList,
TopLevelDocumentMetaclass, get_document)
@@ -52,26 +53,40 @@ class DeReference(object):
[i.__class__ == doc_type for i in items.values()]):
return items
elif not field.dbref:
# We must turn the ObjectIds into DBRefs
# Recursively dig into the sub items of a list/dict
# to turn the ObjectIds into DBRefs
def _get_items_from_list(items):
new_items = []
for v in items:
value = v
if isinstance(v, dict):
value = _get_items_from_dict(v)
elif isinstance(v, list):
value = _get_items_from_list(v)
elif not isinstance(v, (DBRef, Document)):
value = field.to_python(v)
new_items.append(value)
return new_items
def _get_items_from_dict(items):
new_items = {}
for k, v in iteritems(items):
value = v
if isinstance(v, list):
value = _get_items_from_list(v)
elif isinstance(v, dict):
value = _get_items_from_dict(v)
elif not isinstance(v, (DBRef, Document)):
value = field.to_python(v)
new_items[k] = value
return new_items
if not hasattr(items, 'items'):
def _get_items(items):
new_items = []
for v in items:
if isinstance(v, list):
new_items.append(_get_items(v))
elif not isinstance(v, (DBRef, Document)):
new_items.append(field.to_python(v))
else:
new_items.append(v)
return new_items
items = _get_items(items)
items = _get_items_from_list(items)
else:
items = {
k: (v if isinstance(v, (DBRef, Document))
else field.to_python(v))
for k, v in items.iteritems()
}
items = _get_items_from_dict(items)
self.reference_map = self._find_references(items)
self.object_map = self._fetch_objects(doc_type=doc_type)
@@ -98,7 +113,7 @@ class DeReference(object):
depth += 1
for item in iterator:
if isinstance(item, (Document, EmbeddedDocument)):
for field_name, field in item._fields.iteritems():
for field_name, field in iteritems(item._fields):
v = item._data.get(field_name, None)
if isinstance(v, LazyReference):
# LazyReference inherits DBRef but should not be dereferenced here !
@@ -110,7 +125,7 @@ class DeReference(object):
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
field_cls = getattr(getattr(field, 'field', None), 'document_type', None)
references = self._find_references(v, depth)
for key, refs in references.iteritems():
for key, refs in iteritems(references):
if isinstance(field_cls, (Document, TopLevelDocumentMetaclass)):
key = field_cls
reference_map.setdefault(key, set()).update(refs)
@@ -123,7 +138,7 @@ class DeReference(object):
reference_map.setdefault(get_document(item['_cls']), set()).add(item['_ref'].id)
elif isinstance(item, (dict, list, tuple)) and depth - 1 <= self.max_depth:
references = self._find_references(item, depth - 1)
for key, refs in references.iteritems():
for key, refs in iteritems(references):
reference_map.setdefault(key, set()).update(refs)
return reference_map
@@ -132,7 +147,7 @@ class DeReference(object):
"""Fetch all references and convert to their document objects
"""
object_map = {}
for collection, dbrefs in self.reference_map.iteritems():
for collection, dbrefs in iteritems(self.reference_map):
# we use getattr instead of hasattr because hasattr swallows any exception under python2
# so it could hide nasty things without raising exceptions (cfr bug #1688))
@@ -143,7 +158,7 @@ class DeReference(object):
refs = [dbref for dbref in dbrefs
if (col_name, dbref) not in object_map]
references = collection.objects.in_bulk(refs)
for key, doc in references.iteritems():
for key, doc in iteritems(references):
object_map[(col_name, key)] = doc
else: # Generic reference: use the refs data to convert to document
if isinstance(doc_type, (ListField, DictField, MapField)):
@@ -215,7 +230,7 @@ class DeReference(object):
data = []
else:
is_list = False
iterator = items.iteritems()
iterator = iteritems(items)
data = {}
depth += 1

View File

@@ -5,6 +5,7 @@ from bson.dbref import DBRef
import pymongo
from pymongo.read_preferences import ReadPreference
import six
from six import iteritems
from mongoengine import signals
from mongoengine.base import (BaseDict, BaseDocument, BaseList,
@@ -17,7 +18,7 @@ from mongoengine.context_managers import (set_write_concern,
switch_db)
from mongoengine.errors import (InvalidDocumentError, InvalidQueryError,
SaveConditionError)
from mongoengine.python_support import IS_PYMONGO_3
from mongoengine.pymongo_support import IS_PYMONGO_3, list_collection_names
from mongoengine.queryset import (NotUniqueError, OperationError,
QuerySet, transform)
@@ -90,9 +91,15 @@ class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
return data
def save(self, *args, **kwargs):
warnings.warn("EmbeddedDocument.save is deprecated and will be removed in a next version of mongoengine."
"Use the parent document's .save() or ._instance.save()",
DeprecationWarning, stacklevel=2)
self._instance.save(*args, **kwargs)
def reload(self, *args, **kwargs):
warnings.warn("EmbeddedDocument.reload is deprecated and will be removed in a next version of mongoengine."
"Use the parent document's .reload() or ._instance.reload()",
DeprecationWarning, stacklevel=2)
self._instance.reload(*args, **kwargs)
@@ -221,7 +228,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
# If the collection already exists and has different options
# (i.e. isn't capped or has different max/size), raise an error.
if collection_name in db.collection_names():
if collection_name in list_collection_names(db, include_system_collections=True):
collection = db[collection_name]
options = collection.options()
if (
@@ -607,7 +614,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
# Delete FileFields separately
FileField = _import_class('FileField')
for name, field in self._fields.iteritems():
for name, field in iteritems(self._fields):
if isinstance(field, FileField):
getattr(self, name).delete()
@@ -1016,7 +1023,7 @@ class DynamicDocument(six.with_metaclass(TopLevelDocumentMetaclass, Document)):
.. note::
There is one caveat on Dynamic Documents: fields cannot start with `_`
There is one caveat on Dynamic Documents: undeclared fields cannot start with `_`
"""
# The __metaclass__ attribute is removed by 2to3 when running with Python3

View File

@@ -1,6 +1,7 @@
from collections import defaultdict
import six
from six import iteritems
__all__ = ('NotRegistered', 'InvalidDocumentError', 'LookUpError',
'DoesNotExist', 'MultipleObjectsReturned', 'InvalidQueryError',
@@ -113,7 +114,7 @@ class ValidationError(AssertionError):
return errors_dict
if isinstance(source, dict):
for field_name, error in source.iteritems():
for field_name, error in iteritems(source):
errors_dict[field_name] = build_dict(error)
elif isinstance(source, ValidationError) and source.errors:
return build_dict(source.errors)
@@ -135,12 +136,12 @@ class ValidationError(AssertionError):
value = ' '.join([generate_key(k) for k in value])
elif isinstance(value, dict):
value = ' '.join(
[generate_key(v, k) for k, v in value.iteritems()])
[generate_key(v, k) for k, v in iteritems(value)])
results = '%s.%s' % (prefix, value) if prefix else value
return results
error_dict = defaultdict(list)
for k, v in self.to_dict().iteritems():
for k, v in iteritems(self.to_dict()):
error_dict[generate_key(v)].append(k)
return ' '.join(['%s: %s' % (k, v) for k, v in error_dict.iteritems()])
return ' '.join(['%s: %s' % (k, v) for k, v in iteritems(error_dict)])

View File

@@ -11,6 +11,7 @@ from bson import Binary, DBRef, ObjectId, SON
import gridfs
import pymongo
import six
from six import iteritems
try:
import dateutil
@@ -794,12 +795,12 @@ class DynamicField(BaseField):
value = {k: v for k, v in enumerate(value)}
data = {}
for k, v in value.iteritems():
for k, v in iteritems(value):
data[k] = self.to_mongo(v, use_db_field, fields)
value = data
if is_list: # Convert back to a list
value = [v for k, v in sorted(data.iteritems(), key=itemgetter(0))]
value = [v for k, v in sorted(iteritems(data), key=itemgetter(0))]
return value
def to_python(self, value):
@@ -944,7 +945,7 @@ def key_has_dot_or_dollar(d):
dictionary contains a dot or a dollar sign.
"""
for k, v in d.items():
if ('.' in k or '$' in k) or (isinstance(v, dict) and key_has_dot_or_dollar(v)):
if ('.' in k or k.startswith('$')) or (isinstance(v, dict) and key_has_dot_or_dollar(v)):
return True
@@ -977,7 +978,7 @@ class DictField(ComplexBaseField):
self.error(msg)
if key_has_dot_or_dollar(value):
self.error('Invalid dictionary key name - keys may not contain "."'
' or "$" characters')
' or startswith "$" characters')
super(DictField, self).validate(value)
def lookup_member(self, member_name):
@@ -1483,6 +1484,12 @@ class BinaryField(BaseField):
if self.max_bytes is not None and len(value) > self.max_bytes:
self.error('Binary value is too long')
def prepare_query_value(self, op, value):
if value is None:
return value
return super(BinaryField, self).prepare_query_value(
op, self.to_mongo(value))
class GridFSError(Exception):
pass
@@ -2251,9 +2258,9 @@ class LazyReferenceField(BaseField):
:param reverse_delete_rule: Determines what to do when the referring
object is deleted
:param passthrough: When trying to access unknown fields, the
:class:`~mongoengine.base.datastructure.LazyReference` instance will
automatically call `fetch()` and try to retrive the field on the fetched
document. Note this only work getting field (not setting or deleting).
:class:`~mongoengine.base.datastructure.LazyReference` instance will
automatically call `fetch()` and try to retrive the field on the fetched
document. Note this only work getting field (not setting or deleting).
"""
# XXX ValidationError raised outside of the "validate" method.
if (

View File

@@ -0,0 +1,21 @@
"""
Helper functions, constants, and types to aid with MongoDB version support
"""
from mongoengine.connection import get_connection
# Constant that can be used to compare the version retrieved with
# get_mongodb_version()
MONGODB_34 = (3, 4)
MONGODB_32 = (3, 2)
MONGODB_3 = (3, 0)
MONGODB_26 = (2, 6)
def get_mongodb_version():
"""Return the version of the connected mongoDB (first 2 digits)
:return: tuple(int, int)
"""
version_list = get_connection().server_info()['versionArray'][:2] # e.g: (3, 2)
return tuple(version_list)

View File

@@ -0,0 +1,33 @@
"""
Helper functions, constants, and types to aid with PyMongo v2.7 - v3.x support.
"""
import pymongo
_PYMONGO_37 = (3, 7)
PYMONGO_VERSION = tuple(pymongo.version_tuple[:2])
IS_PYMONGO_3 = PYMONGO_VERSION[0] >= 3
IS_PYMONGO_GTE_37 = PYMONGO_VERSION >= _PYMONGO_37
def count_documents(collection, filter):
"""Pymongo>3.7 deprecates count in favour of count_documents"""
if IS_PYMONGO_GTE_37:
return collection.count_documents(filter)
else:
count = collection.find(filter).count()
return count
def list_collection_names(db, include_system_collections=False):
"""Pymongo>3.7 deprecates collection_names in favour of list_collection_names"""
if IS_PYMONGO_GTE_37:
collections = db.list_collection_names()
else:
collections = db.collection_names()
if not include_system_collections:
collections = [c for c in collections if not c.startswith('system.')]
return collections

View File

@@ -1,13 +1,8 @@
"""
Helper functions, constants, and types to aid with Python v2.7 - v3.x and
PyMongo v2.7 - v3.x support.
Helper functions, constants, and types to aid with Python v2.7 - v3.x support
"""
import pymongo
import six
IS_PYMONGO_3 = pymongo.version_tuple[0] >= 3
# six.BytesIO resolves to StringIO.StringIO in Py2 and io.BytesIO in Py3.
StringIO = six.BytesIO

View File

@@ -12,6 +12,7 @@ import pymongo
import pymongo.errors
from pymongo.common import validate_read_preference
import six
from six import iteritems
from mongoengine import signals
from mongoengine.base import get_document
@@ -20,7 +21,7 @@ from mongoengine.connection import get_db
from mongoengine.context_managers import set_write_concern, switch_db
from mongoengine.errors import (InvalidQueryError, LookUpError,
NotUniqueError, OperationError)
from mongoengine.python_support import IS_PYMONGO_3
from mongoengine.pymongo_support import IS_PYMONGO_3
from mongoengine.queryset import transform
from mongoengine.queryset.field_list import QueryFieldList
from mongoengine.queryset.visitor import Q, QNode
@@ -188,7 +189,7 @@ class BaseQuerySet(object):
)
if queryset._as_pymongo:
return queryset._get_as_pymongo(queryset._cursor[key])
return queryset._cursor[key]
return queryset._document._from_son(
queryset._cursor[key],
@@ -215,7 +216,7 @@ class BaseQuerySet(object):
# Core functions
def all(self):
"""Returns all documents."""
"""Returns a copy of the current QuerySet."""
return self.__call__()
def filter(self, *q_objs, **query):
@@ -396,7 +397,9 @@ class BaseQuerySet(object):
"""
if self._limit == 0 and with_limit_and_skip is False or self._none:
return 0
return self._cursor.count(with_limit_and_skip=with_limit_and_skip)
count = self._cursor.count(with_limit_and_skip=with_limit_and_skip)
self._cursor_obj = None
return count
def delete(self, write_concern=None, _from_doc_delete=False,
cascade_refs=None):
@@ -496,11 +499,12 @@ class BaseQuerySet(object):
``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 full_result: Return the full result dictionary rather than just the number
updated, e.g. return
``{'n': 2, 'nModified': 2, 'ok': 1.0, 'updatedExisting': True}``.
:param full_result: Return the associated ``pymongo.UpdateResult`` rather than just the number
updated items
:param update: Django-style update keyword arguments
:returns the number of updated documents (unless ``full_result`` is True)
.. versionadded:: 0.2
"""
if not update and not upsert:
@@ -564,7 +568,7 @@ class BaseQuerySet(object):
document = self._document.objects.with_id(atomic_update.upserted_id)
return document
def update_one(self, upsert=False, write_concern=None, **update):
def update_one(self, upsert=False, write_concern=None, full_result=False, **update):
"""Perform an atomic update on the fields of the first document
matched by the query.
@@ -575,12 +579,19 @@ class BaseQuerySet(object):
``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 full_result: Return the associated ``pymongo.UpdateResult`` rather than just the number
updated items
:param update: Django-style update keyword arguments
full_result
:returns the number of updated documents (unless ``full_result`` is True)
.. versionadded:: 0.2
"""
return self.update(
upsert=upsert, multi=False, write_concern=write_concern, **update)
upsert=upsert,
multi=False,
write_concern=write_concern,
full_result=full_result,
**update)
def modify(self, upsert=False, full_response=False, remove=False, new=False, **update):
"""Update and return the updated document.
@@ -688,7 +699,7 @@ class BaseQuerySet(object):
self._document._from_son(doc, only_fields=self.only_fields))
elif self._as_pymongo:
for doc in docs:
doc_map[doc['_id']] = self._get_as_pymongo(doc)
doc_map[doc['_id']] = doc
else:
for doc in docs:
doc_map[doc['_id']] = self._document._from_son(
@@ -746,7 +757,7 @@ class BaseQuerySet(object):
'_read_preference', '_iter', '_scalar', '_as_pymongo',
'_limit', '_skip', '_hint', '_auto_dereference',
'_search_text', 'only_fields', '_max_time_ms',
'_comment')
'_comment', '_batch_size')
for prop in copy_props:
val = getattr(self, prop)
@@ -1484,7 +1495,7 @@ class BaseQuerySet(object):
raw_doc = six.next(self._cursor)
if self._as_pymongo:
return self._get_as_pymongo(raw_doc)
return raw_doc
doc = self._document._from_son(
raw_doc, _auto_dereference=self._auto_dereference,
@@ -1729,13 +1740,13 @@ class BaseQuerySet(object):
}
"""
total, data, types = self.exec_js(freq_func, field)
values = {types.get(k): int(v) for k, v in data.iteritems()}
values = {types.get(k): int(v) for k, v in iteritems(data)}
if normalize:
values = {k: float(v) / total for k, v in values.items()}
frequencies = {}
for k, v in values.iteritems():
for k, v in iteritems(values):
if isinstance(k, float):
if int(k) == k:
k = int(k)
@@ -1831,26 +1842,6 @@ class BaseQuerySet(object):
return tuple(data)
def _get_as_pymongo(self, doc):
"""Clean up a PyMongo doc, removing fields that were only fetched
for the sake of MongoEngine's implementation, and return it.
"""
# Always remove _cls as a MongoEngine's implementation detail.
if '_cls' in doc:
del doc['_cls']
# If the _id was not included in a .only or was excluded in a .exclude,
# remove it from the doc (we always fetch it so that we can properly
# construct documents).
fields = self._loaded_fields
if fields and '_id' in doc and (
(fields.value == QueryFieldList.ONLY and '_id' not in fields.fields) or
(fields.value == QueryFieldList.EXCLUDE and '_id' in fields.fields)
):
del doc['_id']
return doc
def _sub_js_fields(self, code):
"""When fields are specified with [~fieldname] syntax, where
*fieldname* is the Python name of a field, *fieldname* will be

View File

@@ -4,12 +4,13 @@ from bson import ObjectId, SON
from bson.dbref import DBRef
import pymongo
import six
from six import iteritems
from mongoengine.base import UPDATE_OPERATORS
from mongoengine.common import _import_class
from mongoengine.connection import get_connection
from mongoengine.errors import InvalidQueryError
from mongoengine.python_support import IS_PYMONGO_3
from mongoengine.pymongo_support import IS_PYMONGO_3
__all__ = ('query', 'update')
@@ -154,7 +155,7 @@ def query(_doc_cls=None, **kwargs):
if ('$maxDistance' in value_dict or '$minDistance' in value_dict) and \
('$near' in value_dict or '$nearSphere' in value_dict):
value_son = SON()
for k, v in value_dict.iteritems():
for k, v in iteritems(value_dict):
if k == '$maxDistance' or k == '$minDistance':
continue
value_son[k] = v
@@ -345,7 +346,7 @@ def update(_doc_cls=None, **update):
value = {key: {'$each': value}}
elif op in ('push', 'pushAll'):
if parts[-1].isdigit():
key = parts[0]
key = '.'.join(parts[0:-1])
position = int(parts[-1])
# $position expects an iterable. If pushing a single value,
# wrap it in a list.

View File

@@ -2,6 +2,7 @@
import unittest
from mongoengine import *
from mongoengine.pymongo_support import list_collection_names
from mongoengine.queryset import NULLIFY, PULL
from mongoengine.connection import get_db
@@ -27,9 +28,7 @@ class ClassMethodsTest(unittest.TestCase):
self.Person = Person
def tearDown(self):
for collection in self.db.collection_names():
if 'system.' in collection:
continue
for collection in list_collection_names(self.db):
self.db.drop_collection(collection)
def test_definition(self):
@@ -66,10 +65,10 @@ class ClassMethodsTest(unittest.TestCase):
"""
collection_name = 'person'
self.Person(name='Test').save()
self.assertIn(collection_name, self.db.collection_names())
self.assertIn(collection_name, list_collection_names(self.db))
self.Person.drop_collection()
self.assertNotIn(collection_name, self.db.collection_names())
self.assertNotIn(collection_name, list_collection_names(self.db))
def test_register_delete_rule(self):
"""Ensure that register delete rule adds a delete rule to the document
@@ -102,16 +101,16 @@ class ClassMethodsTest(unittest.TestCase):
BlogPost.drop_collection()
BlogPost.ensure_indexes()
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})
BlogPost.ensure_index(['author', 'description'])
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [[('author', 1), ('description', 1)]] })
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': [[('author', 1), ('description', 1)]]})
BlogPost._get_collection().drop_index('author_1_description_1')
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})
BlogPost._get_collection().drop_index('author_1_title_1')
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [[('author', 1), ('title', 1)]], 'extra': [] })
self.assertEqual(BlogPost.compare_indexes(), {'missing': [[('author', 1), ('title', 1)]], 'extra': []})
def test_compare_indexes_inheritance(self):
""" Ensure that the indexes are properly created and that
@@ -140,16 +139,16 @@ class ClassMethodsTest(unittest.TestCase):
BlogPost.ensure_indexes()
BlogPostWithTags.ensure_indexes()
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})
BlogPostWithTags.ensure_index(['author', 'tag_list'])
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [[('_cls', 1), ('author', 1), ('tag_list', 1)]] })
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': [[('_cls', 1), ('author', 1), ('tag_list', 1)]]})
BlogPostWithTags._get_collection().drop_index('_cls_1_author_1_tag_list_1')
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})
BlogPostWithTags._get_collection().drop_index('_cls_1_author_1_tags_1')
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [[('_cls', 1), ('author', 1), ('tags', 1)]], 'extra': [] })
self.assertEqual(BlogPost.compare_indexes(), {'missing': [[('_cls', 1), ('author', 1), ('tags', 1)]], 'extra': []})
def test_compare_indexes_multiple_subclasses(self):
""" Ensure that compare_indexes behaves correctly if called from a
@@ -184,9 +183,9 @@ class ClassMethodsTest(unittest.TestCase):
BlogPostWithTags.ensure_indexes()
BlogPostWithCustomField.ensure_indexes()
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
self.assertEqual(BlogPostWithTags.compare_indexes(), { 'missing': [], 'extra': [] })
self.assertEqual(BlogPostWithCustomField.compare_indexes(), { 'missing': [], 'extra': [] })
self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})
self.assertEqual(BlogPostWithTags.compare_indexes(), {'missing': [], 'extra': []})
self.assertEqual(BlogPostWithCustomField.compare_indexes(), {'missing': [], 'extra': []})
@requires_mongodb_gte_26
def test_compare_indexes_for_text_indexes(self):
@@ -340,7 +339,7 @@ class ClassMethodsTest(unittest.TestCase):
meta = {'collection': collection_name}
Person(name="Test User").save()
self.assertIn(collection_name, self.db.collection_names())
self.assertIn(collection_name, list_collection_names(self.db))
user_obj = self.db[collection_name].find_one()
self.assertEqual(user_obj['name'], "Test User")
@@ -349,7 +348,7 @@ class ClassMethodsTest(unittest.TestCase):
self.assertEqual(user_obj.name, "Test User")
Person.drop_collection()
self.assertNotIn(collection_name, self.db.collection_names())
self.assertNotIn(collection_name, list_collection_names(self.db))
def test_collection_name_and_primary(self):
"""Ensure that a collection with a specified name may be used.

View File

@@ -3,16 +3,14 @@ import unittest
from bson import SON
from mongoengine import *
from mongoengine.connection import get_db
__all__ = ("DeltaTest",)
from mongoengine.pymongo_support import list_collection_names
from tests.utils import MongoDBTestCase
class DeltaTest(unittest.TestCase):
class DeltaTest(MongoDBTestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
super(DeltaTest, self).setUp()
class Person(Document):
name = StringField()
@@ -25,9 +23,7 @@ class DeltaTest(unittest.TestCase):
self.Person = Person
def tearDown(self):
for collection in self.db.collection_names():
if 'system.' in collection:
continue
for collection in list_collection_names(self.db):
self.db.drop_collection(collection)
def test_delta(self):
@@ -863,5 +859,6 @@ class DeltaTest(unittest.TestCase):
self.assertEqual('oops', delta[0]["users.007.rolist"][0]["type"])
self.assertEqual(uinfo.id, delta[0]["users.007.info"])
if __name__ == '__main__':
unittest.main()

View File

@@ -1,16 +1,15 @@
import unittest
from mongoengine import *
from mongoengine.connection import get_db
from tests.utils import MongoDBTestCase
__all__ = ("DynamicTest", )
__all__ = ("TestDynamicDocument", )
class DynamicTest(unittest.TestCase):
class TestDynamicDocument(MongoDBTestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
super(TestDynamicDocument, self).setUp()
class Person(DynamicDocument):
name = StringField()
@@ -98,6 +97,72 @@ class DynamicTest(unittest.TestCase):
self.assertEqual(len(p._data), 4)
self.assertEqual(sorted(p._data.keys()), ['_cls', 'age', 'id', 'name'])
def test_fields_without_underscore(self):
"""Ensure we can query dynamic fields"""
Person = self.Person
p = self.Person(name='Dean')
p.save()
raw_p = Person.objects.as_pymongo().get(id=p.id)
self.assertEqual(
raw_p,
{
'_cls': u'Person',
'_id': p.id,
'name': u'Dean'
}
)
p.name = 'OldDean'
p.newattr = 'garbage'
p.save()
raw_p = Person.objects.as_pymongo().get(id=p.id)
self.assertEqual(
raw_p,
{
'_cls': u'Person',
'_id': p.id,
'name': 'OldDean',
'newattr': u'garbage'
}
)
def test_fields_containing_underscore(self):
"""Ensure we can query dynamic fields"""
class WeirdPerson(DynamicDocument):
name = StringField()
_name = StringField()
WeirdPerson.drop_collection()
p = WeirdPerson(name='Dean', _name='Dean')
p.save()
raw_p = WeirdPerson.objects.as_pymongo().get(id=p.id)
self.assertEqual(
raw_p,
{
'_id': p.id,
'_name': u'Dean',
'name': u'Dean'
}
)
p.name = 'OldDean'
p._name = 'NewDean'
p._newattr1 = 'garbage' # Unknown fields won't be added
p.save()
raw_p = WeirdPerson.objects.as_pymongo().get(id=p.id)
self.assertEqual(
raw_p,
{
'_id': p.id,
'_name': u'NewDean',
'name': u'OldDean',
}
)
def test_dynamic_document_queries(self):
"""Ensure we can query dynamic fields"""
p = self.Person()
@@ -369,5 +434,6 @@ class DynamicTest(unittest.TestCase):
person.save()
self.assertEqual(Person.objects.first().age, 35)
if __name__ == '__main__':
unittest.main()

View File

@@ -5,10 +5,12 @@ from datetime import datetime
from nose.plugins.skip import SkipTest
from pymongo.errors import OperationFailure
import pymongo
from six import iteritems
from mongoengine import *
from mongoengine.connection import get_db
from tests.utils import get_mongodb_version, requires_mongodb_gte_26, MONGODB_32, MONGODB_3
from mongoengine.mongodb_support import get_mongodb_version, MONGODB_32, MONGODB_3
from tests.utils import requires_mongodb_gte_26, requires_mongodb_lte_32, requires_mongodb_gte_34
__all__ = ("IndexesTest", )
@@ -68,7 +70,7 @@ class IndexesTest(unittest.TestCase):
info = BlogPost.objects._collection.index_information()
# _id, '-date', 'tags', ('cat', 'date')
self.assertEqual(len(info), 4)
info = [value['key'] for key, value in info.iteritems()]
info = [value['key'] for key, value in iteritems(info)]
for expected in expected_specs:
self.assertIn(expected['fields'], info)
@@ -100,7 +102,7 @@ class IndexesTest(unittest.TestCase):
# the indices on -date and tags will both contain
# _cls as first element in the key
self.assertEqual(len(info), 4)
info = [value['key'] for key, value in info.iteritems()]
info = [value['key'] for key, value in iteritems(info)]
for expected in expected_specs:
self.assertIn(expected['fields'], info)
@@ -115,7 +117,7 @@ class IndexesTest(unittest.TestCase):
ExtendedBlogPost.ensure_indexes()
info = ExtendedBlogPost.objects._collection.index_information()
info = [value['key'] for key, value in info.iteritems()]
info = [value['key'] for key, value in iteritems(info)]
for expected in expected_specs:
self.assertIn(expected['fields'], info)
@@ -225,7 +227,7 @@ class IndexesTest(unittest.TestCase):
# Indexes are lazy so use list() to perform query
list(Person.objects)
info = Person.objects._collection.index_information()
info = [value['key'] for key, value in info.iteritems()]
info = [value['key'] for key, value in iteritems(info)]
self.assertIn([('rank.title', 1)], info)
def test_explicit_geo2d_index(self):
@@ -245,7 +247,7 @@ class IndexesTest(unittest.TestCase):
Place.ensure_indexes()
info = Place._get_collection().index_information()
info = [value['key'] for key, value in info.iteritems()]
info = [value['key'] for key, value in iteritems(info)]
self.assertIn([('location.point', '2d')], info)
def test_explicit_geo2d_index_embedded(self):
@@ -268,7 +270,7 @@ class IndexesTest(unittest.TestCase):
Place.ensure_indexes()
info = Place._get_collection().index_information()
info = [value['key'] for key, value in info.iteritems()]
info = [value['key'] for key, value in iteritems(info)]
self.assertIn([('current.location.point', '2d')], info)
def test_explicit_geosphere_index(self):
@@ -288,7 +290,7 @@ class IndexesTest(unittest.TestCase):
Place.ensure_indexes()
info = Place._get_collection().index_information()
info = [value['key'] for key, value in info.iteritems()]
info = [value['key'] for key, value in iteritems(info)]
self.assertIn([('location.point', '2dsphere')], info)
def test_explicit_geohaystack_index(self):
@@ -310,7 +312,7 @@ class IndexesTest(unittest.TestCase):
Place.ensure_indexes()
info = Place._get_collection().index_information()
info = [value['key'] for key, value in info.iteritems()]
info = [value['key'] for key, value in iteritems(info)]
self.assertIn([('location.point', 'geoHaystack')], info)
def test_create_geohaystack_index(self):
@@ -322,7 +324,7 @@ class IndexesTest(unittest.TestCase):
Place.create_index({'fields': (')location.point', 'name')}, bucketSize=10)
info = Place._get_collection().index_information()
info = [value['key'] for key, value in info.iteritems()]
info = [value['key'] for key, value in iteritems(info)]
self.assertIn([('location.point', 'geoHaystack'), ('name', 1)], info)
def test_dictionary_indexes(self):
@@ -355,7 +357,7 @@ class IndexesTest(unittest.TestCase):
info = [(value['key'],
value.get('unique', False),
value.get('sparse', False))
for key, value in info.iteritems()]
for key, value in iteritems(info)]
self.assertIn(([('addDate', -1)], True, True), info)
BlogPost.drop_collection()
@@ -476,6 +478,7 @@ class IndexesTest(unittest.TestCase):
def test_covered_index(self):
"""Ensure that covered indexes can be used
"""
IS_MONGODB_3 = get_mongodb_version() >= MONGODB_3
class Test(Document):
a = IntField()
@@ -491,8 +494,6 @@ class IndexesTest(unittest.TestCase):
obj = Test(a=1)
obj.save()
IS_MONGODB_3 = get_mongodb_version() >= MONGODB_3
# Need to be explicit about covered indexes as mongoDB doesn't know if
# the documents returned might have more keys in that here.
query_plan = Test.objects(id=obj.id).exclude('a').explain()
@@ -568,7 +569,7 @@ class IndexesTest(unittest.TestCase):
if pymongo.version != '3.0':
self.assertEqual(BlogPost.objects.hint([('tags', 1)]).count(), 10)
if MONGO_VER == MONGODB_32:
if MONGO_VER >= MONGODB_32:
# Mongo32 throws an error if an index exists (i.e `tags` in our case)
# and you use hint on an index name that does not exist
with self.assertRaises(OperationFailure):
@@ -576,7 +577,7 @@ class IndexesTest(unittest.TestCase):
else:
self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10)
self.assertEqual(BlogPost.objects.hint(TAGS_INDEX_NAME ).count(), 10)
self.assertEqual(BlogPost.objects.hint(TAGS_INDEX_NAME).count(), 10)
with self.assertRaises(Exception):
BlogPost.objects.hint(('tags', 1)).next()
@@ -600,6 +601,32 @@ class IndexesTest(unittest.TestCase):
# Ensure backwards compatibilty for errors
self.assertRaises(OperationError, post2.save)
@requires_mongodb_gte_34
def test_primary_key_unique_not_working_under_mongo_34(self):
"""Relates to #1445"""
class Blog(Document):
id = StringField(primary_key=True, unique=True)
Blog.drop_collection()
with self.assertRaises(OperationFailure) as ctx_err:
Blog(id='garbage').save()
try:
self.assertIn("The field 'unique' is not valid for an _id index specification", str(ctx_err.exception))
except AssertionError:
# error is slightly different on python 3.6
self.assertIn("The field 'background' is not valid for an _id index specification", str(ctx_err.exception))
@requires_mongodb_lte_32
def test_primary_key_unique_working_under_mongo_32(self):
"""Relates to #1445"""
class Blog(Document):
id = StringField(primary_key=True, unique=True)
Blog.drop_collection()
Blog(id='garbage').save()
def test_unique_with(self):
"""Ensure that unique_with constraints are applied to fields.
"""
@@ -759,7 +786,7 @@ class IndexesTest(unittest.TestCase):
You won't create a duplicate but you will update an existing document.
"""
class User(Document):
name = StringField(primary_key=True, unique=True)
name = StringField(primary_key=True)
password = StringField()
User.drop_collection()
@@ -806,7 +833,7 @@ class IndexesTest(unittest.TestCase):
self.fail('Unbound local error at index + pk definition')
info = BlogPost.objects._collection.index_information()
info = [value['key'] for key, value in info.iteritems()]
info = [value['key'] for key, value in iteritems(info)]
index_item = [('_id', 1), ('comments.comment_id', 1)]
self.assertIn(index_item, info)
@@ -854,7 +881,7 @@ class IndexesTest(unittest.TestCase):
}
info = MyDoc.objects._collection.index_information()
info = [value['key'] for key, value in info.iteritems()]
info = [value['key'] for key, value in iteritems(info)]
self.assertIn([('provider_ids.foo', 1)], info)
self.assertIn([('provider_ids.bar', 1)], info)
@@ -936,7 +963,6 @@ class IndexesTest(unittest.TestCase):
# Drop the temporary database at the end
connection.drop_database('tempdatabase')
def test_index_dont_send_cls_option(self):
"""
Ensure that 'cls' option is not sent through ensureIndex. We shouldn't

View File

@@ -2,25 +2,22 @@
import unittest
import warnings
from six import iteritems
from mongoengine import (BooleanField, Document, EmbeddedDocument,
EmbeddedDocumentField, GenericReferenceField,
IntField, ReferenceField, StringField, connect)
from mongoengine.connection import get_db
IntField, ReferenceField, StringField)
from mongoengine.pymongo_support import list_collection_names
from tests.utils import MongoDBTestCase
from tests.fixtures import Base
__all__ = ('InheritanceTest', )
class InheritanceTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
class InheritanceTest(MongoDBTestCase):
def tearDown(self):
for collection in self.db.collection_names():
if 'system.' in collection:
continue
for collection in list_collection_names(self.db):
self.db.drop_collection(collection)
def test_constructor_cls(self):
@@ -36,12 +33,12 @@ class InheritanceTest(unittest.TestCase):
meta = {'allow_inheritance': True}
test_doc = DataDoc(name='test', embed=EmbedData(data='data'))
assert test_doc._cls == 'DataDoc'
assert test_doc.embed._cls == 'EmbedData'
self.assertEqual(test_doc._cls, 'DataDoc')
self.assertEqual(test_doc.embed._cls, 'EmbedData')
test_doc.save()
saved_doc = DataDoc.objects.with_id(test_doc.id)
assert test_doc._cls == saved_doc._cls
assert test_doc.embed._cls == saved_doc.embed._cls
self.assertEqual(test_doc._cls, saved_doc._cls)
self.assertEqual(test_doc.embed._cls, saved_doc.embed._cls)
test_doc.delete()
def test_superclasses(self):
@@ -485,7 +482,7 @@ class InheritanceTest(unittest.TestCase):
meta = {'abstract': True}
class Human(Mammal): pass
for k, v in defaults.iteritems():
for k, v in iteritems(defaults):
for cls in [Animal, Fish, Guppy]:
self.assertEqual(cls._meta[k], v)

View File

@@ -4,12 +4,15 @@ import os
import pickle
import unittest
import uuid
import warnings
import weakref
from datetime import datetime
from bson import DBRef, ObjectId
from pymongo.errors import DuplicateKeyError
from six import iteritems
from mongoengine.pymongo_support import list_collection_names
from tests import fixtures
from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest,
PickleDynamicEmbedded, PickleDynamicTest)
@@ -53,9 +56,7 @@ class InstanceTest(MongoDBTestCase):
self.Job = Job
def tearDown(self):
for collection in self.db.collection_names():
if 'system.' in collection:
continue
for collection in list_collection_names(self.db):
self.db.drop_collection(collection)
def assertDbEqual(self, docs):
@@ -570,7 +571,7 @@ class InstanceTest(MongoDBTestCase):
Post.drop_collection()
Post._get_collection().insert({
Post._get_collection().insert_one({
"title": "Items eclipse",
"items": ["more lorem", "even more ipsum"]
})
@@ -804,7 +805,8 @@ class InstanceTest(MongoDBTestCase):
doc2 = self.Person(name="jim", age=20).save()
docs = [dict(doc1.to_mongo()), dict(doc2.to_mongo())]
assert not doc1.modify({'name': doc2.name}, set__age=100)
n_modified = doc1.modify({'name': doc2.name}, set__age=100)
self.assertEqual(n_modified, 0)
self.assertDbEqual(docs)
@@ -813,7 +815,8 @@ class InstanceTest(MongoDBTestCase):
doc2 = self.Person(id=ObjectId(), name="jim", age=20)
docs = [dict(doc1.to_mongo())]
assert not doc2.modify({'name': doc2.name}, set__age=100)
n_modified = doc2.modify({'name': doc2.name}, set__age=100)
self.assertEqual(n_modified, 0)
self.assertDbEqual(docs)
@@ -829,23 +832,30 @@ class InstanceTest(MongoDBTestCase):
doc.job.name = "Google"
doc.job.years = 3
assert doc.modify(
n_modified = doc.modify(
set__age=21, set__job__name="MongoDB", unset__job__years=True)
self.assertEqual(n_modified, 1)
doc_copy.age = 21
doc_copy.job.name = "MongoDB"
del doc_copy.job.years
assert doc.to_json() == doc_copy.to_json()
assert doc._get_changed_fields() == []
self.assertEqual(doc.to_json(), doc_copy.to_json())
self.assertEqual(doc._get_changed_fields(), [])
self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
@requires_mongodb_gte_26
def test_modify_with_positional_push(self):
class Content(EmbeddedDocument):
keywords = ListField(StringField())
class BlogPost(Document):
tags = ListField(StringField())
content = EmbeddedDocumentField(Content)
post = BlogPost.objects.create(
tags=['python'], content=Content(keywords=['ipsum']))
post = BlogPost.objects.create(tags=['python'])
self.assertEqual(post.tags, ['python'])
post.modify(push__tags__0=['code', 'mongo'])
self.assertEqual(post.tags, ['code', 'mongo', 'python'])
@@ -856,6 +866,16 @@ class InstanceTest(MongoDBTestCase):
['code', 'mongo', 'python']
)
self.assertEqual(post.content.keywords, ['ipsum'])
post.modify(push__content__keywords__0=['lorem'])
self.assertEqual(post.content.keywords, ['lorem', 'ipsum'])
# Assert same order of the list items is maintained in the db
self.assertEqual(
BlogPost._get_collection().find_one({'_id': post.pk})['content']['keywords'],
['lorem', 'ipsum']
)
def test_save(self):
"""Ensure that a document may be saved in the database."""
@@ -1466,7 +1486,7 @@ class InstanceTest(MongoDBTestCase):
Message.drop_collection()
# All objects share the same id, but each in a different collection
user = User(id=1, name='user-name')#.save()
user = User(id=1, name='user-name') # .save()
message = Message(id=1, author=user).save()
message.author.name = 'tutu'
@@ -1984,7 +2004,6 @@ class InstanceTest(MongoDBTestCase):
child_record.delete()
self.assertEqual(Record.objects(name='parent').get().children, [])
def test_reverse_delete_rule_with_custom_id_field(self):
"""Ensure that a referenced document with custom primary key
is also deleted upon deletion.
@@ -2741,7 +2760,7 @@ class InstanceTest(MongoDBTestCase):
User.drop_collection()
User._get_collection().save({
User._get_collection().insert_one({
'name': 'John',
'foo': 'Bar',
'data': [1, 2, 3]
@@ -2757,7 +2776,7 @@ class InstanceTest(MongoDBTestCase):
User.drop_collection()
User._get_collection().save({
User._get_collection().insert_one({
'name': 'John',
'foo': 'Bar',
'data': [1, 2, 3]
@@ -2780,7 +2799,7 @@ class InstanceTest(MongoDBTestCase):
User.drop_collection()
User._get_collection().save({
User._get_collection().insert_one({
'name': 'John',
'thing': {
'name': 'My thing',
@@ -2803,7 +2822,7 @@ class InstanceTest(MongoDBTestCase):
User.drop_collection()
User._get_collection().save({
User._get_collection().insert_one({
'name': 'John',
'thing': {
'name': 'My thing',
@@ -2826,7 +2845,7 @@ class InstanceTest(MongoDBTestCase):
User.drop_collection()
User._get_collection().save({
User._get_collection().insert_one({
'name': 'John',
'thing': {
'name': 'My thing',
@@ -3043,7 +3062,7 @@ class InstanceTest(MongoDBTestCase):
def expand(self):
self.flattened_parameter = {}
for parameter_name, parameter in self.parameters.iteritems():
for parameter_name, parameter in iteritems(self.parameters):
parameter.expand()
class NodesSystem(Document):
@@ -3051,7 +3070,7 @@ class InstanceTest(MongoDBTestCase):
nodes = MapField(ReferenceField(Node, dbref=False))
def save(self, *args, **kwargs):
for node_name, node in self.nodes.iteritems():
for node_name, node in iteritems(self.nodes):
node.expand()
node.save(*args, **kwargs)
super(NodesSystem, self).save(*args, **kwargs)
@@ -3070,6 +3089,24 @@ class InstanceTest(MongoDBTestCase):
"UNDEFINED",
system.nodes["node"].parameters["param"].macros["test"].value)
def test_embedded_document_save_reload_warning(self):
"""Relates to #1570"""
class Embedded(EmbeddedDocument):
pass
class Doc(Document):
emb = EmbeddedDocumentField(Embedded)
doc = Doc(emb=Embedded()).save()
doc.emb.save() # Make sure its still working
with warnings.catch_warnings():
warnings.simplefilter("error", DeprecationWarning)
with self.assertRaises(DeprecationWarning):
doc.emb.save()
with self.assertRaises(DeprecationWarning):
doc.emb.reload()
def test_embedded_document_equality(self):
class Test(Document):
field = StringField(required=True)
@@ -3179,8 +3216,7 @@ class InstanceTest(MongoDBTestCase):
coll = Person._get_collection()
for person in Person.objects.as_pymongo():
if 'height' not in person:
person['height'] = 189
coll.save(person)
coll.update_one({'_id': person['_id']}, {'$set': {'height': 189}})
self.assertEquals(Person.objects(height=189).count(), 1)
@@ -3365,7 +3401,6 @@ class InstanceTest(MongoDBTestCase):
class User(Document):
company = ReferenceField(Company)
# Ensure index creation exception aren't swallowed (#1688)
with self.assertRaises(DuplicateKeyError):
User.objects().select_related()

View File

@@ -32,12 +32,12 @@ class TestJson(unittest.TestCase):
string = StringField(db_field='s')
embedded = EmbeddedDocumentField(Embedded, db_field='e')
doc = Doc( string="Hello", embedded=Embedded(string="Inner Hello"))
doc_json = doc.to_json(sort_keys=True, use_db_field=False,separators=(',', ':'))
doc = Doc(string="Hello", embedded=Embedded(string="Inner Hello"))
doc_json = doc.to_json(sort_keys=True, use_db_field=False, separators=(',', ':'))
expected_json = """{"embedded":{"string":"Inner Hello"},"string":"Hello"}"""
self.assertEqual( doc_json, expected_json)
self.assertEqual(doc_json, expected_json)
def test_json_simple(self):

File diff suppressed because it is too large Load Diff

View File

@@ -24,6 +24,16 @@ TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__), 'mongoengine.png')
TEST_IMAGE2_PATH = os.path.join(os.path.dirname(__file__), 'mongodb_leaf.png')
def get_file(path):
"""Use a BytesIO instead of a file to allow
to have a one-liner and avoid that the file remains opened"""
bytes_io = StringIO()
with open(path, 'rb') as f:
bytes_io.write(f.read())
bytes_io.seek(0)
return bytes_io
class FileTest(MongoDBTestCase):
def tearDown(self):
@@ -247,8 +257,8 @@ class FileTest(MongoDBTestCase):
Animal.drop_collection()
marmot = Animal(genus='Marmota', family='Sciuridae')
marmot_photo = open(TEST_IMAGE_PATH, 'rb') # Retrieve a photo from disk
marmot.photo.put(marmot_photo, content_type='image/jpeg', foo='bar')
marmot_photo_content = get_file(TEST_IMAGE_PATH) # Retrieve a photo from disk
marmot.photo.put(marmot_photo_content, content_type='image/jpeg', foo='bar')
marmot.photo.close()
marmot.save()
@@ -261,11 +271,11 @@ class FileTest(MongoDBTestCase):
the_file = FileField()
TestFile.drop_collection()
test_file = TestFile(the_file=open(TEST_IMAGE_PATH, 'rb')).save()
test_file = TestFile(the_file=get_file(TEST_IMAGE_PATH)).save()
self.assertEqual(test_file.the_file.get().length, 8313)
test_file = TestFile.objects.first()
test_file.the_file = open(TEST_IMAGE2_PATH, 'rb')
test_file.the_file = get_file(TEST_IMAGE2_PATH)
test_file.save()
self.assertEqual(test_file.the_file.get().length, 4971)
@@ -379,7 +389,7 @@ class FileTest(MongoDBTestCase):
self.assertEqual("%s" % e, "Invalid image: cannot identify image file %s" % f)
t = TestImage()
t.image.put(open(TEST_IMAGE_PATH, 'rb'))
t.image.put(get_file(TEST_IMAGE_PATH))
t.save()
t = TestImage.objects.first()
@@ -400,11 +410,11 @@ class FileTest(MongoDBTestCase):
the_file = ImageField()
TestFile.drop_collection()
test_file = TestFile(the_file=open(TEST_IMAGE_PATH, 'rb')).save()
test_file = TestFile(the_file=get_file(TEST_IMAGE_PATH)).save()
self.assertEqual(test_file.the_file.size, (371, 76))
test_file = TestFile.objects.first()
test_file.the_file = open(TEST_IMAGE2_PATH, 'rb')
test_file.the_file = get_file(TEST_IMAGE2_PATH)
test_file.save()
self.assertEqual(test_file.the_file.size, (45, 101))
@@ -418,7 +428,7 @@ class FileTest(MongoDBTestCase):
TestImage.drop_collection()
t = TestImage()
t.image.put(open(TEST_IMAGE_PATH, 'rb'))
t.image.put(get_file(TEST_IMAGE_PATH))
t.save()
t = TestImage.objects.first()
@@ -441,7 +451,7 @@ class FileTest(MongoDBTestCase):
TestImage.drop_collection()
t = TestImage()
t.image.put(open(TEST_IMAGE_PATH, 'rb'))
t.image.put(get_file(TEST_IMAGE_PATH))
t.save()
t = TestImage.objects.first()
@@ -464,7 +474,7 @@ class FileTest(MongoDBTestCase):
TestImage.drop_collection()
t = TestImage()
t.image.put(open(TEST_IMAGE_PATH, 'rb'))
t.image.put(get_file(TEST_IMAGE_PATH))
t.save()
t = TestImage.objects.first()
@@ -542,8 +552,8 @@ class FileTest(MongoDBTestCase):
TestImage.drop_collection()
t = TestImage()
t.image1.put(open(TEST_IMAGE_PATH, 'rb'))
t.image2.put(open(TEST_IMAGE2_PATH, 'rb'))
t.image1.put(get_file(TEST_IMAGE_PATH))
t.image2.put(get_file(TEST_IMAGE2_PATH))
t.save()
test = TestImage.objects.first()
@@ -563,12 +573,10 @@ class FileTest(MongoDBTestCase):
Animal.drop_collection()
marmot = Animal(genus='Marmota', family='Sciuridae')
marmot_photo = open(TEST_IMAGE_PATH, 'rb') # Retrieve a photo from disk
photos_field = marmot._fields['photos'].field
new_proxy = photos_field.get_proxy_obj('photos', marmot)
new_proxy.put(marmot_photo, content_type='image/jpeg', foo='bar')
marmot_photo.close()
with open(TEST_IMAGE_PATH, 'rb') as marmot_photo: # Retrieve a photo from disk
photos_field = marmot._fields['photos'].field
new_proxy = photos_field.get_proxy_obj('photos', marmot)
new_proxy.put(marmot_photo, content_type='image/jpeg', foo='bar')
marmot.photos.append(new_proxy)
marmot.save()
@@ -578,5 +586,6 @@ class FileTest(MongoDBTestCase):
self.assertEqual(marmot.photos[0].foo, 'bar')
self.assertEqual(marmot.photos[0].get().length, 8313)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
import uuid
from nose.plugins.skip import SkipTest
import six
from bson import Binary
from mongoengine import *
from tests.utils import MongoDBTestCase
BIN_VALUE = six.b('\xa9\xf3\x8d(\xd7\x03\x84\xb4k[\x0f\xe3\xa2\x19\x85p[J\xa3\xd2>\xde\xe6\x87\xb1\x7f\xc6\xe6\xd9r\x18\xf5')
class TestBinaryField(MongoDBTestCase):
def test_binary_fields(self):
"""Ensure that binary fields can be stored and retrieved.
"""
class Attachment(Document):
content_type = StringField()
blob = BinaryField()
BLOB = six.b('\xe6\x00\xc4\xff\x07')
MIME_TYPE = 'application/octet-stream'
Attachment.drop_collection()
attachment = Attachment(content_type=MIME_TYPE, blob=BLOB)
attachment.save()
attachment_1 = Attachment.objects().first()
self.assertEqual(MIME_TYPE, attachment_1.content_type)
self.assertEqual(BLOB, six.binary_type(attachment_1.blob))
def test_validation_succeeds(self):
"""Ensure that valid values can be assigned to binary fields.
"""
class AttachmentRequired(Document):
blob = BinaryField(required=True)
class AttachmentSizeLimit(Document):
blob = BinaryField(max_bytes=4)
attachment_required = AttachmentRequired()
self.assertRaises(ValidationError, attachment_required.validate)
attachment_required.blob = Binary(six.b('\xe6\x00\xc4\xff\x07'))
attachment_required.validate()
_5_BYTES = six.b('\xe6\x00\xc4\xff\x07')
_4_BYTES = six.b('\xe6\x00\xc4\xff')
self.assertRaises(ValidationError, AttachmentSizeLimit(blob=_5_BYTES).validate)
AttachmentSizeLimit(blob=_4_BYTES).validate()
def test_validation_fails(self):
"""Ensure that invalid values cannot be assigned to binary fields."""
class Attachment(Document):
blob = BinaryField()
for invalid_data in (2, u'Im_a_unicode', ['some_str']):
self.assertRaises(ValidationError, Attachment(blob=invalid_data).validate)
def test__primary(self):
class Attachment(Document):
id = BinaryField(primary_key=True)
Attachment.drop_collection()
binary_id = uuid.uuid4().bytes
att = Attachment(id=binary_id).save()
self.assertEqual(1, Attachment.objects.count())
self.assertEqual(1, Attachment.objects.filter(id=att.id).count())
att.delete()
self.assertEqual(0, Attachment.objects.count())
def test_primary_filter_by_binary_pk_as_str(self):
raise SkipTest("Querying by id as string is not currently supported")
class Attachment(Document):
id = BinaryField(primary_key=True)
Attachment.drop_collection()
binary_id = uuid.uuid4().bytes
att = Attachment(id=binary_id).save()
self.assertEqual(1, Attachment.objects.filter(id=binary_id).count())
att.delete()
self.assertEqual(0, Attachment.objects.count())
def test_match_querying_with_bytes(self):
class MyDocument(Document):
bin_field = BinaryField()
MyDocument.drop_collection()
doc = MyDocument(bin_field=BIN_VALUE).save()
matched_doc = MyDocument.objects(bin_field=BIN_VALUE).first()
self.assertEqual(matched_doc.id, doc.id)
def test_match_querying_with_binary(self):
class MyDocument(Document):
bin_field = BinaryField()
MyDocument.drop_collection()
doc = MyDocument(bin_field=BIN_VALUE).save()
matched_doc = MyDocument.objects(bin_field=Binary(BIN_VALUE)).first()
self.assertEqual(matched_doc.id, doc.id)
def test_modify_operation__set(self):
"""Ensures no regression of bug #1127"""
class MyDocument(Document):
some_field = StringField()
bin_field = BinaryField()
MyDocument.drop_collection()
doc = MyDocument.objects(some_field='test').modify(
upsert=True, new=True,
set__bin_field=BIN_VALUE
)
self.assertEqual(doc.some_field, 'test')
if six.PY3:
self.assertEqual(doc.bin_field, BIN_VALUE)
else:
self.assertEqual(doc.bin_field, Binary(BIN_VALUE))
def test_update_one(self):
"""Ensures no regression of bug #1127"""
class MyDocument(Document):
bin_field = BinaryField()
MyDocument.drop_collection()
bin_data = six.b('\xe6\x00\xc4\xff\x07')
doc = MyDocument(bin_field=bin_data).save()
n_updated = MyDocument.objects(bin_field=bin_data).update_one(bin_field=BIN_VALUE)
self.assertEqual(n_updated, 1)
fetched = MyDocument.objects.with_id(doc.id)
if six.PY3:
self.assertEqual(fetched.bin_field, BIN_VALUE)
else:
self.assertEqual(fetched.bin_field, Binary(BIN_VALUE))

View File

@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
from mongoengine import *
from tests.utils import MongoDBTestCase, get_as_pymongo
class TestBooleanField(MongoDBTestCase):
def test_storage(self):
class Person(Document):
admin = BooleanField()
person = Person(admin=True)
person.save()
self.assertEqual(
get_as_pymongo(person),
{'_id': person.id,
'admin': True})
def test_validation(self):
"""Ensure that invalid values cannot be assigned to boolean
fields.
"""
class Person(Document):
admin = BooleanField()
person = Person()
person.admin = True
person.validate()
person.admin = 2
self.assertRaises(ValidationError, person.validate)
person.admin = 'Yes'
self.assertRaises(ValidationError, person.validate)
person.admin = 'False'
self.assertRaises(ValidationError, person.validate)
def test_weirdness_constructor(self):
"""When attribute is set in contructor, it gets cast into a bool
which causes some weird behavior. We dont necessarily want to maintain this behavior
but its a known issue
"""
class Person(Document):
admin = BooleanField()
new_person = Person(admin='False')
self.assertTrue(new_person.admin)
new_person = Person(admin='0')
self.assertTrue(new_person.admin)

View File

@@ -0,0 +1,443 @@
# -*- coding: utf-8 -*-
from decimal import Decimal
from mongoengine import *
from tests.utils import MongoDBTestCase
class TestCachedReferenceField(MongoDBTestCase):
def test_get_and_save(self):
"""
Tests #1047: CachedReferenceField creates DBRefs on to_python,
but can't save them on to_mongo.
"""
class Animal(Document):
name = StringField()
tag = StringField()
class Ocorrence(Document):
person = StringField()
animal = CachedReferenceField(Animal)
Animal.drop_collection()
Ocorrence.drop_collection()
Ocorrence(person="testte",
animal=Animal(name="Leopard", tag="heavy").save()).save()
p = Ocorrence.objects.get()
p.person = 'new_testte'
p.save()
def test_general_things(self):
class Animal(Document):
name = StringField()
tag = StringField()
class Ocorrence(Document):
person = StringField()
animal = CachedReferenceField(
Animal, fields=['tag'])
Animal.drop_collection()
Ocorrence.drop_collection()
a = Animal(name="Leopard", tag="heavy")
a.save()
self.assertEqual(Animal._cached_reference_fields, [Ocorrence.animal])
o = Ocorrence(person="teste", animal=a)
o.save()
p = Ocorrence(person="Wilson")
p.save()
self.assertEqual(Ocorrence.objects(animal=None).count(), 1)
self.assertEqual(
a.to_mongo(fields=['tag']), {'tag': 'heavy', "_id": a.pk})
self.assertEqual(o.to_mongo()['animal']['tag'], 'heavy')
# counts
Ocorrence(person="teste 2").save()
Ocorrence(person="teste 3").save()
count = Ocorrence.objects(animal__tag='heavy').count()
self.assertEqual(count, 1)
ocorrence = Ocorrence.objects(animal__tag='heavy').first()
self.assertEqual(ocorrence.person, "teste")
self.assertIsInstance(ocorrence.animal, Animal)
def test_with_decimal(self):
class PersonAuto(Document):
name = StringField()
salary = DecimalField()
class SocialTest(Document):
group = StringField()
person = CachedReferenceField(
PersonAuto,
fields=('salary',))
PersonAuto.drop_collection()
SocialTest.drop_collection()
p = PersonAuto(name="Alberto", salary=Decimal('7000.00'))
p.save()
s = SocialTest(group="dev", person=p)
s.save()
self.assertEqual(
SocialTest.objects._collection.find_one({'person.salary': 7000.00}), {
'_id': s.pk,
'group': s.group,
'person': {
'_id': p.pk,
'salary': 7000.00
}
})
def test_cached_reference_field_reference(self):
class Group(Document):
name = StringField()
class Person(Document):
name = StringField()
group = ReferenceField(Group)
class SocialData(Document):
obs = StringField()
tags = ListField(
StringField())
person = CachedReferenceField(
Person,
fields=('group',))
Group.drop_collection()
Person.drop_collection()
SocialData.drop_collection()
g1 = Group(name='dev')
g1.save()
g2 = Group(name="designers")
g2.save()
p1 = Person(name="Alberto", group=g1)
p1.save()
p2 = Person(name="Andre", group=g1)
p2.save()
p3 = Person(name="Afro design", group=g2)
p3.save()
s1 = SocialData(obs="testing 123", person=p1, tags=['tag1', 'tag2'])
s1.save()
s2 = SocialData(obs="testing 321", person=p3, tags=['tag3', 'tag4'])
s2.save()
self.assertEqual(SocialData.objects._collection.find_one(
{'tags': 'tag2'}), {
'_id': s1.pk,
'obs': 'testing 123',
'tags': ['tag1', 'tag2'],
'person': {
'_id': p1.pk,
'group': g1.pk
}
})
self.assertEqual(SocialData.objects(person__group=g2).count(), 1)
self.assertEqual(SocialData.objects(person__group=g2).first(), s2)
def test_cached_reference_field_push_with_fields(self):
class Product(Document):
name = StringField()
Product.drop_collection()
class Basket(Document):
products = ListField(CachedReferenceField(Product, fields=['name']))
Basket.drop_collection()
product1 = Product(name='abc').save()
product2 = Product(name='def').save()
basket = Basket(products=[product1]).save()
self.assertEqual(
Basket.objects._collection.find_one(),
{
'_id': basket.pk,
'products': [
{
'_id': product1.pk,
'name': product1.name
}
]
}
)
# push to list
basket.update(push__products=product2)
basket.reload()
self.assertEqual(
Basket.objects._collection.find_one(),
{
'_id': basket.pk,
'products': [
{
'_id': product1.pk,
'name': product1.name
},
{
'_id': product2.pk,
'name': product2.name
}
]
}
)
def test_cached_reference_field_update_all(self):
class Person(Document):
TYPES = (
('pf', "PF"),
('pj', "PJ")
)
name = StringField()
tp = StringField(
choices=TYPES
)
father = CachedReferenceField('self', fields=('tp',))
Person.drop_collection()
a1 = Person(name="Wilson Father", tp="pj")
a1.save()
a2 = Person(name='Wilson Junior', tp='pf', father=a1)
a2.save()
self.assertEqual(dict(a2.to_mongo()), {
"_id": a2.pk,
"name": u"Wilson Junior",
"tp": u"pf",
"father": {
"_id": a1.pk,
"tp": u"pj"
}
})
self.assertEqual(Person.objects(father=a1)._query, {
'father._id': a1.pk
})
self.assertEqual(Person.objects(father=a1).count(), 1)
Person.objects.update(set__tp="pf")
Person.father.sync_all()
a2.reload()
self.assertEqual(dict(a2.to_mongo()), {
"_id": a2.pk,
"name": u"Wilson Junior",
"tp": u"pf",
"father": {
"_id": a1.pk,
"tp": u"pf"
}
})
def test_cached_reference_fields_on_embedded_documents(self):
with self.assertRaises(InvalidDocumentError):
class Test(Document):
name = StringField()
type('WrongEmbeddedDocument', (
EmbeddedDocument,), {
'test': CachedReferenceField(Test)
})
def test_cached_reference_auto_sync(self):
class Person(Document):
TYPES = (
('pf', "PF"),
('pj', "PJ")
)
name = StringField()
tp = StringField(
choices=TYPES
)
father = CachedReferenceField('self', fields=('tp',))
Person.drop_collection()
a1 = Person(name="Wilson Father", tp="pj")
a1.save()
a2 = Person(name='Wilson Junior', tp='pf', father=a1)
a2.save()
a1.tp = 'pf'
a1.save()
a2.reload()
self.assertEqual(dict(a2.to_mongo()), {
'_id': a2.pk,
'name': 'Wilson Junior',
'tp': 'pf',
'father': {
'_id': a1.pk,
'tp': 'pf'
}
})
def test_cached_reference_auto_sync_disabled(self):
class Persone(Document):
TYPES = (
('pf', "PF"),
('pj', "PJ")
)
name = StringField()
tp = StringField(
choices=TYPES
)
father = CachedReferenceField(
'self', fields=('tp',), auto_sync=False)
Persone.drop_collection()
a1 = Persone(name="Wilson Father", tp="pj")
a1.save()
a2 = Persone(name='Wilson Junior', tp='pf', father=a1)
a2.save()
a1.tp = 'pf'
a1.save()
self.assertEqual(Persone.objects._collection.find_one({'_id': a2.pk}), {
'_id': a2.pk,
'name': 'Wilson Junior',
'tp': 'pf',
'father': {
'_id': a1.pk,
'tp': 'pj'
}
})
def test_cached_reference_embedded_fields(self):
class Owner(EmbeddedDocument):
TPS = (
('n', "Normal"),
('u', "Urgent")
)
name = StringField()
tp = StringField(
verbose_name="Type",
db_field="t",
choices=TPS)
class Animal(Document):
name = StringField()
tag = StringField()
owner = EmbeddedDocumentField(Owner)
class Ocorrence(Document):
person = StringField()
animal = CachedReferenceField(
Animal, fields=['tag', 'owner.tp'])
Animal.drop_collection()
Ocorrence.drop_collection()
a = Animal(name="Leopard", tag="heavy",
owner=Owner(tp='u', name="Wilson Júnior")
)
a.save()
o = Ocorrence(person="teste", animal=a)
o.save()
self.assertEqual(dict(a.to_mongo(fields=['tag', 'owner.tp'])), {
'_id': a.pk,
'tag': 'heavy',
'owner': {
't': 'u'
}
})
self.assertEqual(o.to_mongo()['animal']['tag'], 'heavy')
self.assertEqual(o.to_mongo()['animal']['owner']['t'], 'u')
# counts
Ocorrence(person="teste 2").save()
Ocorrence(person="teste 3").save()
count = Ocorrence.objects(
animal__tag='heavy', animal__owner__tp='u').count()
self.assertEqual(count, 1)
ocorrence = Ocorrence.objects(
animal__tag='heavy',
animal__owner__tp='u').first()
self.assertEqual(ocorrence.person, "teste")
self.assertIsInstance(ocorrence.animal, Animal)
def test_cached_reference_embedded_list_fields(self):
class Owner(EmbeddedDocument):
name = StringField()
tags = ListField(StringField())
class Animal(Document):
name = StringField()
tag = StringField()
owner = EmbeddedDocumentField(Owner)
class Ocorrence(Document):
person = StringField()
animal = CachedReferenceField(
Animal, fields=['tag', 'owner.tags'])
Animal.drop_collection()
Ocorrence.drop_collection()
a = Animal(name="Leopard", tag="heavy",
owner=Owner(tags=['cool', 'funny'],
name="Wilson Júnior")
)
a.save()
o = Ocorrence(person="teste 2", animal=a)
o.save()
self.assertEqual(dict(a.to_mongo(fields=['tag', 'owner.tags'])), {
'_id': a.pk,
'tag': 'heavy',
'owner': {
'tags': ['cool', 'funny']
}
})
self.assertEqual(o.to_mongo()['animal']['tag'], 'heavy')
self.assertEqual(o.to_mongo()['animal']['owner']['tags'],
['cool', 'funny'])
# counts
Ocorrence(person="teste 2").save()
Ocorrence(person="teste 3").save()
query = Ocorrence.objects(
animal__tag='heavy', animal__owner__tags='cool')._query
self.assertEqual(
query, {'animal.owner.tags': 'cool', 'animal.tag': 'heavy'})
ocorrence = Ocorrence.objects(
animal__tag='heavy',
animal__owner__tags='cool').first()
self.assertEqual(ocorrence.person, "teste 2")
self.assertIsInstance(ocorrence.animal, Animal)

View File

@@ -0,0 +1,184 @@
# -*- coding: utf-8 -*-
import datetime
import math
import itertools
import re
from mongoengine import *
from tests.utils import MongoDBTestCase
class ComplexDateTimeFieldTest(MongoDBTestCase):
def test_complexdatetime_storage(self):
"""Tests for complex datetime fields - which can handle
microseconds without rounding.
"""
class LogEntry(Document):
date = ComplexDateTimeField()
date_with_dots = ComplexDateTimeField(separator='.')
LogEntry.drop_collection()
# Post UTC - microseconds are rounded (down) nearest millisecond and
# dropped - with default datetimefields
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 999)
log = LogEntry()
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1)
# Post UTC - microseconds are rounded (down) nearest millisecond - with
# default datetimefields
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9999)
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1)
# Pre UTC dates microseconds below 1000 are dropped - with default
# datetimefields
d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, 999)
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1)
# Pre UTC microseconds above 1000 is wonky - with default datetimefields
# log.date has an invalid microsecond value so I can't construct
# a date to compare.
for i in range(1001, 3113, 33):
d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, i)
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1)
log1 = LogEntry.objects.get(date=d1)
self.assertEqual(log, log1)
# Test string padding
microsecond = map(int, [math.pow(10, x) for x in range(6)])
mm = dd = hh = ii = ss = [1, 10]
for values in itertools.product([2014], mm, dd, hh, ii, ss, microsecond):
stored = LogEntry(date=datetime.datetime(*values)).to_mongo()['date']
self.assertTrue(re.match('^\d{4},\d{2},\d{2},\d{2},\d{2},\d{2},\d{6}$', stored) is not None)
# Test separator
stored = LogEntry(date_with_dots=datetime.datetime(2014, 1, 1)).to_mongo()['date_with_dots']
self.assertTrue(re.match('^\d{4}.\d{2}.\d{2}.\d{2}.\d{2}.\d{2}.\d{6}$', stored) is not None)
def test_complexdatetime_usage(self):
"""Tests for complex datetime fields - which can handle
microseconds without rounding.
"""
class LogEntry(Document):
date = ComplexDateTimeField()
LogEntry.drop_collection()
d1 = datetime.datetime(1950, 1, 1, 0, 0, 1, 999)
log = LogEntry()
log.date = d1
log.save()
log1 = LogEntry.objects.get(date=d1)
self.assertEqual(log, log1)
# create extra 59 log entries for a total of 60
for i in range(1951, 2010):
d = datetime.datetime(i, 1, 1, 0, 0, 1, 999)
LogEntry(date=d).save()
self.assertEqual(LogEntry.objects.count(), 60)
# Test ordering
logs = LogEntry.objects.order_by("date")
i = 0
while i < 59:
self.assertTrue(logs[i].date <= logs[i + 1].date)
i += 1
logs = LogEntry.objects.order_by("-date")
i = 0
while i < 59:
self.assertTrue(logs[i].date >= logs[i + 1].date)
i += 1
# Test searching
logs = LogEntry.objects.filter(date__gte=datetime.datetime(1980, 1, 1))
self.assertEqual(logs.count(), 30)
logs = LogEntry.objects.filter(date__lte=datetime.datetime(1980, 1, 1))
self.assertEqual(logs.count(), 30)
logs = LogEntry.objects.filter(
date__lte=datetime.datetime(2011, 1, 1),
date__gte=datetime.datetime(2000, 1, 1),
)
self.assertEqual(logs.count(), 10)
LogEntry.drop_collection()
# Test microsecond-level ordering/filtering
for microsecond in (99, 999, 9999, 10000):
LogEntry(
date=datetime.datetime(2015, 1, 1, 0, 0, 0, microsecond)
).save()
logs = list(LogEntry.objects.order_by('date'))
for next_idx, log in enumerate(logs[:-1], start=1):
next_log = logs[next_idx]
self.assertTrue(log.date < next_log.date)
logs = list(LogEntry.objects.order_by('-date'))
for next_idx, log in enumerate(logs[:-1], start=1):
next_log = logs[next_idx]
self.assertTrue(log.date > next_log.date)
logs = LogEntry.objects.filter(
date__lte=datetime.datetime(2015, 1, 1, 0, 0, 0, 10000))
self.assertEqual(logs.count(), 4)
def test_no_default_value(self):
class Log(Document):
timestamp = ComplexDateTimeField()
Log.drop_collection()
log = Log()
self.assertIsNone(log.timestamp)
log.save()
fetched_log = Log.objects.with_id(log.id)
self.assertIsNone(fetched_log.timestamp)
def test_default_static_value(self):
NOW = datetime.datetime.utcnow()
class Log(Document):
timestamp = ComplexDateTimeField(default=NOW)
Log.drop_collection()
log = Log()
self.assertEqual(log.timestamp, NOW)
log.save()
fetched_log = Log.objects.with_id(log.id)
self.assertEqual(fetched_log.timestamp, NOW)
def test_default_callable(self):
NOW = datetime.datetime.utcnow()
class Log(Document):
timestamp = ComplexDateTimeField(default=datetime.datetime.utcnow)
Log.drop_collection()
log = Log()
self.assertGreaterEqual(log.timestamp, NOW)
log.save()
fetched_log = Log.objects.with_id(log.id)
self.assertGreaterEqual(fetched_log.timestamp, NOW)

View File

@@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
import datetime
import six
try:
import dateutil
except ImportError:
dateutil = None
from mongoengine import *
from tests.utils import MongoDBTestCase
class TestDateField(MongoDBTestCase):
def test_date_from_empty_string(self):
"""
Ensure an exception is raised when trying to
cast an empty string to datetime.
"""
class MyDoc(Document):
dt = DateField()
md = MyDoc(dt='')
self.assertRaises(ValidationError, md.save)
def test_date_from_whitespace_string(self):
"""
Ensure an exception is raised when trying to
cast a whitespace-only string to datetime.
"""
class MyDoc(Document):
dt = DateField()
md = MyDoc(dt=' ')
self.assertRaises(ValidationError, md.save)
def test_default_values_today(self):
"""Ensure that default field values are used when creating
a document.
"""
class Person(Document):
day = DateField(default=datetime.date.today)
person = Person()
person.validate()
self.assertEqual(person.day, person.day)
self.assertEqual(person.day, datetime.date.today())
self.assertEqual(person._data['day'], person.day)
def test_date(self):
"""Tests showing pymongo date fields
See: http://api.mongodb.org/python/current/api/bson/son.html#dt
"""
class LogEntry(Document):
date = DateField()
LogEntry.drop_collection()
# Test can save dates
log = LogEntry()
log.date = datetime.date.today()
log.save()
log.reload()
self.assertEqual(log.date, datetime.date.today())
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 999)
d2 = datetime.datetime(1970, 1, 1, 0, 0, 1)
log = LogEntry()
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1.date())
self.assertEqual(log.date, d2.date())
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9999)
d2 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9000)
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1.date())
self.assertEqual(log.date, d2.date())
if not six.PY3:
# Pre UTC dates microseconds below 1000 are dropped
# This does not seem to be true in PY3
d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, 999)
d2 = datetime.datetime(1969, 12, 31, 23, 59, 59)
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1.date())
self.assertEqual(log.date, d2.date())
def test_regular_usage(self):
"""Tests for regular datetime fields"""
class LogEntry(Document):
date = DateField()
LogEntry.drop_collection()
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1)
log = LogEntry()
log.date = d1
log.validate()
log.save()
for query in (d1, d1.isoformat(' ')):
log1 = LogEntry.objects.get(date=query)
self.assertEqual(log, log1)
if dateutil:
log1 = LogEntry.objects.get(date=d1.isoformat('T'))
self.assertEqual(log, log1)
# create additional 19 log entries for a total of 20
for i in range(1971, 1990):
d = datetime.datetime(i, 1, 1, 0, 0, 1)
LogEntry(date=d).save()
self.assertEqual(LogEntry.objects.count(), 20)
# Test ordering
logs = LogEntry.objects.order_by("date")
i = 0
while i < 19:
self.assertTrue(logs[i].date <= logs[i + 1].date)
i += 1
logs = LogEntry.objects.order_by("-date")
i = 0
while i < 19:
self.assertTrue(logs[i].date >= logs[i + 1].date)
i += 1
# Test searching
logs = LogEntry.objects.filter(date__gte=datetime.datetime(1980, 1, 1))
self.assertEqual(logs.count(), 10)
def test_validation(self):
"""Ensure that invalid values cannot be assigned to datetime
fields.
"""
class LogEntry(Document):
time = DateField()
log = LogEntry()
log.time = datetime.datetime.now()
log.validate()
log.time = datetime.date.today()
log.validate()
log.time = datetime.datetime.now().isoformat(' ')
log.validate()
if dateutil:
log.time = datetime.datetime.now().isoformat('T')
log.validate()
log.time = -1
self.assertRaises(ValidationError, log.validate)
log.time = 'ABC'
self.assertRaises(ValidationError, log.validate)

View File

@@ -0,0 +1,203 @@
# -*- coding: utf-8 -*-
import datetime
import six
try:
import dateutil
except ImportError:
dateutil = None
from mongoengine import *
from mongoengine import connection
from tests.utils import MongoDBTestCase
class TestDateTimeField(MongoDBTestCase):
def test_datetime_from_empty_string(self):
"""
Ensure an exception is raised when trying to
cast an empty string to datetime.
"""
class MyDoc(Document):
dt = DateTimeField()
md = MyDoc(dt='')
self.assertRaises(ValidationError, md.save)
def test_datetime_from_whitespace_string(self):
"""
Ensure an exception is raised when trying to
cast a whitespace-only string to datetime.
"""
class MyDoc(Document):
dt = DateTimeField()
md = MyDoc(dt=' ')
self.assertRaises(ValidationError, md.save)
def test_default_value_utcnow(self):
"""Ensure that default field values are used when creating
a document.
"""
class Person(Document):
created = DateTimeField(default=datetime.datetime.utcnow)
utcnow = datetime.datetime.utcnow()
person = Person()
person.validate()
person_created_t0 = person.created
self.assertLess(person.created - utcnow, datetime.timedelta(seconds=1))
self.assertEqual(person_created_t0, person.created) # make sure it does not change
self.assertEqual(person._data['created'], person.created)
def test_handling_microseconds(self):
"""Tests showing pymongo datetime fields handling of microseconds.
Microseconds are rounded to the nearest millisecond and pre UTC
handling is wonky.
See: http://api.mongodb.org/python/current/api/bson/son.html#dt
"""
class LogEntry(Document):
date = DateTimeField()
LogEntry.drop_collection()
# Test can save dates
log = LogEntry()
log.date = datetime.date.today()
log.save()
log.reload()
self.assertEqual(log.date.date(), datetime.date.today())
# Post UTC - microseconds are rounded (down) nearest millisecond and
# dropped
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 999)
d2 = datetime.datetime(1970, 1, 1, 0, 0, 1)
log = LogEntry()
log.date = d1
log.save()
log.reload()
self.assertNotEqual(log.date, d1)
self.assertEqual(log.date, d2)
# Post UTC - microseconds are rounded (down) nearest millisecond
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9999)
d2 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9000)
log.date = d1
log.save()
log.reload()
self.assertNotEqual(log.date, d1)
self.assertEqual(log.date, d2)
if not six.PY3:
# Pre UTC dates microseconds below 1000 are dropped
# This does not seem to be true in PY3
d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, 999)
d2 = datetime.datetime(1969, 12, 31, 23, 59, 59)
log.date = d1
log.save()
log.reload()
self.assertNotEqual(log.date, d1)
self.assertEqual(log.date, d2)
def test_regular_usage(self):
"""Tests for regular datetime fields"""
class LogEntry(Document):
date = DateTimeField()
LogEntry.drop_collection()
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1)
log = LogEntry()
log.date = d1
log.validate()
log.save()
for query in (d1, d1.isoformat(' ')):
log1 = LogEntry.objects.get(date=query)
self.assertEqual(log, log1)
if dateutil:
log1 = LogEntry.objects.get(date=d1.isoformat('T'))
self.assertEqual(log, log1)
# create additional 19 log entries for a total of 20
for i in range(1971, 1990):
d = datetime.datetime(i, 1, 1, 0, 0, 1)
LogEntry(date=d).save()
self.assertEqual(LogEntry.objects.count(), 20)
# Test ordering
logs = LogEntry.objects.order_by("date")
i = 0
while i < 19:
self.assertTrue(logs[i].date <= logs[i + 1].date)
i += 1
logs = LogEntry.objects.order_by("-date")
i = 0
while i < 19:
self.assertTrue(logs[i].date >= logs[i + 1].date)
i += 1
# Test searching
logs = LogEntry.objects.filter(date__gte=datetime.datetime(1980, 1, 1))
self.assertEqual(logs.count(), 10)
logs = LogEntry.objects.filter(date__lte=datetime.datetime(1980, 1, 1))
self.assertEqual(logs.count(), 10)
logs = LogEntry.objects.filter(
date__lte=datetime.datetime(1980, 1, 1),
date__gte=datetime.datetime(1975, 1, 1),
)
self.assertEqual(logs.count(), 5)
def test_datetime_validation(self):
"""Ensure that invalid values cannot be assigned to datetime
fields.
"""
class LogEntry(Document):
time = DateTimeField()
log = LogEntry()
log.time = datetime.datetime.now()
log.validate()
log.time = datetime.date.today()
log.validate()
log.time = datetime.datetime.now().isoformat(' ')
log.validate()
if dateutil:
log.time = datetime.datetime.now().isoformat('T')
log.validate()
log.time = -1
self.assertRaises(ValidationError, log.validate)
log.time = 'ABC'
self.assertRaises(ValidationError, log.validate)
class TestDateTimeTzAware(MongoDBTestCase):
def test_datetime_tz_aware_mark_as_changed(self):
# Reset the connections
connection._connection_settings = {}
connection._connections = {}
connection._dbs = {}
connect(db='mongoenginetest', tz_aware=True)
class LogEntry(Document):
time = DateTimeField()
LogEntry.drop_collection()
LogEntry(time=datetime.datetime(2013, 1, 1, 0, 0, 0)).save()
log = LogEntry.objects.first()
log.time = datetime.datetime(2013, 1, 1, 0, 0, 0)
self.assertEqual(['time'], log._changed_fields)

View File

@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
from decimal import Decimal
from mongoengine import *
from tests.utils import MongoDBTestCase
class TestDecimalField(MongoDBTestCase):
def test_validation(self):
"""Ensure that invalid values cannot be assigned to decimal fields.
"""
class Person(Document):
height = DecimalField(min_value=Decimal('0.1'),
max_value=Decimal('3.5'))
Person.drop_collection()
Person(height=Decimal('1.89')).save()
person = Person.objects.first()
self.assertEqual(person.height, Decimal('1.89'))
person.height = '2.0'
person.save()
person.height = 0.01
self.assertRaises(ValidationError, person.validate)
person.height = Decimal('0.01')
self.assertRaises(ValidationError, person.validate)
person.height = Decimal('4.0')
self.assertRaises(ValidationError, person.validate)
person.height = 'something invalid'
self.assertRaises(ValidationError, person.validate)
person_2 = Person(height='something invalid')
self.assertRaises(ValidationError, person_2.validate)
def test_comparison(self):
class Person(Document):
money = DecimalField()
Person.drop_collection()
Person(money=6).save()
Person(money=7).save()
Person(money=8).save()
Person(money=10).save()
self.assertEqual(2, Person.objects(money__gt=Decimal("7")).count())
self.assertEqual(2, Person.objects(money__gt=7).count())
self.assertEqual(2, Person.objects(money__gt="7").count())
self.assertEqual(3, Person.objects(money__gte="7").count())
def test_storage(self):
class Person(Document):
float_value = DecimalField(precision=4)
string_value = DecimalField(precision=4, force_string=True)
Person.drop_collection()
values_to_store = [10, 10.1, 10.11, "10.111", Decimal("10.1111"), Decimal("10.11111")]
for store_at_creation in [True, False]:
for value in values_to_store:
# to_python is called explicitly if values were sent in the kwargs of __init__
if store_at_creation:
Person(float_value=value, string_value=value).save()
else:
person = Person.objects.create()
person.float_value = value
person.string_value = value
person.save()
# How its stored
expected = [
{'float_value': 10.0, 'string_value': '10.0000'},
{'float_value': 10.1, 'string_value': '10.1000'},
{'float_value': 10.11, 'string_value': '10.1100'},
{'float_value': 10.111, 'string_value': '10.1110'},
{'float_value': 10.1111, 'string_value': '10.1111'},
{'float_value': 10.1111, 'string_value': '10.1111'}]
expected.extend(expected)
actual = list(Person.objects.exclude('id').as_pymongo())
self.assertEqual(expected, actual)
# How it comes out locally
expected = [Decimal('10.0000'), Decimal('10.1000'), Decimal('10.1100'),
Decimal('10.1110'), Decimal('10.1111'), Decimal('10.1111')]
expected.extend(expected)
for field_name in ['float_value', 'string_value']:
actual = list(Person.objects().scalar(field_name))
self.assertEqual(expected, actual)

View File

@@ -0,0 +1,324 @@
# -*- coding: utf-8 -*-
from mongoengine import *
from mongoengine.base import BaseDict
from tests.utils import MongoDBTestCase, get_as_pymongo
class TestDictField(MongoDBTestCase):
def test_storage(self):
class BlogPost(Document):
info = DictField()
BlogPost.drop_collection()
info = {'testkey': 'testvalue'}
post = BlogPost(info=info).save()
self.assertEqual(
get_as_pymongo(post),
{
'_id': post.id,
'info': info
}
)
def test_general_things(self):
"""Ensure that dict types work as expected."""
class BlogPost(Document):
info = DictField()
BlogPost.drop_collection()
post = BlogPost()
post.info = 'my post'
self.assertRaises(ValidationError, post.validate)
post.info = ['test', 'test']
self.assertRaises(ValidationError, post.validate)
post.info = {'$title': 'test'}
self.assertRaises(ValidationError, post.validate)
post.info = {'nested': {'$title': 'test'}}
self.assertRaises(ValidationError, post.validate)
post.info = {'the.title': 'test'}
self.assertRaises(ValidationError, post.validate)
post.info = {'nested': {'the.title': 'test'}}
self.assertRaises(ValidationError, post.validate)
post.info = {1: 'test'}
self.assertRaises(ValidationError, post.validate)
post.info = {'title': 'test'}
post.save()
post = BlogPost()
post.info = {'title': 'dollar_sign', 'details': {'te$t': 'test'}}
post.save()
post = BlogPost()
post.info = {'details': {'test': 'test'}}
post.save()
post = BlogPost()
post.info = {'details': {'test': 3}}
post.save()
self.assertEqual(BlogPost.objects.count(), 4)
self.assertEqual(
BlogPost.objects.filter(info__title__exact='test').count(), 1)
self.assertEqual(
BlogPost.objects.filter(info__details__test__exact='test').count(), 1)
post = BlogPost.objects.filter(info__title__exact='dollar_sign').first()
self.assertIn('te$t', post['info']['details'])
# Confirm handles non strings or non existing keys
self.assertEqual(
BlogPost.objects.filter(info__details__test__exact=5).count(), 0)
self.assertEqual(
BlogPost.objects.filter(info__made_up__test__exact='test').count(), 0)
post = BlogPost.objects.create(info={'title': 'original'})
post.info.update({'title': 'updated'})
post.save()
post.reload()
self.assertEqual('updated', post.info['title'])
post.info.setdefault('authors', [])
post.save()
post.reload()
self.assertEqual([], post.info['authors'])
def test_dictfield_dump_document(self):
"""Ensure a DictField can handle another document's dump."""
class Doc(Document):
field = DictField()
class ToEmbed(Document):
id = IntField(primary_key=True, default=1)
recursive = DictField()
class ToEmbedParent(Document):
id = IntField(primary_key=True, default=1)
recursive = DictField()
meta = {'allow_inheritance': True}
class ToEmbedChild(ToEmbedParent):
pass
to_embed_recursive = ToEmbed(id=1).save()
to_embed = ToEmbed(
id=2, recursive=to_embed_recursive.to_mongo().to_dict()).save()
doc = Doc(field=to_embed.to_mongo().to_dict())
doc.save()
self.assertIsInstance(doc.field, dict)
self.assertEqual(doc.field, {'_id': 2, 'recursive': {'_id': 1, 'recursive': {}}})
# Same thing with a Document with a _cls field
to_embed_recursive = ToEmbedChild(id=1).save()
to_embed_child = ToEmbedChild(
id=2, recursive=to_embed_recursive.to_mongo().to_dict()).save()
doc = Doc(field=to_embed_child.to_mongo().to_dict())
doc.save()
self.assertIsInstance(doc.field, dict)
expected = {
'_id': 2, '_cls': 'ToEmbedParent.ToEmbedChild',
'recursive': {'_id': 1, '_cls': 'ToEmbedParent.ToEmbedChild', 'recursive': {}}
}
self.assertEqual(doc.field, expected)
def test_dictfield_strict(self):
"""Ensure that dict field handles validation if provided a strict field type."""
class Simple(Document):
mapping = DictField(field=IntField())
Simple.drop_collection()
e = Simple()
e.mapping['someint'] = 1
e.save()
# try creating an invalid mapping
with self.assertRaises(ValidationError):
e.mapping['somestring'] = "abc"
e.save()
def test_dictfield_complex(self):
"""Ensure that the dict field can handle the complex types."""
class SettingBase(EmbeddedDocument):
meta = {'allow_inheritance': True}
class StringSetting(SettingBase):
value = StringField()
class IntegerSetting(SettingBase):
value = IntField()
class Simple(Document):
mapping = DictField()
Simple.drop_collection()
e = Simple()
e.mapping['somestring'] = StringSetting(value='foo')
e.mapping['someint'] = IntegerSetting(value=42)
e.mapping['nested_dict'] = {'number': 1, 'string': 'Hi!',
'float': 1.001,
'complex': IntegerSetting(value=42),
'list': [IntegerSetting(value=42),
StringSetting(value='foo')]}
e.save()
e2 = Simple.objects.get(id=e.id)
self.assertIsInstance(e2.mapping['somestring'], StringSetting)
self.assertIsInstance(e2.mapping['someint'], IntegerSetting)
# Test querying
self.assertEqual(
Simple.objects.filter(mapping__someint__value=42).count(), 1)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__number=1).count(), 1)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__complex__value=42).count(), 1)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__list__0__value=42).count(), 1)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__list__1__value='foo').count(), 1)
# Confirm can update
Simple.objects().update(
set__mapping={"someint": IntegerSetting(value=10)})
Simple.objects().update(
set__mapping__nested_dict__list__1=StringSetting(value='Boo'))
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__list__1__value='foo').count(), 0)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__list__1__value='Boo').count(), 1)
def test_push_dict(self):
class MyModel(Document):
events = ListField(DictField())
doc = MyModel(events=[{'a': 1}]).save()
raw_doc = get_as_pymongo(doc)
expected_raw_doc = {
'_id': doc.id,
'events': [{'a': 1}]
}
self.assertEqual(raw_doc, expected_raw_doc)
MyModel.objects(id=doc.id).update(push__events={})
raw_doc = get_as_pymongo(doc)
expected_raw_doc = {
'_id': doc.id,
'events': [{'a': 1}, {}]
}
self.assertEqual(raw_doc, expected_raw_doc)
def test_ensure_unique_default_instances(self):
"""Ensure that every field has it's own unique default instance."""
class D(Document):
data = DictField()
data2 = DictField(default=lambda: {})
d1 = D()
d1.data['foo'] = 'bar'
d1.data2['foo'] = 'bar'
d2 = D()
self.assertEqual(d2.data, {})
self.assertEqual(d2.data2, {})
def test_dict_field_invalid_dict_value(self):
class DictFieldTest(Document):
dictionary = DictField(required=True)
DictFieldTest.drop_collection()
test = DictFieldTest(dictionary=None)
test.dictionary # Just access to test getter
self.assertRaises(ValidationError, test.validate)
test = DictFieldTest(dictionary=False)
test.dictionary # Just access to test getter
self.assertRaises(ValidationError, test.validate)
def test_dict_field_raises_validation_error_if_wrongly_assign_embedded_doc(self):
class DictFieldTest(Document):
dictionary = DictField(required=True)
DictFieldTest.drop_collection()
class Embedded(EmbeddedDocument):
name = StringField()
embed = Embedded(name='garbage')
doc = DictFieldTest(dictionary=embed)
with self.assertRaises(ValidationError) as ctx_err:
doc.validate()
self.assertIn("'dictionary'", str(ctx_err.exception))
self.assertIn('Only dictionaries may be used in a DictField', str(ctx_err.exception))
def test_atomic_update_dict_field(self):
"""Ensure that the entire DictField can be atomically updated."""
class Simple(Document):
mapping = DictField(field=ListField(IntField(required=True)))
Simple.drop_collection()
e = Simple()
e.mapping['someints'] = [1, 2]
e.save()
e.update(set__mapping={"ints": [3, 4]})
e.reload()
self.assertEqual(BaseDict, type(e.mapping))
self.assertEqual({"ints": [3, 4]}, e.mapping)
# try creating an invalid mapping
with self.assertRaises(ValueError):
e.update(set__mapping={"somestrings": ["foo", "bar", ]})
def test_dictfield_with_referencefield_complex_nesting_cases(self):
"""Ensure complex nesting inside DictField handles dereferencing of ReferenceField(dbref=True | False)"""
# Relates to Issue #1453
class Doc(Document):
s = StringField()
class Simple(Document):
mapping0 = DictField(ReferenceField(Doc, dbref=True))
mapping1 = DictField(ReferenceField(Doc, dbref=False))
mapping2 = DictField(ListField(ReferenceField(Doc, dbref=True)))
mapping3 = DictField(ListField(ReferenceField(Doc, dbref=False)))
mapping4 = DictField(DictField(field=ReferenceField(Doc, dbref=True)))
mapping5 = DictField(DictField(field=ReferenceField(Doc, dbref=False)))
mapping6 = DictField(ListField(DictField(ReferenceField(Doc, dbref=True))))
mapping7 = DictField(ListField(DictField(ReferenceField(Doc, dbref=False))))
mapping8 = DictField(ListField(DictField(ListField(ReferenceField(Doc, dbref=True)))))
mapping9 = DictField(ListField(DictField(ListField(ReferenceField(Doc, dbref=False)))))
Doc.drop_collection()
Simple.drop_collection()
d = Doc(s='aa').save()
e = Simple()
e.mapping0['someint'] = e.mapping1['someint'] = d
e.mapping2['someint'] = e.mapping3['someint'] = [d]
e.mapping4['someint'] = e.mapping5['someint'] = {'d': d}
e.mapping6['someint'] = e.mapping7['someint'] = [{'d': d}]
e.mapping8['someint'] = e.mapping9['someint'] = [{'d': [d]}]
e.save()
s = Simple.objects.first()
self.assertIsInstance(s.mapping0['someint'], Doc)
self.assertIsInstance(s.mapping1['someint'], Doc)
self.assertIsInstance(s.mapping2['someint'][0], Doc)
self.assertIsInstance(s.mapping3['someint'][0], Doc)
self.assertIsInstance(s.mapping4['someint']['d'], Doc)
self.assertIsInstance(s.mapping5['someint']['d'], Doc)
self.assertIsInstance(s.mapping6['someint'][0]['d'], Doc)
self.assertIsInstance(s.mapping7['someint'][0]['d'], Doc)
self.assertIsInstance(s.mapping8['someint'][0]['d'][0], Doc)
self.assertIsInstance(s.mapping9['someint'][0]['d'][0], Doc)

View File

@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
import sys
from unittest import SkipTest
from mongoengine import *
from tests.utils import MongoDBTestCase
class TestEmailField(MongoDBTestCase):
def test_generic_behavior(self):
class User(Document):
email = EmailField()
user = User(email='ross@example.com')
user.validate()
user = User(email='ross@example.co.uk')
user.validate()
user = User(email=('Kofq@rhom0e4klgauOhpbpNdogawnyIKvQS0wk2mjqrgGQ5S'
'aJIazqqWkm7.net'))
user.validate()
user = User(email='new-tld@example.technology')
user.validate()
user = User(email='ross@example.com.')
self.assertRaises(ValidationError, user.validate)
# unicode domain
user = User(email=u'user@пример.рф')
user.validate()
# invalid unicode domain
user = User(email=u'user@пример')
self.assertRaises(ValidationError, user.validate)
# invalid data type
user = User(email=123)
self.assertRaises(ValidationError, user.validate)
def test_email_field_unicode_user(self):
# Don't run this test on pypy3, which doesn't support unicode regex:
# https://bitbucket.org/pypy/pypy/issues/1821/regular-expression-doesnt-find-unicode
if sys.version_info[:2] == (3, 2):
raise SkipTest('unicode email addresses are not supported on PyPy 3')
class User(Document):
email = EmailField()
# unicode user shouldn't validate by default...
user = User(email=u'Dörte@Sörensen.example.com')
self.assertRaises(ValidationError, user.validate)
# ...but it should be fine with allow_utf8_user set to True
class User(Document):
email = EmailField(allow_utf8_user=True)
user = User(email=u'Dörte@Sörensen.example.com')
user.validate()
def test_email_field_domain_whitelist(self):
class User(Document):
email = EmailField()
# localhost domain shouldn't validate by default...
user = User(email='me@localhost')
self.assertRaises(ValidationError, user.validate)
# ...but it should be fine if it's whitelisted
class User(Document):
email = EmailField(domain_whitelist=['localhost'])
user = User(email='me@localhost')
user.validate()
def test_email_field_ip_domain(self):
class User(Document):
email = EmailField()
valid_ipv4 = 'email@[127.0.0.1]'
valid_ipv6 = 'email@[2001:dB8::1]'
invalid_ip = 'email@[324.0.0.1]'
# IP address as a domain shouldn't validate by default...
user = User(email=valid_ipv4)
self.assertRaises(ValidationError, user.validate)
user = User(email=valid_ipv6)
self.assertRaises(ValidationError, user.validate)
user = User(email=invalid_ip)
self.assertRaises(ValidationError, user.validate)
# ...but it should be fine with allow_ip_domain set to True
class User(Document):
email = EmailField(allow_ip_domain=True)
user = User(email=valid_ipv4)
user.validate()
user = User(email=valid_ipv6)
user.validate()
# invalid IP should still fail validation
user = User(email=invalid_ip)
self.assertRaises(ValidationError, user.validate)
def test_email_field_honors_regex(self):
class User(Document):
email = EmailField(regex=r'\w+@example.com')
# Fails regex validation
user = User(email='me@foo.com')
self.assertRaises(ValidationError, user.validate)
# Passes regex validation
user = User(email='me@example.com')
self.assertIsNone(user.validate())

View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
import six
from mongoengine import *
from tests.utils import MongoDBTestCase
class TestFloatField(MongoDBTestCase):
def test_float_ne_operator(self):
class TestDocument(Document):
float_fld = FloatField()
TestDocument.drop_collection()
TestDocument(float_fld=None).save()
TestDocument(float_fld=1).save()
self.assertEqual(1, TestDocument.objects(float_fld__ne=None).count())
self.assertEqual(1, TestDocument.objects(float_fld__ne=1).count())
def test_validation(self):
"""Ensure that invalid values cannot be assigned to float fields.
"""
class Person(Document):
height = FloatField(min_value=0.1, max_value=3.5)
class BigPerson(Document):
height = FloatField()
person = Person()
person.height = 1.89
person.validate()
person.height = '2.0'
self.assertRaises(ValidationError, person.validate)
person.height = 0.01
self.assertRaises(ValidationError, person.validate)
person.height = 4.0
self.assertRaises(ValidationError, person.validate)
person_2 = Person(height='something invalid')
self.assertRaises(ValidationError, person_2.validate)
big_person = BigPerson()
for value, value_type in enumerate(six.integer_types):
big_person.height = value_type(value)
big_person.validate()
big_person.height = 2 ** 500
big_person.validate()
big_person.height = 2 ** 100000 # Too big for a float value
self.assertRaises(ValidationError, big_person.validate)

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
from mongoengine import *
from tests.utils import MongoDBTestCase
class TestIntField(MongoDBTestCase):
def test_int_validation(self):
"""Ensure that invalid values cannot be assigned to int fields.
"""
class Person(Document):
age = IntField(min_value=0, max_value=110)
person = Person()
person.age = 0
person.validate()
person.age = 50
person.validate()
person.age = 110
person.validate()
person.age = -1
self.assertRaises(ValidationError, person.validate)
person.age = 120
self.assertRaises(ValidationError, person.validate)
person.age = 'ten'
self.assertRaises(ValidationError, person.validate)
def test_ne_operator(self):
class TestDocument(Document):
int_fld = IntField()
TestDocument.drop_collection()
TestDocument(int_fld=None).save()
TestDocument(int_fld=1).save()
self.assertEqual(1, TestDocument.objects(int_fld__ne=None).count())
self.assertEqual(1, TestDocument.objects(int_fld__ne=1).count())

View File

@@ -0,0 +1,524 @@
# -*- coding: utf-8 -*-
from bson import DBRef, ObjectId
from mongoengine import *
from mongoengine.base import LazyReference
from tests.utils import MongoDBTestCase
class TestLazyReferenceField(MongoDBTestCase):
def test_lazy_reference_config(self):
# Make sure ReferenceField only accepts a document class or a string
# with a document class name.
self.assertRaises(ValidationError, LazyReferenceField, EmbeddedDocument)
def test_lazy_reference_simple(self):
class Animal(Document):
name = StringField()
tag = StringField()
class Ocurrence(Document):
person = StringField()
animal = LazyReferenceField(Animal)
Animal.drop_collection()
Ocurrence.drop_collection()
animal = Animal(name="Leopard", tag="heavy").save()
Ocurrence(person="test", animal=animal).save()
p = Ocurrence.objects.get()
self.assertIsInstance(p.animal, LazyReference)
fetched_animal = p.animal.fetch()
self.assertEqual(fetched_animal, animal)
# `fetch` keep cache on referenced document by default...
animal.tag = "not so heavy"
animal.save()
double_fetch = p.animal.fetch()
self.assertIs(fetched_animal, double_fetch)
self.assertEqual(double_fetch.tag, "heavy")
# ...unless specified otherwise
fetch_force = p.animal.fetch(force=True)
self.assertIsNot(fetch_force, fetched_animal)
self.assertEqual(fetch_force.tag, "not so heavy")
def test_lazy_reference_fetch_invalid_ref(self):
class Animal(Document):
name = StringField()
tag = StringField()
class Ocurrence(Document):
person = StringField()
animal = LazyReferenceField(Animal)
Animal.drop_collection()
Ocurrence.drop_collection()
animal = Animal(name="Leopard", tag="heavy").save()
Ocurrence(person="test", animal=animal).save()
animal.delete()
p = Ocurrence.objects.get()
self.assertIsInstance(p.animal, LazyReference)
with self.assertRaises(DoesNotExist):
p.animal.fetch()
def test_lazy_reference_set(self):
class Animal(Document):
meta = {'allow_inheritance': True}
name = StringField()
tag = StringField()
class Ocurrence(Document):
person = StringField()
animal = LazyReferenceField(Animal)
Animal.drop_collection()
Ocurrence.drop_collection()
class SubAnimal(Animal):
nick = StringField()
animal = Animal(name="Leopard", tag="heavy").save()
sub_animal = SubAnimal(nick='doggo', name='dog').save()
for ref in (
animal,
animal.pk,
DBRef(animal._get_collection_name(), animal.pk),
LazyReference(Animal, animal.pk),
sub_animal,
sub_animal.pk,
DBRef(sub_animal._get_collection_name(), sub_animal.pk),
LazyReference(SubAnimal, sub_animal.pk),
):
p = Ocurrence(person="test", animal=ref).save()
p.reload()
self.assertIsInstance(p.animal, LazyReference)
p.animal.fetch()
def test_lazy_reference_bad_set(self):
class Animal(Document):
name = StringField()
tag = StringField()
class Ocurrence(Document):
person = StringField()
animal = LazyReferenceField(Animal)
Animal.drop_collection()
Ocurrence.drop_collection()
class BadDoc(Document):
pass
animal = Animal(name="Leopard", tag="heavy").save()
baddoc = BadDoc().save()
for bad in (
42,
'foo',
baddoc,
DBRef(baddoc._get_collection_name(), animal.pk),
LazyReference(BadDoc, animal.pk)
):
with self.assertRaises(ValidationError):
p = Ocurrence(person="test", animal=bad).save()
def test_lazy_reference_query_conversion(self):
"""Ensure that LazyReferenceFields can be queried using objects and values
of the type of the primary key of the referenced object.
"""
class Member(Document):
user_num = IntField(primary_key=True)
class BlogPost(Document):
title = StringField()
author = LazyReferenceField(Member, dbref=False)
Member.drop_collection()
BlogPost.drop_collection()
m1 = Member(user_num=1)
m1.save()
m2 = Member(user_num=2)
m2.save()
post1 = BlogPost(title='post 1', author=m1)
post1.save()
post2 = BlogPost(title='post 2', author=m2)
post2.save()
post = BlogPost.objects(author=m1).first()
self.assertEqual(post.id, post1.id)
post = BlogPost.objects(author=m2).first()
self.assertEqual(post.id, post2.id)
# Same thing by passing a LazyReference instance
post = BlogPost.objects(author=LazyReference(Member, m2.pk)).first()
self.assertEqual(post.id, post2.id)
def test_lazy_reference_query_conversion_dbref(self):
"""Ensure that LazyReferenceFields can be queried using objects and values
of the type of the primary key of the referenced object.
"""
class Member(Document):
user_num = IntField(primary_key=True)
class BlogPost(Document):
title = StringField()
author = LazyReferenceField(Member, dbref=True)
Member.drop_collection()
BlogPost.drop_collection()
m1 = Member(user_num=1)
m1.save()
m2 = Member(user_num=2)
m2.save()
post1 = BlogPost(title='post 1', author=m1)
post1.save()
post2 = BlogPost(title='post 2', author=m2)
post2.save()
post = BlogPost.objects(author=m1).first()
self.assertEqual(post.id, post1.id)
post = BlogPost.objects(author=m2).first()
self.assertEqual(post.id, post2.id)
# Same thing by passing a LazyReference instance
post = BlogPost.objects(author=LazyReference(Member, m2.pk)).first()
self.assertEqual(post.id, post2.id)
def test_lazy_reference_passthrough(self):
class Animal(Document):
name = StringField()
tag = StringField()
class Ocurrence(Document):
animal = LazyReferenceField(Animal, passthrough=False)
animal_passthrough = LazyReferenceField(Animal, passthrough=True)
Animal.drop_collection()
Ocurrence.drop_collection()
animal = Animal(name="Leopard", tag="heavy").save()
Ocurrence(animal=animal, animal_passthrough=animal).save()
p = Ocurrence.objects.get()
self.assertIsInstance(p.animal, LazyReference)
with self.assertRaises(KeyError):
p.animal['name']
with self.assertRaises(AttributeError):
p.animal.name
self.assertEqual(p.animal.pk, animal.pk)
self.assertEqual(p.animal_passthrough.name, "Leopard")
self.assertEqual(p.animal_passthrough['name'], "Leopard")
# Should not be able to access referenced document's methods
with self.assertRaises(AttributeError):
p.animal.save
with self.assertRaises(KeyError):
p.animal['save']
def test_lazy_reference_not_set(self):
class Animal(Document):
name = StringField()
tag = StringField()
class Ocurrence(Document):
person = StringField()
animal = LazyReferenceField(Animal)
Animal.drop_collection()
Ocurrence.drop_collection()
Ocurrence(person='foo').save()
p = Ocurrence.objects.get()
self.assertIs(p.animal, None)
def test_lazy_reference_equality(self):
class Animal(Document):
name = StringField()
tag = StringField()
Animal.drop_collection()
animal = Animal(name="Leopard", tag="heavy").save()
animalref = LazyReference(Animal, animal.pk)
self.assertEqual(animal, animalref)
self.assertEqual(animalref, animal)
other_animalref = LazyReference(Animal, ObjectId("54495ad94c934721ede76f90"))
self.assertNotEqual(animal, other_animalref)
self.assertNotEqual(other_animalref, animal)
def test_lazy_reference_embedded(self):
class Animal(Document):
name = StringField()
tag = StringField()
class EmbeddedOcurrence(EmbeddedDocument):
in_list = ListField(LazyReferenceField(Animal))
direct = LazyReferenceField(Animal)
class Ocurrence(Document):
in_list = ListField(LazyReferenceField(Animal))
in_embedded = EmbeddedDocumentField(EmbeddedOcurrence)
direct = LazyReferenceField(Animal)
Animal.drop_collection()
Ocurrence.drop_collection()
animal1 = Animal('doggo').save()
animal2 = Animal('cheeta').save()
def check_fields_type(occ):
self.assertIsInstance(occ.direct, LazyReference)
for elem in occ.in_list:
self.assertIsInstance(elem, LazyReference)
self.assertIsInstance(occ.in_embedded.direct, LazyReference)
for elem in occ.in_embedded.in_list:
self.assertIsInstance(elem, LazyReference)
occ = Ocurrence(
in_list=[animal1, animal2],
in_embedded={'in_list': [animal1, animal2], 'direct': animal1},
direct=animal1
).save()
check_fields_type(occ)
occ.reload()
check_fields_type(occ)
occ.direct = animal1.id
occ.in_list = [animal1.id, animal2.id]
occ.in_embedded.direct = animal1.id
occ.in_embedded.in_list = [animal1.id, animal2.id]
check_fields_type(occ)
class TestGenericLazyReferenceField(MongoDBTestCase):
def test_generic_lazy_reference_simple(self):
class Animal(Document):
name = StringField()
tag = StringField()
class Ocurrence(Document):
person = StringField()
animal = GenericLazyReferenceField()
Animal.drop_collection()
Ocurrence.drop_collection()
animal = Animal(name="Leopard", tag="heavy").save()
Ocurrence(person="test", animal=animal).save()
p = Ocurrence.objects.get()
self.assertIsInstance(p.animal, LazyReference)
fetched_animal = p.animal.fetch()
self.assertEqual(fetched_animal, animal)
# `fetch` keep cache on referenced document by default...
animal.tag = "not so heavy"
animal.save()
double_fetch = p.animal.fetch()
self.assertIs(fetched_animal, double_fetch)
self.assertEqual(double_fetch.tag, "heavy")
# ...unless specified otherwise
fetch_force = p.animal.fetch(force=True)
self.assertIsNot(fetch_force, fetched_animal)
self.assertEqual(fetch_force.tag, "not so heavy")
def test_generic_lazy_reference_choices(self):
class Animal(Document):
name = StringField()
class Vegetal(Document):
name = StringField()
class Mineral(Document):
name = StringField()
class Ocurrence(Document):
living_thing = GenericLazyReferenceField(choices=[Animal, Vegetal])
thing = GenericLazyReferenceField()
Animal.drop_collection()
Vegetal.drop_collection()
Mineral.drop_collection()
Ocurrence.drop_collection()
animal = Animal(name="Leopard").save()
vegetal = Vegetal(name="Oak").save()
mineral = Mineral(name="Granite").save()
occ_animal = Ocurrence(living_thing=animal, thing=animal).save()
occ_vegetal = Ocurrence(living_thing=vegetal, thing=vegetal).save()
with self.assertRaises(ValidationError):
Ocurrence(living_thing=mineral).save()
occ = Ocurrence.objects.get(living_thing=animal)
self.assertEqual(occ, occ_animal)
self.assertIsInstance(occ.thing, LazyReference)
self.assertIsInstance(occ.living_thing, LazyReference)
occ.thing = vegetal
occ.living_thing = vegetal
occ.save()
occ.thing = mineral
occ.living_thing = mineral
with self.assertRaises(ValidationError):
occ.save()
def test_generic_lazy_reference_set(self):
class Animal(Document):
meta = {'allow_inheritance': True}
name = StringField()
tag = StringField()
class Ocurrence(Document):
person = StringField()
animal = GenericLazyReferenceField()
Animal.drop_collection()
Ocurrence.drop_collection()
class SubAnimal(Animal):
nick = StringField()
animal = Animal(name="Leopard", tag="heavy").save()
sub_animal = SubAnimal(nick='doggo', name='dog').save()
for ref in (
animal,
LazyReference(Animal, animal.pk),
{'_cls': 'Animal', '_ref': DBRef(animal._get_collection_name(), animal.pk)},
sub_animal,
LazyReference(SubAnimal, sub_animal.pk),
{'_cls': 'SubAnimal', '_ref': DBRef(sub_animal._get_collection_name(), sub_animal.pk)},
):
p = Ocurrence(person="test", animal=ref).save()
p.reload()
self.assertIsInstance(p.animal, (LazyReference, Document))
p.animal.fetch()
def test_generic_lazy_reference_bad_set(self):
class Animal(Document):
name = StringField()
tag = StringField()
class Ocurrence(Document):
person = StringField()
animal = GenericLazyReferenceField(choices=['Animal'])
Animal.drop_collection()
Ocurrence.drop_collection()
class BadDoc(Document):
pass
animal = Animal(name="Leopard", tag="heavy").save()
baddoc = BadDoc().save()
for bad in (
42,
'foo',
baddoc,
LazyReference(BadDoc, animal.pk)
):
with self.assertRaises(ValidationError):
p = Ocurrence(person="test", animal=bad).save()
def test_generic_lazy_reference_query_conversion(self):
class Member(Document):
user_num = IntField(primary_key=True)
class BlogPost(Document):
title = StringField()
author = GenericLazyReferenceField()
Member.drop_collection()
BlogPost.drop_collection()
m1 = Member(user_num=1)
m1.save()
m2 = Member(user_num=2)
m2.save()
post1 = BlogPost(title='post 1', author=m1)
post1.save()
post2 = BlogPost(title='post 2', author=m2)
post2.save()
post = BlogPost.objects(author=m1).first()
self.assertEqual(post.id, post1.id)
post = BlogPost.objects(author=m2).first()
self.assertEqual(post.id, post2.id)
# Same thing by passing a LazyReference instance
post = BlogPost.objects(author=LazyReference(Member, m2.pk)).first()
self.assertEqual(post.id, post2.id)
def test_generic_lazy_reference_not_set(self):
class Animal(Document):
name = StringField()
tag = StringField()
class Ocurrence(Document):
person = StringField()
animal = GenericLazyReferenceField()
Animal.drop_collection()
Ocurrence.drop_collection()
Ocurrence(person='foo').save()
p = Ocurrence.objects.get()
self.assertIs(p.animal, None)
def test_generic_lazy_reference_embedded(self):
class Animal(Document):
name = StringField()
tag = StringField()
class EmbeddedOcurrence(EmbeddedDocument):
in_list = ListField(GenericLazyReferenceField())
direct = GenericLazyReferenceField()
class Ocurrence(Document):
in_list = ListField(GenericLazyReferenceField())
in_embedded = EmbeddedDocumentField(EmbeddedOcurrence)
direct = GenericLazyReferenceField()
Animal.drop_collection()
Ocurrence.drop_collection()
animal1 = Animal('doggo').save()
animal2 = Animal('cheeta').save()
def check_fields_type(occ):
self.assertIsInstance(occ.direct, LazyReference)
for elem in occ.in_list:
self.assertIsInstance(elem, LazyReference)
self.assertIsInstance(occ.in_embedded.direct, LazyReference)
for elem in occ.in_embedded.in_list:
self.assertIsInstance(elem, LazyReference)
occ = Ocurrence(
in_list=[animal1, animal2],
in_embedded={'in_list': [animal1, animal2], 'direct': animal1},
direct=animal1
).save()
check_fields_type(occ)
occ.reload()
check_fields_type(occ)
animal1_ref = {'_cls': 'Animal', '_ref': DBRef(animal1._get_collection_name(), animal1.pk)}
animal2_ref = {'_cls': 'Animal', '_ref': DBRef(animal2._get_collection_name(), animal2.pk)}
occ.direct = animal1_ref
occ.in_list = [animal1_ref, animal2_ref]
occ.in_embedded.direct = animal1_ref
occ.in_embedded.in_list = [animal1_ref, animal2_ref]
check_fields_type(occ)

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
import six
try:
from bson.int64 import Int64
except ImportError:
Int64 = long
from mongoengine import *
from mongoengine.connection import get_db
from tests.utils import MongoDBTestCase
class TestLongField(MongoDBTestCase):
def test_long_field_is_considered_as_int64(self):
"""
Tests that long fields are stored as long in mongo, even if long
value is small enough to be an int.
"""
class TestLongFieldConsideredAsInt64(Document):
some_long = LongField()
doc = TestLongFieldConsideredAsInt64(some_long=42).save()
db = get_db()
self.assertIsInstance(db.test_long_field_considered_as_int64.find()[0]['some_long'], Int64)
self.assertIsInstance(doc.some_long, six.integer_types)
def test_long_validation(self):
"""Ensure that invalid values cannot be assigned to long fields.
"""
class TestDocument(Document):
value = LongField(min_value=0, max_value=110)
doc = TestDocument()
doc.value = 50
doc.validate()
doc.value = -1
self.assertRaises(ValidationError, doc.validate)
doc.age = 120
self.assertRaises(ValidationError, doc.validate)
doc.age = 'ten'
self.assertRaises(ValidationError, doc.validate)
def test_long_ne_operator(self):
class TestDocument(Document):
long_fld = LongField()
TestDocument.drop_collection()
TestDocument(long_fld=None).save()
TestDocument(long_fld=1).save()
self.assertEqual(1, TestDocument.objects(long_fld__ne=None).count())

View File

@@ -0,0 +1,144 @@
# -*- coding: utf-8 -*-
import datetime
from mongoengine import *
from tests.utils import MongoDBTestCase
class TestMapField(MongoDBTestCase):
def test_mapfield(self):
"""Ensure that the MapField handles the declared type."""
class Simple(Document):
mapping = MapField(IntField())
Simple.drop_collection()
e = Simple()
e.mapping['someint'] = 1
e.save()
with self.assertRaises(ValidationError):
e.mapping['somestring'] = "abc"
e.save()
with self.assertRaises(ValidationError):
class NoDeclaredType(Document):
mapping = MapField()
def test_complex_mapfield(self):
"""Ensure that the MapField can handle complex declared types."""
class SettingBase(EmbeddedDocument):
meta = {"allow_inheritance": True}
class StringSetting(SettingBase):
value = StringField()
class IntegerSetting(SettingBase):
value = IntField()
class Extensible(Document):
mapping = MapField(EmbeddedDocumentField(SettingBase))
Extensible.drop_collection()
e = Extensible()
e.mapping['somestring'] = StringSetting(value='foo')
e.mapping['someint'] = IntegerSetting(value=42)
e.save()
e2 = Extensible.objects.get(id=e.id)
self.assertIsInstance(e2.mapping['somestring'], StringSetting)
self.assertIsInstance(e2.mapping['someint'], IntegerSetting)
with self.assertRaises(ValidationError):
e.mapping['someint'] = 123
e.save()
def test_embedded_mapfield_db_field(self):
class Embedded(EmbeddedDocument):
number = IntField(default=0, db_field='i')
class Test(Document):
my_map = MapField(field=EmbeddedDocumentField(Embedded),
db_field='x')
Test.drop_collection()
test = Test()
test.my_map['DICTIONARY_KEY'] = Embedded(number=1)
test.save()
Test.objects.update_one(inc__my_map__DICTIONARY_KEY__number=1)
test = Test.objects.get()
self.assertEqual(test.my_map['DICTIONARY_KEY'].number, 2)
doc = self.db.test.find_one()
self.assertEqual(doc['x']['DICTIONARY_KEY']['i'], 2)
def test_mapfield_numerical_index(self):
"""Ensure that MapField accept numeric strings as indexes."""
class Embedded(EmbeddedDocument):
name = StringField()
class Test(Document):
my_map = MapField(EmbeddedDocumentField(Embedded))
Test.drop_collection()
test = Test()
test.my_map['1'] = Embedded(name='test')
test.save()
test.my_map['1'].name = 'test updated'
test.save()
def test_map_field_lookup(self):
"""Ensure MapField lookups succeed on Fields without a lookup
method.
"""
class Action(EmbeddedDocument):
operation = StringField()
object = StringField()
class Log(Document):
name = StringField()
visited = MapField(DateTimeField())
actions = MapField(EmbeddedDocumentField(Action))
Log.drop_collection()
Log(name="wilson", visited={'friends': datetime.datetime.now()},
actions={'friends': Action(operation='drink', object='beer')}).save()
self.assertEqual(1, Log.objects(
visited__friends__exists=True).count())
self.assertEqual(1, Log.objects(
actions__friends__operation='drink',
actions__friends__object='beer').count())
def test_map_field_unicode(self):
class Info(EmbeddedDocument):
description = StringField()
value_list = ListField(field=StringField())
class BlogPost(Document):
info_dict = MapField(field=EmbeddedDocumentField(Info))
BlogPost.drop_collection()
tree = BlogPost(info_dict={
u"éééé": {
'description': u"VALUE: éééé"
}
})
tree.save()
self.assertEqual(
BlogPost.objects.get(id=tree.id).info_dict[u"éééé"].description,
u"VALUE: éééé"
)

View File

@@ -0,0 +1,219 @@
# -*- coding: utf-8 -*-
from bson import SON, DBRef
from mongoengine import *
from tests.utils import MongoDBTestCase
class TestReferenceField(MongoDBTestCase):
def test_reference_validation(self):
"""Ensure that invalid document objects cannot be assigned to
reference fields.
"""
class User(Document):
name = StringField()
class BlogPost(Document):
content = StringField()
author = ReferenceField(User)
User.drop_collection()
BlogPost.drop_collection()
# Make sure ReferenceField only accepts a document class or a string
# with a document class name.
self.assertRaises(ValidationError, ReferenceField, EmbeddedDocument)
user = User(name='Test User')
# Ensure that the referenced object must have been saved
post1 = BlogPost(content='Chips and gravy taste good.')
post1.author = user
self.assertRaises(ValidationError, post1.save)
# Check that an invalid object type cannot be used
post2 = BlogPost(content='Chips and chilli taste good.')
post1.author = post2
self.assertRaises(ValidationError, post1.validate)
# Ensure ObjectID's are accepted as references
user_object_id = user.pk
post3 = BlogPost(content="Chips and curry sauce taste good.")
post3.author = user_object_id
post3.save()
# Make sure referencing a saved document of the right type works
user.save()
post1.author = user
post1.save()
# Make sure referencing a saved document of the *wrong* type fails
post2.save()
post1.author = post2
self.assertRaises(ValidationError, post1.validate)
def test_objectid_reference_fields(self):
"""Make sure storing Object ID references works."""
class Person(Document):
name = StringField()
parent = ReferenceField('self')
Person.drop_collection()
p1 = Person(name="John").save()
Person(name="Ross", parent=p1.pk).save()
p = Person.objects.get(name="Ross")
self.assertEqual(p.parent, p1)
def test_dbref_reference_fields(self):
"""Make sure storing references as bson.dbref.DBRef works."""
class Person(Document):
name = StringField()
parent = ReferenceField('self', dbref=True)
Person.drop_collection()
p1 = Person(name="John").save()
Person(name="Ross", parent=p1).save()
self.assertEqual(
Person._get_collection().find_one({'name': 'Ross'})['parent'],
DBRef('person', p1.pk)
)
p = Person.objects.get(name="Ross")
self.assertEqual(p.parent, p1)
def test_dbref_to_mongo(self):
"""Make sure that calling to_mongo on a ReferenceField which
has dbref=False, but actually actually contains a DBRef returns
an ID of that DBRef.
"""
class Person(Document):
name = StringField()
parent = ReferenceField('self', dbref=False)
p = Person(
name='Steve',
parent=DBRef('person', 'abcdefghijklmnop')
)
self.assertEqual(p.to_mongo(), SON([
('name', u'Steve'),
('parent', 'abcdefghijklmnop')
]))
def test_objectid_reference_fields(self):
class Person(Document):
name = StringField()
parent = ReferenceField('self', dbref=False)
Person.drop_collection()
p1 = Person(name="John").save()
Person(name="Ross", parent=p1).save()
col = Person._get_collection()
data = col.find_one({'name': 'Ross'})
self.assertEqual(data['parent'], p1.pk)
p = Person.objects.get(name="Ross")
self.assertEqual(p.parent, p1)
def test_undefined_reference(self):
"""Ensure that ReferenceFields may reference undefined Documents.
"""
class Product(Document):
name = StringField()
company = ReferenceField('Company')
class Company(Document):
name = StringField()
Product.drop_collection()
Company.drop_collection()
ten_gen = Company(name='10gen')
ten_gen.save()
mongodb = Product(name='MongoDB', company=ten_gen)
mongodb.save()
me = Product(name='MongoEngine')
me.save()
obj = Product.objects(company=ten_gen).first()
self.assertEqual(obj, mongodb)
self.assertEqual(obj.company, ten_gen)
obj = Product.objects(company=None).first()
self.assertEqual(obj, me)
obj = Product.objects.get(company=None)
self.assertEqual(obj, me)
def test_reference_query_conversion(self):
"""Ensure that ReferenceFields can be queried using objects and values
of the type of the primary key of the referenced object.
"""
class Member(Document):
user_num = IntField(primary_key=True)
class BlogPost(Document):
title = StringField()
author = ReferenceField(Member, dbref=False)
Member.drop_collection()
BlogPost.drop_collection()
m1 = Member(user_num=1)
m1.save()
m2 = Member(user_num=2)
m2.save()
post1 = BlogPost(title='post 1', author=m1)
post1.save()
post2 = BlogPost(title='post 2', author=m2)
post2.save()
post = BlogPost.objects(author=m1).first()
self.assertEqual(post.id, post1.id)
post = BlogPost.objects(author=m2).first()
self.assertEqual(post.id, post2.id)
def test_reference_query_conversion_dbref(self):
"""Ensure that ReferenceFields can be queried using objects and values
of the type of the primary key of the referenced object.
"""
class Member(Document):
user_num = IntField(primary_key=True)
class BlogPost(Document):
title = StringField()
author = ReferenceField(Member, dbref=True)
Member.drop_collection()
BlogPost.drop_collection()
m1 = Member(user_num=1)
m1.save()
m2 = Member(user_num=2)
m2.save()
post1 = BlogPost(title='post 1', author=m1)
post1.save()
post2 = BlogPost(title='post 2', author=m2)
post2.save()
post = BlogPost.objects(author=m1).first()
self.assertEqual(post.id, post1.id)
post = BlogPost.objects(author=m2).first()
self.assertEqual(post.id, post2.id)

View File

@@ -0,0 +1,271 @@
# -*- coding: utf-8 -*-
from mongoengine import *
from tests.utils import MongoDBTestCase
class TestSequenceField(MongoDBTestCase):
def test_sequence_field(self):
class Person(Document):
id = SequenceField(primary_key=True)
name = StringField()
self.db['mongoengine.counters'].drop()
Person.drop_collection()
for x in range(10):
Person(name="Person %s" % x).save()
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
self.assertEqual(c['next'], 10)
ids = [i.id for i in Person.objects]
self.assertEqual(ids, range(1, 11))
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
self.assertEqual(c['next'], 10)
Person.id.set_next_value(1000)
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
self.assertEqual(c['next'], 1000)
def test_sequence_field_get_next_value(self):
class Person(Document):
id = SequenceField(primary_key=True)
name = StringField()
self.db['mongoengine.counters'].drop()
Person.drop_collection()
for x in range(10):
Person(name="Person %s" % x).save()
self.assertEqual(Person.id.get_next_value(), 11)
self.db['mongoengine.counters'].drop()
self.assertEqual(Person.id.get_next_value(), 1)
class Person(Document):
id = SequenceField(primary_key=True, value_decorator=str)
name = StringField()
self.db['mongoengine.counters'].drop()
Person.drop_collection()
for x in range(10):
Person(name="Person %s" % x).save()
self.assertEqual(Person.id.get_next_value(), '11')
self.db['mongoengine.counters'].drop()
self.assertEqual(Person.id.get_next_value(), '1')
def test_sequence_field_sequence_name(self):
class Person(Document):
id = SequenceField(primary_key=True, sequence_name='jelly')
name = StringField()
self.db['mongoengine.counters'].drop()
Person.drop_collection()
for x in range(10):
Person(name="Person %s" % x).save()
c = self.db['mongoengine.counters'].find_one({'_id': 'jelly.id'})
self.assertEqual(c['next'], 10)
ids = [i.id for i in Person.objects]
self.assertEqual(ids, range(1, 11))
c = self.db['mongoengine.counters'].find_one({'_id': 'jelly.id'})
self.assertEqual(c['next'], 10)
Person.id.set_next_value(1000)
c = self.db['mongoengine.counters'].find_one({'_id': 'jelly.id'})
self.assertEqual(c['next'], 1000)
def test_multiple_sequence_fields(self):
class Person(Document):
id = SequenceField(primary_key=True)
counter = SequenceField()
name = StringField()
self.db['mongoengine.counters'].drop()
Person.drop_collection()
for x in range(10):
Person(name="Person %s" % x).save()
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
self.assertEqual(c['next'], 10)
ids = [i.id for i in Person.objects]
self.assertEqual(ids, range(1, 11))
counters = [i.counter for i in Person.objects]
self.assertEqual(counters, range(1, 11))
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
self.assertEqual(c['next'], 10)
Person.id.set_next_value(1000)
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
self.assertEqual(c['next'], 1000)
Person.counter.set_next_value(999)
c = self.db['mongoengine.counters'].find_one({'_id': 'person.counter'})
self.assertEqual(c['next'], 999)
def test_sequence_fields_reload(self):
class Animal(Document):
counter = SequenceField()
name = StringField()
self.db['mongoengine.counters'].drop()
Animal.drop_collection()
a = Animal(name="Boi").save()
self.assertEqual(a.counter, 1)
a.reload()
self.assertEqual(a.counter, 1)
a.counter = None
self.assertEqual(a.counter, 2)
a.save()
self.assertEqual(a.counter, 2)
a = Animal.objects.first()
self.assertEqual(a.counter, 2)
a.reload()
self.assertEqual(a.counter, 2)
def test_multiple_sequence_fields_on_docs(self):
class Animal(Document):
id = SequenceField(primary_key=True)
name = StringField()
class Person(Document):
id = SequenceField(primary_key=True)
name = StringField()
self.db['mongoengine.counters'].drop()
Animal.drop_collection()
Person.drop_collection()
for x in range(10):
Animal(name="Animal %s" % x).save()
Person(name="Person %s" % x).save()
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
self.assertEqual(c['next'], 10)
c = self.db['mongoengine.counters'].find_one({'_id': 'animal.id'})
self.assertEqual(c['next'], 10)
ids = [i.id for i in Person.objects]
self.assertEqual(ids, range(1, 11))
id = [i.id for i in Animal.objects]
self.assertEqual(id, range(1, 11))
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
self.assertEqual(c['next'], 10)
c = self.db['mongoengine.counters'].find_one({'_id': 'animal.id'})
self.assertEqual(c['next'], 10)
def test_sequence_field_value_decorator(self):
class Person(Document):
id = SequenceField(primary_key=True, value_decorator=str)
name = StringField()
self.db['mongoengine.counters'].drop()
Person.drop_collection()
for x in range(10):
p = Person(name="Person %s" % x)
p.save()
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
self.assertEqual(c['next'], 10)
ids = [i.id for i in Person.objects]
self.assertEqual(ids, map(str, range(1, 11)))
c = self.db['mongoengine.counters'].find_one({'_id': 'person.id'})
self.assertEqual(c['next'], 10)
def test_embedded_sequence_field(self):
class Comment(EmbeddedDocument):
id = SequenceField()
content = StringField(required=True)
class Post(Document):
title = StringField(required=True)
comments = ListField(EmbeddedDocumentField(Comment))
self.db['mongoengine.counters'].drop()
Post.drop_collection()
Post(title="MongoEngine",
comments=[Comment(content="NoSQL Rocks"),
Comment(content="MongoEngine Rocks")]).save()
c = self.db['mongoengine.counters'].find_one({'_id': 'comment.id'})
self.assertEqual(c['next'], 2)
post = Post.objects.first()
self.assertEqual(1, post.comments[0].id)
self.assertEqual(2, post.comments[1].id)
def test_inherited_sequencefield(self):
class Base(Document):
name = StringField()
counter = SequenceField()
meta = {'abstract': True}
class Foo(Base):
pass
class Bar(Base):
pass
bar = Bar(name='Bar')
bar.save()
foo = Foo(name='Foo')
foo.save()
self.assertTrue('base.counter' in
self.db['mongoengine.counters'].find().distinct('_id'))
self.assertFalse(('foo.counter' or 'bar.counter') in
self.db['mongoengine.counters'].find().distinct('_id'))
self.assertNotEqual(foo.counter, bar.counter)
self.assertEqual(foo._fields['counter'].owner_document, Base)
self.assertEqual(bar._fields['counter'].owner_document, Base)
def test_no_inherited_sequencefield(self):
class Base(Document):
name = StringField()
meta = {'abstract': True}
class Foo(Base):
counter = SequenceField()
class Bar(Base):
counter = SequenceField()
bar = Bar(name='Bar')
bar.save()
foo = Foo(name='Foo')
foo.save()
self.assertFalse('base.counter' in
self.db['mongoengine.counters'].find().distinct('_id'))
self.assertTrue(('foo.counter' and 'bar.counter') in
self.db['mongoengine.counters'].find().distinct('_id'))
self.assertEqual(foo.counter, bar.counter)
self.assertEqual(foo._fields['counter'].owner_document, Foo)
self.assertEqual(bar._fields['counter'].owner_document, Bar)

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
from mongoengine import *
from tests.utils import MongoDBTestCase
class TestURLField(MongoDBTestCase):
def test_validation(self):
"""Ensure that URLFields validate urls properly."""
class Link(Document):
url = URLField()
link = Link()
link.url = 'google'
self.assertRaises(ValidationError, link.validate)
link.url = 'http://www.google.com:8080'
link.validate()
def test_unicode_url_validation(self):
"""Ensure unicode URLs are validated properly."""
class Link(Document):
url = URLField()
link = Link()
link.url = u'http://привет.com'
# TODO fix URL validation - this *IS* a valid URL
# For now we just want to make sure that the error message is correct
with self.assertRaises(ValidationError) as ctx_err:
link.validate()
self.assertEqual(unicode(ctx_err.exception),
u"ValidationError (Link:None) (Invalid URL: http://\u043f\u0440\u0438\u0432\u0435\u0442.com: ['url'])")
def test_url_scheme_validation(self):
"""Ensure that URLFields validate urls with specific schemes properly.
"""
class Link(Document):
url = URLField()
class SchemeLink(Document):
url = URLField(schemes=['ws', 'irc'])
link = Link()
link.url = 'ws://google.com'
self.assertRaises(ValidationError, link.validate)
scheme_link = SchemeLink()
scheme_link.url = 'ws://google.com'
scheme_link.validate()
def test_underscore_allowed_in_domains_names(self):
class Link(Document):
url = URLField()
link = Link()
link.url = 'https://san_leandro-ca.geebo.com'
link.validate()

View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
import uuid
from mongoengine import *
from tests.utils import MongoDBTestCase, get_as_pymongo
class Person(Document):
api_key = UUIDField(binary=False)
class TestUUIDField(MongoDBTestCase):
def test_storage(self):
uid = uuid.uuid4()
person = Person(api_key=uid).save()
self.assertEqual(
get_as_pymongo(person),
{'_id': person.id,
'api_key': str(uid)
}
)
def test_field_string(self):
"""Test UUID fields storing as String
"""
Person.drop_collection()
uu = uuid.uuid4()
Person(api_key=uu).save()
self.assertEqual(1, Person.objects(api_key=uu).count())
self.assertEqual(uu, Person.objects.first().api_key)
person = Person()
valid = (uuid.uuid4(), uuid.uuid1())
for api_key in valid:
person.api_key = api_key
person.validate()
invalid = ('9d159858-549b-4975-9f98-dd2f987c113g',
'9d159858-549b-4975-9f98-dd2f987c113')
for api_key in invalid:
person.api_key = api_key
self.assertRaises(ValidationError, person.validate)
def test_field_binary(self):
"""Test UUID fields storing as Binary object."""
Person.drop_collection()
uu = uuid.uuid4()
Person(api_key=uu).save()
self.assertEqual(1, Person.objects(api_key=uu).count())
self.assertEqual(uu, Person.objects.first().api_key)
person = Person()
valid = (uuid.uuid4(), uuid.uuid1())
for api_key in valid:
person.api_key = api_key
person.validate()
invalid = ('9d159858-549b-4975-9f98-dd2f987c113g',
'9d159858-549b-4975-9f98-dd2f987c113')
for api_key in invalid:
person.api_key = api_key
self.assertRaises(ValidationError, person.validate)

View File

@@ -48,6 +48,7 @@ class PickleSignalsTest(Document):
def post_delete(self, sender, document, **kwargs):
pickled = pickle.dumps(document)
signals.post_save.connect(PickleSignalsTest.post_save, sender=PickleSignalsTest)
signals.post_delete.connect(PickleSignalsTest.post_delete, sender=PickleSignalsTest)

View File

@@ -208,7 +208,7 @@ class OnlyExcludeAllTest(unittest.TestCase):
BlogPost.drop_collection()
post = BlogPost(content='Had a good coffee today...', various={'test_dynamic':{'some': True}})
post = BlogPost(content='Had a good coffee today...', various={'test_dynamic': {'some': True}})
post.author = User(name='Test User')
post.comments = [Comment(title='I aggree', text='Great post!'), Comment(title='Coffee', text='I hate coffee')]
post.save()
@@ -413,7 +413,6 @@ class OnlyExcludeAllTest(unittest.TestCase):
numbers = Numbers.objects.fields(embedded__n={"$slice": [-5, 10]}).get()
self.assertEqual(numbers.embedded.n, [-5, -4, -3, -2, -1])
def test_exclude_from_subclasses_docs(self):
class Base(Document):
@@ -436,5 +435,6 @@ class OnlyExcludeAllTest(unittest.TestCase):
self.assertRaises(LookUpError, Base.objects.exclude, "made_up")
if __name__ == '__main__':
unittest.main()

View File

@@ -534,11 +534,11 @@ class GeoQueriesTest(MongoDBTestCase):
Location.drop_collection()
Location(loc=[1,2]).save()
Location(loc=[1, 2]).save()
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["loc"], {"type": "Point", "coordinates": [1, 2]})
Location.objects.update(set__loc=[2,1])
Location.objects.update(set__loc=[2, 1])
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["loc"], {"type": "Point", "coordinates": [2, 1]})

View File

@@ -6,10 +6,12 @@ from mongoengine.connection import connect
__author__ = 'stas'
class Person(Document):
name = StringField()
age = IntField()
class TestQuerysetPickable(unittest.TestCase):
"""
Test for adding pickling support for QuerySet instances
@@ -18,7 +20,7 @@ class TestQuerysetPickable(unittest.TestCase):
def setUp(self):
super(TestQuerysetPickable, self).setUp()
connection = connect(db="test") #type: pymongo.mongo_client.MongoClient
connection = connect(db="test") # type: pymongo.mongo_client.MongoClient
connection.drop_database("test")
@@ -27,7 +29,6 @@ class TestQuerysetPickable(unittest.TestCase):
age=21
)
def test_picke_simple_qs(self):
qs = Person.objects.all()
@@ -46,10 +47,10 @@ class TestQuerysetPickable(unittest.TestCase):
self.assertEqual(qs.count(), loadedQs.count())
#can update loadedQs
# can update loadedQs
loadedQs.update(age=23)
#check
# check
self.assertEqual(Person.objects.first().age, 23)
def test_pickle_support_filtration(self):
@@ -70,7 +71,7 @@ class TestQuerysetPickable(unittest.TestCase):
self.assertEqual(loaded.count(), 2)
self.assertEqual(loaded.filter(name="Bob").first().age, 23)

View File

@@ -12,18 +12,17 @@ from pymongo.errors import ConfigurationError
from pymongo.read_preferences import ReadPreference
from pymongo.results import UpdateResult
import six
from six import iteritems
from mongoengine import *
from mongoengine.connection import get_connection, get_db
from mongoengine.context_managers import query_counter, switch_db
from mongoengine.errors import InvalidQueryError
from mongoengine.python_support import IS_PYMONGO_3
from mongoengine.mongodb_support import get_mongodb_version, MONGODB_32
from mongoengine.pymongo_support import IS_PYMONGO_3
from mongoengine.queryset import (DoesNotExist, MultipleObjectsReturned,
QuerySet, QuerySetManager, queryset_manager)
from tests.utils import requires_mongodb_gte_26, skip_pymongo3, get_mongodb_version, MONGODB_32
__all__ = ("QuerySetTest",)
from tests.utils import requires_mongodb_gte_26, skip_pymongo3
class db_ops_tracker(query_counter):
@@ -395,6 +394,16 @@ class QuerySetTest(unittest.TestCase):
with self.assertRaises(ValueError):
list(qs)
def test_batch_size_cloned(self):
class A(Document):
s = StringField()
# test that batch size gets cloned
qs = A.objects.batch_size(5)
self.assertEqual(qs._batch_size, 5)
qs_clone = qs.clone()
self.assertEqual(qs_clone._batch_size, 5)
def test_update_write_concern(self):
"""Test that passing write_concern works"""
self.Person.drop_collection()
@@ -851,8 +860,8 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(q, 0)
Blog.objects.insert(blogs, load_bulk=False)
if MONGO_VER == MONGODB_32:
self.assertEqual(q, 1) # 1 entry containing the list of inserts
if MONGO_VER >= MONGODB_32:
self.assertEqual(q, 1) # 1 entry containing the list of inserts
else:
self.assertEqual(q, len(blogs)) # 1 entry per doc inserted
@@ -868,8 +877,8 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(q, 0)
Blog.objects.insert(blogs)
if MONGO_VER == MONGODB_32:
self.assertEqual(q, 2) # 1 for insert 1 for fetch
if MONGO_VER >= MONGODB_32:
self.assertEqual(q, 2) # 1 for insert 1 for fetch
else:
self.assertEqual(q, len(blogs)+1) # + 1 to fetch all docs
@@ -1203,7 +1212,7 @@ class QuerySetTest(unittest.TestCase):
"""Ensure filters can be chained together.
"""
class Blog(Document):
id = StringField(unique=True, primary_key=True)
id = StringField(primary_key=True)
class BlogPost(Document):
blog = ReferenceField(Blog)
@@ -1315,7 +1324,7 @@ class QuerySetTest(unittest.TestCase):
order_by() w/o any arguments.
"""
MONGO_VER = self.mongodb_version
ORDER_BY_KEY = 'sort' if MONGO_VER == MONGODB_32 else '$orderby'
ORDER_BY_KEY = 'sort' if MONGO_VER >= MONGODB_32 else '$orderby'
class BlogPost(Document):
title = StringField()
@@ -2233,6 +2242,19 @@ class QuerySetTest(unittest.TestCase):
bar.reload()
self.assertEqual(len(bar.foos), 0)
def test_update_one_check_return_with_full_result(self):
class BlogTag(Document):
name = StringField(required=True)
BlogTag.drop_collection()
BlogTag(name='garbage').save()
default_update = BlogTag.objects.update_one(name='new')
self.assertEqual(default_update, 1)
full_result_update = BlogTag.objects.update_one(name='new', full_result=True)
self.assertIsInstance(full_result_update, UpdateResult)
def test_update_one_pop_generic_reference(self):
class BlogTag(Document):
@@ -2510,8 +2532,8 @@ class QuerySetTest(unittest.TestCase):
def test_comment(self):
"""Make sure adding a comment to the query gets added to the query"""
MONGO_VER = self.mongodb_version
QUERY_KEY = 'filter' if MONGO_VER == MONGODB_32 else '$query'
COMMENT_KEY = 'comment' if MONGO_VER == MONGODB_32 else '$comment'
QUERY_KEY = 'filter' if MONGO_VER >= MONGODB_32 else '$query'
COMMENT_KEY = 'comment' if MONGO_VER >= MONGODB_32 else '$comment'
class User(Document):
age = IntField()
@@ -3335,7 +3357,7 @@ class QuerySetTest(unittest.TestCase):
meta = {'indexes': [
{'fields': ['$title', "$content"],
'default_language': 'portuguese',
'weight': {'title': 10, 'content': 2}
'weights': {'title': 10, 'content': 2}
}
]}
@@ -4026,7 +4048,7 @@ class QuerySetTest(unittest.TestCase):
info = [(value['key'],
value.get('unique', False),
value.get('sparse', False))
for key, value in info.iteritems()]
for key, value in iteritems(info)]
self.assertIn(([('_cls', 1), ('message', 1)], False, False), info)
def test_where(self):
@@ -4037,7 +4059,7 @@ class QuerySetTest(unittest.TestCase):
fielda = IntField()
fieldb = IntField()
IntPair.objects._collection.remove()
IntPair.drop_collection()
a = IntPair(fielda=1, fieldb=1)
b = IntPair(fielda=1, fieldb=2)
@@ -4628,14 +4650,12 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(doc_objects, Doc.objects.from_json(json_data))
def test_as_pymongo(self):
from decimal import Decimal
class LastLogin(EmbeddedDocument):
location = StringField()
ip = StringField()
class User(Document):
id = ObjectIdField('_id')
id = StringField(primary_key=True)
name = StringField()
age = IntField()
price = DecimalField()
@@ -4643,9 +4663,10 @@ class QuerySetTest(unittest.TestCase):
User.drop_collection()
User.objects.create(name="Bob Dole", age=89, price=Decimal('1.11'))
User.objects.create(id='Bob', name="Bob Dole", age=89, price=Decimal('1.11'))
User.objects.create(
name="Barack Obama",
id='Barak',
name="Barak Obama",
age=51,
price=Decimal('2.22'),
last_login=LastLogin(
@@ -4673,7 +4694,7 @@ class QuerySetTest(unittest.TestCase):
self.assertIsInstance(results[1], dict)
self.assertEqual(results[0]['name'], 'Bob Dole')
self.assertEqual(results[0]['price'], 1.11)
self.assertEqual(results[1]['name'], 'Barack Obama')
self.assertEqual(results[1]['name'], 'Barak Obama')
self.assertEqual(results[1]['price'], 2.22)
users = User.objects.only('name', 'last_login').as_pymongo()
@@ -4681,16 +4702,36 @@ class QuerySetTest(unittest.TestCase):
self.assertIsInstance(results[0], dict)
self.assertIsInstance(results[1], dict)
self.assertEqual(results[0], {
'_id': 'Bob',
'name': 'Bob Dole'
})
self.assertEqual(results[1], {
'name': 'Barack Obama',
'_id': 'Barak',
'name': 'Barak Obama',
'last_login': {
'location': 'White House',
'ip': '104.107.108.116'
}
})
def test_as_pymongo_returns_cls_attribute_when_using_inheritance(self):
class User(Document):
name = StringField()
meta = {'allow_inheritance': True}
User.drop_collection()
user = User(name="Bob Dole").save()
result = User.objects.as_pymongo().first()
self.assertEqual(
result,
{
'_cls': 'User',
'_id': user.id,
'name': 'Bob Dole'
}
)
def test_as_pymongo_json_limit_fields(self):
class User(Document):
@@ -4706,19 +4747,49 @@ class QuerySetTest(unittest.TestCase):
serialized_user = User.objects.exclude(
'password_salt', 'password_hash').as_pymongo()[0]
self.assertEqual(set(['_id', 'email']), set(serialized_user.keys()))
self.assertEqual({'_id', 'email'}, set(serialized_user.keys()))
serialized_user = User.objects.exclude(
'id', 'password_salt', 'password_hash').to_json()
self.assertEqual('[{"email": "ross@example.com"}]', serialized_user)
serialized_user = User.objects.exclude(
'password_salt').only('email').as_pymongo()[0]
self.assertEqual(set(['email']), set(serialized_user.keys()))
serialized_user = User.objects.only('email').as_pymongo()[0]
self.assertEqual({'_id', 'email'}, set(serialized_user.keys()))
serialized_user = User.objects.exclude(
'password_salt').only('email').to_json()
self.assertEqual('[{"email": "ross@example.com"}]', serialized_user)
'password_salt').only('email').as_pymongo()[0]
self.assertEqual({'_id', 'email'}, set(serialized_user.keys()))
serialized_user = User.objects.exclude(
'password_salt', 'id').only('email').as_pymongo()[0]
self.assertEqual({'email'}, set(serialized_user.keys()))
serialized_user = User.objects.exclude(
'password_salt', 'id').only('email').to_json()
self.assertEqual('[{"email": "ross@example.com"}]',
serialized_user)
def test_only_after_count(self):
"""Test that only() works after count()"""
class User(Document):
name = StringField()
age = IntField()
address = StringField()
User.drop_collection()
user = User(name="User", age=50,
address="Moscow, Russia").save()
user_queryset = User.objects(age=50)
result = user_queryset.only("name", "age").as_pymongo().first()
self.assertEqual(result, {"_id": user.id, "name": "User", "age": 50})
result = user_queryset.count()
self.assertEqual(result, 1)
result = user_queryset.only("name", "age").as_pymongo().first()
self.assertEqual(result, {"_id": user.id, "name": "User", "age": 50})
def test_no_dereference(self):
@@ -5068,7 +5139,7 @@ class QuerySetTest(unittest.TestCase):
def test_query_reference_to_custom_pk_doc(self):
class A(Document):
id = StringField(unique=True, primary_key=True)
id = StringField(primary_key=True)
class B(Document):
a = ReferenceField(A)
@@ -5173,7 +5244,7 @@ class QuerySetTest(unittest.TestCase):
def test_bool_with_ordering(self):
MONGO_VER = self.mongodb_version
ORDER_BY_KEY = 'sort' if MONGO_VER == MONGODB_32 else '$orderby'
ORDER_BY_KEY = 'sort' if MONGO_VER >= MONGODB_32 else '$orderby'
class Person(Document):
name = StringField()
@@ -5323,7 +5394,7 @@ class QuerySetTest(unittest.TestCase):
Person.drop_collection()
Person._get_collection().insert({'name': 'a', 'id': ''})
Person._get_collection().insert_one({'name': 'a', 'id': ''})
for p in Person.objects():
self.assertEqual(p.name, 'a')

View File

@@ -275,7 +275,6 @@ class QTest(unittest.TestCase):
with self.assertRaises(InvalidQueryError):
self.Person.objects.filter('user1')
def test_q_regex(self):
"""Ensure that Q objects can be queried using regexes.
"""

View File

@@ -14,7 +14,7 @@ from mongoengine import (
connect, register_connection,
Document, DateTimeField
)
from mongoengine.python_support import IS_PYMONGO_3
from mongoengine.pymongo_support import IS_PYMONGO_3
import mongoengine.connection
from mongoengine.connection import (MongoEngineConnectionError, get_db,
get_connection)
@@ -99,11 +99,11 @@ class ConnectionTest(unittest.TestCase):
conn = get_connection()
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host=['mongodb://localhost'], is_mock=True, alias='testdb2')
connect(host=['mongodb://localhost'], is_mock=True, alias='testdb2')
conn = get_connection('testdb2')
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host=['localhost'], is_mock=True, alias='testdb3')
connect(host=['localhost'], is_mock=True, alias='testdb3')
conn = get_connection('testdb3')
self.assertIsInstance(conn, mongomock.MongoClient)
@@ -111,11 +111,11 @@ class ConnectionTest(unittest.TestCase):
conn = get_connection('testdb4')
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host=['mongodb://localhost:27017', 'mongodb://localhost:27018'], is_mock=True, alias='testdb5')
connect(host=['mongodb://localhost:27017', 'mongodb://localhost:27018'], is_mock=True, alias='testdb5')
conn = get_connection('testdb5')
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host=['localhost:27017', 'localhost:27018'], is_mock=True, alias='testdb6')
connect(host=['localhost:27017', 'localhost:27018'], is_mock=True, alias='testdb6')
conn = get_connection('testdb6')
self.assertIsInstance(conn, mongomock.MongoClient)
@@ -147,12 +147,12 @@ class ConnectionTest(unittest.TestCase):
def test_connect_uri(self):
"""Ensure that the connect() method works properly with URIs."""
c = connect(db='mongoenginetest', alias='admin')
c.admin.system.users.remove({})
c.mongoenginetest.system.users.remove({})
c.admin.system.users.delete_many({})
c.mongoenginetest.system.users.delete_many({})
c.admin.add_user("admin", "password")
c.admin.command("createUser", "admin", pwd="password", roles=["root"])
c.admin.authenticate("admin", "password")
c.mongoenginetest.add_user("username", "password")
c.admin.command("createUser", "username", pwd="password", roles=["dbOwner"])
if not IS_PYMONGO_3:
self.assertRaises(
@@ -169,8 +169,8 @@ class ConnectionTest(unittest.TestCase):
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, 'mongoenginetest')
c.admin.system.users.remove({})
c.mongoenginetest.system.users.remove({})
c.admin.system.users.delete_many({})
c.mongoenginetest.system.users.delete_many({})
def test_connect_uri_without_db(self):
"""Ensure connect() method works properly if the URI doesn't
@@ -217,8 +217,9 @@ class ConnectionTest(unittest.TestCase):
"""
# Create users
c = connect('mongoenginetest')
c.admin.system.users.remove({})
c.admin.add_user('username2', 'password')
c.admin.system.users.delete_many({})
c.admin.command("createUser", "username2", pwd="password", roles=["dbOwner"])
# Authentication fails without "authSource"
if IS_PYMONGO_3:
@@ -246,7 +247,7 @@ class ConnectionTest(unittest.TestCase):
self.assertEqual(db.name, 'mongoenginetest')
# Clear all users
authd_conn.admin.system.users.remove({})
authd_conn.admin.system.users.delete_many({})
def test_register_connection(self):
"""Ensure that connections with different aliases may be registered.

View File

@@ -5,6 +5,7 @@ from mongoengine.connection import get_db
from mongoengine.context_managers import (switch_db, switch_collection,
no_sub_classes, no_dereference,
query_counter)
from mongoengine.pymongo_support import count_documents
class ContextManagersTest(unittest.TestCase):
@@ -240,7 +241,7 @@ class ContextManagersTest(unittest.TestCase):
collection.drop()
def issue_1_count_query():
collection.find({}).count()
count_documents(collection, {})
def issue_1_insert_query():
collection.insert_one({'test': 'garbage'})
@@ -302,5 +303,6 @@ class ContextManagersTest(unittest.TestCase):
_ = db.system.indexes.find_one() # queries on db.system.indexes are ignored as well
self.assertEqual(q, 1)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,22 +1,361 @@
import unittest
from mongoengine.base.datastructures import StrictDict, BaseList
from mongoengine import Document
from mongoengine.base.datastructures import StrictDict, BaseList, BaseDict
class DocumentStub(object):
def __init__(self):
self._changed_fields = []
def _mark_as_changed(self, key):
self._changed_fields.append(key)
class TestBaseDict(unittest.TestCase):
@staticmethod
def _get_basedict(dict_items):
"""Get a BaseList bound to a fake document instance"""
fake_doc = DocumentStub()
base_list = BaseDict(dict_items, instance=None, name='my_name')
base_list._instance = fake_doc # hack to inject the mock, it does not work in the constructor
return base_list
def test___init___(self):
class MyDoc(Document):
pass
dict_items = {'k': 'v'}
doc = MyDoc()
base_dict = BaseDict(dict_items, instance=doc, name='my_name')
self.assertIsInstance(base_dict._instance, Document)
self.assertEqual(base_dict._name, 'my_name')
self.assertEqual(base_dict, dict_items)
def test_setdefault_calls_mark_as_changed(self):
base_dict = self._get_basedict({})
base_dict.setdefault('k', 'v')
self.assertEqual(base_dict._instance._changed_fields, [base_dict._name])
def test_popitems_calls_mark_as_changed(self):
base_dict = self._get_basedict({'k': 'v'})
self.assertEqual(base_dict.popitem(), ('k', 'v'))
self.assertEqual(base_dict._instance._changed_fields, [base_dict._name])
self.assertFalse(base_dict)
def test_pop_calls_mark_as_changed(self):
base_dict = self._get_basedict({'k': 'v'})
self.assertEqual(base_dict.pop('k'), 'v')
self.assertEqual(base_dict._instance._changed_fields, [base_dict._name])
self.assertFalse(base_dict)
def test_pop_calls_does_not_mark_as_changed_when_it_fails(self):
base_dict = self._get_basedict({'k': 'v'})
with self.assertRaises(KeyError):
base_dict.pop('X')
self.assertFalse(base_dict._instance._changed_fields)
def test_clear_calls_mark_as_changed(self):
base_dict = self._get_basedict({'k': 'v'})
base_dict.clear()
self.assertEqual(base_dict._instance._changed_fields, ['my_name'])
self.assertEqual(base_dict, {})
def test___delitem___calls_mark_as_changed(self):
base_dict = self._get_basedict({'k': 'v'})
del base_dict['k']
self.assertEqual(base_dict._instance._changed_fields, ['my_name.k'])
self.assertEqual(base_dict, {})
def test___getitem____KeyError(self):
base_dict = self._get_basedict({})
with self.assertRaises(KeyError):
base_dict['new']
def test___getitem____simple_value(self):
base_dict = self._get_basedict({'k': 'v'})
base_dict['k'] = 'v'
def test___getitem____sublist_gets_converted_to_BaseList(self):
base_dict = self._get_basedict({'k': [0, 1, 2]})
sub_list = base_dict['k']
self.assertEqual(sub_list, [0, 1, 2])
self.assertIsInstance(sub_list, BaseList)
self.assertIs(sub_list._instance, base_dict._instance)
self.assertEqual(sub_list._name, 'my_name.k')
self.assertEqual(base_dict._instance._changed_fields, [])
# Challenge mark_as_changed from sublist
sub_list[1] = None
self.assertEqual(base_dict._instance._changed_fields, ['my_name.k.1'])
def test___getitem____subdict_gets_converted_to_BaseDict(self):
base_dict = self._get_basedict({'k': {'subk': 'subv'}})
sub_dict = base_dict['k']
self.assertEqual(sub_dict, {'subk': 'subv'})
self.assertIsInstance(sub_dict, BaseDict)
self.assertIs(sub_dict._instance, base_dict._instance)
self.assertEqual(sub_dict._name, 'my_name.k')
self.assertEqual(base_dict._instance._changed_fields, [])
# Challenge mark_as_changed from subdict
sub_dict['subk'] = None
self.assertEqual(base_dict._instance._changed_fields, ['my_name.k.subk'])
def test_get_sublist_gets_converted_to_BaseList_just_like__getitem__(self):
base_dict = self._get_basedict({'k': [0, 1, 2]})
sub_list = base_dict.get('k')
self.assertEqual(sub_list, [0, 1, 2])
self.assertIsInstance(sub_list, BaseList)
def test_get_returns_the_same_as___getitem__(self):
base_dict = self._get_basedict({'k': [0, 1, 2]})
get_ = base_dict.get('k')
getitem_ = base_dict['k']
self.assertEqual(get_, getitem_)
def test_get_default(self):
base_dict = self._get_basedict({})
sentinel = object()
self.assertEqual(base_dict.get('new'), None)
self.assertIs(base_dict.get('new', sentinel), sentinel)
def test___setitem___calls_mark_as_changed(self):
base_dict = self._get_basedict({})
base_dict['k'] = 'v'
self.assertEqual(base_dict._instance._changed_fields, ['my_name.k'])
self.assertEqual(base_dict, {'k': 'v'})
def test_update_calls_mark_as_changed(self):
base_dict = self._get_basedict({})
base_dict.update({'k': 'v'})
self.assertEqual(base_dict._instance._changed_fields, ['my_name'])
def test___setattr____not_tracked_by_changes(self):
base_dict = self._get_basedict({})
base_dict.a_new_attr = 'test'
self.assertEqual(base_dict._instance._changed_fields, [])
def test___delattr____tracked_by_changes(self):
# This is probably a bug as __setattr__ is not tracked
# This is even bad because it could be that there is an attribute
# with the same name as a key
base_dict = self._get_basedict({})
base_dict.a_new_attr = 'test'
del base_dict.a_new_attr
self.assertEqual(base_dict._instance._changed_fields, ['my_name.a_new_attr'])
class TestBaseList(unittest.TestCase):
def test_iter_simple(self):
@staticmethod
def _get_baselist(list_items):
"""Get a BaseList bound to a fake document instance"""
fake_doc = DocumentStub()
base_list = BaseList(list_items, instance=None, name='my_name')
base_list._instance = fake_doc # hack to inject the mock, it does not work in the constructor
return base_list
def test___init___(self):
class MyDoc(Document):
pass
list_items = [True]
doc = MyDoc()
base_list = BaseList(list_items, instance=doc, name='my_name')
self.assertIsInstance(base_list._instance, Document)
self.assertEqual(base_list._name, 'my_name')
self.assertEqual(base_list, list_items)
def test___iter__(self):
values = [True, False, True, False]
base_list = BaseList(values, instance=None, name='my_name')
self.assertEqual(values, list(base_list))
def test_iter_allow_modification_while_iterating_withou_error(self):
def test___iter___allow_modification_while_iterating_withou_error(self):
# regular list allows for this, thus this subclass must comply to that
base_list = BaseList([True, False, True, False], instance=None, name='my_name')
for idx, val in enumerate(base_list):
if val:
base_list.pop(idx)
def test_append_calls_mark_as_changed(self):
base_list = self._get_baselist([])
self.assertFalse(base_list._instance._changed_fields)
base_list.append(True)
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
def test_subclass_append(self):
# Due to the way mark_as_changed_wrapper is implemented
# it is good to test subclasses
class SubBaseList(BaseList):
pass
base_list = SubBaseList([], instance=None, name='my_name')
base_list.append(True)
def test___getitem__using_simple_index(self):
base_list = self._get_baselist([0, 1, 2])
self.assertEqual(base_list[0], 0)
self.assertEqual(base_list[1], 1)
self.assertEqual(base_list[-1], 2)
def test___getitem__using_slice(self):
base_list = self._get_baselist([0, 1, 2])
self.assertEqual(base_list[1:3], [1, 2])
self.assertEqual(base_list[0:3:2], [0, 2])
def test___getitem___using_slice_returns_list(self):
# Bug: using slice does not properly handles the instance
# and mark_as_changed behaviour.
base_list = self._get_baselist([0, 1, 2])
sliced = base_list[1:3]
self.assertEqual(sliced, [1, 2])
self.assertIsInstance(sliced, list)
self.assertEqual(base_list._instance._changed_fields, [])
def test___getitem__sublist_returns_BaseList_bound_to_instance(self):
base_list = self._get_baselist(
[
[1, 2],
[3, 4]
]
)
sub_list = base_list[0]
self.assertEqual(sub_list, [1, 2])
self.assertIsInstance(sub_list, BaseList)
self.assertIs(sub_list._instance, base_list._instance)
self.assertEqual(sub_list._name, 'my_name.0')
self.assertEqual(base_list._instance._changed_fields, [])
# Challenge mark_as_changed from sublist
sub_list[1] = None
self.assertEqual(base_list._instance._changed_fields, ['my_name.0.1'])
def test___getitem__subdict_returns_BaseList_bound_to_instance(self):
base_list = self._get_baselist(
[
{'subk': 'subv'}
]
)
sub_dict = base_list[0]
self.assertEqual(sub_dict, {'subk': 'subv'})
self.assertIsInstance(sub_dict, BaseDict)
self.assertIs(sub_dict._instance, base_list._instance)
self.assertEqual(sub_dict._name, 'my_name.0')
self.assertEqual(base_list._instance._changed_fields, [])
# Challenge mark_as_changed from subdict
sub_dict['subk'] = None
self.assertEqual(base_list._instance._changed_fields, ['my_name.0.subk'])
def test_extend_calls_mark_as_changed(self):
base_list = self._get_baselist([])
base_list.extend([True])
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
def test_insert_calls_mark_as_changed(self):
base_list = self._get_baselist([])
base_list.insert(0, True)
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
def test_remove_calls_mark_as_changed(self):
base_list = self._get_baselist([True])
base_list.remove(True)
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
def test_remove_not_mark_as_changed_when_it_fails(self):
base_list = self._get_baselist([True])
with self.assertRaises(ValueError):
base_list.remove(False)
self.assertFalse(base_list._instance._changed_fields)
def test_pop_calls_mark_as_changed(self):
base_list = self._get_baselist([True])
base_list.pop()
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
def test_reverse_calls_mark_as_changed(self):
base_list = self._get_baselist([True, False])
base_list.reverse()
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
def test___delitem___calls_mark_as_changed(self):
base_list = self._get_baselist([True])
del base_list[0]
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
def test___setitem___calls_with_full_slice_mark_as_changed(self):
base_list = self._get_baselist([])
base_list[:] = [0, 1] # Will use __setslice__ under py2 and __setitem__ under py3
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
self.assertEqual(base_list, [0, 1])
def test___setitem___calls_with_partial_slice_mark_as_changed(self):
base_list = self._get_baselist([0, 1, 2])
base_list[0:2] = [1, 0] # Will use __setslice__ under py2 and __setitem__ under py3
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
self.assertEqual(base_list, [1, 0, 2])
def test___setitem___calls_with_step_slice_mark_as_changed(self):
base_list = self._get_baselist([0, 1, 2])
base_list[0:3:2] = [-1, -2] # uses __setitem__ in both py2 & 3
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
self.assertEqual(base_list, [-1, 1, -2])
def test___setitem___with_slice(self):
base_list = self._get_baselist([0, 1, 2, 3, 4, 5])
base_list[0:6:2] = [None, None, None]
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
self.assertEqual(base_list, [None, 1, None, 3, None, 5])
def test___setitem___item_0_calls_mark_as_changed(self):
base_list = self._get_baselist([True])
base_list[0] = False
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
self.assertEqual(base_list, [False])
def test___setitem___item_1_calls_mark_as_changed(self):
base_list = self._get_baselist([True, True])
base_list[1] = False
self.assertEqual(base_list._instance._changed_fields, ['my_name.1'])
self.assertEqual(base_list, [True, False])
def test___delslice___calls_mark_as_changed(self):
base_list = self._get_baselist([0, 1])
del base_list[0:1]
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
self.assertEqual(base_list, [1])
def test___iadd___calls_mark_as_changed(self):
base_list = self._get_baselist([True])
base_list += [False]
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
def test___imul___calls_mark_as_changed(self):
base_list = self._get_baselist([True])
self.assertEqual(base_list._instance._changed_fields, [])
base_list *= 2
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
def test_sort_calls_not_marked_as_changed_when_it_fails(self):
base_list = self._get_baselist([True])
with self.assertRaises(TypeError):
base_list.sort(key=1)
self.assertEqual(base_list._instance._changed_fields, [])
def test_sort_calls_mark_as_changed(self):
base_list = self._get_baselist([True, False])
base_list.sort()
self.assertEqual(base_list._instance._changed_fields, ['my_name'])
def test_sort_calls_with_key(self):
base_list = self._get_baselist([1, 2, 11])
base_list.sort(key=lambda i: str(i))
self.assertEqual(base_list, [1, 11, 2])
class TestStrictDict(unittest.TestCase):
def strict_dict_class(self, *args, **kwargs):
@@ -87,8 +426,8 @@ class TestStrictDict(unittest.TestCase):
def test_mappings_protocol(self):
d = self.dtype(a=1, b=2)
assert dict(d) == {'a': 1, 'b': 2}
assert dict(**d) == {'a': 1, 'b': 2}
self.assertEqual(dict(d), {'a': 1, 'b': 2})
self.assertEqual(dict(**d), {'a': 1, 'b': 2})
if __name__ == '__main__':

View File

@@ -2,6 +2,7 @@
import unittest
from bson import DBRef, ObjectId
from six import iteritems
from mongoengine import *
from mongoengine.connection import get_db
@@ -632,7 +633,7 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
for k, m in iteritems(group_obj.members):
self.assertIsInstance(m, User)
# Document select_related
@@ -645,7 +646,7 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
for k, m in iteritems(group_obj.members):
self.assertIsInstance(m, User)
# Queryset select_related
@@ -659,7 +660,7 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
for k, m in iteritems(group_obj.members):
self.assertIsInstance(m, User)
User.drop_collection()
@@ -714,7 +715,7 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
for k, m in iteritems(group_obj.members):
self.assertIn('User', m.__class__.__name__)
# Document select_related
@@ -730,7 +731,7 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
for k, m in iteritems(group_obj.members):
self.assertIn('User', m.__class__.__name__)
# Queryset select_related
@@ -747,7 +748,7 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
for k, m in iteritems(group_obj.members):
self.assertIn('User', m.__class__.__name__)
Group.objects.delete()
@@ -805,7 +806,7 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
for k, m in iteritems(group_obj.members):
self.assertIsInstance(m, UserA)
# Document select_related
@@ -821,7 +822,7 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
for k, m in iteritems(group_obj.members):
self.assertIsInstance(m, UserA)
# Queryset select_related
@@ -838,7 +839,7 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
for k, m in iteritems(group_obj.members):
self.assertIsInstance(m, UserA)
UserA.drop_collection()
@@ -893,7 +894,7 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
for k, m in iteritems(group_obj.members):
self.assertIn('User', m.__class__.__name__)
# Document select_related
@@ -909,7 +910,7 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
for k, m in iteritems(group_obj.members):
self.assertIn('User', m.__class__.__name__)
# Queryset select_related
@@ -926,7 +927,7 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
for k, m in iteritems(group_obj.members):
self.assertIn('User', m.__class__.__name__)
Group.objects.delete()
@@ -1064,7 +1065,6 @@ class FieldTest(unittest.TestCase):
self.assertEqual(msg.author, user)
self.assertEqual(msg.author.name, 'new-name')
def test_list_lookup_not_checked_in_map(self):
"""Ensure we dereference list data correctly
"""
@@ -1286,5 +1286,6 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 2)
if __name__ == '__main__':
unittest.main()

View File

@@ -2,7 +2,7 @@ import unittest
from pymongo import ReadPreference
from mongoengine.python_support import IS_PYMONGO_3
from mongoengine.pymongo_support import IS_PYMONGO_3
if IS_PYMONGO_3:
from pymongo import MongoClient
@@ -47,5 +47,6 @@ class ConnectionTest(unittest.TestCase):
self.assertEqual(conn.read_preference, READ_PREF)
if __name__ == '__main__':
unittest.main()

View File

@@ -39,7 +39,6 @@ class SignalTests(unittest.TestCase):
def post_init(cls, sender, document, **kwargs):
signal_output.append('post_init signal, %s, document._created = %s' % (document, document._created))
@classmethod
def pre_save(cls, sender, document, **kwargs):
signal_output.append('pre_save signal, %s' % document)
@@ -247,7 +246,7 @@ class SignalTests(unittest.TestCase):
def load_existing_author():
a = self.Author(name='Bill Shakespeare')
a.save()
self.get_signal_output(lambda: None) # eliminate signal output
self.get_signal_output(lambda: None) # eliminate signal output
a1 = self.Author.objects(name='Bill Shakespeare')[0]
self.assertEqual(self.get_signal_output(create_author), [
@@ -431,5 +430,6 @@ class SignalTests(unittest.TestCase):
{}
])
if __name__ == '__main__':
unittest.main()

View File

@@ -1,22 +1,17 @@
import operator
import unittest
from nose.plugins.skip import SkipTest
from mongoengine import connect
from mongoengine.connection import get_db, get_connection
from mongoengine.python_support import IS_PYMONGO_3
from mongoengine.connection import get_db
from mongoengine.mongodb_support import get_mongodb_version, MONGODB_26, MONGODB_3, MONGODB_32, MONGODB_34
from mongoengine.pymongo_support import IS_PYMONGO_3
MONGO_TEST_DB = 'mongoenginetest' # standard name for the test database
# Constant that can be used to compare the version retrieved with
# get_mongodb_version()
MONGODB_26 = (2, 6)
MONGODB_3 = (3,0)
MONGODB_32 = (3, 2)
class MongoDBTestCase(unittest.TestCase):
"""Base class for tests that need a mongodb connection
It ensures that the db is clean at the beginning and dropped at the end automatically
@@ -33,46 +28,56 @@ class MongoDBTestCase(unittest.TestCase):
cls._connection.drop_database(MONGO_TEST_DB)
def get_mongodb_version():
"""Return the version of the connected mongoDB (first 2 digits)
:return: tuple(int, int)
"""
version_list = get_connection().server_info()['versionArray'][:2] # e.g: (3, 2)
return tuple(version_list)
def get_as_pymongo(doc):
"""Fetch the pymongo version of a certain Document"""
return doc.__class__.objects.as_pymongo().get(id=doc.id)
def _decorated_with_ver_requirement(func, version):
def _decorated_with_ver_requirement(func, mongo_version_req, oper):
"""Return a given function decorated with the version requirement
for a particular MongoDB version tuple.
:param version: The version required (tuple(int, int))
:param mongo_version_req: The mongodb version requirement (tuple(int, int))
:param oper: The operator to apply (e.g: operator.ge)
"""
def _inner(*args, **kwargs):
MONGODB_V = get_mongodb_version()
if MONGODB_V >= version:
mongodb_v = get_mongodb_version()
if oper(mongodb_v, mongo_version_req):
return func(*args, **kwargs)
raise SkipTest('Needs MongoDB v{}+'.format('.'.join(str(n) for n in version)))
raise SkipTest('Needs MongoDB v{}+'.format('.'.join(str(n) for n in mongo_version_req)))
_inner.__name__ = func.__name__
_inner.__doc__ = func.__doc__
return _inner
def requires_mongodb_gte_34(func):
"""Raise a SkipTest exception if we're working with MongoDB version
lower than v3.4
"""
return _decorated_with_ver_requirement(func, MONGODB_34, oper=operator.ge)
def requires_mongodb_lte_32(func):
"""Raise a SkipTest exception if we're working with MongoDB version
greater than v3.2.
"""
return _decorated_with_ver_requirement(func, MONGODB_32, oper=operator.le)
def requires_mongodb_gte_26(func):
"""Raise a SkipTest exception if we're working with MongoDB version
lower than v2.6.
"""
return _decorated_with_ver_requirement(func, MONGODB_26)
return _decorated_with_ver_requirement(func, MONGODB_26, oper=operator.ge)
def requires_mongodb_gte_3(func):
"""Raise a SkipTest exception if we're working with MongoDB version
lower than v3.0.
"""
return _decorated_with_ver_requirement(func, MONGODB_3)
return _decorated_with_ver_requirement(func, MONGODB_3, oper=operator.ge)
def skip_pymongo3(f):