Merge branch 'master' of github.com:MongoEngine/mongoengine into negative_indexes_in_list

This commit is contained in:
Bastien Gérard 2019-07-24 21:14:55 +02:00
commit c5080e4030
86 changed files with 9016 additions and 7443 deletions

View File

@ -22,7 +22,9 @@ python:
- 2.7
- 3.5
- 3.6
- 3.7
- pypy
- pypy3
dist: xenial
@ -30,8 +32,10 @@ env:
global:
- MONGODB_3_4=3.4.17
- MONGODB_3_6=3.6.12
- PYMONGO_3_6=3.6
- PYMONGO_3_4=3.4
matrix:
- MONGODB=${MONGODB_3_4} PYMONGO=3.x
- MONGODB=${MONGODB_3_4} PYMONGO=${PYMONGO_3_6}
matrix:
@ -40,11 +44,9 @@ matrix:
include:
- python: 2.7
env: MONGODB=${MONGODB_3_4} PYMONGO=3.4.x
- python: 3.6
env: MONGODB=${MONGODB_3_6} PYMONGO=3.x
env: MONGODB=${MONGODB_3_4} PYMONGO=${PYMONGO_3_4}
- python: 3.7
env: MONGODB=${MONGODB_3_6} PYMONGO=3.x
env: MONGODB=${MONGODB_3_6} PYMONGO=${PYMONGO_3_6}
install:
@ -52,19 +54,22 @@ install:
- wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-${MONGODB}.tgz
- tar xzf mongodb-linux-x86_64-${MONGODB}.tgz
- ${PWD}/mongodb-linux-x86_64-${MONGODB}/bin/mongod --version
# Install python dependencies
# Install Python dependencies.
- pip install --upgrade pip
- pip install coveralls
- pip install flake8 flake8-import-order
- pip install tox # tox 3.11.0 has requirement virtualenv>=14.0.0
- pip install virtualenv # virtualenv>=14.0.0 has dropped Python 3.2 support (and pypy3 is based on py32)
# Install the tox venv
# Install the tox venv.
- tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -e test
# Install black for Python v3.7 only.
- if [[ $TRAVIS_PYTHON_VERSION == '3.7' ]]; then pip install black; fi
before_script:
- mkdir ${PWD}/mongodb-linux-x86_64-${MONGODB}/data
- ${PWD}/mongodb-linux-x86_64-${MONGODB}/bin/mongod --dbpath ${PWD}/mongodb-linux-x86_64-${MONGODB}/data --logpath ${PWD}/mongodb-linux-x86_64-${MONGODB}/mongodb.log --fork
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then flake8 .; else echo "flake8 only runs on py27"; fi # Run flake8 for py27
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then flake8 .; else echo "flake8 only runs on py27"; fi # Run flake8 for Python 2.7 only
- if [[ $TRAVIS_PYTHON_VERSION == '3.7' ]]; then black --check .; else echo "black only runs on py37"; fi # Run black for Python 3.7 only
- mongo --eval 'db.version();' # Make sure mongo is awake
script:
@ -102,5 +107,5 @@ deploy:
on:
tags: true
repo: MongoEngine/mongoengine
condition: ($PYMONGO = 3.x) && ($MONGODB = 3.4)
condition: ($PYMONGO = ${PYMONGO_3_6}) && ($MONGODB = ${MONGODB_3_4})
python: 2.7

View File

@ -31,12 +31,8 @@ build. You should ensure that your code is properly converted by
Style Guide
-----------
MongoEngine aims to follow `PEP8 <http://www.python.org/dev/peps/pep-0008/>`_
including 4 space indents. When possible we try to stick to 79 character line
limits. However, screens got bigger and an ORM has a strong focus on
readability and if it can help, we accept 119 as maximum line length, in a
similar way as `django does
<https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#python-style>`_
MongoEngine uses `black <https://github.com/python/black>`_ for code
formatting.
Testing
-------

View File

@ -1,11 +1,18 @@
from timeit import repeat
import mongoengine
from mongoengine import (BooleanField, Document, EmailField, EmbeddedDocument,
EmbeddedDocumentField, IntField, ListField,
StringField)
from mongoengine import (
BooleanField,
Document,
EmailField,
EmbeddedDocument,
EmbeddedDocumentField,
IntField,
ListField,
StringField,
)
mongoengine.connect(db='mongoengine_benchmark_test')
mongoengine.connect(db="mongoengine_benchmark_test")
def timeit(f, n=10000):
@ -24,46 +31,41 @@ def test_basic():
def init_book():
return Book(
name='Always be closing',
name="Always be closing",
pages=100,
tags=['self-help', 'sales'],
tags=["self-help", "sales"],
is_published=True,
author_email='alec@example.com',
author_email="alec@example.com",
)
print('Doc initialization: %.3fus' % (timeit(init_book, 1000) * 10**6))
print("Doc initialization: %.3fus" % (timeit(init_book, 1000) * 10 ** 6))
b = init_book()
print('Doc getattr: %.3fus' % (timeit(lambda: b.name, 10000) * 10**6))
print("Doc getattr: %.3fus" % (timeit(lambda: b.name, 10000) * 10 ** 6))
print(
'Doc setattr: %.3fus' % (
timeit(lambda: setattr(b, 'name', 'New name'), 10000) * 10**6
)
"Doc setattr: %.3fus"
% (timeit(lambda: setattr(b, "name", "New name"), 10000) * 10 ** 6)
)
print('Doc to mongo: %.3fus' % (timeit(b.to_mongo, 1000) * 10**6))
print("Doc to mongo: %.3fus" % (timeit(b.to_mongo, 1000) * 10 ** 6))
print('Doc validation: %.3fus' % (timeit(b.validate, 1000) * 10**6))
print("Doc validation: %.3fus" % (timeit(b.validate, 1000) * 10 ** 6))
def save_book():
b._mark_as_changed('name')
b._mark_as_changed('tags')
b._mark_as_changed("name")
b._mark_as_changed("tags")
b.save()
print('Save to database: %.3fus' % (timeit(save_book, 100) * 10**6))
print("Save to database: %.3fus" % (timeit(save_book, 100) * 10 ** 6))
son = b.to_mongo()
print(
'Load from SON: %.3fus' % (
timeit(lambda: Book._from_son(son), 1000) * 10**6
)
"Load from SON: %.3fus" % (timeit(lambda: Book._from_son(son), 1000) * 10 ** 6)
)
print(
'Load from database: %.3fus' % (
timeit(lambda: Book.objects[0], 100) * 10**6
)
"Load from database: %.3fus" % (timeit(lambda: Book.objects[0], 100) * 10 ** 6)
)
def create_and_delete_book():
@ -72,9 +74,8 @@ def test_basic():
b.delete()
print(
'Init + save to database + delete: %.3fms' % (
timeit(create_and_delete_book, 10) * 10**3
)
"Init + save to database + delete: %.3fms"
% (timeit(create_and_delete_book, 10) * 10 ** 3)
)
@ -92,42 +93,36 @@ def test_big_doc():
def init_company():
return Company(
name='MongoDB, Inc.',
name="MongoDB, Inc.",
contacts=[
Contact(
name='Contact %d' % x,
title='CEO',
address='Address %d' % x,
)
Contact(name="Contact %d" % x, title="CEO", address="Address %d" % x)
for x in range(1000)
]
],
)
company = init_company()
print('Big doc to mongo: %.3fms' % (timeit(company.to_mongo, 100) * 10**3))
print("Big doc to mongo: %.3fms" % (timeit(company.to_mongo, 100) * 10 ** 3))
print('Big doc validation: %.3fms' % (timeit(company.validate, 1000) * 10**3))
print("Big doc validation: %.3fms" % (timeit(company.validate, 1000) * 10 ** 3))
company.save()
def save_company():
company._mark_as_changed('name')
company._mark_as_changed('contacts')
company._mark_as_changed("name")
company._mark_as_changed("contacts")
company.save()
print('Save to database: %.3fms' % (timeit(save_company, 100) * 10**3))
print("Save to database: %.3fms" % (timeit(save_company, 100) * 10 ** 3))
son = company.to_mongo()
print(
'Load from SON: %.3fms' % (
timeit(lambda: Company._from_son(son), 100) * 10**3
)
"Load from SON: %.3fms"
% (timeit(lambda: Company._from_son(son), 100) * 10 ** 3)
)
print(
'Load from database: %.3fms' % (
timeit(lambda: Company.objects[0], 100) * 10**3
)
"Load from database: %.3fms"
% (timeit(lambda: Company.objects[0], 100) * 10 ** 3)
)
def create_and_delete_company():
@ -136,13 +131,12 @@ def test_big_doc():
c.delete()
print(
'Init + save to database + delete: %.3fms' % (
timeit(create_and_delete_company, 10) * 10**3
)
"Init + save to database + delete: %.3fms"
% (timeit(create_and_delete_company, 10) * 10 ** 3)
)
if __name__ == '__main__':
if __name__ == "__main__":
test_basic()
print('-' * 100)
print("-" * 100)
test_big_doc()

View File

@ -26,10 +26,10 @@ myNoddys = noddy.find()
[n for n in myNoddys] # iterate
"""
print('-' * 100)
print('PyMongo: Creating 10000 dictionaries.')
print("-" * 100)
print("PyMongo: Creating 10000 dictionaries.")
t = timeit.Timer(stmt=stmt, setup=setup)
print('{}s'.format(t.timeit(1)))
print("{}s".format(t.timeit(1)))
stmt = """
from pymongo import MongoClient, WriteConcern
@ -49,10 +49,10 @@ myNoddys = noddy.find()
[n for n in myNoddys] # iterate
"""
print('-' * 100)
print("-" * 100)
print('PyMongo: Creating 10000 dictionaries (write_concern={"w": 0}).')
t = timeit.Timer(stmt=stmt, setup=setup)
print('{}s'.format(t.timeit(1)))
print("{}s".format(t.timeit(1)))
setup = """
from pymongo import MongoClient
@ -78,10 +78,10 @@ myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print('-' * 100)
print('MongoEngine: Creating 10000 dictionaries.')
print("-" * 100)
print("MongoEngine: Creating 10000 dictionaries.")
t = timeit.Timer(stmt=stmt, setup=setup)
print('{}s'.format(t.timeit(1)))
print("{}s".format(t.timeit(1)))
stmt = """
for i in range(10000):
@ -96,10 +96,10 @@ myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print('-' * 100)
print('MongoEngine: Creating 10000 dictionaries (using a single field assignment).')
print("-" * 100)
print("MongoEngine: Creating 10000 dictionaries (using a single field assignment).")
t = timeit.Timer(stmt=stmt, setup=setup)
print('{}s'.format(t.timeit(1)))
print("{}s".format(t.timeit(1)))
stmt = """
for i in range(10000):
@ -112,10 +112,10 @@ myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print('-' * 100)
print("-" * 100)
print('MongoEngine: Creating 10000 dictionaries (write_concern={"w": 0}).')
t = timeit.Timer(stmt=stmt, setup=setup)
print('{}s'.format(t.timeit(1)))
print("{}s".format(t.timeit(1)))
stmt = """
for i in range(10000):
@ -128,10 +128,12 @@ myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print('-' * 100)
print('MongoEngine: Creating 10000 dictionaries (write_concern={"w": 0}, validate=False).')
print("-" * 100)
print(
'MongoEngine: Creating 10000 dictionaries (write_concern={"w": 0}, validate=False).'
)
t = timeit.Timer(stmt=stmt, setup=setup)
print('{}s'.format(t.timeit(1)))
print("{}s".format(t.timeit(1)))
stmt = """
for i in range(10000):
@ -144,10 +146,12 @@ myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print('-' * 100)
print('MongoEngine: Creating 10000 dictionaries (force_insert=True, write_concern={"w": 0}, validate=False).')
print("-" * 100)
print(
'MongoEngine: Creating 10000 dictionaries (force_insert=True, write_concern={"w": 0}, validate=False).'
)
t = timeit.Timer(stmt=stmt, setup=setup)
print('{}s'.format(t.timeit(1)))
print("{}s".format(t.timeit(1)))
if __name__ == "__main__":

View File

@ -5,204 +5,216 @@ Changelog
Development
===========
- Add Python 3.7 in travis #2058
- (Fill this out as you fix issues and develop your features).
- BREAKING CHANGE: ``class_check`` and ``read_preference`` keyword arguments are no longer available when filtering a ``QuerySet``. #2112
- Instead of ``Doc.objects(foo=bar, read_preference=...)`` use ``Doc.objects(foo=bar).read_preference(...)``.
- Instead of ``Doc.objects(foo=bar, class_check=False)`` use ``Doc.objects(foo=bar).clear_cls_query(...)``.
- This change also renames the private ``QuerySet._initial_query`` attribute to ``_cls_query``.
- BREAKING CHANGE: Removed the deprecated ``format`` param from ``QuerySet.explain``. #2113
- BREAKING CHANGE: Renamed ``MongoEngineConnectionError`` to ``ConnectionFailure``. #2111
- If you catch/use ``MongoEngineConnectionError`` in your code, you'll have to rename it.
- BREAKING CHANGE: Positional arguments when instantiating a document are no longer supported. #2103
- From now on keyword arguments (e.g. ``Doc(field_name=value)``) are required.
- Fix updating/modifying/deleting/reloading a document that's sharded by a field with ``db_field`` specified. #2125
- ``ListField`` now accepts an optional ``max_length`` parameter. #2110
- The codebase is now formatted using ``black``. #2109
Changes in 0.18.2
=================
- Replace deprecated PyMongo v2.x methods with their v3.x equivalents in the ``SequenceField``. #2097
- Various code clarity and documentation improvements.
Changes in 0.18.1
=================
- Fix a bug introduced in 0.18.0 which was causing ``Document.save`` to update all the fields instead of updating only the modified fields. This bug only occurred when using a custom PK. #2082
- Add Python 3.7 to Travis CI. #2058
Changes in 0.18.0
=================
- Drop support for EOL'd MongoDB v2.6, v3.0, and v3.2.
- MongoEngine now requires PyMongo >= v3.4. Travis CI now tests against MongoDB v3.4 v3.6 and PyMongo v3.4 v3.6 (#2017 #2066).
- Improve performance by avoiding a call to `to_mongo` in `Document.save()` #2049
- MongoEngine now requires PyMongo >= v3.4. Travis CI now tests against MongoDB v3.4 v3.6 and PyMongo v3.4 v3.6. #2017 #2066
- Improve performance by avoiding a call to ``to_mongo`` in ``Document.save``. #2049
- Connection/disconnection improvements:
- Expose `mongoengine.connection.disconnect` and `mongoengine.connection.disconnect_all`
- Fix disconnecting #566 #1599 #605 #607 #1213 #565
- Improve documentation of `connect`/`disconnect`
- Fix issue when using multiple connections to the same mongo with different credentials #2047
- `connect` fails immediately when db name contains invalid characters #2031 #1718
- Fix the default write concern of `Document.save` that was overwriting the connection write concern #568
- Fix querying on `List(EmbeddedDocument)` subclasses fields #1961 #1492
- Fix querying on `(Generic)EmbeddedDocument` subclasses fields #475
- Fix `QuerySet.aggregate` so that it takes limit and skip value into account #2029
- Generate unique indices for `SortedListField` and `EmbeddedDocumentListFields` #2020
- BREAKING CHANGE: Changed the behavior of a custom field validator (i.e `validation` parameter of a `Field`). It is now expected to raise a `ValidationError` instead of returning True/False #2050
- BREAKING CHANGES (associated with connect/disconnect fixes):
- Calling `connect` 2 times with the same alias and different parameter will raise an error (should call `disconnect` first).
- `disconnect` now clears `mongoengine.connection._connection_settings`.
- `disconnect` now clears the cached attribute `Document._collection`.
- BREAKING CHANGE: `EmbeddedDocument.save` & `.reload` is no longier exist #1552
- Expose ``mongoengine.connection.disconnect`` and ``mongoengine.connection.disconnect_all``.
- Fix disconnecting. #566 #1599 #605 #607 #1213 #565
- Improve documentation of ``connect``/``disconnect``.
- Fix issue when using multiple connections to the same mongo with different credentials. #2047
- ``connect`` fails immediately when db name contains invalid characters. #2031 #1718
- Fix the default write concern of ``Document.save`` that was overwriting the connection write concern. #568
- Fix querying on ``List(EmbeddedDocument)`` subclasses fields. #1961 #1492
- Fix querying on ``(Generic)EmbeddedDocument`` subclasses fields. #475
- Fix ``QuerySet.aggregate`` so that it takes limit and skip value into account. #2029
- Generate unique indices for ``SortedListField`` and ``EmbeddedDocumentListFields``. #2020
- BREAKING CHANGE: Changed the behavior of a custom field validator (i.e ``validation`` parameter of a ``Field``). It is now expected to raise a ``ValidationError`` instead of returning ``True``/``False``. #2050
- BREAKING CHANGES (associated with connection/disconnection fixes):
- Calling ``connect`` 2 times with the same alias and different parameter will raise an error (should call ``disconnect`` first).
- ``disconnect`` now clears ``mongoengine.connection._connection_settings``.
- ``disconnect`` now clears the cached attribute ``Document._collection``.
- BREAKING CHANGE: ``EmbeddedDocument.save`` & ``.reload`` no longer exist. #1552
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
- Fix reference fields querying the database on each access if value contains orphan DBRefs
- 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
- DEPRECATION: ``EmbeddedDocument.save`` & ``.reload`` are marked as deprecated and will be removed in a next version of MongoEngine. #1552
- Fix ``QuerySet.only`` working improperly after using ``QuerySet.count`` of the same instance of a ``QuerySet``.
- Fix ``batch_size`` that was not copied when cloning a ``QuerySet`` object. #2011
- Fix ``InvalidStringData`` error when using ``modify`` on a ``BinaryField``. #1127
- Fix test suite and CI to support MongoDB v3.4. #1445
- Fix reference fields querying the database on each access if value contains orphan DBRefs.
=================
Changes in 0.16.3
=================
- Fix $push with $position operator not working with lists in embedded document #1965
- Fix ``$push`` with the ``$position`` operator not working with lists in embedded documents. #1965
=================
Changes in 0.16.2
=================
- Fix .save() that fails when called with write_concern=None (regression of 0.16.1) #1958
- Fix ``Document.save`` that fails when called with ``write_concern=None`` (regression of 0.16.1). #1958
=================
Changes in 0.16.1
=================
- Fix `_cls` that is not set properly in Document constructor (regression) #1950
- Fix bug in _delta method - Update of a ListField depends on an unrelated dynamic field update #1733
- Remove deprecated `save()` method and used `insert_one()` #1899
- Fix ``_cls`` that is not set properly in the ``Document`` constructor (regression). #1950
- Fix a bug in the ``_delta`` method - update of a ``ListField`` depends on an unrelated dynamic field update. #1733
- Remove PyMongo's deprecated ``Collection.save`` method and use ``Collection.insert_one`` instead. #1899
=================
Changes in 0.16.0
=================
- Various improvements to the doc
- Improvement to code quality
- POTENTIAL BREAKING CHANGES:
- EmbeddedDocumentField will no longer accept references to Document classes in its constructor #1661
- Get rid of the `basecls` parameter from the DictField constructor (dead code) #1876
- default value of ComplexDateTime is now None (and no longer the current datetime) #1368
- Fix unhashable TypeError when referencing a Document with a compound key in an EmbeddedDocument #1685
- Fix bug where an EmbeddedDocument with the same id as its parent would not be tracked for changes #1768
- Fix the fact that bulk `insert()` was not setting primary keys of inserted documents instances #1919
- Fix bug when referencing the abstract class in a ReferenceField #1920
- Allow modification to the document made in pre_save_post_validation to be taken into account #1202
- Replaced MongoDB 2.4 tests in CI by MongoDB 3.2 #1903
- Fix side effects of using queryset.`no_dereference` on other documents #1677
- Fix TypeError when using lazy django translation objects as translated choices #1879
- Improve 2-3 codebase compatibility #1889
- Fix the support for changing the default value of ComplexDateTime #1368
- Improves error message in case an EmbeddedDocumentListField receives an EmbeddedDocument instance
instead of a list #1877
- Fix the Decimal operator inc/dec #1517 #1320
- Ignore killcursors queries in `query_counter` context manager #1869
- Fix the fact that `query_counter` was modifying the initial profiling_level in case it was != 0 #1870
- Repaired the `no_sub_classes` context manager + fix the fact that it was swallowing exceptions #1865
- Fix index creation error that was swallowed by hasattr under python2 #1688
- QuerySet limit function behaviour: Passing 0 as parameter will return all the documents in the cursor #1611
- bulk insert updates the ids of the input documents instances #1919
- Fix an harmless bug related to GenericReferenceField where modifications in the generic-referenced document
were tracked in the parent #1934
- Improve validator of BinaryField #273
- Implemented lazy regex compiling in Field classes to improve 'import mongoengine' performance #1806
- Updated GridFSProxy.__str__ so that it would always print both the filename and grid_id #710
- Add __repr__ to Q and QCombination #1843
- fix bug in BaseList.__iter__ operator (was occuring when modifying a BaseList while iterating over it) #1676
- Added field `DateField`#513
- ``EmbeddedDocumentField`` will no longer accept references to Document classes in its constructor. #1661
- Get rid of the ``basecls`` parameter from the ``DictField`` constructor (dead code). #1876
- Default value of the ``ComplexDateTime`` field is now ``None`` (and no longer the current datetime). #1368
- Fix an unhashable ``TypeError`` when referencing a ``Document`` with a compound key in an ``EmbeddedDocument``. #1685
- Fix a bug where an ``EmbeddedDocument`` with the same id as its parent would not be tracked for changes. #1768
- Fix the fact that a bulk ``QuerySet.insert`` was not setting primary keys of inserted document instances. #1919
- Fix a bug when referencing an abstract class in a ``ReferenceField``. #1920
- Allow modifications to the document made in ``pre_save_post_validation`` to be taken into account. #1202
- Replace MongoDB v2.4 tests in Travis CI with MongoDB v3.2. #1903
- Fix side effects of using ``QuerySet.no_dereference`` on other documents. #1677
- Fix ``TypeError`` when using lazy Django translation objects as translated choices. #1879
- Improve Python 2-3 codebase compatibility. #1889
- Fix support for changing the default value of the ``ComplexDateTime`` field. #1368
- Improve error message in case an ``EmbeddedDocumentListField`` receives an ``EmbeddedDocument`` instance instead of a list. #1877
- Fix the ``inc`` and ``dec`` operators for the ``DecimalField``. #1517 #1320
- Ignore ``killcursors`` queries in ``query_counter`` context manager. #1869
- Fix the fact that ``query_counter`` was modifying the initial profiling level in case it was != 0. #1870
- Repair the ``no_sub_classes`` context manager + fix the fact that it was swallowing exceptions. #1865
- Fix index creation error that was swallowed by ``hasattr`` under Python 2. #1688
- ``QuerySet.limit`` function behaviour: Passing 0 as parameter will return all the documents in the cursor. #1611
- Bulk insert updates the IDs of the input documents instances. #1919
- Fix a harmless bug related to ``GenericReferenceField`` where modifications in the generic-referenced document were tracked in the parent. #1934
- Improve validation of the ``BinaryField``. #273
- Implement lazy regex compiling in Field classes to improve ``import mongoengine`` performance. #1806
- Update ``GridFSProxy.__str__`` so that it would always print both the filename and grid_id. #710
- Add ``__repr__`` to ``Q`` and ``QCombination`` classes. #1843
- Fix bug in the ``BaseList.__iter__`` operator (was occuring when modifying a BaseList while iterating over it). #1676
- Add a ``DateField``. #513
- Various improvements to the documentation.
- Various code quality improvements.
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
- Fix validation error instance in GenericEmbeddedDocumentField #1067
- Update cached fields when fields argument is given #1712
- Add a db parameter to register_connection for compatibility with connect
- Use insert_one, insert_many in Document.insert #1491
- Use new update_one, update_many on document/queryset update #1491
- Use insert_one, insert_many in Document.insert #1491
- Fix reload(fields) affect changed fields #1371
- Fix Read-only access to database fails when trying to create indexes #1338
- ``Queryset.update/update_one`` methods now return an ``UpdateResult`` when ``full_result=True`` is provided and no longer a dict. #1491
- Improve ``LazyReferenceField`` and ``GenericLazyReferenceField`` with nested fields. #1704
- Fix the subfield resolve error in ``generic_emdedded_document`` query. #1651 #1652
- Use each modifier only with ``$position``. #1673 #1675
- Fix validation errors in the ``GenericEmbeddedDocumentField``. #1067
- Update cached fields when a ``fields`` argument is given. #1712
- Add a ``db`` parameter to ``register_connection`` for compatibility with ``connect``.
- Use PyMongo v3.x's ``insert_one`` and ``insert_many`` in ``Document.insert``. #1491
- Use PyMongo v3.x's ``update_one`` and ``update_many`` in ``Document.update`` and ``QuerySet.update``. #1491
- Fix how ``reload(fields)`` affects changed fields. #1371
- Fix a bug where the read-only access to the database fails when trying to create indexes. #1338
Changes in 0.15.0
=================
- Add LazyReferenceField and GenericLazyReferenceField to address #1230
- Add ``LazyReferenceField`` and ``GenericLazyReferenceField``. #1230
Changes in 0.14.1
=================
- Removed SemiStrictDict and started using a regular dict for `BaseDocument._data` #1630
- Added support for the `$position` param in the `$push` operator #1566
- Fixed `DateTimeField` interpreting an empty string as today #1533
- Added a missing `__ne__` method to the `GridFSProxy` class #1632
- Fixed `BaseQuerySet._fields_to_db_fields` #1553
- Remove ``SemiStrictDict`` and start using a regular dict for ``BaseDocument._data``. #1630
- Add support for the ``$position`` param in the ``$push`` operator. #1566
- Fix ``DateTimeField`` interpreting an empty string as today. #1533
- Add a missing ``__ne__`` method to the ``GridFSProxy`` class. #1632
- Fix ``BaseQuerySet._fields_to_db_fields``. #1553
Changes in 0.14.0
=================
- BREAKING CHANGE: Removed the `coerce_types` param from `QuerySet.as_pymongo` #1549
- POTENTIAL BREAKING CHANGE: Made EmbeddedDocument not hashable by default #1528
- Improved code quality #1531, #1540, #1541, #1547
- BREAKING CHANGE: Remove the ``coerce_types`` param from ``QuerySet.as_pymongo``. #1549
- POTENTIAL BREAKING CHANGE: Make ``EmbeddedDocument`` not hashable by default. #1528
- Improve code quality. #1531, #1540, #1541, #1547
Changes in 0.13.0
=================
- POTENTIAL BREAKING CHANGE: Added Unicode support to the `EmailField`, see
docs/upgrade.rst for details.
- POTENTIAL BREAKING CHANGE: Added Unicode support to the ``EmailField``, see docs/upgrade.rst for details.
Changes in 0.12.0
=================
- POTENTIAL BREAKING CHANGE: Fixed limit/skip/hint/batch_size chaining #1476
- POTENTIAL BREAKING CHANGE: Changed a public `QuerySet.clone_into` method to a private `QuerySet._clone_into` #1476
- Fixed the way `Document.objects.create` works with duplicate IDs #1485
- Fixed connecting to a replica set with PyMongo 2.x #1436
- Fixed using sets in field choices #1481
- Fixed deleting items from a `ListField` #1318
- Fixed an obscure error message when filtering by `field__in=non_iterable`. #1237
- Fixed behavior of a `dec` update operator #1450
- Added a `rename` update operator #1454
- Added validation for the `db_field` parameter #1448
- Fixed the error message displayed when querying an `EmbeddedDocumentField` by an invalid value #1440
- Fixed the error message displayed when validating unicode URLs #1486
- Raise an error when trying to save an abstract document #1449
- POTENTIAL BREAKING CHANGE: Fix ``limit``/``skip``/``hint``/``batch_size`` chaining. #1476
- POTENTIAL BREAKING CHANGE: Change a public ``QuerySet.clone_into`` method to a private ``QuerySet._clone_into``. #1476
- Fix the way ``Document.objects.create`` works with duplicate IDs. #1485
- Fix connecting to a replica set with PyMongo 2.x. #1436
- Fix using sets in field choices. #1481
- Fix deleting items from a ``ListField``. #1318
- Fix an obscure error message when filtering by ``field__in=non_iterable``. #1237
- Fix behavior of a ``dec`` update operator. #1450
- Add a ``rename`` update operator. #1454
- Add validation for the ``db_field`` parameter. #1448
- Fix the error message displayed when querying an ``EmbeddedDocumentField`` by an invalid value. #1440
- Fix the error message displayed when validating Unicode URLs. #1486
- Raise an error when trying to save an abstract document. #1449
Changes in 0.11.0
=================
- BREAKING CHANGE: Renamed `ConnectionError` to `MongoEngineConnectionError` since the former is a built-in exception name in Python v3.x. #1428
- BREAKING CHANGE: Dropped Python 2.6 support. #1428
- BREAKING CHANGE: `from mongoengine.base import ErrorClass` won't work anymore for any error from `mongoengine.errors` (e.g. `ValidationError`). Use `from mongoengine.errors import ErrorClass instead`. #1428
- BREAKING CHANGE: Accessing a broken reference will raise a `DoesNotExist` error. In the past it used to return `None`. #1334
- Fixed absent rounding for DecimalField when `force_string` is set. #1103
- BREAKING CHANGE: Rename ``ConnectionError`` to ``MongoEngineConnectionError`` since the former is a built-in exception name in Python v3.x. #1428
- BREAKING CHANGE: Drop Python v2.6 support. #1428
- BREAKING CHANGE: ``from mongoengine.base import ErrorClass`` won't work anymore for any error from ``mongoengine.errors`` (e.g. ``ValidationError``). Use ``from mongoengine.errors import ErrorClass instead``. #1428
- BREAKING CHANGE: Accessing a broken reference will raise a ``DoesNotExist`` error. In the past it used to return ``None``. #1334
- Fix absent rounding for the ``DecimalField`` when ``force_string`` is set. #1103
Changes in 0.10.8
=================
- Added support for QuerySet.batch_size (#1426)
- Fixed query set iteration within iteration #1427
- Fixed an issue where specifying a MongoDB URI host would override more information than it should #1421
- Added ability to filter the generic reference field by ObjectId and DBRef #1425
- Fixed delete cascade for models with a custom primary key field #1247
- Added ability to specify an authentication mechanism (e.g. X.509) #1333
- Added support for falsey primary keys (e.g. doc.pk = 0) #1354
- Fixed QuerySet#sum/average for fields w/ explicit db_field #1417
- Fixed filtering by embedded_doc=None #1422
- Added support for cursor.comment #1420
- Fixed doc.get_<field>_display #1419
- Fixed __repr__ method of the StrictDict #1424
- Added a deprecation warning for Python 2.6
- Add support for ``QuerySet.batch_size``. (#1426)
- Fix a query set iteration within an iteration. #1427
- Fix an issue where specifying a MongoDB URI host would override more information than it should. #1421
- Add an ability to filter the ``GenericReferenceField`` by an ``ObjectId`` and a ``DBRef``. #1425
- Fix cascading deletes for models with a custom primary key field. #1247
- Add ability to specify an authentication mechanism (e.g. X.509). #1333
- Add support for falsy primary keys (e.g. ``doc.pk = 0``). #1354
- Fix ``QuerySet.sum/average`` for fields w/ an explicit ``db_field``. #1417
- Fix filtering by ``embedded_doc=None``. #1422
- Add support for ``Cursor.comment``. #1420
- Fix ``doc.get_<field>_display`` methods. #1419
- Fix the ``__repr__`` method of the ``StrictDict`` #1424
- Add a deprecation warning for Python v2.6.
Changes in 0.10.7
=================
- Dropped Python 3.2 support #1390
- Fixed the bug where dynamic doc has index inside a dict field #1278
- Fixed: ListField minus index assignment does not work #1128
- Fixed cascade delete mixing among collections #1224
- Add `signal_kwargs` argument to `Document.save`, `Document.delete` and `BaseQuerySet.insert` to be passed to signals calls #1206
- Raise `OperationError` when trying to do a `drop_collection` on document with no collection set.
- count on ListField of EmbeddedDocumentField fails. #1187
- Fixed long fields stored as int32 in Python 3. #1253
- MapField now handles unicodes keys correctly. #1267
- ListField now handles negative indicies correctly. #1270
- Fixed AttributeError when initializing EmbeddedDocument with positional args. #681
- Fixed no_cursor_timeout error with pymongo 3.0+ #1304
- Replaced map-reduce based QuerySet.sum/average with aggregation-based implementations #1336
- Fixed support for `__` to escape field names that match operators names in `update` #1351
- Fixed BaseDocument#_mark_as_changed #1369
- Added support for pickling QuerySet instances. #1397
- Fixed connecting to a list of hosts #1389
- Fixed a bug where accessing broken references wouldn't raise a DoesNotExist error #1334
- Fixed not being able to specify use_db_field=False on ListField(EmbeddedDocumentField) instances #1218
- Improvements to the dictionary fields docs #1383
- Drop Python 3.2 support #1390
- Fix a bug where a dynamic doc has an index inside a dict field. #1278
- Fix: ``ListField`` minus index assignment does not work. #1128
- Fix cascade delete mixing among collections. #1224
- Add ``signal_kwargs`` argument to ``Document.save``, ``Document.delete`` and ``BaseQuerySet.insert`` to be passed to signals calls. #1206
- Raise ``OperationError`` when trying to do a ``drop_collection`` on document with no collection set.
- Fix a bug where a count on ``ListField`` of ``EmbeddedDocumentField`` fails. #1187
- Fix ``LongField`` values stored as int32 in Python 3. #1253
- ``MapField`` now handles unicode keys correctly. #1267
- ``ListField`` now handles negative indicies correctly. #1270
- Fix an ``AttributeError`` when initializing an ``EmbeddedDocument`` with positional args. #681
- Fix a ``no_cursor_timeout`` error with PyMongo v3.x. #1304
- Replace map-reduce based ``QuerySet.sum/average`` with aggregation-based implementations. #1336
- Fix support for ``__`` to escape field names that match operators' names in ``update``. #1351
- Fix ``BaseDocument._mark_as_changed``. #1369
- Add support for pickling ``QuerySet`` instances. #1397
- Fix connecting to a list of hosts. #1389
- Fix a bug where accessing broken references wouldn't raise a ``DoesNotExist`` error. #1334
- Fix not being able to specify ``use_db_field=False`` on ``ListField(EmbeddedDocumentField)`` instances. #1218
- Improvements to the dictionary field's docs. #1383
Changes in 0.10.6
=================
- Add support for mocking MongoEngine based on mongomock. #1151
- Fixed not being able to run tests on Windows. #1153
- Fix not being able to run tests on Windows. #1153
- Allow creation of sparse compound indexes. #1114
- count on ListField of EmbeddedDocumentField fails. #1187
Changes in 0.10.5
=================
@ -210,12 +222,12 @@ Changes in 0.10.5
Changes in 0.10.4
=================
- SaveConditionError is now importable from the top level package. #1165
- upsert_one method added. #1157
- ``SaveConditionError`` is now importable from the top level package. #1165
- Add a ``QuerySet.upsert_one`` method. #1157
Changes in 0.10.3
=================
- Fix `read_preference` (it had chaining issues with PyMongo 2.x and it didn't work at all with PyMongo 3.x) #1042
- Fix ``read_preference`` (it had chaining issues with PyMongo v2.x and it didn't work at all with PyMongo v3.x). #1042
Changes in 0.10.2
=================
@ -225,16 +237,16 @@ Changes in 0.10.2
Changes in 0.10.1
=================
- Fix infinite recursion with CASCADE delete rules under specific conditions. #1046
- Fix CachedReferenceField bug when loading cached docs as DBRef but failing to save them. #1047
- Fix ignored chained options #842
- Document save's save_condition error raises `SaveConditionError` exception #1070
- Fix Document.reload for DynamicDocument. #1050
- StrictDict & SemiStrictDict are shadowed at init time. #1105
- Fix ListField minus index assignment does not work. #1119
- Remove code that marks field as changed when the field has default but not existed in database #1126
- Remove test dependencies (nose and rednose) from install dependencies list. #1079
- Recursively build query when using elemMatch operator. #1130
- Fix infinite recursion with cascade delete rules under specific conditions. #1046
- Fix ``CachedReferenceField`` bug when loading cached docs as ``DBRef`` but failing to save them. #1047
- Fix ignored chained options. #842
- ``Document.save``'s ``save_condition`` error raises a ``SaveConditionError`` exception. #1070
- Fix ``Document.reload`` for the ``DynamicDocument``. #1050
- ``StrictDict`` & ``SemiStrictDict`` are shadowed at init time. #1105
- Fix ``ListField`` negative index assignment not working. #1119
- Remove code that marks a field as changed when the field has a default value but does not exist in the database. #1126
- Remove test dependencies (nose and rednose) from install dependencies. #1079
- Recursively build a query when using the ``elemMatch`` operator. #1130
- Fix instance back references for lists of embedded documents. #1131
Changes in 0.10.0
@ -245,7 +257,7 @@ Changes in 0.10.0
- Removed get_or_create() deprecated since 0.8.0. #300
- Improve Document._created status when switch collection and db #1020
- Queryset update doesn't go through field validation #453
- Added support for specifying authentication source as option `authSource` in URI. #967
- Added support for specifying authentication source as option ``authSource`` in URI. #967
- Fixed mark_as_changed to handle higher/lower level fields changed. #927
- ListField of embedded docs doesn't set the _instance attribute when iterating over it #914
- Support += and *= for ListField #595
@ -261,7 +273,7 @@ Changes in 0.10.0
- Fixes some internal _id handling issue. #961
- Updated URL and Email Field regex validators, added schemes argument to URLField validation. #652
- Capped collection multiple of 256. #1011
- Added `BaseQuerySet.aggregate_sum` and `BaseQuerySet.aggregate_average` methods.
- Added ``BaseQuerySet.aggregate_sum`` and ``BaseQuerySet.aggregate_average`` methods.
- Fix for delete with write_concern {'w': 0}. #1008
- Allow dynamic lookup for more than two parts. #882
- Added support for min_distance on geo queries. #831
@ -270,10 +282,10 @@ Changes in 0.10.0
Changes in 0.9.0
================
- Update FileField when creating a new file #714
- Added `EmbeddedDocumentListField` for Lists of Embedded Documents. #826
- Added ``EmbeddedDocumentListField`` for Lists of Embedded Documents. #826
- ComplexDateTimeField should fall back to None when null=True #864
- Request Support for $min, $max Field update operators #863
- `BaseDict` does not follow `setdefault` #866
- ``BaseDict`` does not follow ``setdefault`` #866
- Add support for $type operator # 766
- Fix tests for pymongo 2.8+ #877
- No module named 'django.utils.importlib' (Django dev) #872
@ -294,13 +306,13 @@ Changes in 0.9.0
- Stop ensure_indexes running on a secondaries unless connection is through mongos #746
- Not overriding default values when loading a subset of fields #399
- Saving document doesn't create new fields in existing collection #620
- Added `Queryset.aggregate` wrapper to aggregation framework #703
- Added ``Queryset.aggregate`` wrapper to aggregation framework #703
- Added support to show original model fields on to_json calls instead of db_field #697
- Added Queryset.search_text to Text indexes searchs #700
- Fixed tests for Django 1.7 #696
- Follow ReferenceFields in EmbeddedDocuments with select_related #690
- Added preliminary support for text indexes #680
- Added `elemMatch` operator as well - `match` is too obscure #653
- Added ``elemMatch`` operator as well - ``match`` is too obscure #653
- Added support for progressive JPEG #486 #548
- Allow strings to be used in index creation #675
- Fixed EmbeddedDoc weakref proxy issue #592
@ -336,11 +348,11 @@ Changes in 0.9.0
- Increase email field length to accommodate new TLDs #726
- index_cls is ignored when deciding to set _cls as index prefix #733
- Make 'db' argument to connection optional #737
- Allow atomic update for the entire `DictField` #742
- Allow atomic update for the entire ``DictField`` #742
- Added MultiPointField, MultiLineField, MultiPolygonField
- Fix multiple connections aliases being rewritten #748
- Fixed a few instances where reverse_delete_rule was written as reverse_delete_rules. #791
- Make `in_bulk()` respect `no_dereference()` #775
- Make ``in_bulk()`` respect ``no_dereference()`` #775
- Handle None from model __str__; Fixes #753 #754
- _get_changed_fields fix for embedded documents with id field. #925
@ -394,18 +406,18 @@ Changes in 0.8.4
Changes in 0.8.3
================
- Fixed EmbeddedDocuments with `id` also storing `_id` (#402)
- Fixed EmbeddedDocuments with ``id`` also storing ``_id`` (#402)
- Added get_proxy_object helper to filefields (#391)
- Added QuerySetNoCache and QuerySet.no_cache() for lower memory consumption (#365)
- Fixed sum and average mapreduce dot notation support (#375, #376, #393)
- Fixed as_pymongo to return the id (#386)
- Document.select_related() now respects `db_alias` (#377)
- Document.select_related() now respects ``db_alias`` (#377)
- Reload uses shard_key if applicable (#384)
- Dynamic fields are ordered based on creation and stored in _fields_ordered (#396)
**Potential breaking change:** http://docs.mongoengine.org/en/latest/upgrade.html#to-0-8-3
- Fixed pickling dynamic documents `_dynamic_fields` (#387)
- Fixed pickling dynamic documents ``_dynamic_fields`` (#387)
- Fixed ListField setslice and delslice dirty tracking (#390)
- Added Django 1.5 PY3 support (#392)
- Added match ($elemMatch) support for EmbeddedDocuments (#379)
@ -446,7 +458,7 @@ Changes in 0.8.0
================
- Fixed querying ReferenceField custom_id (#317)
- Fixed pickle issues with collections (#316)
- Added `get_next_value` preview for SequenceFields (#319)
- Added ``get_next_value`` preview for SequenceFields (#319)
- Added no_sub_classes context manager and queryset helper (#312)
- Querysets now utilises a local cache
- Changed __len__ behaviour in the queryset (#247, #311)
@ -475,7 +487,7 @@ Changes in 0.8.0
- Updated connection to use MongoClient (#262, #274)
- Fixed db_alias and inherited Documents (#143)
- Documentation update for document errors (#124)
- Deprecated `get_or_create` (#35)
- Deprecated ``get_or_create`` (#35)
- Updated inheritable objects created by upsert now contain _cls (#118)
- Added support for creating documents with embedded documents in a single operation (#6)
- Added to_json and from_json to Document (#1)
@ -596,7 +608,7 @@ Changes in 0.7.0
- Fixed UnboundLocalError in composite index with pk field (#88)
- Updated ReferenceField's to optionally store ObjectId strings
this will become the default in 0.8 (#89)
- Added FutureWarning - save will default to `cascade=False` in 0.8
- Added FutureWarning - save will default to ``cascade=False`` in 0.8
- Added example of indexing embedded document fields (#75)
- Fixed ImageField resizing when forcing size (#80)
- Add flexibility for fields handling bad data (#78)
@ -692,7 +704,7 @@ Changes in 0.6.8
================
- Fixed FileField losing reference when no default set
- Removed possible race condition from FileField (grid_file)
- Added assignment to save, can now do: `b = MyDoc(**kwargs).save()`
- Added assignment to save, can now do: ``b = MyDoc(**kwargs).save()``
- Added support for pull operations on nested EmbeddedDocuments
- Added support for choices with GenericReferenceFields
- Added support for choices with GenericEmbeddedDocumentFields
@ -707,7 +719,7 @@ Changes in 0.6.7
- Fixed indexing on '_id' or 'pk' or 'id'
- Invalid data from the DB now raises a InvalidDocumentError
- Cleaned up the Validation Error - docs and code
- Added meta `auto_create_index` so you can disable index creation
- Added meta ``auto_create_index`` so you can disable index creation
- Added write concern options to inserts
- Fixed typo in meta for index options
- Bug fix Read preference now passed correctly
@ -748,7 +760,6 @@ Changes in 0.6.1
Changes in 0.6
==============
- Added FutureWarning to inherited classes not declaring 'allow_inheritance' as the default will change in 0.7
- Added support for covered indexes when inheritance is off
- No longer always upsert on save for items with a '_id'
@ -973,7 +984,6 @@ Changes in v0.1.3
querying takes place
- A few minor bugfixes
Changes in v0.1.2
=================
- Query values may be processed before before being used in queries
@ -982,7 +992,6 @@ Changes in v0.1.2
- Added ``BooleanField``
- Added ``Document.reload()`` method
Changes in v0.1.1
=================
- Documents may now use capped collections

View File

@ -1,16 +1,19 @@
from mongoengine import *
connect('tumblelog')
connect("tumblelog")
class Comment(EmbeddedDocument):
content = StringField()
name = StringField(max_length=120)
class User(Document):
email = StringField(required=True)
first_name = StringField(max_length=50)
last_name = StringField(max_length=50)
class Post(Document):
title = StringField(max_length=120, required=True)
author = ReferenceField(User)
@ -18,54 +21,57 @@ class Post(Document):
comments = ListField(EmbeddedDocumentField(Comment))
# bugfix
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
class TextPost(Post):
content = StringField()
class ImagePost(Post):
image_path = StringField()
class LinkPost(Post):
link_url = StringField()
Post.drop_collection()
john = User(email='jdoe@example.com', first_name='John', last_name='Doe')
john = User(email="jdoe@example.com", first_name="John", last_name="Doe")
john.save()
post1 = TextPost(title='Fun with MongoEngine', author=john)
post1.content = 'Took a look at MongoEngine today, looks pretty cool.'
post1.tags = ['mongodb', 'mongoengine']
post1 = TextPost(title="Fun with MongoEngine", author=john)
post1.content = "Took a look at MongoEngine today, looks pretty cool."
post1.tags = ["mongodb", "mongoengine"]
post1.save()
post2 = LinkPost(title='MongoEngine Documentation', author=john)
post2.link_url = 'http://tractiondigital.com/labs/mongoengine/docs'
post2.tags = ['mongoengine']
post2 = LinkPost(title="MongoEngine Documentation", author=john)
post2.link_url = "http://tractiondigital.com/labs/mongoengine/docs"
post2.tags = ["mongoengine"]
post2.save()
print('ALL POSTS')
print("ALL POSTS")
print()
for post in Post.objects:
print(post.title)
#print '=' * post.title.count()
# print '=' * post.title.count()
print("=" * 20)
if isinstance(post, TextPost):
print(post.content)
if isinstance(post, LinkPost):
print('Link:', post.link_url)
print("Link:", post.link_url)
print()
print()
print('POSTS TAGGED \'MONGODB\'')
print("POSTS TAGGED 'MONGODB'")
print()
for post in Post.objects(tags='mongodb'):
for post in Post.objects(tags="mongodb"):
print(post.title)
print()
num_posts = Post.objects(tags='mongodb').count()
num_posts = Post.objects(tags="mongodb").count()
print('Found %d posts with tag "mongodb"' % num_posts)

View File

@ -20,29 +20,29 @@ import mongoengine
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
sys.path.insert(0, os.path.abspath(".."))
# -- General configuration -----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo']
extensions = ["sphinx.ext.autodoc", "sphinx.ext.todo"]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]
# The suffix of source filenames.
source_suffix = '.rst'
source_suffix = ".rst"
# The encoding of source files.
#source_encoding = 'utf-8'
# source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'index'
master_doc = "index"
# General information about the project.
project = u'MongoEngine'
copyright = u'2009, MongoEngine Authors'
project = u"MongoEngine"
copyright = u"2009, MongoEngine Authors"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@ -55,68 +55,66 @@ release = mongoengine.get_version()
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build.
#unused_docs = []
# unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = ['_build']
exclude_trees = ["_build"]
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'sphinx_rtd_theme'
html_theme = "sphinx_rtd_theme"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {
'canonical_url': 'http://docs.mongoengine.org/en/latest/'
}
html_theme_options = {"canonical_url": "http://docs.mongoengine.org/en/latest/"}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
@ -126,11 +124,11 @@ html_favicon = "favicon.ico"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = ['_static']
# html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
@ -138,69 +136,68 @@ html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
html_sidebars = {
'index': ['globaltoc.html', 'searchbox.html'],
'**': ['localtoc.html', 'relations.html', 'searchbox.html']
"index": ["globaltoc.html", "searchbox.html"],
"**": ["localtoc.html", "relations.html", "searchbox.html"],
}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If false, no module index is generated.
#html_use_modindex = True
# html_use_modindex = True
# If false, no index is generated.
#html_use_index = True
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# html_show_sourcelink = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''
# html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'MongoEnginedoc'
htmlhelp_basename = "MongoEnginedoc"
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
latex_paper_size = 'a4'
latex_paper_size = "a4"
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'MongoEngine.tex', 'MongoEngine Documentation',
'Ross Lawley', 'manual'),
("index", "MongoEngine.tex", "MongoEngine Documentation", "Ross Lawley", "manual")
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True
# latex_use_modindex = True
autoclass_content = 'both'
autoclass_content = "both"

View File

@ -714,11 +714,16 @@ subsequent calls to :meth:`~mongoengine.queryset.QuerySet.order_by`. ::
Shard keys
==========
If your collection is sharded, then you need to specify the shard key as a tuple,
using the :attr:`shard_key` attribute of :attr:`~mongoengine.Document.meta`.
This ensures that the shard key is sent with the query when calling the
:meth:`~mongoengine.document.Document.save` or
:meth:`~mongoengine.document.Document.update` method on an existing
If your collection is sharded by multiple keys, then you can improve shard
routing (and thus the performance of your application) by specifying the shard
key, using the :attr:`shard_key` attribute of
:attr:`~mongoengine.Document.meta`. The shard key should be defined as a tuple.
This ensures that the full shard key is sent with the query when calling
methods such as :meth:`~mongoengine.document.Document.save`,
:meth:`~mongoengine.document.Document.update`,
:meth:`~mongoengine.document.Document.modify`, or
:meth:`~mongoengine.document.Document.delete` on an existing
:class:`~mongoengine.Document` instance::
class LogEntry(Document):
@ -728,7 +733,8 @@ This ensures that the shard key is sent with the query when calling the
data = StringField()
meta = {
'shard_key': ('machine', 'timestamp',)
'shard_key': ('machine', 'timestamp'),
'indexes': ('machine', 'timestamp'),
}
.. _document-inheritance:

View File

@ -18,12 +18,17 @@ from mongoengine.queryset import *
from mongoengine.signals import *
__all__ = (list(document.__all__) + list(fields.__all__) +
list(connection.__all__) + list(queryset.__all__) +
list(signals.__all__) + list(errors.__all__))
__all__ = (
list(document.__all__)
+ list(fields.__all__)
+ list(connection.__all__)
+ list(queryset.__all__)
+ list(signals.__all__)
+ list(errors.__all__)
)
VERSION = (0, 18, 0)
VERSION = (0, 18, 2)
def get_version():
@ -31,7 +36,7 @@ def get_version():
For example, if `VERSION == (0, 10, 7)`, return '0.10.7'.
"""
return '.'.join(map(str, VERSION))
return ".".join(map(str, VERSION))
__version__ = get_version()

View File

@ -12,17 +12,22 @@ from mongoengine.base.metaclasses import *
__all__ = (
# common
'UPDATE_OPERATORS', '_document_registry', 'get_document',
"UPDATE_OPERATORS",
"_document_registry",
"get_document",
# datastructures
'BaseDict', 'BaseList', 'EmbeddedDocumentList', 'LazyReference',
"BaseDict",
"BaseList",
"EmbeddedDocumentList",
"LazyReference",
# document
'BaseDocument',
"BaseDocument",
# fields
'BaseField', 'ComplexBaseField', 'ObjectIdField', 'GeoJsonBaseField',
"BaseField",
"ComplexBaseField",
"ObjectIdField",
"GeoJsonBaseField",
# metaclasses
'DocumentMetaclass', 'TopLevelDocumentMetaclass'
"DocumentMetaclass",
"TopLevelDocumentMetaclass",
)

View File

@ -1,12 +1,25 @@
from mongoengine.errors import NotRegistered
__all__ = ('UPDATE_OPERATORS', 'get_document', '_document_registry')
__all__ = ("UPDATE_OPERATORS", "get_document", "_document_registry")
UPDATE_OPERATORS = {'set', 'unset', 'inc', 'dec', 'mul',
'pop', 'push', 'push_all', 'pull',
'pull_all', 'add_to_set', 'set_on_insert',
'min', 'max', 'rename'}
UPDATE_OPERATORS = {
"set",
"unset",
"inc",
"dec",
"mul",
"pop",
"push",
"push_all",
"pull",
"pull_all",
"add_to_set",
"set_on_insert",
"min",
"max",
"rename",
}
_document_registry = {}
@ -17,25 +30,33 @@ def get_document(name):
doc = _document_registry.get(name, None)
if not doc:
# Possible old style name
single_end = name.split('.')[-1]
compound_end = '.%s' % single_end
possible_match = [k for k in _document_registry
if k.endswith(compound_end) or k == single_end]
single_end = name.split(".")[-1]
compound_end = ".%s" % single_end
possible_match = [
k for k in _document_registry if k.endswith(compound_end) or k == single_end
]
if len(possible_match) == 1:
doc = _document_registry.get(possible_match.pop(), None)
if not doc:
raise NotRegistered("""
raise NotRegistered(
"""
`%s` has not been registered in the document registry.
Importing the document class automatically registers it, has it
been imported?
""".strip() % name)
""".strip()
% name
)
return doc
def _get_documents_by_db(connection_alias, default_connection_alias):
"""Get all registered Documents class attached to a given database"""
def get_doc_alias(doc_cls):
return doc_cls._meta.get('db_alias', default_connection_alias)
return [doc_cls for doc_cls in _document_registry.values()
if get_doc_alias(doc_cls) == connection_alias]
def get_doc_alias(doc_cls):
return doc_cls._meta.get("db_alias", default_connection_alias)
return [
doc_cls
for doc_cls in _document_registry.values()
if get_doc_alias(doc_cls) == connection_alias
]

View File

@ -7,24 +7,36 @@ from six import iteritems
from mongoengine.common import _import_class
from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
__all__ = ('BaseDict', 'StrictDict', '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"""
"""Decorator 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
# Can't use super() in the decorator.
result = parent_method(self, *args, **kwargs)
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"""
"""Decorator 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
# Can't use super() in the decorator.
result = parent_method(self, key, *args, **kwargs)
self._mark_as_changed(key)
return result
return wrapper
@ -36,7 +48,7 @@ class BaseDict(dict):
_name = None
def __init__(self, dict_items, instance, name):
BaseDocument = _import_class('BaseDocument')
BaseDocument = _import_class("BaseDocument")
if isinstance(instance, BaseDocument):
self._instance = weakref.proxy(instance)
@ -53,15 +65,15 @@ class BaseDict(dict):
def __getitem__(self, key):
value = super(BaseDict, self).__getitem__(key)
EmbeddedDocument = _import_class('EmbeddedDocument')
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):
value = BaseDict(value, None, '%s.%s' % (self._name, key))
value = BaseDict(value, None, "%s.%s" % (self._name, key))
super(BaseDict, self).__setitem__(key, value)
value._instance = self._instance
elif isinstance(value, list) and not isinstance(value, BaseList):
value = BaseList(value, None, '%s.%s' % (self._name, key))
value = BaseList(value, None, "%s.%s" % (self._name, key))
super(BaseDict, self).__setitem__(key, value)
value._instance = self._instance
return value
@ -85,9 +97,9 @@ class BaseDict(dict):
setdefault = mark_as_changed_wrapper(dict.setdefault)
def _mark_as_changed(self, key=None):
if hasattr(self._instance, '_mark_as_changed'):
if hasattr(self._instance, "_mark_as_changed"):
if key:
self._instance._mark_as_changed('%s.%s' % (self._name, key))
self._instance._mark_as_changed("%s.%s" % (self._name, key))
else:
self._instance._mark_as_changed(self._name)
@ -100,7 +112,7 @@ class BaseList(list):
_name = None
def __init__(self, list_items, instance, name):
BaseDocument = _import_class('BaseDocument')
BaseDocument = _import_class("BaseDocument")
if isinstance(instance, BaseDocument):
self._instance = weakref.proxy(instance)
@ -118,17 +130,17 @@ class BaseList(list):
# to parent's instance. This is buggy for now but would require more work to be handled properly
return value
EmbeddedDocument = _import_class('EmbeddedDocument')
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))
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))
value = BaseList(value, None, "%s.%s" % (self._name, key))
super(BaseList, self).__setitem__(key, value)
value._instance = self._instance
return value
@ -182,17 +194,14 @@ class BaseList(list):
return self.__getitem__(slice(i, j))
def _mark_as_changed(self, key=None):
if hasattr(self._instance, '_mark_as_changed'):
if hasattr(self._instance, "_mark_as_changed"):
if key:
self._instance._mark_as_changed(
'%s.%s' % (self._name, key % len(self))
)
self._instance._mark_as_changed("%s.%s" % (self._name, key % len(self)))
else:
self._instance._mark_as_changed(self._name)
class EmbeddedDocumentList(BaseList):
def __init__(self, list_items, instance, name):
super(EmbeddedDocumentList, self).__init__(list_items, instance, name)
self._instance = instance
@ -277,12 +286,10 @@ class EmbeddedDocumentList(BaseList):
"""
values = self.__only_matches(self, kwargs)
if len(values) == 0:
raise DoesNotExist(
'%s matching query does not exist.' % self._name
)
raise DoesNotExist("%s matching query does not exist." % self._name)
elif len(values) > 1:
raise MultipleObjectsReturned(
'%d items returned, instead of 1' % len(values)
"%d items returned, instead of 1" % len(values)
)
return values[0]
@ -363,7 +370,7 @@ class EmbeddedDocumentList(BaseList):
class StrictDict(object):
__slots__ = ()
_special_fields = {'get', 'pop', 'iteritems', 'items', 'keys', 'create'}
_special_fields = {"get", "pop", "iteritems", "items", "keys", "create"}
_classes = {}
def __init__(self, **kwargs):
@ -371,14 +378,14 @@ class StrictDict(object):
setattr(self, k, v)
def __getitem__(self, key):
key = '_reserved_' + key if key in self._special_fields else key
key = "_reserved_" + key if key in self._special_fields else key
try:
return getattr(self, key)
except AttributeError:
raise KeyError(key)
def __setitem__(self, key, value):
key = '_reserved_' + key if key in self._special_fields else key
key = "_reserved_" + key if key in self._special_fields else key
return setattr(self, key, value)
def __contains__(self, key):
@ -425,27 +432,32 @@ class StrictDict(object):
@classmethod
def create(cls, allowed_keys):
allowed_keys_tuple = tuple(('_reserved_' + k if k in cls._special_fields else k) for k in allowed_keys)
allowed_keys_tuple = tuple(
("_reserved_" + k if k in cls._special_fields else k) for k in allowed_keys
)
allowed_keys = frozenset(allowed_keys_tuple)
if allowed_keys not in cls._classes:
class SpecificStrictDict(cls):
__slots__ = allowed_keys_tuple
def __repr__(self):
return '{%s}' % ', '.join('"{0!s}": {1!r}'.format(k, v) for k, v in self.items())
return "{%s}" % ", ".join(
'"{0!s}": {1!r}'.format(k, v) for k, v in self.items()
)
cls._classes[allowed_keys] = SpecificStrictDict
return cls._classes[allowed_keys]
class LazyReference(DBRef):
__slots__ = ('_cached_doc', 'passthrough', 'document_type')
__slots__ = ("_cached_doc", "passthrough", "document_type")
def fetch(self, force=False):
if not self._cached_doc or force:
self._cached_doc = self.document_type.objects.get(pk=self.pk)
if not self._cached_doc:
raise DoesNotExist('Trying to dereference unknown document %s' % (self))
raise DoesNotExist("Trying to dereference unknown document %s" % (self))
return self._cached_doc
@property
@ -456,7 +468,9 @@ class LazyReference(DBRef):
self.document_type = document_type
self._cached_doc = cached_doc
self.passthrough = passthrough
super(LazyReference, self).__init__(self.document_type._get_collection_name(), pk)
super(LazyReference, self).__init__(
self.document_type._get_collection_name(), pk
)
def __getitem__(self, name):
if not self.passthrough:
@ -465,7 +479,7 @@ class LazyReference(DBRef):
return document[name]
def __getattr__(self, name):
if not object.__getattribute__(self, 'passthrough'):
if not object.__getattribute__(self, "passthrough"):
raise AttributeError()
document = self.fetch()
try:

File diff suppressed because it is too large Load Diff

View File

@ -8,13 +8,11 @@ import six
from six import iteritems
from mongoengine.base.common import UPDATE_OPERATORS
from mongoengine.base.datastructures import (BaseDict, BaseList,
EmbeddedDocumentList)
from mongoengine.base.datastructures import BaseDict, BaseList, EmbeddedDocumentList
from mongoengine.common import _import_class
from mongoengine.errors import DeprecatedError, ValidationError
__all__ = ('BaseField', 'ComplexBaseField', 'ObjectIdField',
'GeoJsonBaseField')
__all__ = ("BaseField", "ComplexBaseField", "ObjectIdField", "GeoJsonBaseField")
class BaseField(object):
@ -23,6 +21,7 @@ class BaseField(object):
.. versionchanged:: 0.5 - added verbose and help text
"""
name = None
_geo_index = False
_auto_gen = False # Call `generate` to generate a value
@ -34,10 +33,21 @@ class BaseField(object):
creation_counter = 0
auto_creation_counter = -1
def __init__(self, db_field=None, name=None, required=False, default=None,
unique=False, unique_with=None, primary_key=False,
validation=None, choices=None, null=False, sparse=False,
**kwargs):
def __init__(
self,
db_field=None,
name=None,
required=False,
default=None,
unique=False,
unique_with=None,
primary_key=False,
validation=None,
choices=None,
null=False,
sparse=False,
**kwargs
):
"""
:param db_field: The database field to store this field in
(defaults to the name of the field)
@ -65,7 +75,7 @@ class BaseField(object):
existing attributes. Common metadata includes `verbose_name` and
`help_text`.
"""
self.db_field = (db_field or name) if not primary_key else '_id'
self.db_field = (db_field or name) if not primary_key else "_id"
if name:
msg = 'Field\'s "name" attribute deprecated in favour of "db_field"'
@ -82,17 +92,16 @@ class BaseField(object):
self._owner_document = None
# Make sure db_field is a string (if it's explicitly defined).
if (
self.db_field is not None and
not isinstance(self.db_field, six.string_types)
if self.db_field is not None and not isinstance(
self.db_field, six.string_types
):
raise TypeError('db_field should be a string.')
raise TypeError("db_field should be a string.")
# Make sure db_field doesn't contain any forbidden characters.
if isinstance(self.db_field, six.string_types) and (
'.' in self.db_field or
'\0' in self.db_field or
self.db_field.startswith('$')
"." in self.db_field
or "\0" in self.db_field
or self.db_field.startswith("$")
):
raise ValueError(
'field names cannot contain dots (".") or null characters '
@ -102,15 +111,17 @@ class BaseField(object):
# Detect and report conflicts between metadata and base properties.
conflicts = set(dir(self)) & set(kwargs)
if conflicts:
raise TypeError('%s already has attribute(s): %s' % (
self.__class__.__name__, ', '.join(conflicts)))
raise TypeError(
"%s already has attribute(s): %s"
% (self.__class__.__name__, ", ".join(conflicts))
)
# Assign metadata to the instance
# This efficient method is available because no __slots__ are defined.
self.__dict__.update(kwargs)
# Adjust the appropriate creation counter, and save our local copy.
if self.db_field == '_id':
if self.db_field == "_id":
self.creation_counter = BaseField.auto_creation_counter
BaseField.auto_creation_counter -= 1
else:
@ -128,10 +139,9 @@ class BaseField(object):
return instance._data.get(self.name)
def __set__(self, instance, value):
"""Descriptor for assigning a value to a field in a document.
"""
# If setting to None and there is a default
# Then set the value to the default value
"""Descriptor for assigning a value to a field in a document."""
# If setting to None and there is a default value provided for this
# field, then set the value to the default value.
if value is None:
if self.null:
value = None
@ -142,24 +152,29 @@ class BaseField(object):
if instance._initialised:
try:
if (self.name not in instance._data or
instance._data[self.name] != value):
value_has_changed = (
self.name not in instance._data
or instance._data[self.name] != value
)
if value_has_changed:
instance._mark_as_changed(self.name)
except Exception:
# Values cant be compared eg: naive and tz datetimes
# So mark it as changed
# Some values can't be compared and throw an error when we
# attempt to do so (e.g. tz-naive and tz-aware datetimes).
# Mark the field as changed in such cases.
instance._mark_as_changed(self.name)
EmbeddedDocument = _import_class('EmbeddedDocument')
EmbeddedDocument = _import_class("EmbeddedDocument")
if isinstance(value, EmbeddedDocument):
value._instance = weakref.proxy(instance)
elif isinstance(value, (list, tuple)):
for v in value:
if isinstance(v, EmbeddedDocument):
v._instance = weakref.proxy(instance)
instance._data[self.name] = value
def error(self, message='', errors=None, field_name=None):
def error(self, message="", errors=None, field_name=None):
"""Raise a ValidationError."""
field_name = field_name if field_name else self.name
raise ValidationError(message, errors=errors, field_name=field_name)
@ -176,11 +191,11 @@ class BaseField(object):
"""Helper method to call to_mongo with proper inputs."""
f_inputs = self.to_mongo.__code__.co_varnames
ex_vars = {}
if 'fields' in f_inputs:
ex_vars['fields'] = fields
if "fields" in f_inputs:
ex_vars["fields"] = fields
if 'use_db_field' in f_inputs:
ex_vars['use_db_field'] = use_db_field
if "use_db_field" in f_inputs:
ex_vars["use_db_field"] = use_db_field
return self.to_mongo(value, **ex_vars)
@ -195,8 +210,8 @@ class BaseField(object):
pass
def _validate_choices(self, value):
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
Document = _import_class("Document")
EmbeddedDocument = _import_class("EmbeddedDocument")
choice_list = self.choices
if isinstance(next(iter(choice_list)), (list, tuple)):
@ -207,15 +222,13 @@ class BaseField(object):
if isinstance(value, (Document, EmbeddedDocument)):
if not any(isinstance(value, c) for c in choice_list):
self.error(
'Value must be an instance of %s' % (
six.text_type(choice_list)
)
"Value must be an instance of %s" % (six.text_type(choice_list))
)
# Choices which are types other than Documents
else:
values = value if isinstance(value, (list, tuple)) else [value]
if len(set(values) - set(choice_list)):
self.error('Value must be one of %s' % six.text_type(choice_list))
self.error("Value must be one of %s" % six.text_type(choice_list))
def _validate(self, value, **kwargs):
# Check the Choices Constraint
@ -231,13 +244,17 @@ class BaseField(object):
# in favor of having validation raising a ValidationError
ret = self.validation(value)
if ret is not None:
raise DeprecatedError('validation argument for `%s` must not return anything, '
'it should raise a ValidationError if validation fails' % self.name)
raise DeprecatedError(
"validation argument for `%s` must not return anything, "
"it should raise a ValidationError if validation fails"
% self.name
)
except ValidationError as ex:
self.error(str(ex))
else:
raise ValueError('validation argument for `"%s"` must be a '
'callable.' % self.name)
raise ValueError(
'validation argument for `"%s"` must be a ' "callable." % self.name
)
self.validate(value, **kwargs)
@ -271,35 +288,41 @@ class ComplexBaseField(BaseField):
# Document class being used rather than a document object
return self
ReferenceField = _import_class('ReferenceField')
GenericReferenceField = _import_class('GenericReferenceField')
EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField')
ReferenceField = _import_class("ReferenceField")
GenericReferenceField = _import_class("GenericReferenceField")
EmbeddedDocumentListField = _import_class("EmbeddedDocumentListField")
auto_dereference = instance._fields[self.name]._auto_dereference
dereference = (auto_dereference and
(self.field is None or isinstance(self.field,
(GenericReferenceField, ReferenceField))))
dereference = auto_dereference and (
self.field is None
or isinstance(self.field, (GenericReferenceField, ReferenceField))
)
_dereference = _import_class('DeReference')()
_dereference = _import_class("DeReference")()
if (instance._initialised and
dereference and
instance._data.get(self.name) and
not getattr(instance._data[self.name], '_dereferenced', False)):
if (
instance._initialised
and dereference
and instance._data.get(self.name)
and not getattr(instance._data[self.name], "_dereferenced", False)
):
instance._data[self.name] = _dereference(
instance._data.get(self.name), max_depth=1, instance=instance,
name=self.name
instance._data.get(self.name),
max_depth=1,
instance=instance,
name=self.name,
)
if hasattr(instance._data[self.name], '_dereferenced'):
if hasattr(instance._data[self.name], "_dereferenced"):
instance._data[self.name]._dereferenced = True
value = super(ComplexBaseField, self).__get__(instance, owner)
# Convert lists / values so we can watch for any changes on them
if isinstance(value, (list, tuple)):
if (issubclass(type(self), EmbeddedDocumentListField) and
not isinstance(value, EmbeddedDocumentList)):
if issubclass(type(self), EmbeddedDocumentListField) and not isinstance(
value, EmbeddedDocumentList
):
value = EmbeddedDocumentList(value, instance, self.name)
elif not isinstance(value, BaseList):
value = BaseList(value, instance, self.name)
@ -308,12 +331,13 @@ class ComplexBaseField(BaseField):
value = BaseDict(value, instance, self.name)
instance._data[self.name] = value
if (auto_dereference and instance._initialised and
isinstance(value, (BaseList, BaseDict)) and
not value._dereferenced):
value = _dereference(
value, max_depth=1, instance=instance, name=self.name
)
if (
auto_dereference
and instance._initialised
and isinstance(value, (BaseList, BaseDict))
and not value._dereferenced
):
value = _dereference(value, max_depth=1, instance=instance, name=self.name)
value._dereferenced = True
instance._data[self.name] = value
@ -324,16 +348,16 @@ class ComplexBaseField(BaseField):
if isinstance(value, six.string_types):
return value
if hasattr(value, 'to_python'):
if hasattr(value, "to_python"):
return value.to_python()
BaseDocument = _import_class('BaseDocument')
BaseDocument = _import_class("BaseDocument")
if isinstance(value, BaseDocument):
# Something is wrong, return the value as it is
return value
is_list = False
if not hasattr(value, 'items'):
if not hasattr(value, "items"):
try:
is_list = True
value = {idx: v for idx, v in enumerate(value)}
@ -342,50 +366,54 @@ class ComplexBaseField(BaseField):
if self.field:
self.field._auto_dereference = self._auto_dereference
value_dict = {key: self.field.to_python(item)
for key, item in value.items()}
value_dict = {
key: self.field.to_python(item) for key, item in value.items()
}
else:
Document = _import_class('Document')
Document = _import_class("Document")
value_dict = {}
for k, v in value.items():
if isinstance(v, Document):
# We need the id from the saved object to create the DBRef
if v.pk is None:
self.error('You can only reference documents once they'
' have been saved to the database')
self.error(
"You can only reference documents once they"
" have been saved to the database"
)
collection = v._get_collection_name()
value_dict[k] = DBRef(collection, v.pk)
elif hasattr(v, 'to_python'):
elif hasattr(v, "to_python"):
value_dict[k] = v.to_python()
else:
value_dict[k] = self.to_python(v)
if is_list: # Convert back to a list
return [v for _, v in sorted(value_dict.items(),
key=operator.itemgetter(0))]
return [
v for _, v in sorted(value_dict.items(), key=operator.itemgetter(0))
]
return value_dict
def to_mongo(self, value, use_db_field=True, fields=None):
"""Convert a Python type to a MongoDB-compatible type."""
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
GenericReferenceField = _import_class('GenericReferenceField')
Document = _import_class("Document")
EmbeddedDocument = _import_class("EmbeddedDocument")
GenericReferenceField = _import_class("GenericReferenceField")
if isinstance(value, six.string_types):
return value
if hasattr(value, 'to_mongo'):
if hasattr(value, "to_mongo"):
if isinstance(value, Document):
return GenericReferenceField().to_mongo(value)
cls = value.__class__
val = value.to_mongo(use_db_field, fields)
# If it's a document that is not inherited add _cls
if isinstance(value, EmbeddedDocument):
val['_cls'] = cls.__name__
val["_cls"] = cls.__name__
return val
is_list = False
if not hasattr(value, 'items'):
if not hasattr(value, "items"):
try:
is_list = True
value = {k: v for k, v in enumerate(value)}
@ -403,39 +431,42 @@ class ComplexBaseField(BaseField):
if isinstance(v, Document):
# We need the id from the saved object to create the DBRef
if v.pk is None:
self.error('You can only reference documents once they'
' have been saved to the database')
self.error(
"You can only reference documents once they"
" have been saved to the database"
)
# If its a document that is not inheritable it won't have
# any _cls data so make it a generic reference allows
# us to dereference
meta = getattr(v, '_meta', {})
allow_inheritance = meta.get('allow_inheritance')
meta = getattr(v, "_meta", {})
allow_inheritance = meta.get("allow_inheritance")
if not allow_inheritance and not self.field:
value_dict[k] = GenericReferenceField().to_mongo(v)
else:
collection = v._get_collection_name()
value_dict[k] = DBRef(collection, v.pk)
elif hasattr(v, 'to_mongo'):
elif hasattr(v, "to_mongo"):
cls = v.__class__
val = v.to_mongo(use_db_field, fields)
# If it's a document that is not inherited add _cls
if isinstance(v, (Document, EmbeddedDocument)):
val['_cls'] = cls.__name__
val["_cls"] = cls.__name__
value_dict[k] = val
else:
value_dict[k] = self.to_mongo(v, use_db_field, fields)
if is_list: # Convert back to a list
return [v for _, v in sorted(value_dict.items(),
key=operator.itemgetter(0))]
return [
v for _, v in sorted(value_dict.items(), key=operator.itemgetter(0))
]
return value_dict
def validate(self, value):
"""If field is provided ensure the value is valid."""
errors = {}
if self.field:
if hasattr(value, 'iteritems') or hasattr(value, 'items'):
if hasattr(value, "iteritems") or hasattr(value, "items"):
sequence = iteritems(value)
else:
sequence = enumerate(value)
@ -449,11 +480,10 @@ class ComplexBaseField(BaseField):
if errors:
field_class = self.field.__class__.__name__
self.error('Invalid %s item (%s)' % (field_class, value),
errors=errors)
self.error("Invalid %s item (%s)" % (field_class, value), errors=errors)
# Don't allow empty values if required
if self.required and not value:
self.error('Field is required and cannot be empty')
self.error("Field is required and cannot be empty")
def prepare_query_value(self, op, value):
return self.to_mongo(value)
@ -496,7 +526,7 @@ class ObjectIdField(BaseField):
try:
ObjectId(six.text_type(value))
except Exception:
self.error('Invalid Object ID')
self.error("Invalid Object ID")
class GeoJsonBaseField(BaseField):
@ -506,14 +536,14 @@ class GeoJsonBaseField(BaseField):
"""
_geo_index = pymongo.GEOSPHERE
_type = 'GeoBase'
_type = "GeoBase"
def __init__(self, auto_index=True, *args, **kwargs):
"""
:param bool auto_index: Automatically create a '2dsphere' index.\
Defaults to `True`.
"""
self._name = '%sField' % self._type
self._name = "%sField" % self._type
if not auto_index:
self._geo_index = False
super(GeoJsonBaseField, self).__init__(*args, **kwargs)
@ -521,57 +551,58 @@ class GeoJsonBaseField(BaseField):
def validate(self, value):
"""Validate the GeoJson object based on its type."""
if isinstance(value, dict):
if set(value.keys()) == {'type', 'coordinates'}:
if value['type'] != self._type:
self.error('%s type must be "%s"' %
(self._name, self._type))
return self.validate(value['coordinates'])
if set(value.keys()) == {"type", "coordinates"}:
if value["type"] != self._type:
self.error('%s type must be "%s"' % (self._name, self._type))
return self.validate(value["coordinates"])
else:
self.error('%s can only accept a valid GeoJson dictionary'
' or lists of (x, y)' % self._name)
self.error(
"%s can only accept a valid GeoJson dictionary"
" or lists of (x, y)" % self._name
)
return
elif not isinstance(value, (list, tuple)):
self.error('%s can only accept lists of [x, y]' % self._name)
self.error("%s can only accept lists of [x, y]" % self._name)
return
validate = getattr(self, '_validate_%s' % self._type.lower())
validate = getattr(self, "_validate_%s" % self._type.lower())
error = validate(value)
if error:
self.error(error)
def _validate_polygon(self, value, top_level=True):
if not isinstance(value, (list, tuple)):
return 'Polygons must contain list of linestrings'
return "Polygons must contain list of linestrings"
# Quick and dirty validator
try:
value[0][0][0]
except (TypeError, IndexError):
return 'Invalid Polygon must contain at least one valid linestring'
return "Invalid Polygon must contain at least one valid linestring"
errors = []
for val in value:
error = self._validate_linestring(val, False)
if not error and val[0] != val[-1]:
error = 'LineStrings must start and end at the same point'
error = "LineStrings must start and end at the same point"
if error and error not in errors:
errors.append(error)
if errors:
if top_level:
return 'Invalid Polygon:\n%s' % ', '.join(errors)
return "Invalid Polygon:\n%s" % ", ".join(errors)
else:
return '%s' % ', '.join(errors)
return "%s" % ", ".join(errors)
def _validate_linestring(self, value, top_level=True):
"""Validate a linestring."""
if not isinstance(value, (list, tuple)):
return 'LineStrings must contain list of coordinate pairs'
return "LineStrings must contain list of coordinate pairs"
# Quick and dirty validator
try:
value[0][0]
except (TypeError, IndexError):
return 'Invalid LineString must contain at least one valid point'
return "Invalid LineString must contain at least one valid point"
errors = []
for val in value:
@ -580,29 +611,30 @@ class GeoJsonBaseField(BaseField):
errors.append(error)
if errors:
if top_level:
return 'Invalid LineString:\n%s' % ', '.join(errors)
return "Invalid LineString:\n%s" % ", ".join(errors)
else:
return '%s' % ', '.join(errors)
return "%s" % ", ".join(errors)
def _validate_point(self, value):
"""Validate each set of coords"""
if not isinstance(value, (list, tuple)):
return 'Points must be a list of coordinate pairs'
return "Points must be a list of coordinate pairs"
elif not len(value) == 2:
return 'Value (%s) must be a two-dimensional point' % repr(value)
elif (not isinstance(value[0], (float, int)) or
not isinstance(value[1], (float, int))):
return 'Both values (%s) in point must be float or int' % repr(value)
return "Value (%s) must be a two-dimensional point" % repr(value)
elif not isinstance(value[0], (float, int)) or not isinstance(
value[1], (float, int)
):
return "Both values (%s) in point must be float or int" % repr(value)
def _validate_multipoint(self, value):
if not isinstance(value, (list, tuple)):
return 'MultiPoint must be a list of Point'
return "MultiPoint must be a list of Point"
# Quick and dirty validator
try:
value[0][0]
except (TypeError, IndexError):
return 'Invalid MultiPoint must contain at least one valid point'
return "Invalid MultiPoint must contain at least one valid point"
errors = []
for point in value:
@ -611,17 +643,17 @@ class GeoJsonBaseField(BaseField):
errors.append(error)
if errors:
return '%s' % ', '.join(errors)
return "%s" % ", ".join(errors)
def _validate_multilinestring(self, value, top_level=True):
if not isinstance(value, (list, tuple)):
return 'MultiLineString must be a list of LineString'
return "MultiLineString must be a list of LineString"
# Quick and dirty validator
try:
value[0][0][0]
except (TypeError, IndexError):
return 'Invalid MultiLineString must contain at least one valid linestring'
return "Invalid MultiLineString must contain at least one valid linestring"
errors = []
for linestring in value:
@ -631,19 +663,19 @@ class GeoJsonBaseField(BaseField):
if errors:
if top_level:
return 'Invalid MultiLineString:\n%s' % ', '.join(errors)
return "Invalid MultiLineString:\n%s" % ", ".join(errors)
else:
return '%s' % ', '.join(errors)
return "%s" % ", ".join(errors)
def _validate_multipolygon(self, value):
if not isinstance(value, (list, tuple)):
return 'MultiPolygon must be a list of Polygon'
return "MultiPolygon must be a list of Polygon"
# Quick and dirty validator
try:
value[0][0][0][0]
except (TypeError, IndexError):
return 'Invalid MultiPolygon must contain at least one valid Polygon'
return "Invalid MultiPolygon must contain at least one valid Polygon"
errors = []
for polygon in value:
@ -652,9 +684,9 @@ class GeoJsonBaseField(BaseField):
errors.append(error)
if errors:
return 'Invalid MultiPolygon:\n%s' % ', '.join(errors)
return "Invalid MultiPolygon:\n%s" % ", ".join(errors)
def to_mongo(self, value):
if isinstance(value, dict):
return value
return SON([('type', self._type), ('coordinates', value)])
return SON([("type", self._type), ("coordinates", value)])

View File

@ -1,3 +1,4 @@
import itertools
import warnings
import six
@ -7,12 +8,15 @@ from mongoengine.base.common import _document_registry
from mongoengine.base.fields import BaseField, ComplexBaseField, ObjectIdField
from mongoengine.common import _import_class
from mongoengine.errors import InvalidDocumentError
from mongoengine.queryset import (DO_NOTHING, DoesNotExist,
MultipleObjectsReturned,
QuerySetManager)
from mongoengine.queryset import (
DO_NOTHING,
DoesNotExist,
MultipleObjectsReturned,
QuerySetManager,
)
__all__ = ('DocumentMetaclass', 'TopLevelDocumentMetaclass')
__all__ = ("DocumentMetaclass", "TopLevelDocumentMetaclass")
class DocumentMetaclass(type):
@ -24,44 +28,46 @@ class DocumentMetaclass(type):
super_new = super(DocumentMetaclass, mcs).__new__
# If a base class just call super
metaclass = attrs.get('my_metaclass')
metaclass = attrs.get("my_metaclass")
if metaclass and issubclass(metaclass, DocumentMetaclass):
return super_new(mcs, name, bases, attrs)
attrs['_is_document'] = attrs.get('_is_document', False)
attrs['_cached_reference_fields'] = []
attrs["_is_document"] = attrs.get("_is_document", False)
attrs["_cached_reference_fields"] = []
# EmbeddedDocuments could have meta data for inheritance
if 'meta' in attrs:
attrs['_meta'] = attrs.pop('meta')
if "meta" in attrs:
attrs["_meta"] = attrs.pop("meta")
# EmbeddedDocuments should inherit meta data
if '_meta' not in attrs:
if "_meta" not in attrs:
meta = MetaDict()
for base in flattened_bases[::-1]:
# Add any mixin metadata from plain objects
if hasattr(base, 'meta'):
if hasattr(base, "meta"):
meta.merge(base.meta)
elif hasattr(base, '_meta'):
elif hasattr(base, "_meta"):
meta.merge(base._meta)
attrs['_meta'] = meta
attrs['_meta']['abstract'] = False # 789: EmbeddedDocument shouldn't inherit abstract
attrs["_meta"] = meta
attrs["_meta"][
"abstract"
] = False # 789: EmbeddedDocument shouldn't inherit abstract
# If allow_inheritance is True, add a "_cls" string field to the attrs
if attrs['_meta'].get('allow_inheritance'):
StringField = _import_class('StringField')
attrs['_cls'] = StringField()
if attrs["_meta"].get("allow_inheritance"):
StringField = _import_class("StringField")
attrs["_cls"] = StringField()
# Handle document Fields
# Merge all fields from subclasses
doc_fields = {}
for base in flattened_bases[::-1]:
if hasattr(base, '_fields'):
if hasattr(base, "_fields"):
doc_fields.update(base._fields)
# Standard object mixin - merge in any Fields
if not hasattr(base, '_meta'):
if not hasattr(base, "_meta"):
base_fields = {}
for attr_name, attr_value in iteritems(base.__dict__):
if not isinstance(attr_value, BaseField):
@ -84,27 +90,31 @@ class DocumentMetaclass(type):
doc_fields[attr_name] = attr_value
# Count names to ensure no db_field redefinitions
field_names[attr_value.db_field] = field_names.get(
attr_value.db_field, 0) + 1
field_names[attr_value.db_field] = (
field_names.get(attr_value.db_field, 0) + 1
)
# Ensure no duplicate db_fields
duplicate_db_fields = [k for k, v in field_names.items() if v > 1]
if duplicate_db_fields:
msg = ('Multiple db_fields defined for: %s ' %
', '.join(duplicate_db_fields))
msg = "Multiple db_fields defined for: %s " % ", ".join(duplicate_db_fields)
raise InvalidDocumentError(msg)
# Set _fields and db_field maps
attrs['_fields'] = doc_fields
attrs['_db_field_map'] = {k: getattr(v, 'db_field', k)
for k, v in doc_fields.items()}
attrs['_reverse_db_field_map'] = {
v: k for k, v in attrs['_db_field_map'].items()
attrs["_fields"] = doc_fields
attrs["_db_field_map"] = {
k: getattr(v, "db_field", k) for k, v in doc_fields.items()
}
attrs["_reverse_db_field_map"] = {
v: k for k, v in attrs["_db_field_map"].items()
}
attrs['_fields_ordered'] = tuple(i[1] for i in sorted(
(v.creation_counter, v.name)
for v in itervalues(doc_fields)))
attrs["_fields_ordered"] = tuple(
i[1]
for i in sorted(
(v.creation_counter, v.name) for v in itervalues(doc_fields)
)
)
#
# Set document hierarchy
@ -112,32 +122,34 @@ class DocumentMetaclass(type):
superclasses = ()
class_name = [name]
for base in flattened_bases:
if (not getattr(base, '_is_base_cls', True) and
not getattr(base, '_meta', {}).get('abstract', True)):
if not getattr(base, "_is_base_cls", True) and not getattr(
base, "_meta", {}
).get("abstract", True):
# Collate hierarchy for _cls and _subclasses
class_name.append(base.__name__)
if hasattr(base, '_meta'):
if hasattr(base, "_meta"):
# Warn if allow_inheritance isn't set and prevent
# inheritance of classes where inheritance is set to False
allow_inheritance = base._meta.get('allow_inheritance')
if not allow_inheritance and not base._meta.get('abstract'):
raise ValueError('Document %s may not be subclassed. '
'To enable inheritance, use the "allow_inheritance" meta attribute.' %
base.__name__)
allow_inheritance = base._meta.get("allow_inheritance")
if not allow_inheritance and not base._meta.get("abstract"):
raise ValueError(
"Document %s may not be subclassed. "
'To enable inheritance, use the "allow_inheritance" meta attribute.'
% base.__name__
)
# Get superclasses from last base superclass
document_bases = [b for b in flattened_bases
if hasattr(b, '_class_name')]
document_bases = [b for b in flattened_bases if hasattr(b, "_class_name")]
if document_bases:
superclasses = document_bases[0]._superclasses
superclasses += (document_bases[0]._class_name, )
superclasses += (document_bases[0]._class_name,)
_cls = '.'.join(reversed(class_name))
attrs['_class_name'] = _cls
attrs['_superclasses'] = superclasses
attrs['_subclasses'] = (_cls, )
attrs['_types'] = attrs['_subclasses'] # TODO depreciate _types
_cls = ".".join(reversed(class_name))
attrs["_class_name"] = _cls
attrs["_superclasses"] = superclasses
attrs["_subclasses"] = (_cls,)
attrs["_types"] = attrs["_subclasses"] # TODO depreciate _types
# Create the new_class
new_class = super_new(mcs, name, bases, attrs)
@ -148,8 +160,12 @@ class DocumentMetaclass(type):
base._subclasses += (_cls,)
base._types = base._subclasses # TODO depreciate _types
(Document, EmbeddedDocument, DictField,
CachedReferenceField) = mcs._import_classes()
(
Document,
EmbeddedDocument,
DictField,
CachedReferenceField,
) = mcs._import_classes()
if issubclass(new_class, Document):
new_class._collection = None
@ -168,52 +184,55 @@ class DocumentMetaclass(type):
for val in new_class.__dict__.values():
if isinstance(val, classmethod):
f = val.__get__(new_class)
if hasattr(f, '__func__') and not hasattr(f, 'im_func'):
f.__dict__.update({'im_func': getattr(f, '__func__')})
if hasattr(f, '__self__') and not hasattr(f, 'im_self'):
f.__dict__.update({'im_self': getattr(f, '__self__')})
if hasattr(f, "__func__") and not hasattr(f, "im_func"):
f.__dict__.update({"im_func": getattr(f, "__func__")})
if hasattr(f, "__self__") and not hasattr(f, "im_self"):
f.__dict__.update({"im_self": getattr(f, "__self__")})
# Handle delete rules
for field in itervalues(new_class._fields):
f = field
if f.owner_document is None:
f.owner_document = new_class
delete_rule = getattr(f, 'reverse_delete_rule', DO_NOTHING)
delete_rule = getattr(f, "reverse_delete_rule", DO_NOTHING)
if isinstance(f, CachedReferenceField):
if issubclass(new_class, EmbeddedDocument):
raise InvalidDocumentError('CachedReferenceFields is not '
'allowed in EmbeddedDocuments')
raise InvalidDocumentError(
"CachedReferenceFields is not allowed in EmbeddedDocuments"
)
if f.auto_sync:
f.start_listener()
f.document_type._cached_reference_fields.append(f)
if isinstance(f, ComplexBaseField) and hasattr(f, 'field'):
delete_rule = getattr(f.field,
'reverse_delete_rule',
DO_NOTHING)
if isinstance(f, ComplexBaseField) and hasattr(f, "field"):
delete_rule = getattr(f.field, "reverse_delete_rule", DO_NOTHING)
if isinstance(f, DictField) and delete_rule != DO_NOTHING:
msg = ('Reverse delete rules are not supported '
'for %s (field: %s)' %
(field.__class__.__name__, field.name))
msg = (
"Reverse delete rules are not supported "
"for %s (field: %s)" % (field.__class__.__name__, field.name)
)
raise InvalidDocumentError(msg)
f = field.field
if delete_rule != DO_NOTHING:
if issubclass(new_class, EmbeddedDocument):
msg = ('Reverse delete rules are not supported for '
'EmbeddedDocuments (field: %s)' % field.name)
msg = (
"Reverse delete rules are not supported for "
"EmbeddedDocuments (field: %s)" % field.name
)
raise InvalidDocumentError(msg)
f.document_type.register_delete_rule(new_class,
field.name, delete_rule)
f.document_type.register_delete_rule(new_class, field.name, delete_rule)
if (field.name and hasattr(Document, field.name) and
EmbeddedDocument not in new_class.mro()):
msg = ('%s is a document method and not a valid '
'field name' % field.name)
if (
field.name
and hasattr(Document, field.name)
and EmbeddedDocument not in new_class.mro()
):
msg = "%s is a document method and not a valid field name" % field.name
raise InvalidDocumentError(msg)
return new_class
@ -238,10 +257,10 @@ class DocumentMetaclass(type):
@classmethod
def _import_classes(mcs):
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
DictField = _import_class('DictField')
CachedReferenceField = _import_class('CachedReferenceField')
Document = _import_class("Document")
EmbeddedDocument = _import_class("EmbeddedDocument")
DictField = _import_class("DictField")
CachedReferenceField = _import_class("CachedReferenceField")
return Document, EmbeddedDocument, DictField, CachedReferenceField
@ -255,65 +274,67 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
super_new = super(TopLevelDocumentMetaclass, mcs).__new__
# Set default _meta data if base class, otherwise get user defined meta
if attrs.get('my_metaclass') == TopLevelDocumentMetaclass:
if attrs.get("my_metaclass") == TopLevelDocumentMetaclass:
# defaults
attrs['_meta'] = {
'abstract': True,
'max_documents': None,
'max_size': None,
'ordering': [], # default ordering applied at runtime
'indexes': [], # indexes to be ensured at runtime
'id_field': None,
'index_background': False,
'index_drop_dups': False,
'index_opts': None,
'delete_rules': None,
attrs["_meta"] = {
"abstract": True,
"max_documents": None,
"max_size": None,
"ordering": [], # default ordering applied at runtime
"indexes": [], # indexes to be ensured at runtime
"id_field": None,
"index_background": False,
"index_drop_dups": False,
"index_opts": None,
"delete_rules": None,
# allow_inheritance can be True, False, and None. True means
# "allow inheritance", False means "don't allow inheritance",
# None means "do whatever your parent does, or don't allow
# inheritance if you're a top-level class".
'allow_inheritance': None,
"allow_inheritance": None,
}
attrs['_is_base_cls'] = True
attrs['_meta'].update(attrs.get('meta', {}))
attrs["_is_base_cls"] = True
attrs["_meta"].update(attrs.get("meta", {}))
else:
attrs['_meta'] = attrs.get('meta', {})
attrs["_meta"] = attrs.get("meta", {})
# Explicitly set abstract to false unless set
attrs['_meta']['abstract'] = attrs['_meta'].get('abstract', False)
attrs['_is_base_cls'] = False
attrs["_meta"]["abstract"] = attrs["_meta"].get("abstract", False)
attrs["_is_base_cls"] = False
# Set flag marking as document class - as opposed to an object mixin
attrs['_is_document'] = True
attrs["_is_document"] = True
# Ensure queryset_class is inherited
if 'objects' in attrs:
manager = attrs['objects']
if hasattr(manager, 'queryset_class'):
attrs['_meta']['queryset_class'] = manager.queryset_class
if "objects" in attrs:
manager = attrs["objects"]
if hasattr(manager, "queryset_class"):
attrs["_meta"]["queryset_class"] = manager.queryset_class
# Clean up top level meta
if 'meta' in attrs:
del attrs['meta']
if "meta" in attrs:
del attrs["meta"]
# Find the parent document class
parent_doc_cls = [b for b in flattened_bases
if b.__class__ == TopLevelDocumentMetaclass]
parent_doc_cls = [
b for b in flattened_bases if b.__class__ == TopLevelDocumentMetaclass
]
parent_doc_cls = None if not parent_doc_cls else parent_doc_cls[0]
# Prevent classes setting collection different to their parents
# If parent wasn't an abstract class
if (parent_doc_cls and 'collection' in attrs.get('_meta', {}) and
not parent_doc_cls._meta.get('abstract', True)):
msg = 'Trying to set a collection on a subclass (%s)' % name
if (
parent_doc_cls
and "collection" in attrs.get("_meta", {})
and not parent_doc_cls._meta.get("abstract", True)
):
msg = "Trying to set a collection on a subclass (%s)" % name
warnings.warn(msg, SyntaxWarning)
del attrs['_meta']['collection']
del attrs["_meta"]["collection"]
# Ensure abstract documents have abstract bases
if attrs.get('_is_base_cls') or attrs['_meta'].get('abstract'):
if (parent_doc_cls and
not parent_doc_cls._meta.get('abstract', False)):
msg = 'Abstract document cannot have non-abstract base'
if attrs.get("_is_base_cls") or attrs["_meta"].get("abstract"):
if parent_doc_cls and not parent_doc_cls._meta.get("abstract", False):
msg = "Abstract document cannot have non-abstract base"
raise ValueError(msg)
return super_new(mcs, name, bases, attrs)
@ -322,38 +343,43 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
meta = MetaDict()
for base in flattened_bases[::-1]:
# Add any mixin metadata from plain objects
if hasattr(base, 'meta'):
if hasattr(base, "meta"):
meta.merge(base.meta)
elif hasattr(base, '_meta'):
elif hasattr(base, "_meta"):
meta.merge(base._meta)
# Set collection in the meta if its callable
if (getattr(base, '_is_document', False) and
not base._meta.get('abstract')):
collection = meta.get('collection', None)
if getattr(base, "_is_document", False) and not base._meta.get("abstract"):
collection = meta.get("collection", None)
if callable(collection):
meta['collection'] = collection(base)
meta["collection"] = collection(base)
meta.merge(attrs.get('_meta', {})) # Top level meta
meta.merge(attrs.get("_meta", {})) # Top level meta
# Only simple classes (i.e. direct subclasses of Document) may set
# allow_inheritance to False. If the base Document allows inheritance,
# none of its subclasses can override allow_inheritance to False.
simple_class = all([b._meta.get('abstract')
for b in flattened_bases if hasattr(b, '_meta')])
simple_class = all(
[b._meta.get("abstract") for b in flattened_bases if hasattr(b, "_meta")]
)
if (
not simple_class and
meta['allow_inheritance'] is False and
not meta['abstract']
not simple_class
and meta["allow_inheritance"] is False
and not meta["abstract"]
):
raise ValueError('Only direct subclasses of Document may set '
'"allow_inheritance" to False')
raise ValueError(
"Only direct subclasses of Document may set "
'"allow_inheritance" to False'
)
# Set default collection name
if 'collection' not in meta:
meta['collection'] = ''.join('_%s' % c if c.isupper() else c
for c in name).strip('_').lower()
attrs['_meta'] = meta
if "collection" not in meta:
meta["collection"] = (
"".join("_%s" % c if c.isupper() else c for c in name)
.strip("_")
.lower()
)
attrs["_meta"] = meta
# Call super and get the new class
new_class = super_new(mcs, name, bases, attrs)
@ -361,79 +387,93 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
meta = new_class._meta
# Set index specifications
meta['index_specs'] = new_class._build_index_specs(meta['indexes'])
meta["index_specs"] = new_class._build_index_specs(meta["indexes"])
# If collection is a callable - call it and set the value
collection = meta.get('collection')
collection = meta.get("collection")
if callable(collection):
new_class._meta['collection'] = collection(new_class)
new_class._meta["collection"] = collection(new_class)
# Provide a default queryset unless exists or one has been set
if 'objects' not in dir(new_class):
if "objects" not in dir(new_class):
new_class.objects = QuerySetManager()
# Validate the fields and set primary key if needed
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')
current_pk = new_class._meta.get("id_field")
if current_pk and current_pk != field_name:
raise ValueError('Cannot override primary key field')
raise ValueError("Cannot override primary key field")
# Set primary key
if not current_pk:
new_class._meta['id_field'] = field_name
new_class._meta["id_field"] = field_name
new_class.id = field
# Set primary key if not defined by the document
new_class._auto_id_field = getattr(parent_doc_cls,
'_auto_id_field', False)
if not new_class._meta.get('id_field'):
# After 0.10, find not existing names, instead of overwriting
# If the document doesn't explicitly define a primary key field, create
# one. Make it an ObjectIdField and give it a non-clashing name ("id"
# by default, but can be different if that one's taken).
if not new_class._meta.get("id_field"):
id_name, id_db_name = mcs.get_auto_id_names(new_class)
new_class._auto_id_field = True
new_class._meta['id_field'] = id_name
new_class._meta["id_field"] = id_name
new_class._fields[id_name] = ObjectIdField(db_field=id_db_name)
new_class._fields[id_name].name = id_name
new_class.id = new_class._fields[id_name]
new_class._db_field_map[id_name] = id_db_name
new_class._reverse_db_field_map[id_db_name] = id_name
# Prepend id field to _fields_ordered
new_class._fields_ordered = (id_name, ) + new_class._fields_ordered
# Merge in exceptions with parent hierarchy
# Prepend the ID field to _fields_ordered (so that it's *always*
# the first field).
new_class._fields_ordered = (id_name,) + new_class._fields_ordered
# Merge in exceptions with parent hierarchy.
exceptions_to_merge = (DoesNotExist, MultipleObjectsReturned)
module = attrs.get('__module__')
module = attrs.get("__module__")
for exc in exceptions_to_merge:
name = exc.__name__
parents = tuple(getattr(base, name) for base in flattened_bases
if hasattr(base, name)) or (exc,)
# Create new exception and set to new_class
exception = type(name, parents, {'__module__': module})
parents = tuple(
getattr(base, name) for base in flattened_bases if hasattr(base, name)
) or (exc,)
# Create a new exception and set it as an attribute on the new
# class.
exception = type(name, parents, {"__module__": module})
setattr(new_class, name, exception)
return new_class
@classmethod
def get_auto_id_names(mcs, new_class):
id_name, id_db_name = ('id', '_id')
if id_name not in new_class._fields and \
id_db_name not in (v.db_field for v in new_class._fields.values()):
"""Find a name for the automatic ID field for the given new class.
Return a two-element tuple where the first item is the field name (i.e.
the attribute name on the object) and the second element is the DB
field name (i.e. the name of the key stored in MongoDB).
Defaults to ('id', '_id'), or generates a non-clashing name in the form
of ('auto_id_X', '_auto_id_X') if the default name is already taken.
"""
id_name, id_db_name = ("id", "_id")
existing_fields = {field_name for field_name in new_class._fields}
existing_db_fields = {v.db_field for v in new_class._fields.values()}
if id_name not in existing_fields and id_db_name not in existing_db_fields:
return id_name, id_db_name
id_basename, id_db_basename, i = 'auto_id', '_auto_id', 0
while id_name in new_class._fields or \
id_db_name in (v.db_field for v in new_class._fields.values()):
id_name = '{0}_{1}'.format(id_basename, i)
id_db_name = '{0}_{1}'.format(id_db_basename, i)
i += 1
return id_name, id_db_name
id_basename, id_db_basename, i = ("auto_id", "_auto_id", 0)
for i in itertools.count():
id_name = "{0}_{1}".format(id_basename, i)
id_db_name = "{0}_{1}".format(id_db_basename, i)
if id_name not in existing_fields and id_db_name not in existing_db_fields:
return id_name, id_db_name
class MetaDict(dict):
"""Custom dictionary for meta classes.
Handles the merging of set indexes
"""
_merge_options = ('indexes',)
_merge_options = ("indexes",)
def merge(self, new_options):
for k, v in iteritems(new_options):
@ -445,4 +485,5 @@ class MetaDict(dict):
class BasesTuple(tuple):
"""Special class to handle introspection of bases tuple in __new__"""
pass

View File

@ -19,34 +19,44 @@ def _import_class(cls_name):
if cls_name in _class_registry_cache:
return _class_registry_cache.get(cls_name)
doc_classes = ('Document', 'DynamicEmbeddedDocument', 'EmbeddedDocument',
'MapReduceDocument')
doc_classes = (
"Document",
"DynamicEmbeddedDocument",
"EmbeddedDocument",
"MapReduceDocument",
)
# Field Classes
if not _field_list_cache:
from mongoengine.fields import __all__ as fields
_field_list_cache.extend(fields)
from mongoengine.base.fields import __all__ as fields
_field_list_cache.extend(fields)
field_classes = _field_list_cache
deref_classes = ('DeReference',)
deref_classes = ("DeReference",)
if cls_name == 'BaseDocument':
if cls_name == "BaseDocument":
from mongoengine.base import document as module
import_classes = ['BaseDocument']
import_classes = ["BaseDocument"]
elif cls_name in doc_classes:
from mongoengine import document as module
import_classes = doc_classes
elif cls_name in field_classes:
from mongoengine import fields as module
import_classes = field_classes
elif cls_name in deref_classes:
from mongoengine import dereference as module
import_classes = deref_classes
else:
raise ValueError('No import set for: %s' % cls_name)
raise ValueError("No import set for: %s" % cls_name)
for cls in import_classes:
_class_registry_cache[cls] = getattr(module, cls)

View File

@ -3,21 +3,21 @@ from pymongo.database import _check_name
import six
__all__ = [
'DEFAULT_CONNECTION_NAME',
'DEFAULT_DATABASE_NAME',
'MongoEngineConnectionError',
'connect',
'disconnect',
'disconnect_all',
'get_connection',
'get_db',
'register_connection',
"DEFAULT_CONNECTION_NAME",
"DEFAULT_DATABASE_NAME",
"ConnectionFailure",
"connect",
"disconnect",
"disconnect_all",
"get_connection",
"get_db",
"register_connection",
]
DEFAULT_CONNECTION_NAME = 'default'
DEFAULT_DATABASE_NAME = 'test'
DEFAULT_HOST = 'localhost'
DEFAULT_CONNECTION_NAME = "default"
DEFAULT_DATABASE_NAME = "test"
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 27017
_connection_settings = {}
@ -27,10 +27,11 @@ _dbs = {}
READ_PREFERENCE = ReadPreference.PRIMARY
class MongoEngineConnectionError(Exception):
class ConnectionFailure(Exception):
"""Error raised when the database connection can't be established or
when a connection with a requested alias can't be retrieved.
"""
pass
@ -39,18 +40,23 @@ def _check_db_name(name):
This functionality is copied from pymongo Database class constructor.
"""
if not isinstance(name, six.string_types):
raise TypeError('name must be an instance of %s' % six.string_types)
elif name != '$external':
raise TypeError("name must be an instance of %s" % six.string_types)
elif name != "$external":
_check_name(name)
def _get_connection_settings(
db=None, name=None, host=None, port=None,
read_preference=READ_PREFERENCE,
username=None, password=None,
authentication_source=None,
authentication_mechanism=None,
**kwargs):
db=None,
name=None,
host=None,
port=None,
read_preference=READ_PREFERENCE,
username=None,
password=None,
authentication_source=None,
authentication_mechanism=None,
**kwargs
):
"""Get the connection settings as a dict
: param db: the name of the database to use, for compatibility with connect
@ -73,18 +79,18 @@ def _get_connection_settings(
.. versionchanged:: 0.10.6 - added mongomock support
"""
conn_settings = {
'name': name or db or DEFAULT_DATABASE_NAME,
'host': host or DEFAULT_HOST,
'port': port or DEFAULT_PORT,
'read_preference': read_preference,
'username': username,
'password': password,
'authentication_source': authentication_source,
'authentication_mechanism': authentication_mechanism
"name": name or db or DEFAULT_DATABASE_NAME,
"host": host or DEFAULT_HOST,
"port": port or DEFAULT_PORT,
"read_preference": read_preference,
"username": username,
"password": password,
"authentication_source": authentication_source,
"authentication_mechanism": authentication_mechanism,
}
_check_db_name(conn_settings['name'])
conn_host = conn_settings['host']
_check_db_name(conn_settings["name"])
conn_host = conn_settings["host"]
# Host can be a list or a string, so if string, force to a list.
if isinstance(conn_host, six.string_types):
@ -94,32 +100,40 @@ def _get_connection_settings(
for entity in conn_host:
# Handle Mongomock
if entity.startswith('mongomock://'):
conn_settings['is_mock'] = True
if entity.startswith("mongomock://"):
conn_settings["is_mock"] = True
# `mongomock://` is not a valid url prefix and must be replaced by `mongodb://`
resolved_hosts.append(entity.replace('mongomock://', 'mongodb://', 1))
new_entity = entity.replace("mongomock://", "mongodb://", 1)
resolved_hosts.append(new_entity)
uri_dict = uri_parser.parse_uri(new_entity)
database = uri_dict.get("database")
if database:
conn_settings["name"] = database
# Handle URI style connections, only updating connection params which
# were explicitly specified in the URI.
elif '://' in entity:
elif "://" in entity:
uri_dict = uri_parser.parse_uri(entity)
resolved_hosts.append(entity)
if uri_dict.get('database'):
conn_settings['name'] = uri_dict.get('database')
database = uri_dict.get("database")
if database:
conn_settings["name"] = database
for param in ('read_preference', 'username', 'password'):
for param in ("read_preference", "username", "password"):
if uri_dict.get(param):
conn_settings[param] = uri_dict[param]
uri_options = uri_dict['options']
if 'replicaset' in uri_options:
conn_settings['replicaSet'] = uri_options['replicaset']
if 'authsource' in uri_options:
conn_settings['authentication_source'] = uri_options['authsource']
if 'authmechanism' in uri_options:
conn_settings['authentication_mechanism'] = uri_options['authmechanism']
if 'readpreference' in uri_options:
uri_options = uri_dict["options"]
if "replicaset" in uri_options:
conn_settings["replicaSet"] = uri_options["replicaset"]
if "authsource" in uri_options:
conn_settings["authentication_source"] = uri_options["authsource"]
if "authmechanism" in uri_options:
conn_settings["authentication_mechanism"] = uri_options["authmechanism"]
if "readpreference" in uri_options:
read_preferences = (
ReadPreference.NEAREST,
ReadPreference.PRIMARY,
@ -133,40 +147,47 @@ def _get_connection_settings(
# int (e.g. 3).
# TODO simplify the code below once we drop support for
# PyMongo v3.4.
read_pf_mode = uri_options['readpreference']
read_pf_mode = uri_options["readpreference"]
if isinstance(read_pf_mode, six.string_types):
read_pf_mode = read_pf_mode.lower()
for preference in read_preferences:
if (
preference.name.lower() == read_pf_mode or
preference.mode == read_pf_mode
preference.name.lower() == read_pf_mode
or preference.mode == read_pf_mode
):
conn_settings['read_preference'] = preference
conn_settings["read_preference"] = preference
break
else:
resolved_hosts.append(entity)
conn_settings['host'] = resolved_hosts
conn_settings["host"] = resolved_hosts
# Deprecated parameters that should not be passed on
kwargs.pop('slaves', None)
kwargs.pop('is_slave', None)
kwargs.pop("slaves", None)
kwargs.pop("is_slave", None)
conn_settings.update(kwargs)
return conn_settings
def register_connection(alias, db=None, name=None, host=None, port=None,
read_preference=READ_PREFERENCE,
username=None, password=None,
authentication_source=None,
authentication_mechanism=None,
**kwargs):
def register_connection(
alias,
db=None,
name=None,
host=None,
port=None,
read_preference=READ_PREFERENCE,
username=None,
password=None,
authentication_source=None,
authentication_mechanism=None,
**kwargs
):
"""Register the connection settings.
: param alias: the name that will be used to refer to this connection
throughout MongoEngine
: param name: the name of the specific database to use
: param db: the name of the database to use, for compatibility with connect
: param name: the name of the specific database to use
: param host: the host name of the: program: `mongod` instance to connect to
: param port: the port that the: program: `mongod` instance is running on
: param read_preference: The read preference for the collection
@ -185,12 +206,17 @@ def register_connection(alias, db=None, name=None, host=None, port=None,
.. versionchanged:: 0.10.6 - added mongomock support
"""
conn_settings = _get_connection_settings(
db=db, name=name, host=host, port=port,
db=db,
name=name,
host=host,
port=port,
read_preference=read_preference,
username=username, password=password,
username=username,
password=password,
authentication_source=authentication_source,
authentication_mechanism=authentication_mechanism,
**kwargs)
**kwargs
)
_connection_settings[alias] = conn_settings
@ -206,7 +232,7 @@ def disconnect(alias=DEFAULT_CONNECTION_NAME):
if alias in _dbs:
# Detach all cached collections in Documents
for doc_cls in _get_documents_by_db(alias, DEFAULT_CONNECTION_NAME):
if issubclass(doc_cls, Document): # Skip EmbeddedDocument
if issubclass(doc_cls, Document): # Skip EmbeddedDocument
doc_cls._disconnect()
del _dbs[alias]
@ -234,22 +260,24 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
return _connections[alias]
# Validate that the requested alias exists in the _connection_settings.
# Raise MongoEngineConnectionError if it doesn't.
# Raise ConnectionFailure if it doesn't.
if alias not in _connection_settings:
if alias == DEFAULT_CONNECTION_NAME:
msg = 'You have not defined a default connection'
msg = "You have not defined a default connection"
else:
msg = 'Connection with alias "%s" has not been defined' % alias
raise MongoEngineConnectionError(msg)
raise ConnectionFailure(msg)
def _clean_settings(settings_dict):
irrelevant_fields_set = {
'name', 'username', 'password',
'authentication_source', 'authentication_mechanism'
"name",
"username",
"password",
"authentication_source",
"authentication_mechanism",
}
return {
k: v for k, v in settings_dict.items()
if k not in irrelevant_fields_set
k: v for k, v in settings_dict.items() if k not in irrelevant_fields_set
}
raw_conn_settings = _connection_settings[alias].copy()
@ -260,13 +288,12 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
conn_settings = _clean_settings(raw_conn_settings)
# Determine if we should use PyMongo's or mongomock's MongoClient.
is_mock = conn_settings.pop('is_mock', False)
is_mock = conn_settings.pop("is_mock", False)
if is_mock:
try:
import mongomock
except ImportError:
raise RuntimeError('You need mongomock installed to mock '
'MongoEngine.')
raise RuntimeError("You need mongomock installed to mock MongoEngine.")
connection_class = mongomock.MongoClient
else:
connection_class = MongoClient
@ -277,9 +304,7 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
connection = existing_connection
else:
connection = _create_connection(
alias=alias,
connection_class=connection_class,
**conn_settings
alias=alias, connection_class=connection_class, **conn_settings
)
_connections[alias] = connection
return _connections[alias]
@ -288,13 +313,12 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
def _create_connection(alias, connection_class, **connection_settings):
"""
Create the new connection for this alias. Raise
MongoEngineConnectionError if it can't be established.
ConnectionFailure if it can't be established.
"""
try:
return connection_class(**connection_settings)
except Exception as e:
raise MongoEngineConnectionError(
'Cannot connect to database %s :\n%s' % (alias, e))
raise ConnectionFailure("Cannot connect to database %s :\n%s" % (alias, e))
def _find_existing_connection(connection_settings):
@ -316,7 +340,7 @@ def _find_existing_connection(connection_settings):
# Only remove the name but it's important to
# keep the username/password/authentication_source/authentication_mechanism
# to identify if the connection could be shared (cfr https://github.com/MongoEngine/mongoengine/issues/2047)
return {k: v for k, v in settings_dict.items() if k != 'name'}
return {k: v for k, v in settings_dict.items() if k != "name"}
cleaned_conn_settings = _clean_settings(connection_settings)
for db_alias, connection_settings in connection_settings_bis:
@ -332,14 +356,18 @@ def get_db(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
if alias not in _dbs:
conn = get_connection(alias)
conn_settings = _connection_settings[alias]
db = conn[conn_settings['name']]
auth_kwargs = {'source': conn_settings['authentication_source']}
if conn_settings['authentication_mechanism'] is not None:
auth_kwargs['mechanism'] = conn_settings['authentication_mechanism']
db = conn[conn_settings["name"]]
auth_kwargs = {"source": conn_settings["authentication_source"]}
if conn_settings["authentication_mechanism"] is not None:
auth_kwargs["mechanism"] = conn_settings["authentication_mechanism"]
# Authenticate if necessary
if conn_settings['username'] and (conn_settings['password'] or
conn_settings['authentication_mechanism'] == 'MONGODB-X509'):
db.authenticate(conn_settings['username'], conn_settings['password'], **auth_kwargs)
if conn_settings["username"] and (
conn_settings["password"]
or conn_settings["authentication_mechanism"] == "MONGODB-X509"
):
db.authenticate(
conn_settings["username"], conn_settings["password"], **auth_kwargs
)
_dbs[alias] = db
return _dbs[alias]
@ -368,10 +396,10 @@ def connect(db=None, alias=DEFAULT_CONNECTION_NAME, **kwargs):
if new_conn_settings != prev_conn_setting:
err_msg = (
u'A different connection with alias `{}` was already '
u'registered. Use disconnect() first'
u"A different connection with alias `{}` was already "
u"registered. Use disconnect() first"
).format(alias)
raise MongoEngineConnectionError(err_msg)
raise ConnectionFailure(err_msg)
else:
register_connection(alias, db, **kwargs)

View File

@ -7,8 +7,14 @@ 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')
__all__ = (
"switch_db",
"switch_collection",
"no_dereference",
"no_sub_classes",
"query_counter",
"set_write_concern",
)
class switch_db(object):
@ -38,17 +44,17 @@ class switch_db(object):
self.cls = cls
self.collection = cls._get_collection()
self.db_alias = db_alias
self.ori_db_alias = cls._meta.get('db_alias', DEFAULT_CONNECTION_NAME)
self.ori_db_alias = cls._meta.get("db_alias", DEFAULT_CONNECTION_NAME)
def __enter__(self):
"""Change the db_alias and clear the cached collection."""
self.cls._meta['db_alias'] = self.db_alias
self.cls._meta["db_alias"] = self.db_alias
self.cls._collection = None
return self.cls
def __exit__(self, t, value, traceback):
"""Reset the db_alias and collection."""
self.cls._meta['db_alias'] = self.ori_db_alias
self.cls._meta["db_alias"] = self.ori_db_alias
self.cls._collection = self.collection
@ -111,14 +117,15 @@ class no_dereference(object):
"""
self.cls = cls
ReferenceField = _import_class('ReferenceField')
GenericReferenceField = _import_class('GenericReferenceField')
ComplexBaseField = _import_class('ComplexBaseField')
ReferenceField = _import_class("ReferenceField")
GenericReferenceField = _import_class("GenericReferenceField")
ComplexBaseField = _import_class("ComplexBaseField")
self.deref_fields = [k for k, v in iteritems(self.cls._fields)
if isinstance(v, (ReferenceField,
GenericReferenceField,
ComplexBaseField))]
self.deref_fields = [
k
for k, v in iteritems(self.cls._fields)
if isinstance(v, (ReferenceField, GenericReferenceField, ComplexBaseField))
]
def __enter__(self):
"""Change the objects default and _auto_dereference values."""
@ -180,15 +187,12 @@ class query_counter(object):
"""
self.db = get_db()
self.initial_profiling_level = None
self._ctx_query_counter = 0 # number of queries issued by the context
self._ctx_query_counter = 0 # number of queries issued by the context
self._ignored_query = {
'ns':
{'$ne': '%s.system.indexes' % self.db.name},
'op': # MONGODB < 3.2
{'$ne': 'killcursors'},
'command.killCursors': # MONGODB >= 3.2
{'$exists': False}
"ns": {"$ne": "%s.system.indexes" % self.db.name},
"op": {"$ne": "killcursors"}, # MONGODB < 3.2
"command.killCursors": {"$exists": False}, # MONGODB >= 3.2
}
def _turn_on_profiling(self):
@ -238,8 +242,13 @@ 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 = 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
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

@ -2,8 +2,13 @@ from bson import DBRef, SON
import six
from six import iteritems
from mongoengine.base import (BaseDict, BaseList, EmbeddedDocumentList,
TopLevelDocumentMetaclass, get_document)
from mongoengine.base import (
BaseDict,
BaseList,
EmbeddedDocumentList,
TopLevelDocumentMetaclass,
get_document,
)
from mongoengine.base.datastructures import LazyReference
from mongoengine.connection import get_db
from mongoengine.document import Document, EmbeddedDocument
@ -36,21 +41,23 @@ class DeReference(object):
self.max_depth = max_depth
doc_type = None
if instance and isinstance(instance, (Document, EmbeddedDocument,
TopLevelDocumentMetaclass)):
if instance and isinstance(
instance, (Document, EmbeddedDocument, TopLevelDocumentMetaclass)
):
doc_type = instance._fields.get(name)
while hasattr(doc_type, 'field'):
while hasattr(doc_type, "field"):
doc_type = doc_type.field
if isinstance(doc_type, ReferenceField):
field = doc_type
doc_type = doc_type.document_type
is_list = not hasattr(items, 'items')
is_list = not hasattr(items, "items")
if is_list and all([i.__class__ == doc_type for i in items]):
return items
elif not is_list and all(
[i.__class__ == doc_type for i in items.values()]):
[i.__class__ == doc_type for i in items.values()]
):
return items
elif not field.dbref:
# We must turn the ObjectIds into DBRefs
@ -83,7 +90,7 @@ class DeReference(object):
new_items[k] = value
return new_items
if not hasattr(items, 'items'):
if not hasattr(items, "items"):
items = _get_items_from_list(items)
else:
items = _get_items_from_dict(items)
@ -120,13 +127,19 @@ class DeReference(object):
continue
elif isinstance(v, DBRef):
reference_map.setdefault(field.document_type, set()).add(v.id)
elif isinstance(v, (dict, SON)) and '_ref' in v:
reference_map.setdefault(get_document(v['_cls']), set()).add(v['_ref'].id)
elif isinstance(v, (dict, SON)) and "_ref" in v:
reference_map.setdefault(get_document(v["_cls"]), set()).add(
v["_ref"].id
)
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
field_cls = getattr(getattr(field, 'field', None), 'document_type', None)
field_cls = getattr(
getattr(field, "field", None), "document_type", None
)
references = self._find_references(v, depth)
for key, refs in iteritems(references):
if isinstance(field_cls, (Document, TopLevelDocumentMetaclass)):
if isinstance(
field_cls, (Document, TopLevelDocumentMetaclass)
):
key = field_cls
reference_map.setdefault(key, set()).update(refs)
elif isinstance(item, LazyReference):
@ -134,8 +147,10 @@ class DeReference(object):
continue
elif isinstance(item, DBRef):
reference_map.setdefault(item.collection, set()).add(item.id)
elif isinstance(item, (dict, SON)) and '_ref' in item:
reference_map.setdefault(get_document(item['_cls']), set()).add(item['_ref'].id)
elif isinstance(item, (dict, SON)) and "_ref" in item:
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 iteritems(references):
@ -151,12 +166,13 @@ class DeReference(object):
# 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))
ref_document_cls_exists = (getattr(collection, 'objects', None) is not None)
ref_document_cls_exists = getattr(collection, "objects", None) is not None
if ref_document_cls_exists:
col_name = collection._get_collection_name()
refs = [dbref for dbref in dbrefs
if (col_name, dbref) not in object_map]
refs = [
dbref for dbref in dbrefs if (col_name, dbref) not in object_map
]
references = collection.objects.in_bulk(refs)
for key, doc in iteritems(references):
object_map[(col_name, key)] = doc
@ -164,23 +180,26 @@ class DeReference(object):
if isinstance(doc_type, (ListField, DictField, MapField)):
continue
refs = [dbref for dbref in dbrefs
if (collection, dbref) not in object_map]
refs = [
dbref for dbref in dbrefs if (collection, dbref) not in object_map
]
if doc_type:
references = doc_type._get_db()[collection].find({'_id': {'$in': refs}})
references = doc_type._get_db()[collection].find(
{"_id": {"$in": refs}}
)
for ref in references:
doc = doc_type._from_son(ref)
object_map[(collection, doc.id)] = doc
else:
references = get_db()[collection].find({'_id': {'$in': refs}})
references = get_db()[collection].find({"_id": {"$in": refs}})
for ref in references:
if '_cls' in ref:
doc = get_document(ref['_cls'])._from_son(ref)
if "_cls" in ref:
doc = get_document(ref["_cls"])._from_son(ref)
elif doc_type is None:
doc = get_document(
''.join(x.capitalize()
for x in collection.split('_')))._from_son(ref)
"".join(x.capitalize() for x in collection.split("_"))
)._from_son(ref)
else:
doc = doc_type._from_son(ref)
object_map[(collection, doc.id)] = doc
@ -208,19 +227,20 @@ class DeReference(object):
return BaseList(items, instance, name)
if isinstance(items, (dict, SON)):
if '_ref' in items:
if "_ref" in items:
return self.object_map.get(
(items['_ref'].collection, items['_ref'].id), items)
elif '_cls' in items:
doc = get_document(items['_cls'])._from_son(items)
_cls = doc._data.pop('_cls', None)
del items['_cls']
(items["_ref"].collection, items["_ref"].id), items
)
elif "_cls" in items:
doc = get_document(items["_cls"])._from_son(items)
_cls = doc._data.pop("_cls", None)
del items["_cls"]
doc._data = self._attach_objects(doc._data, depth, doc, None)
if _cls is not None:
doc._data['_cls'] = _cls
doc._data["_cls"] = _cls
return doc
if not hasattr(items, 'items'):
if not hasattr(items, "items"):
is_list = True
list_type = BaseList
if isinstance(items, EmbeddedDocumentList):
@ -247,17 +267,25 @@ class DeReference(object):
v = data[k]._data.get(field_name, None)
if isinstance(v, DBRef):
data[k]._data[field_name] = self.object_map.get(
(v.collection, v.id), v)
elif isinstance(v, (dict, SON)) and '_ref' in v:
(v.collection, v.id), v
)
elif isinstance(v, (dict, SON)) and "_ref" in v:
data[k]._data[field_name] = self.object_map.get(
(v['_ref'].collection, v['_ref'].id), v)
(v["_ref"].collection, v["_ref"].id), v
)
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
item_name = six.text_type('{0}.{1}.{2}').format(name, k, field_name)
data[k]._data[field_name] = self._attach_objects(v, depth, instance=instance, name=item_name)
item_name = six.text_type("{0}.{1}.{2}").format(
name, k, field_name
)
data[k]._data[field_name] = self._attach_objects(
v, depth, instance=instance, name=item_name
)
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
item_name = '%s.%s' % (name, k) if name else name
data[k] = self._attach_objects(v, depth - 1, instance=instance, name=item_name)
elif isinstance(v, DBRef) and hasattr(v, 'id'):
item_name = "%s.%s" % (name, k) if name else name
data[k] = self._attach_objects(
v, depth - 1, instance=instance, name=item_name
)
elif isinstance(v, DBRef) and hasattr(v, "id"):
data[k] = self.object_map.get((v.collection, v.id), v)
if instance and name:

View File

@ -8,23 +8,36 @@ import six
from six import iteritems
from mongoengine import signals
from mongoengine.base import (BaseDict, BaseDocument, BaseList,
DocumentMetaclass, EmbeddedDocumentList,
TopLevelDocumentMetaclass, get_document)
from mongoengine.base import (
BaseDict,
BaseDocument,
BaseList,
DocumentMetaclass,
EmbeddedDocumentList,
TopLevelDocumentMetaclass,
get_document,
)
from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.context_managers import (set_write_concern,
switch_collection,
switch_db)
from mongoengine.errors import (InvalidDocumentError, InvalidQueryError,
SaveConditionError)
from mongoengine.context_managers import set_write_concern, switch_collection, switch_db
from mongoengine.errors import (
InvalidDocumentError,
InvalidQueryError,
SaveConditionError,
)
from mongoengine.pymongo_support import list_collection_names
from mongoengine.queryset import (NotUniqueError, OperationError,
QuerySet, transform)
from mongoengine.queryset import NotUniqueError, OperationError, QuerySet, transform
__all__ = ('Document', 'EmbeddedDocument', 'DynamicDocument',
'DynamicEmbeddedDocument', 'OperationError',
'InvalidCollectionError', 'NotUniqueError', 'MapReduceDocument')
__all__ = (
"Document",
"EmbeddedDocument",
"DynamicDocument",
"DynamicEmbeddedDocument",
"OperationError",
"InvalidCollectionError",
"NotUniqueError",
"MapReduceDocument",
)
def includes_cls(fields):
@ -35,7 +48,7 @@ def includes_cls(fields):
first_field = fields[0]
elif isinstance(fields[0], (list, tuple)) and len(fields[0]):
first_field = fields[0][0]
return first_field == '_cls'
return first_field == "_cls"
class InvalidCollectionError(Exception):
@ -56,7 +69,7 @@ class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
:attr:`meta` dictionary.
"""
__slots__ = ('_instance', )
__slots__ = ("_instance",)
# The __metaclass__ attribute is removed by 2to3 when running with Python3
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
@ -85,8 +98,8 @@ class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
data = super(EmbeddedDocument, self).to_mongo(*args, **kwargs)
# remove _id from the SON if it's in it and it's None
if '_id' in data and data['_id'] is None:
del data['_id']
if "_id" in data and data["_id"] is None:
del data["_id"]
return data
@ -147,19 +160,19 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
my_metaclass = TopLevelDocumentMetaclass
__slots__ = ('__objects',)
__slots__ = ("__objects",)
@property
def pk(self):
"""Get the primary key."""
if 'id_field' not in self._meta:
if "id_field" not in self._meta:
return None
return getattr(self, self._meta['id_field'])
return getattr(self, self._meta["id_field"])
@pk.setter
def pk(self, value):
"""Set the primary key."""
return setattr(self, self._meta['id_field'], value)
return setattr(self, self._meta["id_field"], value)
def __hash__(self):
"""Return the hash based on the PK of this document. If it's new
@ -173,7 +186,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
@classmethod
def _get_db(cls):
"""Some Model using other db_alias"""
return get_db(cls._meta.get('db_alias', DEFAULT_CONNECTION_NAME))
return get_db(cls._meta.get("db_alias", DEFAULT_CONNECTION_NAME))
@classmethod
def _disconnect(cls):
@ -190,9 +203,9 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
2. Creates indexes defined in this document's :attr:`meta` dictionary.
This happens only if `auto_create_index` is True.
"""
if not hasattr(cls, '_collection') or cls._collection is None:
if not hasattr(cls, "_collection") or cls._collection is None:
# Get the collection, either capped or regular.
if cls._meta.get('max_size') or cls._meta.get('max_documents'):
if cls._meta.get("max_size") or cls._meta.get("max_documents"):
cls._collection = cls._get_capped_collection()
else:
db = cls._get_db()
@ -203,8 +216,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
# set to False.
# Also there is no need to ensure indexes on slave.
db = cls._get_db()
if cls._meta.get('auto_create_index', True) and\
db.client.is_primary:
if cls._meta.get("auto_create_index", True) and db.client.is_primary:
cls.ensure_indexes()
return cls._collection
@ -216,8 +228,8 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
collection_name = cls._get_collection_name()
# Get max document limit and max byte size from meta.
max_size = cls._meta.get('max_size') or 10 * 2 ** 20 # 10MB default
max_documents = cls._meta.get('max_documents')
max_size = cls._meta.get("max_size") or 10 * 2 ** 20 # 10MB default
max_documents = cls._meta.get("max_documents")
# MongoDB will automatically raise the size to make it a multiple of
# 256 bytes. We raise it here ourselves to be able to reliably compare
@ -227,24 +239,23 @@ 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 list_collection_names(db, include_system_collections=True):
if collection_name in list_collection_names(
db, include_system_collections=True
):
collection = db[collection_name]
options = collection.options()
if (
options.get('max') != max_documents or
options.get('size') != max_size
):
if options.get("max") != max_documents or options.get("size") != max_size:
raise InvalidCollectionError(
'Cannot create collection "{}" as a capped '
'collection as it already exists'.format(cls._collection)
"collection as it already exists".format(cls._collection)
)
return collection
# Create a new capped collection.
opts = {'capped': True, 'size': max_size}
opts = {"capped": True, "size": max_size}
if max_documents:
opts['max'] = max_documents
opts["max"] = max_documents
return db.create_collection(collection_name, **opts)
@ -253,11 +264,11 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
# If '_id' is None, try and set it from self._data. If that
# doesn't exist either, remove '_id' from the SON completely.
if data['_id'] is None:
if self._data.get('id') is None:
del data['_id']
if data["_id"] is None:
if self._data.get("id") is None:
del data["_id"]
else:
data['_id'] = self._data['id']
data["_id"] = self._data["id"]
return data
@ -279,15 +290,17 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
query = {}
if self.pk is None:
raise InvalidDocumentError('The document does not have a primary key.')
raise InvalidDocumentError("The document does not have a primary key.")
id_field = self._meta['id_field']
id_field = self._meta["id_field"]
query = query.copy() if isinstance(query, dict) else query.to_query(self)
if id_field not in query:
query[id_field] = self.pk
elif query[id_field] != self.pk:
raise InvalidQueryError('Invalid document modify query: it must modify only this document.')
raise InvalidQueryError(
"Invalid document modify query: it must modify only this document."
)
# Need to add shard key to query, or you get an error
query.update(self._object_key)
@ -304,9 +317,19 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
return True
def save(self, force_insert=False, validate=True, clean=True,
write_concern=None, cascade=None, cascade_kwargs=None,
_refs=None, save_condition=None, signal_kwargs=None, **kwargs):
def save(
self,
force_insert=False,
validate=True,
clean=True,
write_concern=None,
cascade=None,
cascade_kwargs=None,
_refs=None,
save_condition=None,
signal_kwargs=None,
**kwargs
):
"""Save the :class:`~mongoengine.Document` to the database. If the
document already exists, it will be updated, otherwise it will be
created.
@ -360,8 +383,8 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
"""
signal_kwargs = signal_kwargs or {}
if self._meta.get('abstract'):
raise InvalidDocumentError('Cannot save an abstract document.')
if self._meta.get("abstract"):
raise InvalidDocumentError("Cannot save an abstract document.")
signals.pre_save.send(self.__class__, document=self, **signal_kwargs)
@ -371,15 +394,16 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
if write_concern is None:
write_concern = {}
doc_id = self.to_mongo(fields=[self._meta['id_field']])
created = ('_id' not in doc_id or self._created or force_insert)
doc_id = self.to_mongo(fields=[self._meta["id_field"]])
created = "_id" not in doc_id or self._created or force_insert
signals.pre_save_post_validation.send(self.__class__, document=self,
created=created, **signal_kwargs)
signals.pre_save_post_validation.send(
self.__class__, document=self, created=created, **signal_kwargs
)
# it might be refreshed by the pre_save_post_validation hook, e.g., for etag generation
doc = self.to_mongo()
if self._meta.get('auto_create_index', True):
if self._meta.get("auto_create_index", True):
self.ensure_indexes()
try:
@ -387,44 +411,45 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
if created:
object_id = self._save_create(doc, force_insert, write_concern)
else:
object_id, created = self._save_update(doc, save_condition,
write_concern)
object_id, created = self._save_update(
doc, save_condition, write_concern
)
if cascade is None:
cascade = (self._meta.get('cascade', False) or
cascade_kwargs is not None)
cascade = self._meta.get("cascade", False) or cascade_kwargs is not None
if cascade:
kwargs = {
'force_insert': force_insert,
'validate': validate,
'write_concern': write_concern,
'cascade': cascade
"force_insert": force_insert,
"validate": validate,
"write_concern": write_concern,
"cascade": cascade,
}
if cascade_kwargs: # Allow granular control over cascades
kwargs.update(cascade_kwargs)
kwargs['_refs'] = _refs
kwargs["_refs"] = _refs
self.cascade_save(**kwargs)
except pymongo.errors.DuplicateKeyError as err:
message = u'Tried to save duplicate unique keys (%s)'
message = u"Tried to save duplicate unique keys (%s)"
raise NotUniqueError(message % six.text_type(err))
except pymongo.errors.OperationFailure as err:
message = 'Could not save document (%s)'
if re.match('^E1100[01] duplicate key', six.text_type(err)):
message = "Could not save document (%s)"
if re.match("^E1100[01] duplicate key", six.text_type(err)):
# E11000 - duplicate key error index
# E11001 - duplicate key on update
message = u'Tried to save duplicate unique keys (%s)'
message = u"Tried to save duplicate unique keys (%s)"
raise NotUniqueError(message % six.text_type(err))
raise OperationError(message % six.text_type(err))
# Make sure we store the PK on this document now that it's saved
id_field = self._meta['id_field']
if created or id_field not in self._meta.get('shard_key', []):
id_field = self._meta["id_field"]
if created or id_field not in self._meta.get("shard_key", []):
self[id_field] = self._fields[id_field].to_python(object_id)
signals.post_save.send(self.__class__, document=self,
created=created, **signal_kwargs)
signals.post_save.send(
self.__class__, document=self, created=created, **signal_kwargs
)
self._clear_changed_fields()
self._created = False
@ -442,11 +467,12 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
return wc_collection.insert_one(doc).inserted_id
# insert_one will provoke UniqueError alongside save does not
# therefore, it need to catch and call replace_one.
if '_id' in doc:
if "_id" in doc:
raw_object = wc_collection.find_one_and_replace(
{'_id': doc['_id']}, doc)
{"_id": doc["_id"]}, doc
)
if raw_object:
return doc['_id']
return doc["_id"]
object_id = wc_collection.insert_one(doc).inserted_id
@ -461,9 +487,9 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
update_doc = {}
if updates:
update_doc['$set'] = updates
update_doc["$set"] = updates
if removals:
update_doc['$unset'] = removals
update_doc["$unset"] = removals
return update_doc
@ -473,39 +499,38 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
Helper method, should only be used inside save().
"""
collection = self._get_collection()
object_id = doc['_id']
object_id = doc["_id"]
created = False
select_dict = {}
if save_condition is not None:
select_dict = transform.query(self.__class__, **save_condition)
select_dict['_id'] = object_id
select_dict["_id"] = object_id
# Need to add shard key to query, or you get an error
shard_key = self._meta.get('shard_key', tuple())
shard_key = self._meta.get("shard_key", tuple())
for k in shard_key:
path = self._lookup_field(k.split('.'))
path = self._lookup_field(k.split("."))
actual_key = [p.db_field for p in path]
val = doc
for ak in actual_key:
val = val[ak]
select_dict['.'.join(actual_key)] = val
select_dict[".".join(actual_key)] = val
update_doc = self._get_update_doc()
if update_doc:
upsert = save_condition is None
with set_write_concern(collection, write_concern) as wc_collection:
last_error = wc_collection.update_one(
select_dict,
update_doc,
upsert=upsert
select_dict, update_doc, upsert=upsert
).raw_result
if not upsert and last_error['n'] == 0:
raise SaveConditionError('Race condition preventing'
' document update detected')
if not upsert and last_error["n"] == 0:
raise SaveConditionError(
"Race condition preventing document update detected"
)
if last_error is not None:
updated_existing = last_error.get('updatedExisting')
updated_existing = last_error.get("updatedExisting")
if updated_existing is False:
created = True
# !!! This is bad, means we accidentally created a new,
@ -518,21 +543,20 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
"""Recursively save any references and generic references on the
document.
"""
_refs = kwargs.get('_refs') or []
_refs = kwargs.get("_refs") or []
ReferenceField = _import_class('ReferenceField')
GenericReferenceField = _import_class('GenericReferenceField')
ReferenceField = _import_class("ReferenceField")
GenericReferenceField = _import_class("GenericReferenceField")
for name, cls in self._fields.items():
if not isinstance(cls, (ReferenceField,
GenericReferenceField)):
if not isinstance(cls, (ReferenceField, GenericReferenceField)):
continue
ref = self._data.get(name)
if not ref or isinstance(ref, DBRef):
continue
if not getattr(ref, '_changed_fields', True):
if not getattr(ref, "_changed_fields", True):
continue
ref_id = "%s,%s" % (ref.__class__.__name__, str(ref._data))
@ -544,27 +568,31 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
@property
def _qs(self):
"""Return the queryset to use for updating / reloading / deletions."""
if not hasattr(self, '__objects'):
"""Return the default queryset corresponding to this document."""
if not hasattr(self, "__objects"):
self.__objects = QuerySet(self, self._get_collection())
return self.__objects
@property
def _object_key(self):
"""Get the query dict that can be used to fetch this object from
the database. Most of the time it's a simple PK lookup, but in
case of a sharded collection with a compound shard key, it can
contain a more complex query.
"""Return a query dict that can be used to fetch this document.
Most of the time the dict is a simple PK lookup, but in case of
a sharded collection with a compound shard key, it can contain a more
complex query.
Note that the dict returned by this method uses MongoEngine field
names instead of PyMongo field names (e.g. "pk" instead of "_id",
"some__nested__field" instead of "some.nested.field", etc.).
"""
select_dict = {'pk': self.pk}
shard_key = self.__class__._meta.get('shard_key', tuple())
select_dict = {"pk": self.pk}
shard_key = self.__class__._meta.get("shard_key", tuple())
for k in shard_key:
path = self._lookup_field(k.split('.'))
actual_key = [p.db_field for p in path]
val = self
for ak in actual_key:
val = getattr(val, ak)
select_dict['__'.join(actual_key)] = val
field_parts = k.split(".")
for part in field_parts:
val = getattr(val, part)
select_dict["__".join(field_parts)] = val
return select_dict
def update(self, **kwargs):
@ -575,14 +603,13 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
been saved.
"""
if self.pk is None:
if kwargs.get('upsert', False):
if kwargs.get("upsert", False):
query = self.to_mongo()
if '_cls' in query:
del query['_cls']
if "_cls" in query:
del query["_cls"]
return self._qs.filter(**query).update_one(**kwargs)
else:
raise OperationError(
'attempt to update a document not yet saved')
raise OperationError("attempt to update a document not yet saved")
# Need to add shard key to query, or you get an error
return self._qs.filter(**self._object_key).update_one(**kwargs)
@ -606,16 +633,17 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
signals.pre_delete.send(self.__class__, document=self, **signal_kwargs)
# Delete FileFields separately
FileField = _import_class('FileField')
FileField = _import_class("FileField")
for name, field in iteritems(self._fields):
if isinstance(field, FileField):
getattr(self, name).delete()
try:
self._qs.filter(
**self._object_key).delete(write_concern=write_concern, _from_doc_delete=True)
self._qs.filter(**self._object_key).delete(
write_concern=write_concern, _from_doc_delete=True
)
except pymongo.errors.OperationFailure as err:
message = u'Could not delete document (%s)' % err.message
message = u"Could not delete document (%s)" % err.message
raise OperationError(message)
signals.post_delete.send(self.__class__, document=self, **signal_kwargs)
@ -684,7 +712,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
.. versionadded:: 0.5
"""
DeReference = _import_class('DeReference')
DeReference = _import_class("DeReference")
DeReference()([self], max_depth + 1)
return self
@ -702,20 +730,24 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
if fields and isinstance(fields[0], int):
max_depth = fields[0]
fields = fields[1:]
elif 'max_depth' in kwargs:
max_depth = kwargs['max_depth']
elif "max_depth" in kwargs:
max_depth = kwargs["max_depth"]
if self.pk is None:
raise self.DoesNotExist('Document does not exist')
raise self.DoesNotExist("Document does not exist")
obj = self._qs.read_preference(ReadPreference.PRIMARY).filter(
**self._object_key).only(*fields).limit(
1).select_related(max_depth=max_depth)
obj = (
self._qs.read_preference(ReadPreference.PRIMARY)
.filter(**self._object_key)
.only(*fields)
.limit(1)
.select_related(max_depth=max_depth)
)
if obj:
obj = obj[0]
else:
raise self.DoesNotExist('Document does not exist')
raise self.DoesNotExist("Document does not exist")
for field in obj._data:
if not fields or field in fields:
try:
@ -731,9 +763,11 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
# i.e. obj.update(unset__field=1) followed by obj.reload()
delattr(self, field)
self._changed_fields = list(
set(self._changed_fields) - set(fields)
) if fields else obj._changed_fields
self._changed_fields = (
list(set(self._changed_fields) - set(fields))
if fields
else obj._changed_fields
)
self._created = False
return self
@ -759,7 +793,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
"""Returns an instance of :class:`~bson.dbref.DBRef` useful in
`__raw__` queries."""
if self.pk is None:
msg = 'Only saved documents can have a valid dbref'
msg = "Only saved documents can have a valid dbref"
raise OperationError(msg)
return DBRef(self.__class__._get_collection_name(), self.pk)
@ -768,18 +802,22 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
"""This method registers the delete rules to apply when removing this
object.
"""
classes = [get_document(class_name)
for class_name in cls._subclasses
if class_name != cls.__name__] + [cls]
documents = [get_document(class_name)
for class_name in document_cls._subclasses
if class_name != document_cls.__name__] + [document_cls]
classes = [
get_document(class_name)
for class_name in cls._subclasses
if class_name != cls.__name__
] + [cls]
documents = [
get_document(class_name)
for class_name in document_cls._subclasses
if class_name != document_cls.__name__
] + [document_cls]
for klass in classes:
for document_cls in documents:
delete_rules = klass._meta.get('delete_rules') or {}
delete_rules = klass._meta.get("delete_rules") or {}
delete_rules[(document_cls, field_name)] = rule
klass._meta['delete_rules'] = delete_rules
klass._meta["delete_rules"] = delete_rules
@classmethod
def drop_collection(cls):
@ -794,8 +832,9 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
"""
coll_name = cls._get_collection_name()
if not coll_name:
raise OperationError('Document %s has no collection defined '
'(is it abstract ?)' % cls)
raise OperationError(
"Document %s has no collection defined (is it abstract ?)" % cls
)
cls._collection = None
db = cls._get_db()
db.drop_collection(coll_name)
@ -811,19 +850,18 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
"""
index_spec = cls._build_index_spec(keys)
index_spec = index_spec.copy()
fields = index_spec.pop('fields')
drop_dups = kwargs.get('drop_dups', False)
fields = index_spec.pop("fields")
drop_dups = kwargs.get("drop_dups", False)
if drop_dups:
msg = 'drop_dups is deprecated and is removed when using PyMongo 3+.'
msg = "drop_dups is deprecated and is removed when using PyMongo 3+."
warnings.warn(msg, DeprecationWarning)
index_spec['background'] = background
index_spec["background"] = background
index_spec.update(kwargs)
return cls._get_collection().create_index(fields, **index_spec)
@classmethod
def ensure_index(cls, key_or_list, drop_dups=False, background=False,
**kwargs):
def ensure_index(cls, key_or_list, drop_dups=False, background=False, **kwargs):
"""Ensure that the given indexes are in place. Deprecated in favour
of create_index.
@ -835,7 +873,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
will be removed if PyMongo3+ is used
"""
if drop_dups:
msg = 'drop_dups is deprecated and is removed when using PyMongo 3+.'
msg = "drop_dups is deprecated and is removed when using PyMongo 3+."
warnings.warn(msg, DeprecationWarning)
return cls.create_index(key_or_list, background=background, **kwargs)
@ -848,12 +886,12 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
.. note:: You can disable automatic index creation by setting
`auto_create_index` to False in the documents meta data
"""
background = cls._meta.get('index_background', False)
drop_dups = cls._meta.get('index_drop_dups', False)
index_opts = cls._meta.get('index_opts') or {}
index_cls = cls._meta.get('index_cls', True)
background = cls._meta.get("index_background", False)
drop_dups = cls._meta.get("index_drop_dups", False)
index_opts = cls._meta.get("index_opts") or {}
index_cls = cls._meta.get("index_cls", True)
if drop_dups:
msg = 'drop_dups is deprecated and is removed when using PyMongo 3+.'
msg = "drop_dups is deprecated and is removed when using PyMongo 3+."
warnings.warn(msg, DeprecationWarning)
collection = cls._get_collection()
@ -869,40 +907,39 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
cls_indexed = False
# Ensure document-defined indexes are created
if cls._meta['index_specs']:
index_spec = cls._meta['index_specs']
if cls._meta["index_specs"]:
index_spec = cls._meta["index_specs"]
for spec in index_spec:
spec = spec.copy()
fields = spec.pop('fields')
fields = spec.pop("fields")
cls_indexed = cls_indexed or includes_cls(fields)
opts = index_opts.copy()
opts.update(spec)
# we shouldn't pass 'cls' to the collection.ensureIndex options
# because of https://jira.mongodb.org/browse/SERVER-769
if 'cls' in opts:
del opts['cls']
if "cls" in opts:
del opts["cls"]
collection.create_index(fields, background=background, **opts)
# If _cls is being used (for polymorphism), it needs an index,
# only if another index doesn't begin with _cls
if index_cls and not cls_indexed and cls._meta.get('allow_inheritance'):
if index_cls and not cls_indexed and cls._meta.get("allow_inheritance"):
# we shouldn't pass 'cls' to the collection.ensureIndex options
# because of https://jira.mongodb.org/browse/SERVER-769
if 'cls' in index_opts:
del index_opts['cls']
if "cls" in index_opts:
del index_opts["cls"]
collection.create_index('_cls', background=background,
**index_opts)
collection.create_index("_cls", background=background, **index_opts)
@classmethod
def list_indexes(cls):
""" Lists all of the indexes that should be created for given
collection. It includes all the indexes from super- and sub-classes.
"""
if cls._meta.get('abstract'):
if cls._meta.get("abstract"):
return []
# get all the base classes, subclasses and siblings
@ -910,22 +947,27 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
def get_classes(cls):
if (cls not in classes and
isinstance(cls, TopLevelDocumentMetaclass)):
if cls not in classes and isinstance(cls, TopLevelDocumentMetaclass):
classes.append(cls)
for base_cls in cls.__bases__:
if (isinstance(base_cls, TopLevelDocumentMetaclass) and
base_cls != Document and
not base_cls._meta.get('abstract') and
base_cls._get_collection().full_name == cls._get_collection().full_name and
base_cls not in classes):
if (
isinstance(base_cls, TopLevelDocumentMetaclass)
and base_cls != Document
and not base_cls._meta.get("abstract")
and base_cls._get_collection().full_name
== cls._get_collection().full_name
and base_cls not in classes
):
classes.append(base_cls)
get_classes(base_cls)
for subclass in cls.__subclasses__():
if (isinstance(base_cls, TopLevelDocumentMetaclass) and
subclass._get_collection().full_name == cls._get_collection().full_name and
subclass not in classes):
if (
isinstance(base_cls, TopLevelDocumentMetaclass)
and subclass._get_collection().full_name
== cls._get_collection().full_name
and subclass not in classes
):
classes.append(subclass)
get_classes(subclass)
@ -935,11 +977,11 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
def get_indexes_spec(cls):
indexes = []
if cls._meta['index_specs']:
index_spec = cls._meta['index_specs']
if cls._meta["index_specs"]:
index_spec = cls._meta["index_specs"]
for spec in index_spec:
spec = spec.copy()
fields = spec.pop('fields')
fields = spec.pop("fields")
indexes.append(fields)
return indexes
@ -950,10 +992,10 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
indexes.append(index)
# finish up by appending { '_id': 1 } and { '_cls': 1 }, if needed
if [(u'_id', 1)] not in indexes:
indexes.append([(u'_id', 1)])
if cls._meta.get('index_cls', True) and cls._meta.get('allow_inheritance'):
indexes.append([(u'_cls', 1)])
if [(u"_id", 1)] not in indexes:
indexes.append([(u"_id", 1)])
if cls._meta.get("index_cls", True) and cls._meta.get("allow_inheritance"):
indexes.append([(u"_cls", 1)])
return indexes
@ -967,27 +1009,26 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
existing = []
for info in cls._get_collection().index_information().values():
if '_fts' in info['key'][0]:
index_type = info['key'][0][1]
text_index_fields = info.get('weights').keys()
existing.append(
[(key, index_type) for key in text_index_fields])
if "_fts" in info["key"][0]:
index_type = info["key"][0][1]
text_index_fields = info.get("weights").keys()
existing.append([(key, index_type) for key in text_index_fields])
else:
existing.append(info['key'])
existing.append(info["key"])
missing = [index for index in required if index not in existing]
extra = [index for index in existing if index not in required]
# if { _cls: 1 } is missing, make sure it's *really* necessary
if [(u'_cls', 1)] in missing:
if [(u"_cls", 1)] in missing:
cls_obsolete = False
for index in existing:
if includes_cls(index) and index not in extra:
cls_obsolete = True
break
if cls_obsolete:
missing.remove([(u'_cls', 1)])
missing.remove([(u"_cls", 1)])
return {'missing': missing, 'extra': extra}
return {"missing": missing, "extra": extra}
class DynamicDocument(six.with_metaclass(TopLevelDocumentMetaclass, Document)):
@ -1072,17 +1113,16 @@ class MapReduceDocument(object):
"""Lazy-load the object referenced by ``self.key``. ``self.key``
should be the ``primary_key``.
"""
id_field = self._document()._meta['id_field']
id_field = self._document()._meta["id_field"]
id_field_type = type(id_field)
if not isinstance(self.key, id_field_type):
try:
self.key = id_field_type(self.key)
except Exception:
raise Exception('Could not cast key as %s' %
id_field_type.__name__)
raise Exception("Could not cast key as %s" % id_field_type.__name__)
if not hasattr(self, '_key_object'):
if not hasattr(self, "_key_object"):
self._key_object = self._document.objects.with_id(self.key)
return self._key_object
return self._key_object

View File

@ -3,10 +3,20 @@ from collections import defaultdict
import six
from six import iteritems
__all__ = ('NotRegistered', 'InvalidDocumentError', 'LookUpError',
'DoesNotExist', 'MultipleObjectsReturned', 'InvalidQueryError',
'OperationError', 'NotUniqueError', 'FieldDoesNotExist',
'ValidationError', 'SaveConditionError', 'DeprecatedError')
__all__ = (
"NotRegistered",
"InvalidDocumentError",
"LookUpError",
"DoesNotExist",
"MultipleObjectsReturned",
"InvalidQueryError",
"OperationError",
"NotUniqueError",
"FieldDoesNotExist",
"ValidationError",
"SaveConditionError",
"DeprecatedError",
)
class NotRegistered(Exception):
@ -71,25 +81,25 @@ class ValidationError(AssertionError):
field_name = None
_message = None
def __init__(self, message='', **kwargs):
def __init__(self, message="", **kwargs):
super(ValidationError, self).__init__(message)
self.errors = kwargs.get('errors', {})
self.field_name = kwargs.get('field_name')
self.errors = kwargs.get("errors", {})
self.field_name = kwargs.get("field_name")
self.message = message
def __str__(self):
return six.text_type(self.message)
def __repr__(self):
return '%s(%s,)' % (self.__class__.__name__, self.message)
return "%s(%s,)" % (self.__class__.__name__, self.message)
def __getattribute__(self, name):
message = super(ValidationError, self).__getattribute__(name)
if name == 'message':
if name == "message":
if self.field_name:
message = '%s' % message
message = "%s" % message
if self.errors:
message = '%s(%s)' % (message, self._format_errors())
message = "%s(%s)" % (message, self._format_errors())
return message
def _get_message(self):
@ -128,22 +138,22 @@ class ValidationError(AssertionError):
def _format_errors(self):
"""Returns a string listing all errors within a document"""
def generate_key(value, prefix=''):
def generate_key(value, prefix=""):
if isinstance(value, list):
value = ' '.join([generate_key(k) for k in value])
value = " ".join([generate_key(k) for k in value])
elif isinstance(value, dict):
value = ' '.join(
[generate_key(v, k) for k, v in iteritems(value)])
value = " ".join([generate_key(v, k) for k, v in iteritems(value)])
results = '%s.%s' % (prefix, value) if prefix else value
results = "%s.%s" % (prefix, value) if prefix else value
return results
error_dict = defaultdict(list)
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 iteritems(error_dict)])
return " ".join(["%s: %s" % (k, v) for k, v in iteritems(error_dict)])
class DeprecatedError(Exception):
"""Raise when a user uses a feature that has been Deprecated"""
pass

File diff suppressed because it is too large Load Diff

View File

@ -15,5 +15,5 @@ def get_mongodb_version():
:return: tuple(int, int)
"""
version_list = get_connection().server_info()['versionArray'][:2] # e.g: (3, 2)
version_list = get_connection().server_info()["versionArray"][:2] # e.g: (3, 2)
return tuple(version_list)

View File

@ -27,6 +27,6 @@ def list_collection_names(db, include_system_collections=False):
collections = db.collection_names()
if not include_system_collections:
collections = [c for c in collections if not c.startswith('system.')]
collections = [c for c in collections if not c.startswith("system.")]
return collections

View File

@ -7,11 +7,22 @@ from mongoengine.queryset.visitor import *
# Expose just the public subset of all imported objects and constants.
__all__ = (
'QuerySet', 'QuerySetNoCache', 'Q', 'queryset_manager', 'QuerySetManager',
'QueryFieldList', 'DO_NOTHING', 'NULLIFY', 'CASCADE', 'DENY', 'PULL',
"QuerySet",
"QuerySetNoCache",
"Q",
"queryset_manager",
"QuerySetManager",
"QueryFieldList",
"DO_NOTHING",
"NULLIFY",
"CASCADE",
"DENY",
"PULL",
# Errors that might be related to a queryset, mostly here for backward
# compatibility
'DoesNotExist', 'InvalidQueryError', 'MultipleObjectsReturned',
'NotUniqueError', 'OperationError',
"DoesNotExist",
"InvalidQueryError",
"MultipleObjectsReturned",
"NotUniqueError",
"OperationError",
)

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,15 @@
__all__ = ('QueryFieldList',)
__all__ = ("QueryFieldList",)
class QueryFieldList(object):
"""Object that handles combinations of .only() and .exclude() calls"""
ONLY = 1
EXCLUDE = 0
def __init__(self, fields=None, value=ONLY, always_include=None, _only_called=False):
def __init__(
self, fields=None, value=ONLY, always_include=None, _only_called=False
):
"""The QueryFieldList builder
:param fields: A list of fields used in `.only()` or `.exclude()`
@ -49,7 +52,7 @@ class QueryFieldList(object):
self.fields = f.fields - self.fields
self._clean_slice()
if '_id' in f.fields:
if "_id" in f.fields:
self._id = f.value
if self.always_include:
@ -59,7 +62,7 @@ class QueryFieldList(object):
else:
self.fields -= self.always_include
if getattr(f, '_only_called', False):
if getattr(f, "_only_called", False):
self._only_called = True
return self
@ -73,7 +76,7 @@ class QueryFieldList(object):
if self.slice:
field_list.update(self.slice)
if self._id is not None:
field_list['_id'] = self._id
field_list["_id"] = self._id
return field_list
def reset(self):

View File

@ -1,7 +1,7 @@
from functools import partial
from mongoengine.queryset.queryset import QuerySet
__all__ = ('queryset_manager', 'QuerySetManager')
__all__ = ("queryset_manager", "QuerySetManager")
class QuerySetManager(object):
@ -33,7 +33,7 @@ class QuerySetManager(object):
return self
# owner is the document that contains the QuerySetManager
queryset_class = owner._meta.get('queryset_class', self.default)
queryset_class = owner._meta.get("queryset_class", self.default)
queryset = queryset_class(owner, owner._get_collection())
if self.get_queryset:
arg_count = self.get_queryset.__code__.co_argcount

View File

@ -1,11 +1,24 @@
import six
from mongoengine.errors import OperationError
from mongoengine.queryset.base import (BaseQuerySet, CASCADE, DENY, DO_NOTHING,
NULLIFY, PULL)
from mongoengine.queryset.base import (
BaseQuerySet,
CASCADE,
DENY,
DO_NOTHING,
NULLIFY,
PULL,
)
__all__ = ('QuerySet', 'QuerySetNoCache', 'DO_NOTHING', 'NULLIFY', 'CASCADE',
'DENY', 'PULL')
__all__ = (
"QuerySet",
"QuerySetNoCache",
"DO_NOTHING",
"NULLIFY",
"CASCADE",
"DENY",
"PULL",
)
# The maximum number of items to display in a QuerySet.__repr__
REPR_OUTPUT_SIZE = 20
@ -57,12 +70,12 @@ class QuerySet(BaseQuerySet):
def __repr__(self):
"""Provide a string representation of the QuerySet"""
if self._iter:
return '.. queryset mid-iteration ..'
return ".. queryset mid-iteration .."
self._populate_cache()
data = self._result_cache[:REPR_OUTPUT_SIZE + 1]
data = self._result_cache[: REPR_OUTPUT_SIZE + 1]
if len(data) > REPR_OUTPUT_SIZE:
data[-1] = '...(remaining elements truncated)...'
data[-1] = "...(remaining elements truncated)..."
return repr(data)
def _iter_results(self):
@ -143,10 +156,9 @@ class QuerySet(BaseQuerySet):
.. versionadded:: 0.8.3 Convert to non caching queryset
"""
if self._result_cache is not None:
raise OperationError('QuerySet already cached')
raise OperationError("QuerySet already cached")
return self._clone_into(QuerySetNoCache(self._document,
self._collection))
return self._clone_into(QuerySetNoCache(self._document, self._collection))
class QuerySetNoCache(BaseQuerySet):
@ -165,7 +177,7 @@ class QuerySetNoCache(BaseQuerySet):
.. versionchanged:: 0.6.13 Now doesnt modify the cursor
"""
if self._iter:
return '.. queryset mid-iteration ..'
return ".. queryset mid-iteration .."
data = []
for _ in six.moves.range(REPR_OUTPUT_SIZE + 1):
@ -175,7 +187,7 @@ class QuerySetNoCache(BaseQuerySet):
break
if len(data) > REPR_OUTPUT_SIZE:
data[-1] = '...(remaining elements truncated)...'
data[-1] = "...(remaining elements truncated)..."
self.rewind()
return repr(data)

View File

@ -10,21 +10,54 @@ from mongoengine.base import UPDATE_OPERATORS
from mongoengine.common import _import_class
from mongoengine.errors import InvalidQueryError
__all__ = ('query', 'update')
__all__ = ("query", "update")
COMPARISON_OPERATORS = ('ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'mod',
'all', 'size', 'exists', 'not', 'elemMatch', 'type')
GEO_OPERATORS = ('within_distance', 'within_spherical_distance',
'within_box', 'within_polygon', 'near', 'near_sphere',
'max_distance', 'min_distance', 'geo_within', 'geo_within_box',
'geo_within_polygon', 'geo_within_center',
'geo_within_sphere', 'geo_intersects')
STRING_OPERATORS = ('contains', 'icontains', 'startswith',
'istartswith', 'endswith', 'iendswith',
'exact', 'iexact')
CUSTOM_OPERATORS = ('match',)
MATCH_OPERATORS = (COMPARISON_OPERATORS + GEO_OPERATORS +
STRING_OPERATORS + CUSTOM_OPERATORS)
COMPARISON_OPERATORS = (
"ne",
"gt",
"gte",
"lt",
"lte",
"in",
"nin",
"mod",
"all",
"size",
"exists",
"not",
"elemMatch",
"type",
)
GEO_OPERATORS = (
"within_distance",
"within_spherical_distance",
"within_box",
"within_polygon",
"near",
"near_sphere",
"max_distance",
"min_distance",
"geo_within",
"geo_within_box",
"geo_within_polygon",
"geo_within_center",
"geo_within_sphere",
"geo_intersects",
)
STRING_OPERATORS = (
"contains",
"icontains",
"startswith",
"istartswith",
"endswith",
"iendswith",
"exact",
"iexact",
)
CUSTOM_OPERATORS = ("match",)
MATCH_OPERATORS = (
COMPARISON_OPERATORS + GEO_OPERATORS + STRING_OPERATORS + CUSTOM_OPERATORS
)
# TODO make this less complex
@ -33,11 +66,11 @@ def query(_doc_cls=None, **kwargs):
mongo_query = {}
merge_query = defaultdict(list)
for key, value in sorted(kwargs.items()):
if key == '__raw__':
if key == "__raw__":
mongo_query.update(value)
continue
parts = key.rsplit('__')
parts = key.rsplit("__")
indices = [(i, p) for i, p in enumerate(parts) if p.isdigit()]
parts = [part for part in parts if not part.isdigit()]
# Check for an operator and transform to mongo-style if there is
@ -46,11 +79,11 @@ def query(_doc_cls=None, **kwargs):
op = parts.pop()
# Allow to escape operator-like field name by __
if len(parts) > 1 and parts[-1] == '':
if len(parts) > 1 and parts[-1] == "":
parts.pop()
negate = False
if len(parts) > 1 and parts[-1] == 'not':
if len(parts) > 1 and parts[-1] == "not":
parts.pop()
negate = True
@ -62,8 +95,8 @@ def query(_doc_cls=None, **kwargs):
raise InvalidQueryError(e)
parts = []
CachedReferenceField = _import_class('CachedReferenceField')
GenericReferenceField = _import_class('GenericReferenceField')
CachedReferenceField = _import_class("CachedReferenceField")
GenericReferenceField = _import_class("GenericReferenceField")
cleaned_fields = []
for field in fields:
@ -73,7 +106,7 @@ def query(_doc_cls=None, **kwargs):
append_field = False
# is last and CachedReferenceField
elif isinstance(field, CachedReferenceField) and fields[-1] == field:
parts.append('%s._id' % field.db_field)
parts.append("%s._id" % field.db_field)
else:
parts.append(field.db_field)
@ -83,15 +116,15 @@ def query(_doc_cls=None, **kwargs):
# Convert value to proper value
field = cleaned_fields[-1]
singular_ops = [None, 'ne', 'gt', 'gte', 'lt', 'lte', 'not']
singular_ops = [None, "ne", "gt", "gte", "lt", "lte", "not"]
singular_ops += STRING_OPERATORS
if op in singular_ops:
value = field.prepare_query_value(op, value)
if isinstance(field, CachedReferenceField) and value:
value = value['_id']
value = value["_id"]
elif op in ('in', 'nin', 'all', 'near') and not isinstance(value, dict):
elif op in ("in", "nin", "all", "near") and not isinstance(value, dict):
# Raise an error if the in/nin/all/near param is not iterable.
value = _prepare_query_for_iterable(field, op, value)
@ -101,39 +134,40 @@ def query(_doc_cls=None, **kwargs):
# * If the value is an ObjectId, the key should be "field_name._ref.$id".
if isinstance(field, GenericReferenceField):
if isinstance(value, DBRef):
parts[-1] += '._ref'
parts[-1] += "._ref"
elif isinstance(value, ObjectId):
parts[-1] += '._ref.$id'
parts[-1] += "._ref.$id"
# if op and op not in COMPARISON_OPERATORS:
if op:
if op in GEO_OPERATORS:
value = _geo_operator(field, op, value)
elif op in ('match', 'elemMatch'):
ListField = _import_class('ListField')
EmbeddedDocumentField = _import_class('EmbeddedDocumentField')
elif op in ("match", "elemMatch"):
ListField = _import_class("ListField")
EmbeddedDocumentField = _import_class("EmbeddedDocumentField")
if (
isinstance(value, dict) and
isinstance(field, ListField) and
isinstance(field.field, EmbeddedDocumentField)
isinstance(value, dict)
and isinstance(field, ListField)
and isinstance(field.field, EmbeddedDocumentField)
):
value = query(field.field.document_type, **value)
else:
value = field.prepare_query_value(op, value)
value = {'$elemMatch': value}
value = {"$elemMatch": value}
elif op in CUSTOM_OPERATORS:
NotImplementedError('Custom method "%s" has not '
'been implemented' % op)
NotImplementedError(
'Custom method "%s" has not ' "been implemented" % op
)
elif op not in STRING_OPERATORS:
value = {'$' + op: value}
value = {"$" + op: value}
if negate:
value = {'$not': value}
value = {"$not": value}
for i, part in indices:
parts.insert(i, part)
key = '.'.join(parts)
key = ".".join(parts)
if op is None or key not in mongo_query:
mongo_query[key] = value
@ -142,30 +176,35 @@ def query(_doc_cls=None, **kwargs):
mongo_query[key].update(value)
# $max/minDistance needs to come last - convert to SON
value_dict = mongo_query[key]
if ('$maxDistance' in value_dict or '$minDistance' in value_dict) and \
('$near' in value_dict or '$nearSphere' in value_dict):
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 iteritems(value_dict):
if k == '$maxDistance' or k == '$minDistance':
if k == "$maxDistance" or k == "$minDistance":
continue
value_son[k] = v
# Required for MongoDB >= 2.6, may fail when combining
# PyMongo 3+ and MongoDB < 2.6
near_embedded = False
for near_op in ('$near', '$nearSphere'):
for near_op in ("$near", "$nearSphere"):
if isinstance(value_dict.get(near_op), dict):
value_son[near_op] = SON(value_son[near_op])
if '$maxDistance' in value_dict:
value_son[near_op]['$maxDistance'] = value_dict['$maxDistance']
if '$minDistance' in value_dict:
value_son[near_op]['$minDistance'] = value_dict['$minDistance']
if "$maxDistance" in value_dict:
value_son[near_op]["$maxDistance"] = value_dict[
"$maxDistance"
]
if "$minDistance" in value_dict:
value_son[near_op]["$minDistance"] = value_dict[
"$minDistance"
]
near_embedded = True
if not near_embedded:
if '$maxDistance' in value_dict:
value_son['$maxDistance'] = value_dict['$maxDistance']
if '$minDistance' in value_dict:
value_son['$minDistance'] = value_dict['$minDistance']
if "$maxDistance" in value_dict:
value_son["$maxDistance"] = value_dict["$maxDistance"]
if "$minDistance" in value_dict:
value_son["$minDistance"] = value_dict["$minDistance"]
mongo_query[key] = value_son
else:
# Store for manually merging later
@ -177,10 +216,10 @@ def query(_doc_cls=None, **kwargs):
del mongo_query[k]
if isinstance(v, list):
value = [{k: val} for val in v]
if '$and' in mongo_query.keys():
mongo_query['$and'].extend(value)
if "$and" in mongo_query.keys():
mongo_query["$and"].extend(value)
else:
mongo_query['$and'] = value
mongo_query["$and"] = value
return mongo_query
@ -192,15 +231,15 @@ def update(_doc_cls=None, **update):
mongo_update = {}
for key, value in update.items():
if key == '__raw__':
if key == "__raw__":
mongo_update.update(value)
continue
parts = key.split('__')
parts = key.split("__")
# if there is no operator, default to 'set'
if len(parts) < 3 and parts[0] not in UPDATE_OPERATORS:
parts.insert(0, 'set')
parts.insert(0, "set")
# Check for an operator and transform to mongo-style if there is
op = None
@ -208,13 +247,13 @@ def update(_doc_cls=None, **update):
op = parts.pop(0)
# Convert Pythonic names to Mongo equivalents
operator_map = {
'push_all': 'pushAll',
'pull_all': 'pullAll',
'dec': 'inc',
'add_to_set': 'addToSet',
'set_on_insert': 'setOnInsert'
"push_all": "pushAll",
"pull_all": "pullAll",
"dec": "inc",
"add_to_set": "addToSet",
"set_on_insert": "setOnInsert",
}
if op == 'dec':
if op == "dec":
# Support decrement by flipping a positive value's sign
# and using 'inc'
value = -value
@ -227,7 +266,7 @@ def update(_doc_cls=None, **update):
match = parts.pop()
# Allow to escape operator-like field name by __
if len(parts) > 1 and parts[-1] == '':
if len(parts) > 1 and parts[-1] == "":
parts.pop()
if _doc_cls:
@ -244,8 +283,8 @@ def update(_doc_cls=None, **update):
append_field = True
if isinstance(field, six.string_types):
# Convert the S operator to $
if field == 'S':
field = '$'
if field == "S":
field = "$"
parts.append(field)
append_field = False
else:
@ -253,7 +292,7 @@ def update(_doc_cls=None, **update):
if append_field:
appended_sub_field = False
cleaned_fields.append(field)
if hasattr(field, 'field'):
if hasattr(field, "field"):
cleaned_fields.append(field.field)
appended_sub_field = True
@ -263,52 +302,53 @@ def update(_doc_cls=None, **update):
else:
field = cleaned_fields[-1]
GeoJsonBaseField = _import_class('GeoJsonBaseField')
GeoJsonBaseField = _import_class("GeoJsonBaseField")
if isinstance(field, GeoJsonBaseField):
value = field.to_mongo(value)
if op == 'pull':
if op == "pull":
if field.required or value is not None:
if match in ('in', 'nin') and not isinstance(value, dict):
if match in ("in", "nin") and not isinstance(value, dict):
value = _prepare_query_for_iterable(field, op, value)
else:
value = field.prepare_query_value(op, value)
elif op == 'push' and isinstance(value, (list, tuple, set)):
elif op == "push" and isinstance(value, (list, tuple, set)):
value = [field.prepare_query_value(op, v) for v in value]
elif op in (None, 'set', 'push'):
elif op in (None, "set", "push"):
if field.required or value is not None:
value = field.prepare_query_value(op, value)
elif op in ('pushAll', 'pullAll'):
elif op in ("pushAll", "pullAll"):
value = [field.prepare_query_value(op, v) for v in value]
elif op in ('addToSet', 'setOnInsert'):
elif op in ("addToSet", "setOnInsert"):
if isinstance(value, (list, tuple, set)):
value = [field.prepare_query_value(op, v) for v in value]
elif field.required or value is not None:
value = field.prepare_query_value(op, value)
elif op == 'unset':
elif op == "unset":
value = 1
elif op == 'inc':
elif op == "inc":
value = field.prepare_query_value(op, value)
if match:
match = '$' + match
match = "$" + match
value = {match: value}
key = '.'.join(parts)
key = ".".join(parts)
if 'pull' in op and '.' in key:
if "pull" in op and "." in key:
# Dot operators don't work on pull operations
# unless they point to a list field
# Otherwise it uses nested dict syntax
if op == 'pullAll':
raise InvalidQueryError('pullAll operations only support '
'a single field depth')
if op == "pullAll":
raise InvalidQueryError(
"pullAll operations only support a single field depth"
)
# Look for the last list field and use dot notation until there
field_classes = [c.__class__ for c in cleaned_fields]
field_classes.reverse()
ListField = _import_class('ListField')
EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField')
ListField = _import_class("ListField")
EmbeddedDocumentListField = _import_class("EmbeddedDocumentListField")
if ListField in field_classes or EmbeddedDocumentListField in field_classes:
# Join all fields via dot notation to the last ListField or EmbeddedDocumentListField
# Then process as normal
@ -317,37 +357,36 @@ def update(_doc_cls=None, **update):
else:
_check_field = EmbeddedDocumentListField
last_listField = len(
cleaned_fields) - field_classes.index(_check_field)
key = '.'.join(parts[:last_listField])
last_listField = len(cleaned_fields) - field_classes.index(_check_field)
key = ".".join(parts[:last_listField])
parts = parts[last_listField:]
parts.insert(0, key)
parts.reverse()
for key in parts:
value = {key: value}
elif op == 'addToSet' and isinstance(value, list):
value = {key: {'$each': value}}
elif op in ('push', 'pushAll'):
elif op == "addToSet" and isinstance(value, list):
value = {key: {"$each": value}}
elif op in ("push", "pushAll"):
if parts[-1].isdigit():
key = '.'.join(parts[0:-1])
key = ".".join(parts[0:-1])
position = int(parts[-1])
# $position expects an iterable. If pushing a single value,
# wrap it in a list.
if not isinstance(value, (set, tuple, list)):
value = [value]
value = {key: {'$each': value, '$position': position}}
value = {key: {"$each": value, "$position": position}}
else:
if op == 'pushAll':
op = 'push' # convert to non-deprecated keyword
if op == "pushAll":
op = "push" # convert to non-deprecated keyword
if not isinstance(value, (set, tuple, list)):
value = [value]
value = {key: {'$each': value}}
value = {key: {"$each": value}}
else:
value = {key: value}
else:
value = {key: value}
key = '$' + op
key = "$" + op
if key not in mongo_update:
mongo_update[key] = value
elif key in mongo_update and isinstance(mongo_update[key], dict):
@ -358,45 +397,45 @@ def update(_doc_cls=None, **update):
def _geo_operator(field, op, value):
"""Helper to return the query for a given geo query."""
if op == 'max_distance':
value = {'$maxDistance': value}
elif op == 'min_distance':
value = {'$minDistance': value}
if op == "max_distance":
value = {"$maxDistance": value}
elif op == "min_distance":
value = {"$minDistance": value}
elif field._geo_index == pymongo.GEO2D:
if op == 'within_distance':
value = {'$within': {'$center': value}}
elif op == 'within_spherical_distance':
value = {'$within': {'$centerSphere': value}}
elif op == 'within_polygon':
value = {'$within': {'$polygon': value}}
elif op == 'near':
value = {'$near': value}
elif op == 'near_sphere':
value = {'$nearSphere': value}
elif op == 'within_box':
value = {'$within': {'$box': value}}
else:
raise NotImplementedError('Geo method "%s" has not been '
'implemented for a GeoPointField' % op)
else:
if op == 'geo_within':
value = {'$geoWithin': _infer_geometry(value)}
elif op == 'geo_within_box':
value = {'$geoWithin': {'$box': value}}
elif op == 'geo_within_polygon':
value = {'$geoWithin': {'$polygon': value}}
elif op == 'geo_within_center':
value = {'$geoWithin': {'$center': value}}
elif op == 'geo_within_sphere':
value = {'$geoWithin': {'$centerSphere': value}}
elif op == 'geo_intersects':
value = {'$geoIntersects': _infer_geometry(value)}
elif op == 'near':
value = {'$near': _infer_geometry(value)}
if op == "within_distance":
value = {"$within": {"$center": value}}
elif op == "within_spherical_distance":
value = {"$within": {"$centerSphere": value}}
elif op == "within_polygon":
value = {"$within": {"$polygon": value}}
elif op == "near":
value = {"$near": value}
elif op == "near_sphere":
value = {"$nearSphere": value}
elif op == "within_box":
value = {"$within": {"$box": value}}
else:
raise NotImplementedError(
'Geo method "%s" has not been implemented for a %s '
% (op, field._name)
'Geo method "%s" has not been ' "implemented for a GeoPointField" % op
)
else:
if op == "geo_within":
value = {"$geoWithin": _infer_geometry(value)}
elif op == "geo_within_box":
value = {"$geoWithin": {"$box": value}}
elif op == "geo_within_polygon":
value = {"$geoWithin": {"$polygon": value}}
elif op == "geo_within_center":
value = {"$geoWithin": {"$center": value}}
elif op == "geo_within_sphere":
value = {"$geoWithin": {"$centerSphere": value}}
elif op == "geo_intersects":
value = {"$geoIntersects": _infer_geometry(value)}
elif op == "near":
value = {"$near": _infer_geometry(value)}
else:
raise NotImplementedError(
'Geo method "%s" has not been implemented for a %s ' % (op, field._name)
)
return value
@ -406,51 +445,58 @@ def _infer_geometry(value):
given value.
"""
if isinstance(value, dict):
if '$geometry' in value:
if "$geometry" in value:
return value
elif 'coordinates' in value and 'type' in value:
return {'$geometry': value}
raise InvalidQueryError('Invalid $geometry dictionary should have '
'type and coordinates keys')
elif "coordinates" in value and "type" in value:
return {"$geometry": value}
raise InvalidQueryError(
"Invalid $geometry dictionary should have type and coordinates keys"
)
elif isinstance(value, (list, set)):
# TODO: shouldn't we test value[0][0][0][0] to see if it is MultiPolygon?
try:
value[0][0][0]
return {'$geometry': {'type': 'Polygon', 'coordinates': value}}
return {"$geometry": {"type": "Polygon", "coordinates": value}}
except (TypeError, IndexError):
pass
try:
value[0][0]
return {'$geometry': {'type': 'LineString', 'coordinates': value}}
return {"$geometry": {"type": "LineString", "coordinates": value}}
except (TypeError, IndexError):
pass
try:
value[0]
return {'$geometry': {'type': 'Point', 'coordinates': value}}
return {"$geometry": {"type": "Point", "coordinates": value}}
except (TypeError, IndexError):
pass
raise InvalidQueryError('Invalid $geometry data. Can be either a '
'dictionary or (nested) lists of coordinate(s)')
raise InvalidQueryError(
"Invalid $geometry data. Can be either a "
"dictionary or (nested) lists of coordinate(s)"
)
def _prepare_query_for_iterable(field, op, value):
# We need a special check for BaseDocument, because - although it's iterable - using
# it as such in the context of this method is most definitely a mistake.
BaseDocument = _import_class('BaseDocument')
BaseDocument = _import_class("BaseDocument")
if isinstance(value, BaseDocument):
raise TypeError("When using the `in`, `nin`, `all`, or "
"`near`-operators you can\'t use a "
"`Document`, you must wrap your object "
"in a list (object -> [object]).")
raise TypeError(
"When using the `in`, `nin`, `all`, or "
"`near`-operators you can't use a "
"`Document`, you must wrap your object "
"in a list (object -> [object])."
)
if not hasattr(value, '__iter__'):
raise TypeError("The `in`, `nin`, `all`, or "
"`near`-operators must be applied to an "
"iterable (e.g. a list).")
if not hasattr(value, "__iter__"):
raise TypeError(
"The `in`, `nin`, `all`, or "
"`near`-operators must be applied to an "
"iterable (e.g. a list)."
)
return [field.prepare_query_value(op, v) for v in value]

View File

@ -3,7 +3,7 @@ import copy
from mongoengine.errors import InvalidQueryError
from mongoengine.queryset import transform
__all__ = ('Q', 'QNode')
__all__ = ("Q", "QNode")
class QNodeVisitor(object):
@ -69,9 +69,9 @@ class QueryCompilerVisitor(QNodeVisitor):
self.document = document
def visit_combination(self, combination):
operator = '$and'
operator = "$and"
if combination.operation == combination.OR:
operator = '$or'
operator = "$or"
return {operator: combination.children}
def visit_query(self, query):
@ -96,7 +96,7 @@ class QNode(object):
"""Combine this node with another node into a QCombination
object.
"""
if getattr(other, 'empty', True):
if getattr(other, "empty", True):
return self
if self.empty:
@ -132,8 +132,8 @@ class QCombination(QNode):
self.children.append(node)
def __repr__(self):
op = ' & ' if self.operation is self.AND else ' | '
return '(%s)' % op.join([repr(node) for node in self.children])
op = " & " if self.operation is self.AND else " | "
return "(%s)" % op.join([repr(node) for node in self.children])
def accept(self, visitor):
for i in range(len(self.children)):
@ -156,7 +156,7 @@ class Q(QNode):
self.query = query
def __repr__(self):
return 'Q(**%s)' % repr(self.query)
return "Q(**%s)" % repr(self.query)
def accept(self, visitor):
return visitor.visit_query(self)

View File

@ -1,5 +1,12 @@
__all__ = ('pre_init', 'post_init', 'pre_save', 'pre_save_post_validation',
'post_save', 'pre_delete', 'post_delete')
__all__ = (
"pre_init",
"post_init",
"pre_save",
"pre_save_post_validation",
"post_save",
"pre_delete",
"post_delete",
)
signals_available = False
try:
@ -7,6 +14,7 @@ try:
signals_available = True
except ImportError:
class Namespace(object):
def signal(self, name, doc=None):
return _FakeSignal(name, doc)
@ -23,13 +31,16 @@ except ImportError:
self.__doc__ = doc
def _fail(self, *args, **kwargs):
raise RuntimeError('signalling support is unavailable '
'because the blinker library is '
'not installed.')
raise RuntimeError(
"signalling support is unavailable "
"because the blinker library is "
"not installed."
)
send = lambda *a, **kw: None # noqa
connect = disconnect = has_receivers_for = receivers_for = \
temporarily_connected_to = _fail
connect = (
disconnect
) = has_receivers_for = receivers_for = temporarily_connected_to = _fail
del _fail
@ -37,12 +48,12 @@ except ImportError:
# not put signals in here. Create your own namespace instead.
_signals = Namespace()
pre_init = _signals.signal('pre_init')
post_init = _signals.signal('post_init')
pre_save = _signals.signal('pre_save')
pre_save_post_validation = _signals.signal('pre_save_post_validation')
post_save = _signals.signal('post_save')
pre_delete = _signals.signal('pre_delete')
post_delete = _signals.signal('post_delete')
pre_bulk_insert = _signals.signal('pre_bulk_insert')
post_bulk_insert = _signals.signal('post_bulk_insert')
pre_init = _signals.signal("pre_init")
post_init = _signals.signal("post_init")
pre_save = _signals.signal("pre_save")
pre_save_post_validation = _signals.signal("pre_save_post_validation")
post_save = _signals.signal("post_save")
pre_delete = _signals.signal("pre_delete")
post_delete = _signals.signal("post_delete")
pre_bulk_insert = _signals.signal("pre_bulk_insert")
post_bulk_insert = _signals.signal("post_bulk_insert")

3
requirements-lint.txt Normal file
View File

@ -0,0 +1,3 @@
black
flake8
flake8-import-order

View File

@ -1,7 +1,5 @@
nose
pymongo>=3.4
six==1.10.0
flake8
flake8-import-order
Sphinx==1.5.5
sphinx-rtd-theme==0.2.4

View File

@ -5,7 +5,7 @@ detailed-errors=1
cover-package=mongoengine
[flake8]
ignore=E501,F401,F403,F405,I201,I202,W504, W605
ignore=E501,F401,F403,F405,I201,I202,W504, W605, W503
exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests
max-complexity=47
application-import-names=mongoengine,tests

View File

@ -8,13 +8,10 @@ try:
except ImportError:
pass
DESCRIPTION = (
'MongoEngine is a Python Object-Document '
'Mapper for working with MongoDB.'
)
DESCRIPTION = "MongoEngine is a Python Object-Document Mapper for working with MongoDB."
try:
with open('README.rst') as fin:
with open("README.rst") as fin:
LONG_DESCRIPTION = fin.read()
except Exception:
LONG_DESCRIPTION = None
@ -24,23 +21,23 @@ def get_version(version_tuple):
"""Return the version tuple as a string, e.g. for (0, 10, 7),
return '0.10.7'.
"""
return '.'.join(map(str, version_tuple))
return ".".join(map(str, version_tuple))
# Dirty hack to get version number from monogengine/__init__.py - we can't
# import it as it depends on PyMongo and PyMongo isn't installed until this
# file is read
init = os.path.join(os.path.dirname(__file__), 'mongoengine', '__init__.py')
version_line = list(filter(lambda l: l.startswith('VERSION'), open(init)))[0]
init = os.path.join(os.path.dirname(__file__), "mongoengine", "__init__.py")
version_line = list(filter(lambda l: l.startswith("VERSION"), open(init)))[0]
VERSION = get_version(eval(version_line.split('=')[-1]))
VERSION = get_version(eval(version_line.split("=")[-1]))
CLASSIFIERS = [
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
@ -48,39 +45,40 @@ CLASSIFIERS = [
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
'Topic :: Database',
'Topic :: Software Development :: Libraries :: Python Modules',
"Topic :: Database",
"Topic :: Software Development :: Libraries :: Python Modules",
]
extra_opts = {
'packages': find_packages(exclude=['tests', 'tests.*']),
'tests_require': ['nose', 'coverage==4.2', 'blinker', 'Pillow>=2.0.0']
"packages": find_packages(exclude=["tests", "tests.*"]),
"tests_require": ["nose", "coverage==4.2", "blinker", "Pillow>=2.0.0"],
}
if sys.version_info[0] == 3:
extra_opts['use_2to3'] = True
if 'test' in sys.argv or 'nosetests' in sys.argv:
extra_opts['packages'] = find_packages()
extra_opts['package_data'] = {
'tests': ['fields/mongoengine.png', 'fields/mongodb_leaf.png']}
extra_opts["use_2to3"] = True
if "test" in sys.argv or "nosetests" in sys.argv:
extra_opts["packages"] = find_packages()
extra_opts["package_data"] = {
"tests": ["fields/mongoengine.png", "fields/mongodb_leaf.png"]
}
else:
extra_opts['tests_require'] += ['python-dateutil']
extra_opts["tests_require"] += ["python-dateutil"]
setup(
name='mongoengine',
name="mongoengine",
version=VERSION,
author='Harry Marr',
author_email='harry.marr@gmail.com',
author="Harry Marr",
author_email="harry.marr@gmail.com",
maintainer="Stefan Wojcik",
maintainer_email="wojcikstefan@gmail.com",
url='http://mongoengine.org/',
download_url='https://github.com/MongoEngine/mongoengine/tarball/master',
license='MIT',
url="http://mongoengine.org/",
download_url="https://github.com/MongoEngine/mongoengine/tarball/master",
license="MIT",
include_package_data=True,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
platforms=['any'],
platforms=["any"],
classifiers=CLASSIFIERS,
install_requires=['pymongo>=3.4', 'six'],
test_suite='nose.collector',
install_requires=["pymongo>=3.4", "six"],
test_suite="nose.collector",
**extra_opts
)

View File

@ -9,34 +9,32 @@ import warnings
from mongoengine import *
__all__ = ('AllWarnings', )
__all__ = ("AllWarnings",)
class AllWarnings(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
connect(db="mongoenginetest")
self.warning_list = []
self.showwarning_default = warnings.showwarning
warnings.showwarning = self.append_to_warning_list
def append_to_warning_list(self, message, category, *args):
self.warning_list.append({"message": message,
"category": category})
self.warning_list.append({"message": message, "category": category})
def tearDown(self):
# restore default handling of warnings
warnings.showwarning = self.showwarning_default
def test_document_collection_syntax_warning(self):
class NonAbstractBase(Document):
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
class InheritedDocumentFailTest(NonAbstractBase):
meta = {'collection': 'fail'}
meta = {"collection": "fail"}
warning = self.warning_list[0]
self.assertEqual(SyntaxWarning, warning["category"])
self.assertEqual('non_abstract_base',
InheritedDocumentFailTest._get_collection_name())
self.assertEqual(
"non_abstract_base", InheritedDocumentFailTest._get_collection_name()
)

View File

@ -9,5 +9,5 @@ from .instance import *
from .json_serialisation import *
from .validation import *
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -7,13 +7,12 @@ from mongoengine.pymongo_support import list_collection_names
from mongoengine.queryset import NULLIFY, PULL
from mongoengine.connection import get_db
__all__ = ("ClassMethodsTest", )
__all__ = ("ClassMethodsTest",)
class ClassMethodsTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
connect(db="mongoenginetest")
self.db = get_db()
class Person(Document):
@ -33,11 +32,13 @@ class ClassMethodsTest(unittest.TestCase):
def test_definition(self):
"""Ensure that document may be defined using fields.
"""
self.assertEqual(['_cls', 'age', 'id', 'name'],
sorted(self.Person._fields.keys()))
self.assertEqual(["IntField", "ObjectIdField", "StringField", "StringField"],
sorted([x.__class__.__name__ for x in
self.Person._fields.values()]))
self.assertEqual(
["_cls", "age", "id", "name"], sorted(self.Person._fields.keys())
)
self.assertEqual(
["IntField", "ObjectIdField", "StringField", "StringField"],
sorted([x.__class__.__name__ for x in self.Person._fields.values()]),
)
def test_get_db(self):
"""Ensure that get_db returns the expected db.
@ -49,21 +50,21 @@ class ClassMethodsTest(unittest.TestCase):
"""Ensure that get_collection_name returns the expected collection
name.
"""
collection_name = 'person'
collection_name = "person"
self.assertEqual(collection_name, self.Person._get_collection_name())
def test_get_collection(self):
"""Ensure that get_collection returns the expected collection.
"""
collection_name = 'person'
collection_name = "person"
collection = self.Person._get_collection()
self.assertEqual(self.db[collection_name], collection)
def test_drop_collection(self):
"""Ensure that the collection may be dropped from the database.
"""
collection_name = 'person'
self.Person(name='Test').save()
collection_name = "person"
self.Person(name="Test").save()
self.assertIn(collection_name, list_collection_names(self.db))
self.Person.drop_collection()
@ -73,14 +74,16 @@ class ClassMethodsTest(unittest.TestCase):
"""Ensure that register delete rule adds a delete rule to the document
meta.
"""
class Job(Document):
employee = ReferenceField(self.Person)
self.assertEqual(self.Person._meta.get('delete_rules'), None)
self.assertEqual(self.Person._meta.get("delete_rules"), None)
self.Person.register_delete_rule(Job, 'employee', NULLIFY)
self.assertEqual(self.Person._meta['delete_rules'],
{(Job, 'employee'): NULLIFY})
self.Person.register_delete_rule(Job, "employee", NULLIFY)
self.assertEqual(
self.Person._meta["delete_rules"], {(Job, "employee"): NULLIFY}
)
def test_compare_indexes(self):
""" Ensure that the indexes are properly created and that
@ -93,23 +96,27 @@ class ClassMethodsTest(unittest.TestCase):
description = StringField()
tags = StringField()
meta = {
'indexes': [('author', 'title')]
}
meta = {"indexes": [("author", "title")]}
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)]]})
BlogPost.ensure_index(["author", "description"])
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': []})
BlogPost._get_collection().drop_index("author_1_description_1")
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': []})
BlogPost._get_collection().drop_index("author_1_title_1")
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
@ -122,32 +129,34 @@ class ClassMethodsTest(unittest.TestCase):
title = StringField()
description = StringField()
meta = {
'allow_inheritance': True
}
meta = {"allow_inheritance": True}
class BlogPostWithTags(BlogPost):
tags = StringField()
tag_list = ListField(StringField())
meta = {
'indexes': [('author', 'tags')]
}
meta = {"indexes": [("author", "tags")]}
BlogPost.drop_collection()
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)]]})
BlogPostWithTags.ensure_index(["author", "tag_list"])
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': []})
BlogPostWithTags._get_collection().drop_index("_cls_1_author_1_tag_list_1")
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': []})
BlogPostWithTags._get_collection().drop_index("_cls_1_author_1_tags_1")
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
@ -159,32 +168,30 @@ class ClassMethodsTest(unittest.TestCase):
title = StringField()
description = StringField()
meta = {
'allow_inheritance': True
}
meta = {"allow_inheritance": True}
class BlogPostWithTags(BlogPost):
tags = StringField()
tag_list = ListField(StringField())
meta = {
'indexes': [('author', 'tags')]
}
meta = {"indexes": [("author", "tags")]}
class BlogPostWithCustomField(BlogPost):
custom = DictField()
meta = {
'indexes': [('author', 'custom')]
}
meta = {"indexes": [("author", "custom")]}
BlogPost.ensure_indexes()
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": []}
)
def test_compare_indexes_for_text_indexes(self):
""" Ensure that compare_indexes behaves correctly for text indexes """
@ -192,17 +199,20 @@ class ClassMethodsTest(unittest.TestCase):
class Doc(Document):
a = StringField()
b = StringField()
meta = {'indexes': [
{'fields': ['$a', "$b"],
'default_language': 'english',
'weights': {'a': 10, 'b': 2}
}
]}
meta = {
"indexes": [
{
"fields": ["$a", "$b"],
"default_language": "english",
"weights": {"a": 10, "b": 2},
}
]
}
Doc.drop_collection()
Doc.ensure_indexes()
actual = Doc.compare_indexes()
expected = {'missing': [], 'extra': []}
expected = {"missing": [], "extra": []}
self.assertEqual(actual, expected)
def test_list_indexes_inheritance(self):
@ -215,23 +225,17 @@ class ClassMethodsTest(unittest.TestCase):
title = StringField()
description = StringField()
meta = {
'allow_inheritance': True
}
meta = {"allow_inheritance": True}
class BlogPostWithTags(BlogPost):
tags = StringField()
meta = {
'indexes': [('author', 'tags')]
}
meta = {"indexes": [("author", "tags")]}
class BlogPostWithTagsAndExtraText(BlogPostWithTags):
extra_text = StringField()
meta = {
'indexes': [('author', 'tags', 'extra_text')]
}
meta = {"indexes": [("author", "tags", "extra_text")]}
BlogPost.drop_collection()
@ -239,17 +243,21 @@ class ClassMethodsTest(unittest.TestCase):
BlogPostWithTags.ensure_indexes()
BlogPostWithTagsAndExtraText.ensure_indexes()
self.assertEqual(BlogPost.list_indexes(),
BlogPostWithTags.list_indexes())
self.assertEqual(BlogPost.list_indexes(),
BlogPostWithTagsAndExtraText.list_indexes())
self.assertEqual(BlogPost.list_indexes(),
[[('_cls', 1), ('author', 1), ('tags', 1)],
[('_cls', 1), ('author', 1), ('tags', 1), ('extra_text', 1)],
[(u'_id', 1)], [('_cls', 1)]])
self.assertEqual(BlogPost.list_indexes(), BlogPostWithTags.list_indexes())
self.assertEqual(
BlogPost.list_indexes(), BlogPostWithTagsAndExtraText.list_indexes()
)
self.assertEqual(
BlogPost.list_indexes(),
[
[("_cls", 1), ("author", 1), ("tags", 1)],
[("_cls", 1), ("author", 1), ("tags", 1), ("extra_text", 1)],
[(u"_id", 1)],
[("_cls", 1)],
],
)
def test_register_delete_rule_inherited(self):
class Vaccine(Document):
name = StringField(required=True)
@ -257,15 +265,17 @@ class ClassMethodsTest(unittest.TestCase):
class Animal(Document):
family = StringField(required=True)
vaccine_made = ListField(ReferenceField("Vaccine", reverse_delete_rule=PULL))
vaccine_made = ListField(
ReferenceField("Vaccine", reverse_delete_rule=PULL)
)
meta = {"allow_inheritance": True, "indexes": ["family"]}
class Cat(Animal):
name = StringField(required=True)
self.assertEqual(Vaccine._meta['delete_rules'][(Animal, 'vaccine_made')], PULL)
self.assertEqual(Vaccine._meta['delete_rules'][(Cat, 'vaccine_made')], PULL)
self.assertEqual(Vaccine._meta["delete_rules"][(Animal, "vaccine_made")], PULL)
self.assertEqual(Vaccine._meta["delete_rules"][(Cat, "vaccine_made")], PULL)
def test_collection_naming(self):
"""Ensure that a collection with a specified name may be used.
@ -273,74 +283,73 @@ class ClassMethodsTest(unittest.TestCase):
class DefaultNamingTest(Document):
pass
self.assertEqual('default_naming_test',
DefaultNamingTest._get_collection_name())
self.assertEqual(
"default_naming_test", DefaultNamingTest._get_collection_name()
)
class CustomNamingTest(Document):
meta = {'collection': 'pimp_my_collection'}
meta = {"collection": "pimp_my_collection"}
self.assertEqual('pimp_my_collection',
CustomNamingTest._get_collection_name())
self.assertEqual("pimp_my_collection", CustomNamingTest._get_collection_name())
class DynamicNamingTest(Document):
meta = {'collection': lambda c: "DYNAMO"}
self.assertEqual('DYNAMO', DynamicNamingTest._get_collection_name())
meta = {"collection": lambda c: "DYNAMO"}
self.assertEqual("DYNAMO", DynamicNamingTest._get_collection_name())
# Use Abstract class to handle backwards compatibility
class BaseDocument(Document):
meta = {
'abstract': True,
'collection': lambda c: c.__name__.lower()
}
meta = {"abstract": True, "collection": lambda c: c.__name__.lower()}
class OldNamingConvention(BaseDocument):
pass
self.assertEqual('oldnamingconvention',
OldNamingConvention._get_collection_name())
self.assertEqual(
"oldnamingconvention", OldNamingConvention._get_collection_name()
)
class InheritedAbstractNamingTest(BaseDocument):
meta = {'collection': 'wibble'}
self.assertEqual('wibble',
InheritedAbstractNamingTest._get_collection_name())
meta = {"collection": "wibble"}
self.assertEqual("wibble", InheritedAbstractNamingTest._get_collection_name())
# Mixin tests
class BaseMixin(object):
meta = {
'collection': lambda c: c.__name__.lower()
}
meta = {"collection": lambda c: c.__name__.lower()}
class OldMixinNamingConvention(Document, BaseMixin):
pass
self.assertEqual('oldmixinnamingconvention',
OldMixinNamingConvention._get_collection_name())
self.assertEqual(
"oldmixinnamingconvention", OldMixinNamingConvention._get_collection_name()
)
class BaseMixin(object):
meta = {
'collection': lambda c: c.__name__.lower()
}
meta = {"collection": lambda c: c.__name__.lower()}
class BaseDocument(Document, BaseMixin):
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
class MyDocument(BaseDocument):
pass
self.assertEqual('basedocument', MyDocument._get_collection_name())
self.assertEqual("basedocument", MyDocument._get_collection_name())
def test_custom_collection_name_operations(self):
"""Ensure that a collection with a specified name is used as expected.
"""
collection_name = 'personCollTest'
collection_name = "personCollTest"
class Person(Document):
name = StringField()
meta = {'collection': collection_name}
meta = {"collection": collection_name}
Person(name="Test User").save()
self.assertIn(collection_name, list_collection_names(self.db))
user_obj = self.db[collection_name].find_one()
self.assertEqual(user_obj['name'], "Test User")
self.assertEqual(user_obj["name"], "Test User")
user_obj = Person.objects[0]
self.assertEqual(user_obj.name, "Test User")
@ -354,7 +363,7 @@ class ClassMethodsTest(unittest.TestCase):
class Person(Document):
name = StringField(primary_key=True)
meta = {'collection': 'app'}
meta = {"collection": "app"}
Person(name="Test User").save()
@ -364,5 +373,5 @@ class ClassMethodsTest(unittest.TestCase):
Person.drop_collection()
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

File diff suppressed because it is too large Load Diff

View File

@ -3,17 +3,16 @@ import unittest
from mongoengine import *
from tests.utils import MongoDBTestCase
__all__ = ("TestDynamicDocument", )
__all__ = ("TestDynamicDocument",)
class TestDynamicDocument(MongoDBTestCase):
def setUp(self):
super(TestDynamicDocument, self).setUp()
class Person(DynamicDocument):
name = StringField()
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
Person.drop_collection()
@ -26,8 +25,7 @@ class TestDynamicDocument(MongoDBTestCase):
p.name = "James"
p.age = 34
self.assertEqual(p.to_mongo(), {"_cls": "Person", "name": "James",
"age": 34})
self.assertEqual(p.to_mongo(), {"_cls": "Person", "name": "James", "age": 34})
self.assertEqual(p.to_mongo().keys(), ["_cls", "name", "age"])
p.save()
self.assertEqual(p.to_mongo().keys(), ["_id", "_cls", "name", "age"])
@ -35,7 +33,7 @@ class TestDynamicDocument(MongoDBTestCase):
self.assertEqual(self.Person.objects.first().age, 34)
# Confirm no changes to self.Person
self.assertFalse(hasattr(self.Person, 'age'))
self.assertFalse(hasattr(self.Person, "age"))
def test_change_scope_of_variable(self):
"""Test changing the scope of a dynamic field has no adverse effects"""
@ -45,11 +43,11 @@ class TestDynamicDocument(MongoDBTestCase):
p.save()
p = self.Person.objects.get()
p.misc = {'hello': 'world'}
p.misc = {"hello": "world"}
p.save()
p = self.Person.objects.get()
self.assertEqual(p.misc, {'hello': 'world'})
self.assertEqual(p.misc, {"hello": "world"})
def test_delete_dynamic_field(self):
"""Test deleting a dynamic field works"""
@ -60,23 +58,23 @@ class TestDynamicDocument(MongoDBTestCase):
p.save()
p = self.Person.objects.get()
p.misc = {'hello': 'world'}
p.misc = {"hello": "world"}
p.save()
p = self.Person.objects.get()
self.assertEqual(p.misc, {'hello': 'world'})
self.assertEqual(p.misc, {"hello": "world"})
collection = self.db[self.Person._get_collection_name()]
obj = collection.find_one()
self.assertEqual(sorted(obj.keys()), ['_cls', '_id', 'misc', 'name'])
self.assertEqual(sorted(obj.keys()), ["_cls", "_id", "misc", "name"])
del p.misc
p.save()
p = self.Person.objects.get()
self.assertFalse(hasattr(p, 'misc'))
self.assertFalse(hasattr(p, "misc"))
obj = collection.find_one()
self.assertEqual(sorted(obj.keys()), ['_cls', '_id', 'name'])
self.assertEqual(sorted(obj.keys()), ["_cls", "_id", "name"])
def test_reload_after_unsetting(self):
p = self.Person()
@ -91,77 +89,52 @@ class TestDynamicDocument(MongoDBTestCase):
p.update(age=1)
self.assertEqual(len(p._data), 3)
self.assertEqual(sorted(p._data.keys()), ['_cls', 'id', 'name'])
self.assertEqual(sorted(p._data.keys()), ["_cls", "id", "name"])
p.reload()
self.assertEqual(len(p._data), 4)
self.assertEqual(sorted(p._data.keys()), ['_cls', 'age', 'id', 'name'])
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 = 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'
}
)
self.assertEqual(raw_p, {"_cls": u"Person", "_id": p.id, "name": u"Dean"})
p.name = 'OldDean'
p.newattr = 'garbage'
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'
}
{"_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 = 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'
}
)
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.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',
}
)
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"""
@ -193,26 +166,25 @@ class TestDynamicDocument(MongoDBTestCase):
p2.age = 10
p2.save()
self.assertEqual(Person.objects(age__icontains='ten').count(), 2)
self.assertEqual(Person.objects(age__icontains="ten").count(), 2)
self.assertEqual(Person.objects(age__gte=10).count(), 1)
def test_complex_data_lookups(self):
"""Ensure you can query dynamic document dynamic fields"""
p = self.Person()
p.misc = {'hello': 'world'}
p.misc = {"hello": "world"}
p.save()
self.assertEqual(1, self.Person.objects(misc__hello='world').count())
self.assertEqual(1, self.Person.objects(misc__hello="world").count())
def test_three_level_complex_data_lookups(self):
"""Ensure you can query three level document dynamic fields"""
p = self.Person.objects.create(
misc={'hello': {'hello2': 'world'}}
)
self.assertEqual(1, self.Person.objects(misc__hello__hello2='world').count())
p = self.Person.objects.create(misc={"hello": {"hello2": "world"}})
self.assertEqual(1, self.Person.objects(misc__hello__hello2="world").count())
def test_complex_embedded_document_validation(self):
"""Ensure embedded dynamic documents may be validated"""
class Embedded(DynamicEmbeddedDocument):
content = URLField()
@ -222,10 +194,10 @@ class TestDynamicDocument(MongoDBTestCase):
Doc.drop_collection()
doc = Doc()
embedded_doc_1 = Embedded(content='http://mongoengine.org')
embedded_doc_1 = Embedded(content="http://mongoengine.org")
embedded_doc_1.validate()
embedded_doc_2 = Embedded(content='this is not a url')
embedded_doc_2 = Embedded(content="this is not a url")
self.assertRaises(ValidationError, embedded_doc_2.validate)
doc.embedded_field_1 = embedded_doc_1
@ -234,15 +206,17 @@ class TestDynamicDocument(MongoDBTestCase):
def test_inheritance(self):
"""Ensure that dynamic document plays nice with inheritance"""
class Employee(self.Person):
salary = IntField()
Employee.drop_collection()
self.assertIn('name', Employee._fields)
self.assertIn('salary', Employee._fields)
self.assertEqual(Employee._get_collection_name(),
self.Person._get_collection_name())
self.assertIn("name", Employee._fields)
self.assertIn("salary", Employee._fields)
self.assertEqual(
Employee._get_collection_name(), self.Person._get_collection_name()
)
joe_bloggs = Employee()
joe_bloggs.name = "Joe Bloggs"
@ -258,6 +232,7 @@ class TestDynamicDocument(MongoDBTestCase):
def test_embedded_dynamic_document(self):
"""Test dynamic embedded documents"""
class Embedded(DynamicEmbeddedDocument):
pass
@ -268,78 +243,88 @@ class TestDynamicDocument(MongoDBTestCase):
doc = Doc()
embedded_1 = Embedded()
embedded_1.string_field = 'hello'
embedded_1.string_field = "hello"
embedded_1.int_field = 1
embedded_1.dict_field = {'hello': 'world'}
embedded_1.list_field = ['1', 2, {'hello': 'world'}]
embedded_1.dict_field = {"hello": "world"}
embedded_1.list_field = ["1", 2, {"hello": "world"}]
doc.embedded_field = embedded_1
self.assertEqual(doc.to_mongo(), {
"embedded_field": {
"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ['1', 2, {'hello': 'world'}]
}
})
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc.embedded_field.__class__, Embedded)
self.assertEqual(doc.embedded_field.string_field, "hello")
self.assertEqual(doc.embedded_field.int_field, 1)
self.assertEqual(doc.embedded_field.dict_field, {'hello': 'world'})
self.assertEqual(doc.embedded_field.list_field,
['1', 2, {'hello': 'world'}])
def test_complex_embedded_documents(self):
"""Test complex dynamic embedded documents setups"""
class Embedded(DynamicEmbeddedDocument):
pass
class Doc(DynamicDocument):
pass
Doc.drop_collection()
doc = Doc()
embedded_1 = Embedded()
embedded_1.string_field = 'hello'
embedded_1.int_field = 1
embedded_1.dict_field = {'hello': 'world'}
embedded_2 = Embedded()
embedded_2.string_field = 'hello'
embedded_2.int_field = 1
embedded_2.dict_field = {'hello': 'world'}
embedded_2.list_field = ['1', 2, {'hello': 'world'}]
embedded_1.list_field = ['1', 2, embedded_2]
doc.embedded_field = embedded_1
self.assertEqual(doc.to_mongo(), {
"embedded_field": {
"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ['1', 2,
{"_cls": "Embedded",
self.assertEqual(
doc.to_mongo(),
{
"embedded_field": {
"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ['1', 2, {'hello': 'world'}]}
]
}
})
"list_field": ["1", 2, {"hello": "world"}],
}
},
)
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc.embedded_field.__class__, Embedded)
self.assertEqual(doc.embedded_field.string_field, "hello")
self.assertEqual(doc.embedded_field.int_field, 1)
self.assertEqual(doc.embedded_field.dict_field, {"hello": "world"})
self.assertEqual(doc.embedded_field.list_field, ["1", 2, {"hello": "world"}])
def test_complex_embedded_documents(self):
"""Test complex dynamic embedded documents setups"""
class Embedded(DynamicEmbeddedDocument):
pass
class Doc(DynamicDocument):
pass
Doc.drop_collection()
doc = Doc()
embedded_1 = Embedded()
embedded_1.string_field = "hello"
embedded_1.int_field = 1
embedded_1.dict_field = {"hello": "world"}
embedded_2 = Embedded()
embedded_2.string_field = "hello"
embedded_2.int_field = 1
embedded_2.dict_field = {"hello": "world"}
embedded_2.list_field = ["1", 2, {"hello": "world"}]
embedded_1.list_field = ["1", 2, embedded_2]
doc.embedded_field = embedded_1
self.assertEqual(
doc.to_mongo(),
{
"embedded_field": {
"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": [
"1",
2,
{
"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ["1", 2, {"hello": "world"}],
},
],
}
},
)
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc.embedded_field.__class__, Embedded)
self.assertEqual(doc.embedded_field.string_field, "hello")
self.assertEqual(doc.embedded_field.int_field, 1)
self.assertEqual(doc.embedded_field.dict_field, {'hello': 'world'})
self.assertEqual(doc.embedded_field.list_field[0], '1')
self.assertEqual(doc.embedded_field.dict_field, {"hello": "world"})
self.assertEqual(doc.embedded_field.list_field[0], "1")
self.assertEqual(doc.embedded_field.list_field[1], 2)
embedded_field = doc.embedded_field.list_field[2]
@ -347,9 +332,8 @@ class TestDynamicDocument(MongoDBTestCase):
self.assertEqual(embedded_field.__class__, Embedded)
self.assertEqual(embedded_field.string_field, "hello")
self.assertEqual(embedded_field.int_field, 1)
self.assertEqual(embedded_field.dict_field, {'hello': 'world'})
self.assertEqual(embedded_field.list_field, ['1', 2,
{'hello': 'world'}])
self.assertEqual(embedded_field.dict_field, {"hello": "world"})
self.assertEqual(embedded_field.list_field, ["1", 2, {"hello": "world"}])
def test_dynamic_and_embedded(self):
"""Ensure embedded documents play nicely"""
@ -392,10 +376,15 @@ class TestDynamicDocument(MongoDBTestCase):
Person.drop_collection()
Person(name="Eric", address=Address(city="San Francisco", street_number="1337")).save()
Person(
name="Eric", address=Address(city="San Francisco", street_number="1337")
).save()
self.assertEqual(Person.objects.first().address.street_number, '1337')
self.assertEqual(Person.objects.only('address__street_number').first().address.street_number, '1337')
self.assertEqual(Person.objects.first().address.street_number, "1337")
self.assertEqual(
Person.objects.only("address__street_number").first().address.street_number,
"1337",
)
def test_dynamic_and_embedded_dict_access(self):
"""Ensure embedded dynamic documents work with dict[] style access"""
@ -435,5 +424,5 @@ class TestDynamicDocument(MongoDBTestCase):
self.assertEqual(Person.objects.first().age, 35)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

File diff suppressed because it is too large Load Diff

View File

@ -4,18 +4,24 @@ import warnings
from six import iteritems
from mongoengine import (BooleanField, Document, EmbeddedDocument,
EmbeddedDocumentField, GenericReferenceField,
IntField, ReferenceField, StringField)
from mongoengine import (
BooleanField,
Document,
EmbeddedDocument,
EmbeddedDocumentField,
GenericReferenceField,
IntField,
ReferenceField,
StringField,
)
from mongoengine.pymongo_support import list_collection_names
from tests.utils import MongoDBTestCase
from tests.fixtures import Base
__all__ = ('InheritanceTest', )
__all__ = ("InheritanceTest",)
class InheritanceTest(MongoDBTestCase):
def tearDown(self):
for collection in list_collection_names(self.db):
self.db.drop_collection(collection)
@ -25,16 +31,16 @@ class InheritanceTest(MongoDBTestCase):
# and when object gets reloaded (prevent regression of #1950)
class EmbedData(EmbeddedDocument):
data = StringField()
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
class DataDoc(Document):
name = StringField()
embed = EmbeddedDocumentField(EmbedData)
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
test_doc = DataDoc(name='test', embed=EmbedData(data='data'))
self.assertEqual(test_doc._cls, 'DataDoc')
self.assertEqual(test_doc.embed._cls, 'EmbedData')
test_doc = DataDoc(name="test", embed=EmbedData(data="data"))
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)
self.assertEqual(test_doc._cls, saved_doc._cls)
@ -44,163 +50,234 @@ class InheritanceTest(MongoDBTestCase):
def test_superclasses(self):
"""Ensure that the correct list of superclasses is assembled.
"""
class Animal(Document):
meta = {'allow_inheritance': True}
class Fish(Animal): pass
class Guppy(Fish): pass
class Mammal(Animal): pass
class Dog(Mammal): pass
class Human(Mammal): pass
meta = {"allow_inheritance": True}
class Fish(Animal):
pass
class Guppy(Fish):
pass
class Mammal(Animal):
pass
class Dog(Mammal):
pass
class Human(Mammal):
pass
self.assertEqual(Animal._superclasses, ())
self.assertEqual(Fish._superclasses, ('Animal',))
self.assertEqual(Guppy._superclasses, ('Animal', 'Animal.Fish'))
self.assertEqual(Mammal._superclasses, ('Animal',))
self.assertEqual(Dog._superclasses, ('Animal', 'Animal.Mammal'))
self.assertEqual(Human._superclasses, ('Animal', 'Animal.Mammal'))
self.assertEqual(Fish._superclasses, ("Animal",))
self.assertEqual(Guppy._superclasses, ("Animal", "Animal.Fish"))
self.assertEqual(Mammal._superclasses, ("Animal",))
self.assertEqual(Dog._superclasses, ("Animal", "Animal.Mammal"))
self.assertEqual(Human._superclasses, ("Animal", "Animal.Mammal"))
def test_external_superclasses(self):
"""Ensure that the correct list of super classes is assembled when
importing part of the model.
"""
class Animal(Base): pass
class Fish(Animal): pass
class Guppy(Fish): pass
class Mammal(Animal): pass
class Dog(Mammal): pass
class Human(Mammal): pass
self.assertEqual(Animal._superclasses, ('Base', ))
self.assertEqual(Fish._superclasses, ('Base', 'Base.Animal',))
self.assertEqual(Guppy._superclasses, ('Base', 'Base.Animal',
'Base.Animal.Fish'))
self.assertEqual(Mammal._superclasses, ('Base', 'Base.Animal',))
self.assertEqual(Dog._superclasses, ('Base', 'Base.Animal',
'Base.Animal.Mammal'))
self.assertEqual(Human._superclasses, ('Base', 'Base.Animal',
'Base.Animal.Mammal'))
class Animal(Base):
pass
class Fish(Animal):
pass
class Guppy(Fish):
pass
class Mammal(Animal):
pass
class Dog(Mammal):
pass
class Human(Mammal):
pass
self.assertEqual(Animal._superclasses, ("Base",))
self.assertEqual(Fish._superclasses, ("Base", "Base.Animal"))
self.assertEqual(
Guppy._superclasses, ("Base", "Base.Animal", "Base.Animal.Fish")
)
self.assertEqual(Mammal._superclasses, ("Base", "Base.Animal"))
self.assertEqual(
Dog._superclasses, ("Base", "Base.Animal", "Base.Animal.Mammal")
)
self.assertEqual(
Human._superclasses, ("Base", "Base.Animal", "Base.Animal.Mammal")
)
def test_subclasses(self):
"""Ensure that the correct list of _subclasses (subclasses) is
assembled.
"""
class Animal(Document):
meta = {'allow_inheritance': True}
class Fish(Animal): pass
class Guppy(Fish): pass
class Mammal(Animal): pass
class Dog(Mammal): pass
class Human(Mammal): pass
self.assertEqual(Animal._subclasses, ('Animal',
'Animal.Fish',
'Animal.Fish.Guppy',
'Animal.Mammal',
'Animal.Mammal.Dog',
'Animal.Mammal.Human'))
self.assertEqual(Fish._subclasses, ('Animal.Fish',
'Animal.Fish.Guppy',))
self.assertEqual(Guppy._subclasses, ('Animal.Fish.Guppy',))
self.assertEqual(Mammal._subclasses, ('Animal.Mammal',
'Animal.Mammal.Dog',
'Animal.Mammal.Human'))
self.assertEqual(Human._subclasses, ('Animal.Mammal.Human',))
class Animal(Document):
meta = {"allow_inheritance": True}
class Fish(Animal):
pass
class Guppy(Fish):
pass
class Mammal(Animal):
pass
class Dog(Mammal):
pass
class Human(Mammal):
pass
self.assertEqual(
Animal._subclasses,
(
"Animal",
"Animal.Fish",
"Animal.Fish.Guppy",
"Animal.Mammal",
"Animal.Mammal.Dog",
"Animal.Mammal.Human",
),
)
self.assertEqual(Fish._subclasses, ("Animal.Fish", "Animal.Fish.Guppy"))
self.assertEqual(Guppy._subclasses, ("Animal.Fish.Guppy",))
self.assertEqual(
Mammal._subclasses,
("Animal.Mammal", "Animal.Mammal.Dog", "Animal.Mammal.Human"),
)
self.assertEqual(Human._subclasses, ("Animal.Mammal.Human",))
def test_external_subclasses(self):
"""Ensure that the correct list of _subclasses (subclasses) is
assembled when importing part of the model.
"""
class Animal(Base): pass
class Fish(Animal): pass
class Guppy(Fish): pass
class Mammal(Animal): pass
class Dog(Mammal): pass
class Human(Mammal): pass
self.assertEqual(Animal._subclasses, ('Base.Animal',
'Base.Animal.Fish',
'Base.Animal.Fish.Guppy',
'Base.Animal.Mammal',
'Base.Animal.Mammal.Dog',
'Base.Animal.Mammal.Human'))
self.assertEqual(Fish._subclasses, ('Base.Animal.Fish',
'Base.Animal.Fish.Guppy',))
self.assertEqual(Guppy._subclasses, ('Base.Animal.Fish.Guppy',))
self.assertEqual(Mammal._subclasses, ('Base.Animal.Mammal',
'Base.Animal.Mammal.Dog',
'Base.Animal.Mammal.Human'))
self.assertEqual(Human._subclasses, ('Base.Animal.Mammal.Human',))
class Animal(Base):
pass
class Fish(Animal):
pass
class Guppy(Fish):
pass
class Mammal(Animal):
pass
class Dog(Mammal):
pass
class Human(Mammal):
pass
self.assertEqual(
Animal._subclasses,
(
"Base.Animal",
"Base.Animal.Fish",
"Base.Animal.Fish.Guppy",
"Base.Animal.Mammal",
"Base.Animal.Mammal.Dog",
"Base.Animal.Mammal.Human",
),
)
self.assertEqual(
Fish._subclasses, ("Base.Animal.Fish", "Base.Animal.Fish.Guppy")
)
self.assertEqual(Guppy._subclasses, ("Base.Animal.Fish.Guppy",))
self.assertEqual(
Mammal._subclasses,
(
"Base.Animal.Mammal",
"Base.Animal.Mammal.Dog",
"Base.Animal.Mammal.Human",
),
)
self.assertEqual(Human._subclasses, ("Base.Animal.Mammal.Human",))
def test_dynamic_declarations(self):
"""Test that declaring an extra class updates meta data"""
class Animal(Document):
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
self.assertEqual(Animal._superclasses, ())
self.assertEqual(Animal._subclasses, ('Animal',))
self.assertEqual(Animal._subclasses, ("Animal",))
# Test dynamically adding a class changes the meta data
class Fish(Animal):
pass
self.assertEqual(Animal._superclasses, ())
self.assertEqual(Animal._subclasses, ('Animal', 'Animal.Fish'))
self.assertEqual(Animal._subclasses, ("Animal", "Animal.Fish"))
self.assertEqual(Fish._superclasses, ('Animal', ))
self.assertEqual(Fish._subclasses, ('Animal.Fish',))
self.assertEqual(Fish._superclasses, ("Animal",))
self.assertEqual(Fish._subclasses, ("Animal.Fish",))
# Test dynamically adding an inherited class changes the meta data
class Pike(Fish):
pass
self.assertEqual(Animal._superclasses, ())
self.assertEqual(Animal._subclasses, ('Animal', 'Animal.Fish',
'Animal.Fish.Pike'))
self.assertEqual(
Animal._subclasses, ("Animal", "Animal.Fish", "Animal.Fish.Pike")
)
self.assertEqual(Fish._superclasses, ('Animal', ))
self.assertEqual(Fish._subclasses, ('Animal.Fish', 'Animal.Fish.Pike'))
self.assertEqual(Fish._superclasses, ("Animal",))
self.assertEqual(Fish._subclasses, ("Animal.Fish", "Animal.Fish.Pike"))
self.assertEqual(Pike._superclasses, ('Animal', 'Animal.Fish'))
self.assertEqual(Pike._subclasses, ('Animal.Fish.Pike',))
self.assertEqual(Pike._superclasses, ("Animal", "Animal.Fish"))
self.assertEqual(Pike._subclasses, ("Animal.Fish.Pike",))
def test_inheritance_meta_data(self):
"""Ensure that document may inherit fields from a superclass document.
"""
class Person(Document):
name = StringField()
age = IntField()
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
class Employee(Person):
salary = IntField()
self.assertEqual(['_cls', 'age', 'id', 'name', 'salary'],
sorted(Employee._fields.keys()))
self.assertEqual(Employee._get_collection_name(),
Person._get_collection_name())
self.assertEqual(
["_cls", "age", "id", "name", "salary"], sorted(Employee._fields.keys())
)
self.assertEqual(Employee._get_collection_name(), Person._get_collection_name())
def test_inheritance_to_mongo_keys(self):
"""Ensure that document may inherit fields from a superclass document.
"""
class Person(Document):
name = StringField()
age = IntField()
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
class Employee(Person):
salary = IntField()
self.assertEqual(['_cls', 'age', 'id', 'name', 'salary'],
sorted(Employee._fields.keys()))
self.assertEqual(Person(name="Bob", age=35).to_mongo().keys(),
['_cls', 'name', 'age'])
self.assertEqual(Employee(name="Bob", age=35, salary=0).to_mongo().keys(),
['_cls', 'name', 'age', 'salary'])
self.assertEqual(Employee._get_collection_name(),
Person._get_collection_name())
self.assertEqual(
["_cls", "age", "id", "name", "salary"], sorted(Employee._fields.keys())
)
self.assertEqual(
Person(name="Bob", age=35).to_mongo().keys(), ["_cls", "name", "age"]
)
self.assertEqual(
Employee(name="Bob", age=35, salary=0).to_mongo().keys(),
["_cls", "name", "age", "salary"],
)
self.assertEqual(Employee._get_collection_name(), Person._get_collection_name())
def test_indexes_and_multiple_inheritance(self):
""" Ensure that all of the indexes are created for a document with
@ -210,18 +287,12 @@ class InheritanceTest(MongoDBTestCase):
class A(Document):
a = StringField()
meta = {
'allow_inheritance': True,
'indexes': ['a']
}
meta = {"allow_inheritance": True, "indexes": ["a"]}
class B(Document):
b = StringField()
meta = {
'allow_inheritance': True,
'indexes': ['b']
}
meta = {"allow_inheritance": True, "indexes": ["b"]}
class C(A, B):
pass
@ -233,8 +304,12 @@ class InheritanceTest(MongoDBTestCase):
C.ensure_indexes()
self.assertEqual(
sorted([idx['key'] for idx in C._get_collection().index_information().values()]),
sorted([[(u'_cls', 1), (u'b', 1)], [(u'_id', 1)], [(u'_cls', 1), (u'a', 1)]])
sorted(
[idx["key"] for idx in C._get_collection().index_information().values()]
),
sorted(
[[(u"_cls", 1), (u"b", 1)], [(u"_id", 1)], [(u"_cls", 1), (u"a", 1)]]
),
)
def test_polymorphic_queries(self):
@ -242,11 +317,19 @@ class InheritanceTest(MongoDBTestCase):
"""
class Animal(Document):
meta = {'allow_inheritance': True}
class Fish(Animal): pass
class Mammal(Animal): pass
class Dog(Mammal): pass
class Human(Mammal): pass
meta = {"allow_inheritance": True}
class Fish(Animal):
pass
class Mammal(Animal):
pass
class Dog(Mammal):
pass
class Human(Mammal):
pass
Animal.drop_collection()
@ -269,58 +352,68 @@ class InheritanceTest(MongoDBTestCase):
"""Ensure that inheritance is disabled by default on simple
classes and that _cls will not be used.
"""
class Animal(Document):
name = StringField()
# can't inherit because Animal didn't explicitly allow inheritance
with self.assertRaises(ValueError) as cm:
class Dog(Animal):
pass
self.assertIn("Document Animal may not be subclassed", str(cm.exception))
# Check that _cls etc aren't present on simple documents
dog = Animal(name='dog').save()
self.assertEqual(dog.to_mongo().keys(), ['_id', 'name'])
dog = Animal(name="dog").save()
self.assertEqual(dog.to_mongo().keys(), ["_id", "name"])
collection = self.db[Animal._get_collection_name()]
obj = collection.find_one()
self.assertNotIn('_cls', obj)
self.assertNotIn("_cls", obj)
def test_cant_turn_off_inheritance_on_subclass(self):
"""Ensure if inheritance is on in a subclass you cant turn it off.
"""
class Animal(Document):
name = StringField()
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
with self.assertRaises(ValueError) as cm:
class Mammal(Animal):
meta = {'allow_inheritance': False}
self.assertEqual(str(cm.exception), 'Only direct subclasses of Document may set "allow_inheritance" to False')
meta = {"allow_inheritance": False}
self.assertEqual(
str(cm.exception),
'Only direct subclasses of Document may set "allow_inheritance" to False',
)
def test_allow_inheritance_abstract_document(self):
"""Ensure that abstract documents can set inheritance rules and that
_cls will not be used.
"""
class FinalDocument(Document):
meta = {'abstract': True,
'allow_inheritance': False}
meta = {"abstract": True, "allow_inheritance": False}
class Animal(FinalDocument):
name = StringField()
with self.assertRaises(ValueError) as cm:
class Mammal(Animal):
pass
# Check that _cls isn't present in simple documents
doc = Animal(name='dog')
self.assertNotIn('_cls', doc.to_mongo())
doc = Animal(name="dog")
self.assertNotIn("_cls", doc.to_mongo())
def test_using_abstract_class_in_reference_field(self):
# Ensures no regression of #1920
class AbstractHuman(Document):
meta = {'abstract': True}
meta = {"abstract": True}
class Dad(AbstractHuman):
name = StringField()
@ -329,130 +422,122 @@ class InheritanceTest(MongoDBTestCase):
dad = ReferenceField(AbstractHuman) # Referencing the abstract class
address = StringField()
dad = Dad(name='5').save()
Home(dad=dad, address='street').save()
dad = Dad(name="5").save()
Home(dad=dad, address="street").save()
home = Home.objects.first()
home.address = 'garbage'
home.save() # Was failing with ValidationError
home.address = "garbage"
home.save() # Was failing with ValidationError
def test_abstract_class_referencing_self(self):
# Ensures no regression of #1920
class Human(Document):
meta = {'abstract': True}
creator = ReferenceField('self', dbref=True)
meta = {"abstract": True}
creator = ReferenceField("self", dbref=True)
class User(Human):
name = StringField()
user = User(name='John').save()
user2 = User(name='Foo', creator=user).save()
user = User(name="John").save()
user2 = User(name="Foo", creator=user).save()
user2 = User.objects.with_id(user2.id)
user2.name = 'Bar'
user2.save() # Was failing with ValidationError
user2.name = "Bar"
user2.save() # Was failing with ValidationError
def test_abstract_handle_ids_in_metaclass_properly(self):
class City(Document):
continent = StringField()
meta = {'abstract': True,
'allow_inheritance': False}
meta = {"abstract": True, "allow_inheritance": False}
class EuropeanCity(City):
name = StringField()
berlin = EuropeanCity(name='Berlin', continent='Europe')
berlin = EuropeanCity(name="Berlin", continent="Europe")
self.assertEqual(len(berlin._db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._reverse_db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._fields_ordered), 3)
self.assertEqual(berlin._fields_ordered[0], 'id')
self.assertEqual(berlin._fields_ordered[0], "id")
def test_auto_id_not_set_if_specific_in_parent_class(self):
class City(Document):
continent = StringField()
city_id = IntField(primary_key=True)
meta = {'abstract': True,
'allow_inheritance': False}
meta = {"abstract": True, "allow_inheritance": False}
class EuropeanCity(City):
name = StringField()
berlin = EuropeanCity(name='Berlin', continent='Europe')
berlin = EuropeanCity(name="Berlin", continent="Europe")
self.assertEqual(len(berlin._db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._reverse_db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._fields_ordered), 3)
self.assertEqual(berlin._fields_ordered[0], 'city_id')
self.assertEqual(berlin._fields_ordered[0], "city_id")
def test_auto_id_vs_non_pk_id_field(self):
class City(Document):
continent = StringField()
id = IntField()
meta = {'abstract': True,
'allow_inheritance': False}
meta = {"abstract": True, "allow_inheritance": False}
class EuropeanCity(City):
name = StringField()
berlin = EuropeanCity(name='Berlin', continent='Europe')
berlin = EuropeanCity(name="Berlin", continent="Europe")
self.assertEqual(len(berlin._db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._reverse_db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._fields_ordered), 4)
self.assertEqual(berlin._fields_ordered[0], 'auto_id_0')
self.assertEqual(berlin._fields_ordered[0], "auto_id_0")
berlin.save()
self.assertEqual(berlin.pk, berlin.auto_id_0)
def test_abstract_document_creation_does_not_fail(self):
class City(Document):
continent = StringField()
meta = {'abstract': True,
'allow_inheritance': False}
meta = {"abstract": True, "allow_inheritance": False}
city = City(continent='asia')
city = City(continent="asia")
self.assertEqual(None, city.pk)
# TODO: expected error? Shouldn't we create a new error type?
with self.assertRaises(KeyError):
setattr(city, 'pk', 1)
setattr(city, "pk", 1)
def test_allow_inheritance_embedded_document(self):
"""Ensure embedded documents respect inheritance."""
class Comment(EmbeddedDocument):
content = StringField()
with self.assertRaises(ValueError):
class SpecialComment(Comment):
pass
doc = Comment(content='test')
self.assertNotIn('_cls', doc.to_mongo())
doc = Comment(content="test")
self.assertNotIn("_cls", doc.to_mongo())
class Comment(EmbeddedDocument):
content = StringField()
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
doc = Comment(content='test')
self.assertIn('_cls', doc.to_mongo())
doc = Comment(content="test")
self.assertIn("_cls", doc.to_mongo())
def test_document_inheritance(self):
"""Ensure mutliple inheritance of abstract documents
"""
class DateCreatedDocument(Document):
meta = {
'allow_inheritance': True,
'abstract': True,
}
meta = {"allow_inheritance": True, "abstract": True}
class DateUpdatedDocument(Document):
meta = {
'allow_inheritance': True,
'abstract': True,
}
meta = {"allow_inheritance": True, "abstract": True}
try:
class MyDocument(DateCreatedDocument, DateUpdatedDocument):
pass
except Exception:
self.assertTrue(False, "Couldn't create MyDocument class")
@ -460,47 +545,55 @@ class InheritanceTest(MongoDBTestCase):
"""Ensure that a document superclass can be marked as abstract
thereby not using it as the name for the collection."""
defaults = {'index_background': True,
'index_drop_dups': True,
'index_opts': {'hello': 'world'},
'allow_inheritance': True,
'queryset_class': 'QuerySet',
'db_alias': 'myDB',
'shard_key': ('hello', 'world')}
defaults = {
"index_background": True,
"index_drop_dups": True,
"index_opts": {"hello": "world"},
"allow_inheritance": True,
"queryset_class": "QuerySet",
"db_alias": "myDB",
"shard_key": ("hello", "world"),
}
meta_settings = {'abstract': True}
meta_settings = {"abstract": True}
meta_settings.update(defaults)
class Animal(Document):
name = StringField()
meta = meta_settings
class Fish(Animal): pass
class Guppy(Fish): pass
class Fish(Animal):
pass
class Guppy(Fish):
pass
class Mammal(Animal):
meta = {'abstract': True}
class Human(Mammal): pass
meta = {"abstract": True}
class Human(Mammal):
pass
for k, v in iteritems(defaults):
for cls in [Animal, Fish, Guppy]:
self.assertEqual(cls._meta[k], v)
self.assertNotIn('collection', Animal._meta)
self.assertNotIn('collection', Mammal._meta)
self.assertNotIn("collection", Animal._meta)
self.assertNotIn("collection", Mammal._meta)
self.assertEqual(Animal._get_collection_name(), None)
self.assertEqual(Mammal._get_collection_name(), None)
self.assertEqual(Fish._get_collection_name(), 'fish')
self.assertEqual(Guppy._get_collection_name(), 'fish')
self.assertEqual(Human._get_collection_name(), 'human')
self.assertEqual(Fish._get_collection_name(), "fish")
self.assertEqual(Guppy._get_collection_name(), "fish")
self.assertEqual(Human._get_collection_name(), "human")
# ensure that a subclass of a non-abstract class can't be abstract
with self.assertRaises(ValueError):
class EvilHuman(Human):
evil = BooleanField(default=True)
meta = {'abstract': True}
meta = {"abstract": True}
def test_abstract_embedded_documents(self):
# 789: EmbeddedDocument shouldn't inherit abstract
@ -519,7 +612,7 @@ class InheritanceTest(MongoDBTestCase):
class Drink(Document):
name = StringField()
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
class Drinker(Document):
drink = GenericReferenceField()
@ -528,13 +621,13 @@ class InheritanceTest(MongoDBTestCase):
warnings.simplefilter("error")
class AcloholicDrink(Drink):
meta = {'collection': 'booze'}
meta = {"collection": "booze"}
except SyntaxWarning:
warnings.simplefilter("ignore")
class AlcoholicDrink(Drink):
meta = {'collection': 'booze'}
meta = {"collection": "booze"}
else:
raise AssertionError("SyntaxWarning should be triggered")
@ -545,13 +638,13 @@ class InheritanceTest(MongoDBTestCase):
AlcoholicDrink.drop_collection()
Drinker.drop_collection()
red_bull = Drink(name='Red Bull')
red_bull = Drink(name="Red Bull")
red_bull.save()
programmer = Drinker(drink=red_bull)
programmer.save()
beer = AlcoholicDrink(name='Beer')
beer = AlcoholicDrink(name="Beer")
beer.save()
real_person = Drinker(drink=beer)
real_person.save()
@ -560,5 +653,5 @@ class InheritanceTest(MongoDBTestCase):
self.assertEqual(Drinker.objects[1].drink.name, beer.name)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

File diff suppressed because it is too large Load Diff

View File

@ -13,9 +13,8 @@ __all__ = ("TestJson",)
class TestJson(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
connect(db="mongoenginetest")
def test_json_names(self):
"""
@ -25,22 +24,24 @@ class TestJson(unittest.TestCase):
a to_json with the original class names and not the abreviated
mongodb document keys
"""
class Embedded(EmbeddedDocument):
string = StringField(db_field='s')
string = StringField(db_field="s")
class Doc(Document):
string = StringField(db_field='s')
embedded = EmbeddedDocumentField(Embedded, db_field='e')
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_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)
def test_json_simple(self):
class Embedded(EmbeddedDocument):
string = StringField()
@ -49,12 +50,14 @@ class TestJson(unittest.TestCase):
embedded_field = EmbeddedDocumentField(Embedded)
def __eq__(self, other):
return (self.string == other.string and
self.embedded_field == other.embedded_field)
return (
self.string == other.string
and self.embedded_field == other.embedded_field
)
doc = Doc(string="Hi", embedded_field=Embedded(string="Hi"))
doc_json = doc.to_json(sort_keys=True, separators=(',', ':'))
doc_json = doc.to_json(sort_keys=True, separators=(",", ":"))
expected_json = """{"embedded_field":{"string":"Hi"},"string":"Hi"}"""
self.assertEqual(doc_json, expected_json)
@ -68,41 +71,43 @@ class TestJson(unittest.TestCase):
pass
class Doc(Document):
string_field = StringField(default='1')
string_field = StringField(default="1")
int_field = IntField(default=1)
float_field = FloatField(default=1.1)
boolean_field = BooleanField(default=True)
datetime_field = DateTimeField(default=datetime.now)
embedded_document_field = EmbeddedDocumentField(EmbeddedDoc,
default=lambda: EmbeddedDoc())
embedded_document_field = EmbeddedDocumentField(
EmbeddedDoc, default=lambda: EmbeddedDoc()
)
list_field = ListField(default=lambda: [1, 2, 3])
dict_field = DictField(default=lambda: {"hello": "world"})
objectid_field = ObjectIdField(default=ObjectId)
reference_field = ReferenceField(Simple, default=lambda:
Simple().save())
reference_field = ReferenceField(Simple, default=lambda: Simple().save())
map_field = MapField(IntField(), default=lambda: {"simple": 1})
decimal_field = DecimalField(default=1.0)
complex_datetime_field = ComplexDateTimeField(default=datetime.now)
url_field = URLField(default="http://mongoengine.org")
dynamic_field = DynamicField(default=1)
generic_reference_field = GenericReferenceField(
default=lambda: Simple().save())
sorted_list_field = SortedListField(IntField(),
default=lambda: [1, 2, 3])
default=lambda: Simple().save()
)
sorted_list_field = SortedListField(IntField(), default=lambda: [1, 2, 3])
email_field = EmailField(default="ross@example.com")
geo_point_field = GeoPointField(default=lambda: [1, 2])
sequence_field = SequenceField()
uuid_field = UUIDField(default=uuid.uuid4)
generic_embedded_document_field = GenericEmbeddedDocumentField(
default=lambda: EmbeddedDoc())
default=lambda: EmbeddedDoc()
)
def __eq__(self, other):
import json
return json.loads(self.to_json()) == json.loads(other.to_json())
doc = Doc()
self.assertEqual(doc, Doc.from_json(doc.to_json()))
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -8,49 +8,56 @@ __all__ = ("ValidatorErrorTest",)
class ValidatorErrorTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
connect(db="mongoenginetest")
def test_to_dict(self):
"""Ensure a ValidationError handles error to_dict correctly.
"""
error = ValidationError('root')
error = ValidationError("root")
self.assertEqual(error.to_dict(), {})
# 1st level error schema
error.errors = {'1st': ValidationError('bad 1st'), }
self.assertIn('1st', error.to_dict())
self.assertEqual(error.to_dict()['1st'], 'bad 1st')
error.errors = {"1st": ValidationError("bad 1st")}
self.assertIn("1st", error.to_dict())
self.assertEqual(error.to_dict()["1st"], "bad 1st")
# 2nd level error schema
error.errors = {'1st': ValidationError('bad 1st', errors={
'2nd': ValidationError('bad 2nd'),
})}
self.assertIn('1st', error.to_dict())
self.assertIsInstance(error.to_dict()['1st'], dict)
self.assertIn('2nd', error.to_dict()['1st'])
self.assertEqual(error.to_dict()['1st']['2nd'], 'bad 2nd')
error.errors = {
"1st": ValidationError(
"bad 1st", errors={"2nd": ValidationError("bad 2nd")}
)
}
self.assertIn("1st", error.to_dict())
self.assertIsInstance(error.to_dict()["1st"], dict)
self.assertIn("2nd", error.to_dict()["1st"])
self.assertEqual(error.to_dict()["1st"]["2nd"], "bad 2nd")
# moar levels
error.errors = {'1st': ValidationError('bad 1st', errors={
'2nd': ValidationError('bad 2nd', errors={
'3rd': ValidationError('bad 3rd', errors={
'4th': ValidationError('Inception'),
}),
}),
})}
self.assertIn('1st', error.to_dict())
self.assertIn('2nd', error.to_dict()['1st'])
self.assertIn('3rd', error.to_dict()['1st']['2nd'])
self.assertIn('4th', error.to_dict()['1st']['2nd']['3rd'])
self.assertEqual(error.to_dict()['1st']['2nd']['3rd']['4th'],
'Inception')
error.errors = {
"1st": ValidationError(
"bad 1st",
errors={
"2nd": ValidationError(
"bad 2nd",
errors={
"3rd": ValidationError(
"bad 3rd", errors={"4th": ValidationError("Inception")}
)
},
)
},
)
}
self.assertIn("1st", error.to_dict())
self.assertIn("2nd", error.to_dict()["1st"])
self.assertIn("3rd", error.to_dict()["1st"]["2nd"])
self.assertIn("4th", error.to_dict()["1st"]["2nd"]["3rd"])
self.assertEqual(error.to_dict()["1st"]["2nd"]["3rd"]["4th"], "Inception")
self.assertEqual(error.message, "root(2nd.3rd.4th.Inception: ['1st'])")
def test_model_validation(self):
class User(Document):
username = StringField(primary_key=True)
name = StringField(required=True)
@ -59,9 +66,10 @@ class ValidatorErrorTest(unittest.TestCase):
User().validate()
except ValidationError as e:
self.assertIn("User:None", e.message)
self.assertEqual(e.to_dict(), {
'username': 'Field is required',
'name': 'Field is required'})
self.assertEqual(
e.to_dict(),
{"username": "Field is required", "name": "Field is required"},
)
user = User(username="RossC0", name="Ross").save()
user.name = None
@ -69,14 +77,13 @@ class ValidatorErrorTest(unittest.TestCase):
user.save()
except ValidationError as e:
self.assertIn("User:RossC0", e.message)
self.assertEqual(e.to_dict(), {
'name': 'Field is required'})
self.assertEqual(e.to_dict(), {"name": "Field is required"})
def test_fields_rewrite(self):
class BasePerson(Document):
name = StringField()
age = IntField()
meta = {'abstract': True}
meta = {"abstract": True}
class Person(BasePerson):
name = StringField(required=True)
@ -87,6 +94,7 @@ class ValidatorErrorTest(unittest.TestCase):
def test_embedded_document_validation(self):
"""Ensure that embedded documents may be validated.
"""
class Comment(EmbeddedDocument):
date = DateTimeField()
content = StringField(required=True)
@ -94,7 +102,7 @@ class ValidatorErrorTest(unittest.TestCase):
comment = Comment()
self.assertRaises(ValidationError, comment.validate)
comment.content = 'test'
comment.content = "test"
comment.validate()
comment.date = 4
@ -105,20 +113,20 @@ class ValidatorErrorTest(unittest.TestCase):
self.assertEqual(comment._instance, None)
def test_embedded_db_field_validate(self):
class SubDoc(EmbeddedDocument):
val = IntField(required=True)
class Doc(Document):
id = StringField(primary_key=True)
e = EmbeddedDocumentField(SubDoc, db_field='eb')
e = EmbeddedDocumentField(SubDoc, db_field="eb")
try:
Doc(id="bad").validate()
except ValidationError as e:
self.assertIn("SubDoc:None", e.message)
self.assertEqual(e.to_dict(), {
"e": {'val': 'OK could not be converted to int'}})
self.assertEqual(
e.to_dict(), {"e": {"val": "OK could not be converted to int"}}
)
Doc.drop_collection()
@ -127,24 +135,24 @@ class ValidatorErrorTest(unittest.TestCase):
doc = Doc.objects.first()
keys = doc._data.keys()
self.assertEqual(2, len(keys))
self.assertIn('e', keys)
self.assertIn('id', keys)
self.assertIn("e", keys)
self.assertIn("id", keys)
doc.e.val = "OK"
try:
doc.save()
except ValidationError as e:
self.assertIn("Doc:test", e.message)
self.assertEqual(e.to_dict(), {
"e": {'val': 'OK could not be converted to int'}})
self.assertEqual(
e.to_dict(), {"e": {"val": "OK could not be converted to int"}}
)
def test_embedded_weakref(self):
class SubDoc(EmbeddedDocument):
val = IntField(required=True)
class Doc(Document):
e = EmbeddedDocumentField(SubDoc, db_field='eb')
e = EmbeddedDocumentField(SubDoc, db_field="eb")
Doc.drop_collection()
@ -167,9 +175,10 @@ class ValidatorErrorTest(unittest.TestCase):
Test to ensure a ReferenceField can store a reference to a parent
class when inherited. Issue #954.
"""
class Parent(Document):
meta = {'allow_inheritance': True}
reference = ReferenceField('self')
meta = {"allow_inheritance": True}
reference = ReferenceField("self")
class Child(Parent):
pass
@ -190,9 +199,10 @@ class ValidatorErrorTest(unittest.TestCase):
Test to ensure a ReferenceField can store a reference to a parent
class when inherited and when set via attribute. Issue #954.
"""
class Parent(Document):
meta = {'allow_inheritance': True}
reference = ReferenceField('self')
meta = {"allow_inheritance": True}
reference = ReferenceField("self")
class Child(Parent):
pass
@ -210,5 +220,5 @@ class ValidatorErrorTest(unittest.TestCase):
self.fail("ValidationError raised: %s" % e.message)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

File diff suppressed because it is too large Load Diff

View File

@ -14,36 +14,37 @@ from mongoengine.python_support import StringIO
try:
from PIL import Image
HAS_PIL = True
except ImportError:
HAS_PIL = False
from tests.utils import MongoDBTestCase
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')
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:
with open(path, "rb") as f:
bytes_io.write(f.read())
bytes_io.seek(0)
return bytes_io
class FileTest(MongoDBTestCase):
def tearDown(self):
self.db.drop_collection('fs.files')
self.db.drop_collection('fs.chunks')
self.db.drop_collection("fs.files")
self.db.drop_collection("fs.chunks")
def test_file_field_optional(self):
# Make sure FileField is optional and not required
class DemoFile(Document):
the_file = FileField()
DemoFile.objects.create()
def test_file_fields(self):
@ -55,8 +56,8 @@ class FileTest(MongoDBTestCase):
PutFile.drop_collection()
text = six.b('Hello, World!')
content_type = 'text/plain'
text = six.b("Hello, World!")
content_type = "text/plain"
putfile = PutFile()
putfile.the_file.put(text, content_type=content_type, filename="hello")
@ -64,7 +65,10 @@ class FileTest(MongoDBTestCase):
result = PutFile.objects.first()
self.assertEqual(putfile, result)
self.assertEqual("%s" % result.the_file, "<GridFSProxy: hello (%s)>" % result.the_file.grid_id)
self.assertEqual(
"%s" % result.the_file,
"<GridFSProxy: hello (%s)>" % result.the_file.grid_id,
)
self.assertEqual(result.the_file.read(), text)
self.assertEqual(result.the_file.content_type, content_type)
result.the_file.delete() # Remove file from GridFS
@ -89,14 +93,15 @@ class FileTest(MongoDBTestCase):
def test_file_fields_stream(self):
"""Ensure that file fields can be written to and their data retrieved
"""
class StreamFile(Document):
the_file = FileField()
StreamFile.drop_collection()
text = six.b('Hello, World!')
more_text = six.b('Foo Bar')
content_type = 'text/plain'
text = six.b("Hello, World!")
more_text = six.b("Foo Bar")
content_type = "text/plain"
streamfile = StreamFile()
streamfile.the_file.new_file(content_type=content_type)
@ -124,14 +129,15 @@ class FileTest(MongoDBTestCase):
"""Ensure that a file field can be written to after it has been saved as
None
"""
class StreamFile(Document):
the_file = FileField()
StreamFile.drop_collection()
text = six.b('Hello, World!')
more_text = six.b('Foo Bar')
content_type = 'text/plain'
text = six.b("Hello, World!")
more_text = six.b("Foo Bar")
content_type = "text/plain"
streamfile = StreamFile()
streamfile.save()
@ -157,12 +163,11 @@ class FileTest(MongoDBTestCase):
self.assertTrue(result.the_file.read() is None)
def test_file_fields_set(self):
class SetFile(Document):
the_file = FileField()
text = six.b('Hello, World!')
more_text = six.b('Foo Bar')
text = six.b("Hello, World!")
more_text = six.b("Foo Bar")
SetFile.drop_collection()
@ -184,7 +189,6 @@ class FileTest(MongoDBTestCase):
result.the_file.delete()
def test_file_field_no_default(self):
class GridDocument(Document):
the_file = FileField()
@ -199,7 +203,7 @@ class FileTest(MongoDBTestCase):
doc_a.save()
doc_b = GridDocument.objects.with_id(doc_a.id)
doc_b.the_file.replace(f, filename='doc_b')
doc_b.the_file.replace(f, filename="doc_b")
doc_b.save()
self.assertNotEqual(doc_b.the_file.grid_id, None)
@ -208,13 +212,13 @@ class FileTest(MongoDBTestCase):
self.assertEqual(doc_b.the_file.grid_id, doc_c.the_file.grid_id)
# Test with default
doc_d = GridDocument(the_file=six.b(''))
doc_d = GridDocument(the_file=six.b(""))
doc_d.save()
doc_e = GridDocument.objects.with_id(doc_d.id)
self.assertEqual(doc_d.the_file.grid_id, doc_e.the_file.grid_id)
doc_e.the_file.replace(f, filename='doc_e')
doc_e.the_file.replace(f, filename="doc_e")
doc_e.save()
doc_f = GridDocument.objects.with_id(doc_e.id)
@ -222,11 +226,12 @@ class FileTest(MongoDBTestCase):
db = GridDocument._get_db()
grid_fs = gridfs.GridFS(db)
self.assertEqual(['doc_b', 'doc_e'], grid_fs.list())
self.assertEqual(["doc_b", "doc_e"], grid_fs.list())
def test_file_uniqueness(self):
"""Ensure that each instance of a FileField is unique
"""
class TestFile(Document):
name = StringField()
the_file = FileField()
@ -234,7 +239,7 @@ class FileTest(MongoDBTestCase):
# First instance
test_file = TestFile()
test_file.name = "Hello, World!"
test_file.the_file.put(six.b('Hello, World!'))
test_file.the_file.put(six.b("Hello, World!"))
test_file.save()
# Second instance
@ -255,20 +260,21 @@ class FileTest(MongoDBTestCase):
photo = FileField()
Animal.drop_collection()
marmot = Animal(genus='Marmota', family='Sciuridae')
marmot = Animal(genus="Marmota", family="Sciuridae")
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.put(marmot_photo_content, content_type="image/jpeg", foo="bar")
marmot.photo.close()
marmot.save()
marmot = Animal.objects.get()
self.assertEqual(marmot.photo.content_type, 'image/jpeg')
self.assertEqual(marmot.photo.foo, 'bar')
self.assertEqual(marmot.photo.content_type, "image/jpeg")
self.assertEqual(marmot.photo.foo, "bar")
def test_file_reassigning(self):
class TestFile(Document):
the_file = FileField()
TestFile.drop_collection()
test_file = TestFile(the_file=get_file(TEST_IMAGE_PATH)).save()
@ -282,13 +288,15 @@ class FileTest(MongoDBTestCase):
def test_file_boolean(self):
"""Ensure that a boolean test of a FileField indicates its presence
"""
class TestFile(Document):
the_file = FileField()
TestFile.drop_collection()
test_file = TestFile()
self.assertFalse(bool(test_file.the_file))
test_file.the_file.put(six.b('Hello, World!'), content_type='text/plain')
test_file.the_file.put(six.b("Hello, World!"), content_type="text/plain")
test_file.save()
self.assertTrue(bool(test_file.the_file))
@ -297,6 +305,7 @@ class FileTest(MongoDBTestCase):
def test_file_cmp(self):
"""Test comparing against other types"""
class TestFile(Document):
the_file = FileField()
@ -305,11 +314,12 @@ class FileTest(MongoDBTestCase):
def test_file_disk_space(self):
""" Test disk space usage when we delete/replace a file """
class TestFile(Document):
the_file = FileField()
text = six.b('Hello, World!')
content_type = 'text/plain'
text = six.b("Hello, World!")
content_type = "text/plain"
testfile = TestFile()
testfile.the_file.put(text, content_type=content_type, filename="hello")
@ -352,7 +362,7 @@ class FileTest(MongoDBTestCase):
testfile.the_file.put(text, content_type=content_type, filename="hello")
testfile.save()
text = six.b('Bonjour, World!')
text = six.b("Bonjour, World!")
testfile.the_file.replace(text, content_type=content_type, filename="hello")
testfile.save()
@ -370,7 +380,7 @@ class FileTest(MongoDBTestCase):
def test_image_field(self):
if not HAS_PIL:
raise SkipTest('PIL not installed')
raise SkipTest("PIL not installed")
class TestImage(Document):
image = ImageField()
@ -386,7 +396,9 @@ class FileTest(MongoDBTestCase):
t.image.put(f)
self.fail("Should have raised an invalidation error")
except ValidationError as e:
self.assertEqual("%s" % e, "Invalid image: cannot identify image file %s" % f)
self.assertEqual(
"%s" % e, "Invalid image: cannot identify image file %s" % f
)
t = TestImage()
t.image.put(get_file(TEST_IMAGE_PATH))
@ -394,7 +406,7 @@ class FileTest(MongoDBTestCase):
t = TestImage.objects.first()
self.assertEqual(t.image.format, 'PNG')
self.assertEqual(t.image.format, "PNG")
w, h = t.image.size
self.assertEqual(w, 371)
@ -404,10 +416,11 @@ class FileTest(MongoDBTestCase):
def test_image_field_reassigning(self):
if not HAS_PIL:
raise SkipTest('PIL not installed')
raise SkipTest("PIL not installed")
class TestFile(Document):
the_file = ImageField()
TestFile.drop_collection()
test_file = TestFile(the_file=get_file(TEST_IMAGE_PATH)).save()
@ -420,7 +433,7 @@ class FileTest(MongoDBTestCase):
def test_image_field_resize(self):
if not HAS_PIL:
raise SkipTest('PIL not installed')
raise SkipTest("PIL not installed")
class TestImage(Document):
image = ImageField(size=(185, 37))
@ -433,7 +446,7 @@ class FileTest(MongoDBTestCase):
t = TestImage.objects.first()
self.assertEqual(t.image.format, 'PNG')
self.assertEqual(t.image.format, "PNG")
w, h = t.image.size
self.assertEqual(w, 185)
@ -443,7 +456,7 @@ class FileTest(MongoDBTestCase):
def test_image_field_resize_force(self):
if not HAS_PIL:
raise SkipTest('PIL not installed')
raise SkipTest("PIL not installed")
class TestImage(Document):
image = ImageField(size=(185, 37, True))
@ -456,7 +469,7 @@ class FileTest(MongoDBTestCase):
t = TestImage.objects.first()
self.assertEqual(t.image.format, 'PNG')
self.assertEqual(t.image.format, "PNG")
w, h = t.image.size
self.assertEqual(w, 185)
@ -466,7 +479,7 @@ class FileTest(MongoDBTestCase):
def test_image_field_thumbnail(self):
if not HAS_PIL:
raise SkipTest('PIL not installed')
raise SkipTest("PIL not installed")
class TestImage(Document):
image = ImageField(thumbnail_size=(92, 18))
@ -479,19 +492,18 @@ class FileTest(MongoDBTestCase):
t = TestImage.objects.first()
self.assertEqual(t.image.thumbnail.format, 'PNG')
self.assertEqual(t.image.thumbnail.format, "PNG")
self.assertEqual(t.image.thumbnail.width, 92)
self.assertEqual(t.image.thumbnail.height, 18)
t.image.delete()
def test_file_multidb(self):
register_connection('test_files', 'test_files')
register_connection("test_files", "test_files")
class TestFile(Document):
name = StringField()
the_file = FileField(db_alias="test_files",
collection_name="macumba")
the_file = FileField(db_alias="test_files", collection_name="macumba")
TestFile.drop_collection()
@ -502,23 +514,21 @@ class FileTest(MongoDBTestCase):
# First instance
test_file = TestFile()
test_file.name = "Hello, World!"
test_file.the_file.put(six.b('Hello, World!'),
name="hello.txt")
test_file.the_file.put(six.b("Hello, World!"), name="hello.txt")
test_file.save()
data = get_db("test_files").macumba.files.find_one()
self.assertEqual(data.get('name'), 'hello.txt')
self.assertEqual(data.get("name"), "hello.txt")
test_file = TestFile.objects.first()
self.assertEqual(test_file.the_file.read(), six.b('Hello, World!'))
self.assertEqual(test_file.the_file.read(), six.b("Hello, World!"))
test_file = TestFile.objects.first()
test_file.the_file = six.b('HELLO, WORLD!')
test_file.the_file = six.b("HELLO, WORLD!")
test_file.save()
test_file = TestFile.objects.first()
self.assertEqual(test_file.the_file.read(),
six.b('HELLO, WORLD!'))
self.assertEqual(test_file.the_file.read(), six.b("HELLO, WORLD!"))
def test_copyable(self):
class PutFile(Document):
@ -526,8 +536,8 @@ class FileTest(MongoDBTestCase):
PutFile.drop_collection()
text = six.b('Hello, World!')
content_type = 'text/plain'
text = six.b("Hello, World!")
content_type = "text/plain"
putfile = PutFile()
putfile.the_file.put(text, content_type=content_type)
@ -542,7 +552,7 @@ class FileTest(MongoDBTestCase):
def test_get_image_by_grid_id(self):
if not HAS_PIL:
raise SkipTest('PIL not installed')
raise SkipTest("PIL not installed")
class TestImage(Document):
@ -559,8 +569,9 @@ class FileTest(MongoDBTestCase):
test = TestImage.objects.first()
grid_id = test.image1.grid_id
self.assertEqual(1, TestImage.objects(Q(image1=grid_id)
or Q(image2=grid_id)).count())
self.assertEqual(
1, TestImage.objects(Q(image1=grid_id) or Q(image2=grid_id)).count()
)
def test_complex_field_filefield(self):
"""Ensure you can add meta data to file"""
@ -571,21 +582,21 @@ class FileTest(MongoDBTestCase):
photos = ListField(FileField())
Animal.drop_collection()
marmot = Animal(genus='Marmota', family='Sciuridae')
marmot = Animal(genus="Marmota", family="Sciuridae")
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')
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()
marmot = Animal.objects.get()
self.assertEqual(marmot.photos[0].content_type, 'image/jpeg')
self.assertEqual(marmot.photos[0].foo, 'bar')
self.assertEqual(marmot.photos[0].content_type, "image/jpeg")
self.assertEqual(marmot.photos[0].foo, "bar")
self.assertEqual(marmot.photos[0].get().length, 8313)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -4,28 +4,27 @@ import unittest
from mongoengine import *
from mongoengine.connection import get_db
__all__ = ("GeoFieldTest", )
__all__ = ("GeoFieldTest",)
class GeoFieldTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
connect(db="mongoenginetest")
self.db = get_db()
def _test_for_expected_error(self, Cls, loc, expected):
try:
Cls(loc=loc).validate()
self.fail('Should not validate the location {0}'.format(loc))
self.fail("Should not validate the location {0}".format(loc))
except ValidationError as e:
self.assertEqual(expected, e.to_dict()['loc'])
self.assertEqual(expected, e.to_dict()["loc"])
def test_geopoint_validation(self):
class Location(Document):
loc = GeoPointField()
invalid_coords = [{"x": 1, "y": 2}, 5, "a"]
expected = 'GeoPointField can only accept tuples or lists of (x, y)'
expected = "GeoPointField can only accept tuples or lists of (x, y)"
for coord in invalid_coords:
self._test_for_expected_error(Location, coord, expected)
@ -40,7 +39,7 @@ class GeoFieldTest(unittest.TestCase):
expected = "Both values (%s) in point must be float or int" % repr(coord)
self._test_for_expected_error(Location, coord, expected)
invalid_coords = [21, 4, 'a']
invalid_coords = [21, 4, "a"]
for coord in invalid_coords:
expected = "GeoPointField can only accept tuples or lists of (x, y)"
self._test_for_expected_error(Location, coord, expected)
@ -50,7 +49,9 @@ class GeoFieldTest(unittest.TestCase):
loc = PointField()
invalid_coords = {"x": 1, "y": 2}
expected = 'PointField can only accept a valid GeoJson dictionary or lists of (x, y)'
expected = (
"PointField can only accept a valid GeoJson dictionary or lists of (x, y)"
)
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": []}
@ -77,19 +78,16 @@ class GeoFieldTest(unittest.TestCase):
self._test_for_expected_error(Location, coord, expected)
Location(loc=[1, 2]).validate()
Location(loc={
"type": "Point",
"coordinates": [
81.4471435546875,
23.61432859499169
]}).validate()
Location(
loc={"type": "Point", "coordinates": [81.4471435546875, 23.61432859499169]}
).validate()
def test_linestring_validation(self):
class Location(Document):
loc = LineStringField()
invalid_coords = {"x": 1, "y": 2}
expected = 'LineStringField can only accept a valid GeoJson dictionary or lists of (x, y)'
expected = "LineStringField can only accept a valid GeoJson dictionary or lists of (x, y)"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": [[]]}
@ -97,7 +95,9 @@ class GeoFieldTest(unittest.TestCase):
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "LineString", "coordinates": [[1, 2, 3]]}
expected = "Invalid LineString:\nValue ([1, 2, 3]) must be a two-dimensional point"
expected = (
"Invalid LineString:\nValue ([1, 2, 3]) must be a two-dimensional point"
)
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [5, "a"]
@ -105,16 +105,25 @@ class GeoFieldTest(unittest.TestCase):
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[1]]
expected = "Invalid LineString:\nValue (%s) must be a two-dimensional point" % repr(invalid_coords[0])
expected = (
"Invalid LineString:\nValue (%s) must be a two-dimensional point"
% repr(invalid_coords[0])
)
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[1, 2, 3]]
expected = "Invalid LineString:\nValue (%s) must be a two-dimensional point" % repr(invalid_coords[0])
expected = (
"Invalid LineString:\nValue (%s) must be a two-dimensional point"
% repr(invalid_coords[0])
)
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[{}, {}]], [("a", "b")]]
for coord in invalid_coords:
expected = "Invalid LineString:\nBoth values (%s) in point must be float or int" % repr(coord[0])
expected = (
"Invalid LineString:\nBoth values (%s) in point must be float or int"
% repr(coord[0])
)
self._test_for_expected_error(Location, coord, expected)
Location(loc=[[1, 2], [3, 4], [5, 6], [1, 2]]).validate()
@ -124,7 +133,9 @@ class GeoFieldTest(unittest.TestCase):
loc = PolygonField()
invalid_coords = {"x": 1, "y": 2}
expected = 'PolygonField can only accept a valid GeoJson dictionary or lists of (x, y)'
expected = (
"PolygonField can only accept a valid GeoJson dictionary or lists of (x, y)"
)
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": [[]]}
@ -136,7 +147,9 @@ class GeoFieldTest(unittest.TestCase):
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[5, "a"]]]
expected = "Invalid Polygon:\nBoth values ([5, 'a']) in point must be float or int"
expected = (
"Invalid Polygon:\nBoth values ([5, 'a']) in point must be float or int"
)
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[]]]
@ -162,7 +175,7 @@ class GeoFieldTest(unittest.TestCase):
loc = MultiPointField()
invalid_coords = {"x": 1, "y": 2}
expected = 'MultiPointField can only accept a valid GeoJson dictionary or lists of (x, y)'
expected = "MultiPointField can only accept a valid GeoJson dictionary or lists of (x, y)"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": [[]]}
@ -188,19 +201,19 @@ class GeoFieldTest(unittest.TestCase):
self._test_for_expected_error(Location, coord, expected)
Location(loc=[[1, 2]]).validate()
Location(loc={
"type": "MultiPoint",
"coordinates": [
[1, 2],
[81.4471435546875, 23.61432859499169]
]}).validate()
Location(
loc={
"type": "MultiPoint",
"coordinates": [[1, 2], [81.4471435546875, 23.61432859499169]],
}
).validate()
def test_multilinestring_validation(self):
class Location(Document):
loc = MultiLineStringField()
invalid_coords = {"x": 1, "y": 2}
expected = 'MultiLineStringField can only accept a valid GeoJson dictionary or lists of (x, y)'
expected = "MultiLineStringField can only accept a valid GeoJson dictionary or lists of (x, y)"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": [[]]}
@ -216,16 +229,25 @@ class GeoFieldTest(unittest.TestCase):
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[1]]]
expected = "Invalid MultiLineString:\nValue (%s) must be a two-dimensional point" % repr(invalid_coords[0][0])
expected = (
"Invalid MultiLineString:\nValue (%s) must be a two-dimensional point"
% repr(invalid_coords[0][0])
)
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[1, 2, 3]]]
expected = "Invalid MultiLineString:\nValue (%s) must be a two-dimensional point" % repr(invalid_coords[0][0])
expected = (
"Invalid MultiLineString:\nValue (%s) must be a two-dimensional point"
% repr(invalid_coords[0][0])
)
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[{}, {}]]], [[("a", "b")]]]
for coord in invalid_coords:
expected = "Invalid MultiLineString:\nBoth values (%s) in point must be float or int" % repr(coord[0][0])
expected = (
"Invalid MultiLineString:\nBoth values (%s) in point must be float or int"
% repr(coord[0][0])
)
self._test_for_expected_error(Location, coord, expected)
Location(loc=[[[1, 2], [3, 4], [5, 6], [1, 2]]]).validate()
@ -235,7 +257,7 @@ class GeoFieldTest(unittest.TestCase):
loc = MultiPolygonField()
invalid_coords = {"x": 1, "y": 2}
expected = 'MultiPolygonField can only accept a valid GeoJson dictionary or lists of (x, y)'
expected = "MultiPolygonField can only accept a valid GeoJson dictionary or lists of (x, y)"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": [[]]}
@ -243,7 +265,9 @@ class GeoFieldTest(unittest.TestCase):
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MultiPolygon", "coordinates": [[[[1, 2, 3]]]]}
expected = "Invalid MultiPolygon:\nValue ([1, 2, 3]) must be a two-dimensional point"
expected = (
"Invalid MultiPolygon:\nValue ([1, 2, 3]) must be a two-dimensional point"
)
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[5, "a"]]]]
@ -255,7 +279,9 @@ class GeoFieldTest(unittest.TestCase):
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[1, 2, 3]]]]
expected = "Invalid MultiPolygon:\nValue ([1, 2, 3]) must be a two-dimensional point"
expected = (
"Invalid MultiPolygon:\nValue ([1, 2, 3]) must be a two-dimensional point"
)
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[{}, {}]]], [[("a", "b")]]]
@ -263,7 +289,9 @@ class GeoFieldTest(unittest.TestCase):
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[1, 2], [3, 4]]]]
expected = "Invalid MultiPolygon:\nLineStrings must start and end at the same point"
expected = (
"Invalid MultiPolygon:\nLineStrings must start and end at the same point"
)
self._test_for_expected_error(Location, invalid_coords, expected)
Location(loc=[[[[1, 2], [3, 4], [5, 6], [1, 2]]]]).validate()
@ -271,17 +299,19 @@ class GeoFieldTest(unittest.TestCase):
def test_indexes_geopoint(self):
"""Ensure that indexes are created automatically for GeoPointFields.
"""
class Event(Document):
title = StringField()
location = GeoPointField()
geo_indicies = Event._geo_indices()
self.assertEqual(geo_indicies, [{'fields': [('location', '2d')]}])
self.assertEqual(geo_indicies, [{"fields": [("location", "2d")]}])
def test_geopoint_embedded_indexes(self):
"""Ensure that indexes are created automatically for GeoPointFields on
embedded documents.
"""
class Venue(EmbeddedDocument):
location = GeoPointField()
name = StringField()
@ -291,11 +321,12 @@ class GeoFieldTest(unittest.TestCase):
venue = EmbeddedDocumentField(Venue)
geo_indicies = Event._geo_indices()
self.assertEqual(geo_indicies, [{'fields': [('venue.location', '2d')]}])
self.assertEqual(geo_indicies, [{"fields": [("venue.location", "2d")]}])
def test_indexes_2dsphere(self):
"""Ensure that indexes are created automatically for GeoPointFields.
"""
class Event(Document):
title = StringField()
point = PointField()
@ -303,13 +334,14 @@ class GeoFieldTest(unittest.TestCase):
polygon = PolygonField()
geo_indicies = Event._geo_indices()
self.assertIn({'fields': [('line', '2dsphere')]}, geo_indicies)
self.assertIn({'fields': [('polygon', '2dsphere')]}, geo_indicies)
self.assertIn({'fields': [('point', '2dsphere')]}, geo_indicies)
self.assertIn({"fields": [("line", "2dsphere")]}, geo_indicies)
self.assertIn({"fields": [("polygon", "2dsphere")]}, geo_indicies)
self.assertIn({"fields": [("point", "2dsphere")]}, geo_indicies)
def test_indexes_2dsphere_embedded(self):
"""Ensure that indexes are created automatically for GeoPointFields.
"""
class Venue(EmbeddedDocument):
name = StringField()
point = PointField()
@ -321,12 +353,11 @@ class GeoFieldTest(unittest.TestCase):
venue = EmbeddedDocumentField(Venue)
geo_indicies = Event._geo_indices()
self.assertIn({'fields': [('venue.line', '2dsphere')]}, geo_indicies)
self.assertIn({'fields': [('venue.polygon', '2dsphere')]}, geo_indicies)
self.assertIn({'fields': [('venue.point', '2dsphere')]}, geo_indicies)
self.assertIn({"fields": [("venue.line", "2dsphere")]}, geo_indicies)
self.assertIn({"fields": [("venue.polygon", "2dsphere")]}, geo_indicies)
self.assertIn({"fields": [("venue.point", "2dsphere")]}, geo_indicies)
def test_geo_indexes_recursion(self):
class Location(Document):
name = StringField()
location = GeoPointField()
@ -338,11 +369,11 @@ class GeoFieldTest(unittest.TestCase):
Location.drop_collection()
Parent.drop_collection()
Parent(name='Berlin').save()
Parent(name="Berlin").save()
info = Parent._get_collection().index_information()
self.assertNotIn('location_2d', info)
self.assertNotIn("location_2d", info)
info = Location._get_collection().index_information()
self.assertIn('location_2d', info)
self.assertIn("location_2d", info)
self.assertEqual(len(Parent._geo_indices()), 0)
self.assertEqual(len(Location._geo_indices()), 1)
@ -354,9 +385,7 @@ class GeoFieldTest(unittest.TestCase):
location = PointField(auto_index=False)
datetime = DateTimeField()
meta = {
'indexes': [[("location", "2dsphere"), ("datetime", 1)]]
}
meta = {"indexes": [[("location", "2dsphere"), ("datetime", 1)]]}
self.assertEqual([], Log._geo_indices())
@ -364,8 +393,10 @@ class GeoFieldTest(unittest.TestCase):
Log.ensure_indexes()
info = Log._get_collection().index_information()
self.assertEqual(info["location_2dsphere_datetime_1"]["key"],
[('location', '2dsphere'), ('datetime', 1)])
self.assertEqual(
info["location_2dsphere_datetime_1"]["key"],
[("location", "2dsphere"), ("datetime", 1)],
)
# Test listing explicitly
class Log(Document):
@ -373,9 +404,7 @@ class GeoFieldTest(unittest.TestCase):
datetime = DateTimeField()
meta = {
'indexes': [
{'fields': [("location", "2dsphere"), ("datetime", 1)]}
]
"indexes": [{"fields": [("location", "2dsphere"), ("datetime", 1)]}]
}
self.assertEqual([], Log._geo_indices())
@ -384,9 +413,11 @@ class GeoFieldTest(unittest.TestCase):
Log.ensure_indexes()
info = Log._get_collection().index_information()
self.assertEqual(info["location_2dsphere_datetime_1"]["key"],
[('location', '2dsphere'), ('datetime', 1)])
self.assertEqual(
info["location_2dsphere_datetime_1"]["key"],
[("location", "2dsphere"), ("datetime", 1)],
)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -9,19 +9,22 @@ 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')
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'
BLOB = six.b("\xe6\x00\xc4\xff\x07")
MIME_TYPE = "application/octet-stream"
Attachment.drop_collection()
@ -35,6 +38,7 @@ class TestBinaryField(MongoDBTestCase):
def test_validation_succeeds(self):
"""Ensure that valid values can be assigned to binary fields.
"""
class AttachmentRequired(Document):
blob = BinaryField(required=True)
@ -43,11 +47,11 @@ class TestBinaryField(MongoDBTestCase):
attachment_required = AttachmentRequired()
self.assertRaises(ValidationError, attachment_required.validate)
attachment_required.blob = Binary(six.b('\xe6\x00\xc4\xff\x07'))
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')
_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()
@ -57,7 +61,7 @@ class TestBinaryField(MongoDBTestCase):
class Attachment(Document):
blob = BinaryField()
for invalid_data in (2, u'Im_a_unicode', ['some_str']):
for invalid_data in (2, u"Im_a_unicode", ["some_str"]):
self.assertRaises(ValidationError, Attachment(blob=invalid_data).validate)
def test__primary(self):
@ -108,17 +112,17 @@ class TestBinaryField(MongoDBTestCase):
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
doc = MyDocument.objects(some_field="test").modify(
upsert=True, new=True, set__bin_field=BIN_VALUE
)
self.assertEqual(doc.some_field, 'test')
self.assertEqual(doc.some_field, "test")
if six.PY3:
self.assertEqual(doc.bin_field, BIN_VALUE)
else:
@ -126,15 +130,18 @@ class TestBinaryField(MongoDBTestCase):
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')
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)
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:

View File

@ -11,15 +11,13 @@ class TestBooleanField(MongoDBTestCase):
person = Person(admin=True)
person.save()
self.assertEqual(
get_as_pymongo(person),
{'_id': person.id,
'admin': True})
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()
@ -29,9 +27,9 @@ class TestBooleanField(MongoDBTestCase):
person.admin = 2
self.assertRaises(ValidationError, person.validate)
person.admin = 'Yes'
person.admin = "Yes"
self.assertRaises(ValidationError, person.validate)
person.admin = 'False'
person.admin = "False"
self.assertRaises(ValidationError, person.validate)
def test_weirdness_constructor(self):
@ -39,11 +37,12 @@ class TestBooleanField(MongoDBTestCase):
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')
new_person = Person(admin="False")
self.assertTrue(new_person.admin)
new_person = Person(admin='0')
new_person = Person(admin="0")
self.assertTrue(new_person.admin)

View File

@ -7,12 +7,12 @@ 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()
@ -24,10 +24,11 @@ class TestCachedReferenceField(MongoDBTestCase):
Animal.drop_collection()
Ocorrence.drop_collection()
Ocorrence(person="testte",
animal=Animal(name="Leopard", tag="heavy").save()).save()
Ocorrence(
person="testte", animal=Animal(name="Leopard", tag="heavy").save()
).save()
p = Ocorrence.objects.get()
p.person = 'new_testte'
p.person = "new_testte"
p.save()
def test_general_things(self):
@ -37,8 +38,7 @@ class TestCachedReferenceField(MongoDBTestCase):
class Ocorrence(Document):
person = StringField()
animal = CachedReferenceField(
Animal, fields=['tag'])
animal = CachedReferenceField(Animal, fields=["tag"])
Animal.drop_collection()
Ocorrence.drop_collection()
@ -55,19 +55,18 @@ class TestCachedReferenceField(MongoDBTestCase):
self.assertEqual(Ocorrence.objects(animal=None).count(), 1)
self.assertEqual(
a.to_mongo(fields=['tag']), {'tag': 'heavy', "_id": a.pk})
self.assertEqual(a.to_mongo(fields=["tag"]), {"tag": "heavy", "_id": a.pk})
self.assertEqual(o.to_mongo()['animal']['tag'], 'heavy')
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()
count = Ocorrence.objects(animal__tag="heavy").count()
self.assertEqual(count, 1)
ocorrence = Ocorrence.objects(animal__tag='heavy').first()
ocorrence = Ocorrence.objects(animal__tag="heavy").first()
self.assertEqual(ocorrence.person, "teste")
self.assertIsInstance(ocorrence.animal, Animal)
@ -78,28 +77,21 @@ class TestCachedReferenceField(MongoDBTestCase):
class SocialTest(Document):
group = StringField()
person = CachedReferenceField(
PersonAuto,
fields=('salary',))
person = CachedReferenceField(PersonAuto, fields=("salary",))
PersonAuto.drop_collection()
SocialTest.drop_collection()
p = PersonAuto(name="Alberto", salary=Decimal('7000.00'))
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
}
})
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):
@ -111,17 +103,14 @@ class TestCachedReferenceField(MongoDBTestCase):
class SocialData(Document):
obs = StringField()
tags = ListField(
StringField())
person = CachedReferenceField(
Person,
fields=('group',))
tags = ListField(StringField())
person = CachedReferenceField(Person, fields=("group",))
Group.drop_collection()
Person.drop_collection()
SocialData.drop_collection()
g1 = Group(name='dev')
g1 = Group(name="dev")
g1.save()
g2 = Group(name="designers")
@ -136,22 +125,21 @@ class TestCachedReferenceField(MongoDBTestCase):
p3 = Person(name="Afro design", group=g2)
p3.save()
s1 = SocialData(obs="testing 123", person=p1, tags=['tag1', 'tag2'])
s1 = SocialData(obs="testing 123", person=p1, tags=["tag1", "tag2"])
s1.save()
s2 = SocialData(obs="testing 321", person=p3, tags=['tag3', 'tag4'])
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._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)
@ -163,23 +151,18 @@ class TestCachedReferenceField(MongoDBTestCase):
Product.drop_collection()
class Basket(Document):
products = ListField(CachedReferenceField(Product, fields=['name']))
products = ListField(CachedReferenceField(Product, fields=["name"]))
Basket.drop_collection()
product1 = Product(name='abc').save()
product2 = Product(name='def').save()
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
}
]
}
"_id": basket.pk,
"products": [{"_id": product1.pk, "name": product1.name}],
},
)
# push to list
basket.update(push__products=product2)
@ -187,161 +170,135 @@ class TestCachedReferenceField(MongoDBTestCase):
self.assertEqual(
Basket.objects._collection.find_one(),
{
'_id': basket.pk,
'products': [
{
'_id': product1.pk,
'name': product1.name
},
{
'_id': product2.pk,
'name': product2.name
}
]
}
"_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")
)
TYPES = (("pf", "PF"), ("pj", "PJ"))
name = StringField()
tp = StringField(choices=TYPES)
father = CachedReferenceField('self', fields=('tp',))
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 = Person(name="Wilson Junior", tp="pf", father=a1)
a2.save()
a2 = Person.objects.with_id(a2.id)
self.assertEqual(a2.father.tp, a1.tp)
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(
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)._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"
}
})
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)
})
type(
"WrongEmbeddedDocument",
(EmbeddedDocument,),
{"test": CachedReferenceField(Test)},
)
def test_cached_reference_auto_sync(self):
class Person(Document):
TYPES = (
('pf', "PF"),
('pj', "PJ")
)
TYPES = (("pf", "PF"), ("pj", "PJ"))
name = StringField()
tp = StringField(
choices=TYPES
)
tp = StringField(choices=TYPES)
father = CachedReferenceField('self', fields=('tp',))
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 = Person(name="Wilson Junior", tp="pf", father=a1)
a2.save()
a1.tp = 'pf'
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'
}
})
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")
)
TYPES = (("pf", "PF"), ("pj", "PJ"))
name = StringField()
tp = StringField(
choices=TYPES
)
tp = StringField(choices=TYPES)
father = CachedReferenceField(
'self', fields=('tp',), auto_sync=False)
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 = Persone(name="Wilson Junior", tp="pf", father=a1)
a2.save()
a1.tp = 'pf'
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'
}
})
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")
)
TPS = (("n", "Normal"), ("u", "Urgent"))
name = StringField()
tp = StringField(
verbose_name="Type",
db_field="t",
choices=TPS)
tp = StringField(verbose_name="Type", db_field="t", choices=TPS)
class Animal(Document):
name = StringField()
@ -351,43 +308,38 @@ class TestCachedReferenceField(MongoDBTestCase):
class Ocorrence(Document):
person = StringField()
animal = CachedReferenceField(
Animal, fields=['tag', 'owner.tp'])
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 = 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')
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")
# Check to_mongo with fields
self.assertNotIn('animal', o.to_mongo(fields=['person']))
self.assertNotIn("animal", o.to_mongo(fields=["person"]))
# counts
Ocorrence(person="teste 2").save()
Ocorrence(person="teste 3").save()
count = Ocorrence.objects(
animal__tag='heavy', animal__owner__tp='u').count()
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()
animal__tag="heavy", animal__owner__tp="u"
).first()
self.assertEqual(ocorrence.person, "teste")
self.assertIsInstance(ocorrence.animal, Animal)
@ -404,43 +356,39 @@ class TestCachedReferenceField(MongoDBTestCase):
class Ocorrence(Document):
person = StringField()
animal = CachedReferenceField(
Animal, fields=['tag', 'owner.tags'])
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 = 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(
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'])
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'})
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()
animal__tag="heavy", animal__owner__tags="cool"
).first()
self.assertEqual(ocorrence.person, "teste 2")
self.assertIsInstance(ocorrence.animal, Animal)

View File

@ -14,9 +14,10 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
"""Tests for complex datetime fields - which can handle
microseconds without rounding.
"""
class LogEntry(Document):
date = ComplexDateTimeField()
date_with_dots = ComplexDateTimeField(separator='.')
date_with_dots = ComplexDateTimeField(separator=".")
LogEntry.drop_collection()
@ -62,17 +63,25 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
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)
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)
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()
@ -123,22 +132,21 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
# Test microsecond-level ordering/filtering
for microsecond in (99, 999, 9999, 10000):
LogEntry(
date=datetime.datetime(2015, 1, 1, 0, 0, 0, microsecond)
).save()
LogEntry(date=datetime.datetime(2015, 1, 1, 0, 0, 0, microsecond)).save()
logs = list(LogEntry.objects.order_by('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 = list(LogEntry.objects.order_by('-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))
date__lte=datetime.datetime(2015, 1, 1, 0, 0, 0, 10000)
)
self.assertEqual(logs.count(), 4)
def test_no_default_value(self):
@ -156,6 +164,7 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
def test_default_static_value(self):
NOW = datetime.datetime.utcnow()
class Log(Document):
timestamp = ComplexDateTimeField(default=NOW)

View File

@ -18,10 +18,11 @@ class TestDateField(MongoDBTestCase):
Ensure an exception is raised when trying to
cast an empty string to datetime.
"""
class MyDoc(Document):
dt = DateField()
md = MyDoc(dt='')
md = MyDoc(dt="")
self.assertRaises(ValidationError, md.save)
def test_date_from_whitespace_string(self):
@ -29,16 +30,18 @@ class TestDateField(MongoDBTestCase):
Ensure an exception is raised when trying to
cast a whitespace-only string to datetime.
"""
class MyDoc(Document):
dt = DateField()
md = MyDoc(dt=' ')
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)
@ -46,13 +49,14 @@ class TestDateField(MongoDBTestCase):
person.validate()
self.assertEqual(person.day, person.day)
self.assertEqual(person.day, datetime.date.today())
self.assertEqual(person._data['day'], person.day)
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()
@ -95,6 +99,7 @@ class TestDateField(MongoDBTestCase):
def test_regular_usage(self):
"""Tests for regular datetime fields"""
class LogEntry(Document):
date = DateField()
@ -106,12 +111,12 @@ class TestDateField(MongoDBTestCase):
log.validate()
log.save()
for query in (d1, d1.isoformat(' ')):
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'))
log1 = LogEntry.objects.get(date=d1.isoformat("T"))
self.assertEqual(log, log1)
# create additional 19 log entries for a total of 20
@ -142,6 +147,7 @@ class TestDateField(MongoDBTestCase):
"""Ensure that invalid values cannot be assigned to datetime
fields.
"""
class LogEntry(Document):
time = DateField()
@ -152,14 +158,14 @@ class TestDateField(MongoDBTestCase):
log.time = datetime.date.today()
log.validate()
log.time = datetime.datetime.now().isoformat(' ')
log.time = datetime.datetime.now().isoformat(" ")
log.validate()
if dateutil:
log.time = datetime.datetime.now().isoformat('T')
log.time = datetime.datetime.now().isoformat("T")
log.validate()
log.time = -1
self.assertRaises(ValidationError, log.validate)
log.time = 'ABC'
log.time = "ABC"
self.assertRaises(ValidationError, log.validate)

View File

@ -19,10 +19,11 @@ class TestDateTimeField(MongoDBTestCase):
Ensure an exception is raised when trying to
cast an empty string to datetime.
"""
class MyDoc(Document):
dt = DateTimeField()
md = MyDoc(dt='')
md = MyDoc(dt="")
self.assertRaises(ValidationError, md.save)
def test_datetime_from_whitespace_string(self):
@ -30,16 +31,18 @@ class TestDateTimeField(MongoDBTestCase):
Ensure an exception is raised when trying to
cast a whitespace-only string to datetime.
"""
class MyDoc(Document):
dt = DateTimeField()
md = MyDoc(dt=' ')
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=dt.datetime.utcnow)
@ -48,8 +51,10 @@ class TestDateTimeField(MongoDBTestCase):
person.validate()
person_created_t0 = person.created
self.assertLess(person.created - utcnow, dt.timedelta(seconds=1))
self.assertEqual(person_created_t0, person.created) # make sure it does not change
self.assertEqual(person._data['created'], person.created)
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.
@ -58,6 +63,7 @@ class TestDateTimeField(MongoDBTestCase):
See: http://api.mongodb.org/python/current/api/bson/son.html#dt
"""
class LogEntry(Document):
date = DateTimeField()
@ -103,6 +109,7 @@ class TestDateTimeField(MongoDBTestCase):
def test_regular_usage(self):
"""Tests for regular datetime fields"""
class LogEntry(Document):
date = DateTimeField()
@ -114,12 +121,12 @@ class TestDateTimeField(MongoDBTestCase):
log.validate()
log.save()
for query in (d1, d1.isoformat(' ')):
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'))
log1 = LogEntry.objects.get(date=d1.isoformat("T"))
self.assertEqual(log, log1)
# create additional 19 log entries for a total of 20
@ -150,8 +157,7 @@ class TestDateTimeField(MongoDBTestCase):
self.assertEqual(logs.count(), 10)
logs = LogEntry.objects.filter(
date__lte=dt.datetime(1980, 1, 1),
date__gte=dt.datetime(1975, 1, 1),
date__lte=dt.datetime(1980, 1, 1), date__gte=dt.datetime(1975, 1, 1)
)
self.assertEqual(logs.count(), 5)
@ -159,6 +165,7 @@ class TestDateTimeField(MongoDBTestCase):
"""Ensure that invalid values cannot be assigned to datetime
fields.
"""
class LogEntry(Document):
time = DateTimeField()
@ -169,32 +176,32 @@ class TestDateTimeField(MongoDBTestCase):
log.time = dt.date.today()
log.validate()
log.time = dt.datetime.now().isoformat(' ')
log.time = dt.datetime.now().isoformat(" ")
log.validate()
log.time = '2019-05-16 21:42:57.897847'
log.time = "2019-05-16 21:42:57.897847"
log.validate()
if dateutil:
log.time = dt.datetime.now().isoformat('T')
log.time = dt.datetime.now().isoformat("T")
log.validate()
log.time = -1
self.assertRaises(ValidationError, log.validate)
log.time = 'ABC'
log.time = "ABC"
self.assertRaises(ValidationError, log.validate)
log.time = '2019-05-16 21:GARBAGE:12'
log.time = "2019-05-16 21:GARBAGE:12"
self.assertRaises(ValidationError, log.validate)
log.time = '2019-05-16 21:42:57.GARBAGE'
log.time = "2019-05-16 21:42:57.GARBAGE"
self.assertRaises(ValidationError, log.validate)
log.time = '2019-05-16 21:42:57.123.456'
log.time = "2019-05-16 21:42:57.123.456"
self.assertRaises(ValidationError, log.validate)
def test_parse_datetime_as_str(self):
class DTDoc(Document):
date = DateTimeField()
date_str = '2019-03-02 22:26:01'
date_str = "2019-03-02 22:26:01"
# make sure that passing a parsable datetime works
dtd = DTDoc()
@ -206,7 +213,7 @@ class TestDateTimeField(MongoDBTestCase):
self.assertIsInstance(dtd.date, dt.datetime)
self.assertEqual(str(dtd.date), date_str)
dtd.date = 'January 1st, 9999999999'
dtd.date = "January 1st, 9999999999"
self.assertRaises(ValidationError, dtd.validate)
@ -217,7 +224,7 @@ class TestDateTimeTzAware(MongoDBTestCase):
connection._connections = {}
connection._dbs = {}
connect(db='mongoenginetest', tz_aware=True)
connect(db="mongoenginetest", tz_aware=True)
class LogEntry(Document):
time = DateTimeField()
@ -228,4 +235,4 @@ class TestDateTimeTzAware(MongoDBTestCase):
log = LogEntry.objects.first()
log.time = dt.datetime(2013, 1, 1, 0, 0, 0)
self.assertEqual(['time'], log._changed_fields)
self.assertEqual(["time"], log._changed_fields)

View File

@ -7,32 +7,31 @@ 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'))
height = DecimalField(min_value=Decimal("0.1"), max_value=Decimal("3.5"))
Person.drop_collection()
Person(height=Decimal('1.89')).save()
Person(height=Decimal("1.89")).save()
person = Person.objects.first()
self.assertEqual(person.height, Decimal('1.89'))
self.assertEqual(person.height, Decimal("1.89"))
person.height = '2.0'
person.height = "2.0"
person.save()
person.height = 0.01
self.assertRaises(ValidationError, person.validate)
person.height = Decimal('0.01')
person.height = Decimal("0.01")
self.assertRaises(ValidationError, person.validate)
person.height = Decimal('4.0')
person.height = Decimal("4.0")
self.assertRaises(ValidationError, person.validate)
person.height = 'something invalid'
person.height = "something invalid"
self.assertRaises(ValidationError, person.validate)
person_2 = Person(height='something invalid')
person_2 = Person(height="something invalid")
self.assertRaises(ValidationError, person_2.validate)
def test_comparison(self):
@ -58,7 +57,14 @@ class TestDecimalField(MongoDBTestCase):
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")]
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__
@ -72,20 +78,27 @@ class TestDecimalField(MongoDBTestCase):
# 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'}]
{"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())
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 = [
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']:
for field_name in ["float_value", "string_value"]:
actual = list(Person.objects().scalar(field_name))
self.assertEqual(expected, actual)

View File

@ -6,95 +6,92 @@ 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'}
info = {"testkey": "testvalue"}
post = BlogPost(info=info).save()
self.assertEqual(
get_as_pymongo(post),
{
'_id': post.id,
'info': info
}
)
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'
post.info = "my post"
self.assertRaises(ValidationError, post.validate)
post.info = ['test', 'test']
post.info = ["test", "test"]
self.assertRaises(ValidationError, post.validate)
post.info = {'$title': 'test'}
post.info = {"$title": "test"}
self.assertRaises(ValidationError, post.validate)
post.info = {'nested': {'$title': 'test'}}
post.info = {"nested": {"$title": "test"}}
self.assertRaises(ValidationError, post.validate)
post.info = {'the.title': 'test'}
post.info = {"the.title": "test"}
self.assertRaises(ValidationError, post.validate)
post.info = {'nested': {'the.title': 'test'}}
post.info = {"nested": {"the.title": "test"}}
self.assertRaises(ValidationError, post.validate)
post.info = {1: 'test'}
post.info = {1: "test"}
self.assertRaises(ValidationError, post.validate)
post.info = {'title': 'test'}
post.info = {"title": "test"}
post.save()
post = BlogPost()
post.info = {'title': 'dollar_sign', 'details': {'te$t': 'test'}}
post.info = {"title": "dollar_sign", "details": {"te$t": "test"}}
post.save()
post = BlogPost()
post.info = {'details': {'test': 'test'}}
post.info = {"details": {"test": "test"}}
post.save()
post = BlogPost()
post.info = {'details': {'test': 3}}
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__title__exact='test').count(), 1)
self.assertEqual(
BlogPost.objects.filter(info__details__test__exact='test').count(), 1)
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'])
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)
BlogPost.objects.filter(info__details__test__exact=5).count(), 0
)
self.assertEqual(
BlogPost.objects.filter(info__made_up__test__exact='test').count(), 0)
BlogPost.objects.filter(info__made_up__test__exact="test").count(), 0
)
post = BlogPost.objects.create(info={'title': 'original'})
post.info.update({'title': 'updated'})
post = BlogPost.objects.create(info={"title": "original"})
post.info.update({"title": "updated"})
post.save()
post.reload()
self.assertEqual('updated', post.info['title'])
self.assertEqual("updated", post.info["title"])
post.info.setdefault('authors', [])
post.info.setdefault("authors", [])
post.save()
post.reload()
self.assertEqual([], post.info['authors'])
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()
@ -106,51 +103,62 @@ class TestDictField(MongoDBTestCase):
id = IntField(primary_key=True, default=1)
recursive = DictField()
meta = {'allow_inheritance': True}
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()
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': {}}})
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()
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': {}}
"_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.mapping["someint"] = 1
e.save()
# try creating an invalid mapping
with self.assertRaises(ValidationError):
e.mapping['somestring'] = "abc"
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}
meta = {"allow_inheritance": True}
class StringSetting(SettingBase):
value = StringField()
@ -164,70 +172,72 @@ class TestDictField(MongoDBTestCase):
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.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)
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__someint__value=42).count(), 1)
Simple.objects.filter(mapping__nested_dict__number=1).count(), 1
)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__number=1).count(), 1)
Simple.objects.filter(mapping__nested_dict__complex__value=42).count(), 1
)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__complex__value=42).count(), 1)
Simple.objects.filter(mapping__nested_dict__list__0__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)
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={"someint": IntegerSetting(value=10)})
Simple.objects().update(
set__mapping__nested_dict__list__1=StringSetting(value='Boo'))
set__mapping__nested_dict__list__1=StringSetting(value="Boo")
)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__list__1__value='foo').count(), 0)
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)
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()
doc = MyModel(events=[{"a": 1}]).save()
raw_doc = get_as_pymongo(doc)
expected_raw_doc = {
'_id': doc.id,
'events': [{'a': 1}]
}
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}, {}]
}
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'
d1.data["foo"] = "bar"
d1.data2["foo"] = "bar"
d2 = D()
self.assertEqual(d2.data, {})
self.assertEqual(d2.data2, {})
@ -255,22 +265,25 @@ class TestDictField(MongoDBTestCase):
class Embedded(EmbeddedDocument):
name = StringField()
embed = Embedded(name='garbage')
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))
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.mapping["someints"] = [1, 2]
e.save()
e.update(set__mapping={"ints": [3, 4]})
e.reload()
@ -279,7 +292,7 @@ class TestDictField(MongoDBTestCase):
# try creating an invalid mapping
with self.assertRaises(ValueError):
e.update(set__mapping={"somestrings": ["foo", "bar", ]})
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)"""
@ -296,29 +309,33 @@ class TestDictField(MongoDBTestCase):
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)))))
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()
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.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)
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

@ -12,28 +12,29 @@ class TestEmailField(MongoDBTestCase):
class User(Document):
email = EmailField()
user = User(email='ross@example.com')
user = User(email="ross@example.com")
user.validate()
user = User(email='ross@example.co.uk')
user = User(email="ross@example.co.uk")
user.validate()
user = User(email=('Kofq@rhom0e4klgauOhpbpNdogawnyIKvQS0wk2mjqrgGQ5S'
'aJIazqqWkm7.net'))
user = User(
email=("Kofq@rhom0e4klgauOhpbpNdogawnyIKvQS0wk2mjqrgGQ5SaJIazqqWkm7.net")
)
user.validate()
user = User(email='new-tld@example.technology')
user = User(email="new-tld@example.technology")
user.validate()
user = User(email='ross@example.com.')
user = User(email="ross@example.com.")
self.assertRaises(ValidationError, user.validate)
# unicode domain
user = User(email=u'user@пример.рф')
user = User(email=u"user@пример.рф")
user.validate()
# invalid unicode domain
user = User(email=u'user@пример')
user = User(email=u"user@пример")
self.assertRaises(ValidationError, user.validate)
# invalid data type
@ -44,20 +45,20 @@ class TestEmailField(MongoDBTestCase):
# 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')
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')
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 = User(email=u"Dörte@Sörensen.example.com")
user.validate()
def test_email_field_domain_whitelist(self):
@ -65,22 +66,22 @@ class TestEmailField(MongoDBTestCase):
email = EmailField()
# localhost domain shouldn't validate by default...
user = User(email='me@localhost')
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'])
email = EmailField(domain_whitelist=["localhost"])
user = User(email='me@localhost')
user = User(email="me@localhost")
user.validate()
def test_email_domain_validation_fails_if_invalid_idn(self):
class User(Document):
email = EmailField()
invalid_idn = '.google.com'
user = User(email='me@%s' % invalid_idn)
invalid_idn = ".google.com"
user = User(email="me@%s" % invalid_idn)
with self.assertRaises(ValidationError) as ctx_err:
user.validate()
self.assertIn("domain failed IDN encoding", str(ctx_err.exception))
@ -89,9 +90,9 @@ class TestEmailField(MongoDBTestCase):
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]'
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)
@ -119,12 +120,12 @@ class TestEmailField(MongoDBTestCase):
def test_email_field_honors_regex(self):
class User(Document):
email = EmailField(regex=r'\w+@example.com')
email = EmailField(regex=r"\w+@example.com")
# Fails regex validation
user = User(email='me@foo.com')
user = User(email="me@foo.com")
self.assertRaises(ValidationError, user.validate)
# Passes regex validation
user = User(email='me@example.com')
user = User(email="me@example.com")
self.assertIsNone(user.validate())

View File

@ -1,7 +1,18 @@
# -*- coding: utf-8 -*-
from mongoengine import Document, StringField, ValidationError, EmbeddedDocument, EmbeddedDocumentField, \
InvalidQueryError, LookUpError, IntField, GenericEmbeddedDocumentField, ListField, EmbeddedDocumentListField, \
ReferenceField
from mongoengine import (
Document,
StringField,
ValidationError,
EmbeddedDocument,
EmbeddedDocumentField,
InvalidQueryError,
LookUpError,
IntField,
GenericEmbeddedDocumentField,
ListField,
EmbeddedDocumentListField,
ReferenceField,
)
from tests.utils import MongoDBTestCase
@ -14,22 +25,24 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
field = EmbeddedDocumentField(MyDoc)
self.assertEqual(field.document_type_obj, MyDoc)
field2 = EmbeddedDocumentField('MyDoc')
self.assertEqual(field2.document_type_obj, 'MyDoc')
field2 = EmbeddedDocumentField("MyDoc")
self.assertEqual(field2.document_type_obj, "MyDoc")
def test___init___throw_error_if_document_type_is_not_EmbeddedDocument(self):
with self.assertRaises(ValidationError):
EmbeddedDocumentField(dict)
def test_document_type_throw_error_if_not_EmbeddedDocument_subclass(self):
class MyDoc(Document):
name = StringField()
emb = EmbeddedDocumentField('MyDoc')
emb = EmbeddedDocumentField("MyDoc")
with self.assertRaises(ValidationError) as ctx:
emb.document_type
self.assertIn('Invalid embedded document class provided to an EmbeddedDocumentField', str(ctx.exception))
self.assertIn(
"Invalid embedded document class provided to an EmbeddedDocumentField",
str(ctx.exception),
)
def test_embedded_document_field_only_allow_subclasses_of_embedded_document(self):
# Relates to #1661
@ -37,12 +50,14 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
name = StringField()
with self.assertRaises(ValidationError):
class MyFailingDoc(Document):
emb = EmbeddedDocumentField(MyDoc)
with self.assertRaises(ValidationError):
class MyFailingdoc2(Document):
emb = EmbeddedDocumentField('MyDoc')
emb = EmbeddedDocumentField("MyDoc")
def test_query_embedded_document_attribute(self):
class AdminSettings(EmbeddedDocument):
@ -55,34 +70,31 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
Person.drop_collection()
p = Person(
settings=AdminSettings(foo1='bar1', foo2='bar2'),
name='John',
).save()
p = Person(settings=AdminSettings(foo1="bar1", foo2="bar2"), name="John").save()
# Test non exiting attribute
with self.assertRaises(InvalidQueryError) as ctx_err:
Person.objects(settings__notexist='bar').first()
Person.objects(settings__notexist="bar").first()
self.assertEqual(unicode(ctx_err.exception), u'Cannot resolve field "notexist"')
with self.assertRaises(LookUpError):
Person.objects.only('settings.notexist')
Person.objects.only("settings.notexist")
# Test existing attribute
self.assertEqual(Person.objects(settings__foo1='bar1').first().id, p.id)
only_p = Person.objects.only('settings.foo1').first()
self.assertEqual(Person.objects(settings__foo1="bar1").first().id, p.id)
only_p = Person.objects.only("settings.foo1").first()
self.assertEqual(only_p.settings.foo1, p.settings.foo1)
self.assertIsNone(only_p.settings.foo2)
self.assertIsNone(only_p.name)
exclude_p = Person.objects.exclude('settings.foo1').first()
exclude_p = Person.objects.exclude("settings.foo1").first()
self.assertIsNone(exclude_p.settings.foo1)
self.assertEqual(exclude_p.settings.foo2, p.settings.foo2)
self.assertEqual(exclude_p.name, p.name)
def test_query_embedded_document_attribute_with_inheritance(self):
class BaseSettings(EmbeddedDocument):
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
base_foo = StringField()
class AdminSettings(BaseSettings):
@ -93,26 +105,26 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
Person.drop_collection()
p = Person(settings=AdminSettings(base_foo='basefoo', sub_foo='subfoo'))
p = Person(settings=AdminSettings(base_foo="basefoo", sub_foo="subfoo"))
p.save()
# Test non exiting attribute
with self.assertRaises(InvalidQueryError) as ctx_err:
self.assertEqual(Person.objects(settings__notexist='bar').first().id, p.id)
self.assertEqual(Person.objects(settings__notexist="bar").first().id, p.id)
self.assertEqual(unicode(ctx_err.exception), u'Cannot resolve field "notexist"')
# Test existing attribute
self.assertEqual(Person.objects(settings__base_foo='basefoo').first().id, p.id)
self.assertEqual(Person.objects(settings__sub_foo='subfoo').first().id, p.id)
self.assertEqual(Person.objects(settings__base_foo="basefoo").first().id, p.id)
self.assertEqual(Person.objects(settings__sub_foo="subfoo").first().id, p.id)
only_p = Person.objects.only('settings.base_foo', 'settings._cls').first()
self.assertEqual(only_p.settings.base_foo, 'basefoo')
only_p = Person.objects.only("settings.base_foo", "settings._cls").first()
self.assertEqual(only_p.settings.base_foo, "basefoo")
self.assertIsNone(only_p.settings.sub_foo)
def test_query_list_embedded_document_with_inheritance(self):
class Post(EmbeddedDocument):
title = StringField(max_length=120, required=True)
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
class TextPost(Post):
content = StringField()
@ -123,8 +135,8 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
class Record(Document):
posts = ListField(EmbeddedDocumentField(Post))
record_movie = Record(posts=[MoviePost(author='John', title='foo')]).save()
record_text = Record(posts=[TextPost(content='a', title='foo')]).save()
record_movie = Record(posts=[MoviePost(author="John", title="foo")]).save()
record_text = Record(posts=[TextPost(content="a", title="foo")]).save()
records = list(Record.objects(posts__author=record_movie.posts[0].author))
self.assertEqual(len(records), 1)
@ -134,11 +146,10 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
self.assertEqual(len(records), 1)
self.assertEqual(records[0].id, record_text.id)
self.assertEqual(Record.objects(posts__title='foo').count(), 2)
self.assertEqual(Record.objects(posts__title="foo").count(), 2)
class TestGenericEmbeddedDocumentField(MongoDBTestCase):
def test_generic_embedded_document(self):
class Car(EmbeddedDocument):
name = StringField()
@ -153,8 +164,8 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
Person.drop_collection()
person = Person(name='Test User')
person.like = Car(name='Fiat')
person = Person(name="Test User")
person.like = Car(name="Fiat")
person.save()
person = Person.objects.first()
@ -168,6 +179,7 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
def test_generic_embedded_document_choices(self):
"""Ensure you can limit GenericEmbeddedDocument choices."""
class Car(EmbeddedDocument):
name = StringField()
@ -181,8 +193,8 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
Person.drop_collection()
person = Person(name='Test User')
person.like = Car(name='Fiat')
person = Person(name="Test User")
person.like = Car(name="Fiat")
self.assertRaises(ValidationError, person.validate)
person.like = Dish(food="arroz", number=15)
@ -195,6 +207,7 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
"""Ensure you can limit GenericEmbeddedDocument choices inside
a list field.
"""
class Car(EmbeddedDocument):
name = StringField()
@ -208,8 +221,8 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
Person.drop_collection()
person = Person(name='Test User')
person.likes = [Car(name='Fiat')]
person = Person(name="Test User")
person.likes = [Car(name="Fiat")]
self.assertRaises(ValidationError, person.validate)
person.likes = [Dish(food="arroz", number=15)]
@ -222,25 +235,23 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
"""
Ensure fields with document choices validate given a valid choice.
"""
class UserComments(EmbeddedDocument):
author = StringField()
message = StringField()
class BlogPost(Document):
comments = ListField(
GenericEmbeddedDocumentField(choices=(UserComments,))
)
comments = ListField(GenericEmbeddedDocumentField(choices=(UserComments,)))
# Ensure Validation Passes
BlogPost(comments=[
UserComments(author='user2', message='message2'),
]).save()
BlogPost(comments=[UserComments(author="user2", message="message2")]).save()
def test_choices_validation_documents_invalid(self):
"""
Ensure fields with document choices validate given an invalid choice.
This should throw a ValidationError exception.
"""
class UserComments(EmbeddedDocument):
author = StringField()
message = StringField()
@ -250,31 +261,28 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
message = StringField()
class BlogPost(Document):
comments = ListField(
GenericEmbeddedDocumentField(choices=(UserComments,))
)
comments = ListField(GenericEmbeddedDocumentField(choices=(UserComments,)))
# Single Entry Failure
post = BlogPost(comments=[
ModeratorComments(author='mod1', message='message1'),
])
post = BlogPost(comments=[ModeratorComments(author="mod1", message="message1")])
self.assertRaises(ValidationError, post.save)
# Mixed Entry Failure
post = BlogPost(comments=[
ModeratorComments(author='mod1', message='message1'),
UserComments(author='user2', message='message2'),
])
post = BlogPost(
comments=[
ModeratorComments(author="mod1", message="message1"),
UserComments(author="user2", message="message2"),
]
)
self.assertRaises(ValidationError, post.save)
def test_choices_validation_documents_inheritance(self):
"""
Ensure fields with document choices validate given subclass of choice.
"""
class Comments(EmbeddedDocument):
meta = {
'abstract': True
}
meta = {"abstract": True}
author = StringField()
message = StringField()
@ -282,14 +290,10 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
pass
class BlogPost(Document):
comments = ListField(
GenericEmbeddedDocumentField(choices=(Comments,))
)
comments = ListField(GenericEmbeddedDocumentField(choices=(Comments,)))
# Save Valid EmbeddedDocument Type
BlogPost(comments=[
UserComments(author='user2', message='message2'),
]).save()
BlogPost(comments=[UserComments(author="user2", message="message2")]).save()
def test_query_generic_embedded_document_attribute(self):
class AdminSettings(EmbeddedDocument):
@ -299,28 +303,30 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
foo2 = StringField()
class Person(Document):
settings = GenericEmbeddedDocumentField(choices=(AdminSettings, NonAdminSettings))
settings = GenericEmbeddedDocumentField(
choices=(AdminSettings, NonAdminSettings)
)
Person.drop_collection()
p1 = Person(settings=AdminSettings(foo1='bar1')).save()
p2 = Person(settings=NonAdminSettings(foo2='bar2')).save()
p1 = Person(settings=AdminSettings(foo1="bar1")).save()
p2 = Person(settings=NonAdminSettings(foo2="bar2")).save()
# Test non exiting attribute
with self.assertRaises(InvalidQueryError) as ctx_err:
Person.objects(settings__notexist='bar').first()
Person.objects(settings__notexist="bar").first()
self.assertEqual(unicode(ctx_err.exception), u'Cannot resolve field "notexist"')
with self.assertRaises(LookUpError):
Person.objects.only('settings.notexist')
Person.objects.only("settings.notexist")
# Test existing attribute
self.assertEqual(Person.objects(settings__foo1='bar1').first().id, p1.id)
self.assertEqual(Person.objects(settings__foo2='bar2').first().id, p2.id)
self.assertEqual(Person.objects(settings__foo1="bar1").first().id, p1.id)
self.assertEqual(Person.objects(settings__foo2="bar2").first().id, p2.id)
def test_query_generic_embedded_document_attribute_with_inheritance(self):
class BaseSettings(EmbeddedDocument):
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
base_foo = StringField()
class AdminSettings(BaseSettings):
@ -331,14 +337,14 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
Person.drop_collection()
p = Person(settings=AdminSettings(base_foo='basefoo', sub_foo='subfoo'))
p = Person(settings=AdminSettings(base_foo="basefoo", sub_foo="subfoo"))
p.save()
# Test non exiting attribute
with self.assertRaises(InvalidQueryError) as ctx_err:
self.assertEqual(Person.objects(settings__notexist='bar').first().id, p.id)
self.assertEqual(Person.objects(settings__notexist="bar").first().id, p.id)
self.assertEqual(unicode(ctx_err.exception), u'Cannot resolve field "notexist"')
# Test existing attribute
self.assertEqual(Person.objects(settings__base_foo='basefoo').first().id, p.id)
self.assertEqual(Person.objects(settings__sub_foo='subfoo').first().id, p.id)
self.assertEqual(Person.objects(settings__base_foo="basefoo").first().id, p.id)
self.assertEqual(Person.objects(settings__sub_foo="subfoo").first().id, p.id)

View File

@ -7,7 +7,6 @@ from tests.utils import MongoDBTestCase
class TestFloatField(MongoDBTestCase):
def test_float_ne_operator(self):
class TestDocument(Document):
float_fld = FloatField()
@ -23,6 +22,7 @@ class TestFloatField(MongoDBTestCase):
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)
@ -33,7 +33,7 @@ class TestFloatField(MongoDBTestCase):
person.height = 1.89
person.validate()
person.height = '2.0'
person.height = "2.0"
self.assertRaises(ValidationError, person.validate)
person.height = 0.01
@ -42,7 +42,7 @@ class TestFloatField(MongoDBTestCase):
person.height = 4.0
self.assertRaises(ValidationError, person.validate)
person_2 = Person(height='something invalid')
person_2 = Person(height="something invalid")
self.assertRaises(ValidationError, person_2.validate)
big_person = BigPerson()

View File

@ -5,10 +5,10 @@ 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)
@ -26,7 +26,7 @@ class TestIntField(MongoDBTestCase):
self.assertRaises(ValidationError, person.validate)
person.age = 120
self.assertRaises(ValidationError, person.validate)
person.age = 'ten'
person.age = "ten"
self.assertRaises(ValidationError, person.validate)
def test_ne_operator(self):

View File

@ -25,7 +25,7 @@ class TestLazyReferenceField(MongoDBTestCase):
animal = Animal()
oc = Ocurrence(animal=animal)
self.assertIn('LazyReference', repr(oc.animal))
self.assertIn("LazyReference", repr(oc.animal))
def test___getattr___unknown_attr_raises_attribute_error(self):
class Animal(Document):
@ -93,7 +93,7 @@ class TestLazyReferenceField(MongoDBTestCase):
def test_lazy_reference_set(self):
class Animal(Document):
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
name = StringField()
tag = StringField()
@ -109,18 +109,17 @@ class TestLazyReferenceField(MongoDBTestCase):
nick = StringField()
animal = Animal(name="Leopard", tag="heavy").save()
sub_animal = SubAnimal(nick='doggo', name='dog').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),
):
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)
@ -144,12 +143,12 @@ class TestLazyReferenceField(MongoDBTestCase):
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)
):
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()
@ -157,6 +156,7 @@ class TestLazyReferenceField(MongoDBTestCase):
"""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)
@ -172,10 +172,10 @@ class TestLazyReferenceField(MongoDBTestCase):
m2 = Member(user_num=2)
m2.save()
post1 = BlogPost(title='post 1', author=m1)
post1 = BlogPost(title="post 1", author=m1)
post1.save()
post2 = BlogPost(title='post 2', author=m2)
post2 = BlogPost(title="post 2", author=m2)
post2.save()
post = BlogPost.objects(author=m1).first()
@ -192,6 +192,7 @@ class TestLazyReferenceField(MongoDBTestCase):
"""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)
@ -207,10 +208,10 @@ class TestLazyReferenceField(MongoDBTestCase):
m2 = Member(user_num=2)
m2.save()
post1 = BlogPost(title='post 1', author=m1)
post1 = BlogPost(title="post 1", author=m1)
post1.save()
post2 = BlogPost(title='post 2', author=m2)
post2 = BlogPost(title="post 2", author=m2)
post2.save()
post = BlogPost.objects(author=m1).first()
@ -240,19 +241,19 @@ class TestLazyReferenceField(MongoDBTestCase):
p = Ocurrence.objects.get()
self.assertIsInstance(p.animal, LazyReference)
with self.assertRaises(KeyError):
p.animal['name']
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")
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']
p.animal["save"]
def test_lazy_reference_not_set(self):
class Animal(Document):
@ -266,7 +267,7 @@ class TestLazyReferenceField(MongoDBTestCase):
Animal.drop_collection()
Ocurrence.drop_collection()
Ocurrence(person='foo').save()
Ocurrence(person="foo").save()
p = Ocurrence.objects.get()
self.assertIs(p.animal, None)
@ -303,8 +304,8 @@ class TestLazyReferenceField(MongoDBTestCase):
Animal.drop_collection()
Ocurrence.drop_collection()
animal1 = Animal('doggo').save()
animal2 = Animal('cheeta').save()
animal1 = Animal(name="doggo").save()
animal2 = Animal(name="cheeta").save()
def check_fields_type(occ):
self.assertIsInstance(occ.direct, LazyReference)
@ -316,8 +317,8 @@ class TestLazyReferenceField(MongoDBTestCase):
occ = Ocurrence(
in_list=[animal1, animal2],
in_embedded={'in_list': [animal1, animal2], 'direct': animal1},
direct=animal1
in_embedded={"in_list": [animal1, animal2], "direct": animal1},
direct=animal1,
).save()
check_fields_type(occ)
occ.reload()
@ -403,7 +404,7 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
def test_generic_lazy_reference_set(self):
class Animal(Document):
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
name = StringField()
tag = StringField()
@ -419,16 +420,18 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
nick = StringField()
animal = Animal(name="Leopard", tag="heavy").save()
sub_animal = SubAnimal(nick='doggo', name='dog').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)},
):
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))
@ -441,7 +444,7 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
class Ocurrence(Document):
person = StringField()
animal = GenericLazyReferenceField(choices=['Animal'])
animal = GenericLazyReferenceField(choices=["Animal"])
Animal.drop_collection()
Ocurrence.drop_collection()
@ -451,12 +454,7 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
animal = Animal(name="Leopard", tag="heavy").save()
baddoc = BadDoc().save()
for bad in (
42,
'foo',
baddoc,
LazyReference(BadDoc, animal.pk)
):
for bad in (42, "foo", baddoc, LazyReference(BadDoc, animal.pk)):
with self.assertRaises(ValidationError):
p = Ocurrence(person="test", animal=bad).save()
@ -476,10 +474,10 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
m2 = Member(user_num=2)
m2.save()
post1 = BlogPost(title='post 1', author=m1)
post1 = BlogPost(title="post 1", author=m1)
post1.save()
post2 = BlogPost(title='post 2', author=m2)
post2 = BlogPost(title="post 2", author=m2)
post2.save()
post = BlogPost.objects(author=m1).first()
@ -504,7 +502,7 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
Animal.drop_collection()
Ocurrence.drop_collection()
Ocurrence(person='foo').save()
Ocurrence(person="foo").save()
p = Ocurrence.objects.get()
self.assertIs(p.animal, None)
@ -515,7 +513,7 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
class Ocurrence(Document):
person = StringField()
animal = GenericLazyReferenceField('Animal')
animal = GenericLazyReferenceField("Animal")
Animal.drop_collection()
Ocurrence.drop_collection()
@ -542,8 +540,8 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
Animal.drop_collection()
Ocurrence.drop_collection()
animal1 = Animal('doggo').save()
animal2 = Animal('cheeta').save()
animal1 = Animal(name="doggo").save()
animal2 = Animal(name="cheeta").save()
def check_fields_type(occ):
self.assertIsInstance(occ.direct, LazyReference)
@ -555,14 +553,20 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
occ = Ocurrence(
in_list=[animal1, animal2],
in_embedded={'in_list': [animal1, animal2], 'direct': animal1},
direct=animal1
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)}
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

View File

@ -13,23 +13,26 @@ 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(
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)
@ -41,7 +44,7 @@ class TestLongField(MongoDBTestCase):
self.assertRaises(ValidationError, doc.validate)
doc.value = 120
self.assertRaises(ValidationError, doc.validate)
doc.value = 'ten'
doc.value = "ten"
self.assertRaises(ValidationError, doc.validate)
def test_long_ne_operator(self):

View File

@ -7,23 +7,24 @@ 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.mapping["someint"] = 1
e.save()
with self.assertRaises(ValidationError):
e.mapping['somestring'] = "abc"
e.mapping["somestring"] = "abc"
e.save()
with self.assertRaises(ValidationError):
class NoDeclaredType(Document):
mapping = MapField()
@ -45,38 +46,37 @@ class TestMapField(MongoDBTestCase):
Extensible.drop_collection()
e = Extensible()
e.mapping['somestring'] = StringSetting(value='foo')
e.mapping['someint'] = IntegerSetting(value=42)
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)
self.assertIsInstance(e2.mapping["somestring"], StringSetting)
self.assertIsInstance(e2.mapping["someint"], IntegerSetting)
with self.assertRaises(ValidationError):
e.mapping['someint'] = 123
e.mapping["someint"] = 123
e.save()
def test_embedded_mapfield_db_field(self):
class Embedded(EmbeddedDocument):
number = IntField(default=0, db_field='i')
number = IntField(default=0, db_field="i")
class Test(Document):
my_map = MapField(field=EmbeddedDocumentField(Embedded),
db_field='x')
my_map = MapField(field=EmbeddedDocumentField(Embedded), db_field="x")
Test.drop_collection()
test = Test()
test.my_map['DICTIONARY_KEY'] = Embedded(number=1)
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)
self.assertEqual(test.my_map["DICTIONARY_KEY"].number, 2)
doc = self.db.test.find_one()
self.assertEqual(doc['x']['DICTIONARY_KEY']['i'], 2)
self.assertEqual(doc["x"]["DICTIONARY_KEY"]["i"], 2)
def test_mapfield_numerical_index(self):
"""Ensure that MapField accept numeric strings as indexes."""
@ -90,9 +90,9 @@ class TestMapField(MongoDBTestCase):
Test.drop_collection()
test = Test()
test.my_map['1'] = Embedded(name='test')
test.my_map["1"] = Embedded(name="test")
test.save()
test.my_map['1'].name = 'test updated'
test.my_map["1"].name = "test updated"
test.save()
def test_map_field_lookup(self):
@ -110,15 +110,20 @@ class TestMapField(MongoDBTestCase):
actions = MapField(EmbeddedDocumentField(Action))
Log.drop_collection()
Log(name="wilson", visited={'friends': datetime.datetime.now()},
actions={'friends': Action(operation='drink', object='beer')}).save()
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(visited__friends__exists=True).count())
self.assertEqual(1, Log.objects(
actions__friends__operation='drink',
actions__friends__object='beer').count())
self.assertEqual(
1,
Log.objects(
actions__friends__operation="drink", actions__friends__object="beer"
).count(),
)
def test_map_field_unicode(self):
class Info(EmbeddedDocument):
@ -130,15 +135,11 @@ class TestMapField(MongoDBTestCase):
BlogPost.drop_collection()
tree = BlogPost(info_dict={
u"éééé": {
'description': u"VALUE: éééé"
}
})
tree = BlogPost(info_dict={u"éééé": {"description": u"VALUE: éééé"}})
tree.save()
self.assertEqual(
BlogPost.objects.get(id=tree.id).info_dict[u"éééé"].description,
u"VALUE: éééé"
u"VALUE: éééé",
)

View File

@ -26,15 +26,15 @@ class TestReferenceField(MongoDBTestCase):
# with a document class name.
self.assertRaises(ValidationError, ReferenceField, EmbeddedDocument)
user = User(name='Test User')
user = User(name="Test User")
# Ensure that the referenced object must have been saved
post1 = BlogPost(content='Chips and gravy taste good.')
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.')
post2 = BlogPost(content="Chips and chilli taste good.")
post1.author = post2
self.assertRaises(ValidationError, post1.validate)
@ -59,7 +59,7 @@ class TestReferenceField(MongoDBTestCase):
class Person(Document):
name = StringField()
parent = ReferenceField('self')
parent = ReferenceField("self")
Person.drop_collection()
@ -74,7 +74,7 @@ class TestReferenceField(MongoDBTestCase):
class Person(Document):
name = StringField()
parent = ReferenceField('self', dbref=True)
parent = ReferenceField("self", dbref=True)
Person.drop_collection()
@ -82,8 +82,8 @@ class TestReferenceField(MongoDBTestCase):
Person(name="Ross", parent=p1).save()
self.assertEqual(
Person._get_collection().find_one({'name': 'Ross'})['parent'],
DBRef('person', p1.pk)
Person._get_collection().find_one({"name": "Ross"})["parent"],
DBRef("person", p1.pk),
)
p = Person.objects.get(name="Ross")
@ -97,21 +97,17 @@ class TestReferenceField(MongoDBTestCase):
class Person(Document):
name = StringField()
parent = ReferenceField('self', dbref=False)
parent = ReferenceField("self", dbref=False)
p = Person(
name='Steve',
parent=DBRef('person', 'abcdefghijklmnop')
p = Person(name="Steve", parent=DBRef("person", "abcdefghijklmnop"))
self.assertEqual(
p.to_mongo(), SON([("name", u"Steve"), ("parent", "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)
parent = ReferenceField("self", dbref=False)
Person.drop_collection()
@ -119,8 +115,8 @@ class TestReferenceField(MongoDBTestCase):
Person(name="Ross", parent=p1).save()
col = Person._get_collection()
data = col.find_one({'name': 'Ross'})
self.assertEqual(data['parent'], p1.pk)
data = col.find_one({"name": "Ross"})
self.assertEqual(data["parent"], p1.pk)
p = Person.objects.get(name="Ross")
self.assertEqual(p.parent, p1)
@ -128,9 +124,10 @@ class TestReferenceField(MongoDBTestCase):
def test_undefined_reference(self):
"""Ensure that ReferenceFields may reference undefined Documents.
"""
class Product(Document):
name = StringField()
company = ReferenceField('Company')
company = ReferenceField("Company")
class Company(Document):
name = StringField()
@ -138,12 +135,12 @@ class TestReferenceField(MongoDBTestCase):
Product.drop_collection()
Company.drop_collection()
ten_gen = Company(name='10gen')
ten_gen = Company(name="10gen")
ten_gen.save()
mongodb = Product(name='MongoDB', company=ten_gen)
mongodb = Product(name="MongoDB", company=ten_gen)
mongodb.save()
me = Product(name='MongoEngine')
me = Product(name="MongoEngine")
me.save()
obj = Product.objects(company=ten_gen).first()
@ -160,6 +157,7 @@ class TestReferenceField(MongoDBTestCase):
"""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)
@ -175,10 +173,10 @@ class TestReferenceField(MongoDBTestCase):
m2 = Member(user_num=2)
m2.save()
post1 = BlogPost(title='post 1', author=m1)
post1 = BlogPost(title="post 1", author=m1)
post1.save()
post2 = BlogPost(title='post 2', author=m2)
post2 = BlogPost(title="post 2", author=m2)
post2.save()
post = BlogPost.objects(author=m1).first()
@ -191,6 +189,7 @@ class TestReferenceField(MongoDBTestCase):
"""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)
@ -206,10 +205,10 @@ class TestReferenceField(MongoDBTestCase):
m2 = Member(user_num=2)
m2.save()
post1 = BlogPost(title='post 1', author=m1)
post1 = BlogPost(title="post 1", author=m1)
post1.save()
post2 = BlogPost(title='post 2', author=m2)
post2 = BlogPost(title="post 2", author=m2)
post2.save()
post = BlogPost.objects(author=m1).first()

View File

@ -11,38 +11,38 @@ class TestSequenceField(MongoDBTestCase):
id = SequenceField(primary_key=True)
name = StringField()
self.db['mongoengine.counters'].drop()
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)
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)
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)
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()
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.db["mongoengine.counters"].drop()
self.assertEqual(Person.id.get_next_value(), 1)
@ -50,40 +50,40 @@ class TestSequenceField(MongoDBTestCase):
id = SequenceField(primary_key=True, value_decorator=str)
name = StringField()
self.db['mongoengine.counters'].drop()
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(), "11")
self.db["mongoengine.counters"].drop()
self.assertEqual(Person.id.get_next_value(), '1')
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')
id = SequenceField(primary_key=True, sequence_name="jelly")
name = StringField()
self.db['mongoengine.counters'].drop()
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)
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)
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)
c = self.db["mongoengine.counters"].find_one({"_id": "jelly.id"})
self.assertEqual(c["next"], 1000)
def test_multiple_sequence_fields(self):
class Person(Document):
@ -91,14 +91,14 @@ class TestSequenceField(MongoDBTestCase):
counter = SequenceField()
name = StringField()
self.db['mongoengine.counters'].drop()
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)
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))
@ -106,23 +106,23 @@ class TestSequenceField(MongoDBTestCase):
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)
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)
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)
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()
self.db["mongoengine.counters"].drop()
Animal.drop_collection()
a = Animal(name="Boi").save()
@ -151,7 +151,7 @@ class TestSequenceField(MongoDBTestCase):
id = SequenceField(primary_key=True)
name = StringField()
self.db['mongoengine.counters'].drop()
self.db["mongoengine.counters"].drop()
Animal.drop_collection()
Person.drop_collection()
@ -159,11 +159,11 @@ class TestSequenceField(MongoDBTestCase):
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": "person.id"})
self.assertEqual(c["next"], 10)
c = self.db['mongoengine.counters'].find_one({'_id': 'animal.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))
@ -171,32 +171,32 @@ class TestSequenceField(MongoDBTestCase):
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": "person.id"})
self.assertEqual(c["next"], 10)
c = self.db['mongoengine.counters'].find_one({'_id': 'animal.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()
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)
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)
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
self.assertEqual(c["next"], 10)
def test_embedded_sequence_field(self):
class Comment(EmbeddedDocument):
@ -207,14 +207,18 @@ class TestSequenceField(MongoDBTestCase):
title = StringField(required=True)
comments = ListField(EmbeddedDocumentField(Comment))
self.db['mongoengine.counters'].drop()
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(
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)
@ -223,7 +227,7 @@ class TestSequenceField(MongoDBTestCase):
class Base(Document):
name = StringField()
counter = SequenceField()
meta = {'abstract': True}
meta = {"abstract": True}
class Foo(Base):
pass
@ -231,24 +235,27 @@ class TestSequenceField(MongoDBTestCase):
class Bar(Base):
pass
bar = Bar(name='Bar')
bar = Bar(name="Bar")
bar.save()
foo = Foo(name='Foo')
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.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)
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}
meta = {"abstract": True}
class Foo(Base):
counter = SequenceField()
@ -256,16 +263,19 @@ class TestSequenceField(MongoDBTestCase):
class Bar(Base):
counter = SequenceField()
bar = Bar(name='Bar')
bar = Bar(name="Bar")
bar.save()
foo = Foo(name='Foo')
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.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)
self.assertEqual(foo._fields["counter"].owner_document, Foo)
self.assertEqual(bar._fields["counter"].owner_document, Bar)

View File

@ -5,49 +5,53 @@ 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'
link.url = "google"
self.assertRaises(ValidationError, link.validate)
link.url = 'http://www.google.com:8080'
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'
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'])")
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'])
url = URLField(schemes=["ws", "irc"])
link = Link()
link.url = 'ws://google.com'
link.url = "ws://google.com"
self.assertRaises(ValidationError, link.validate)
scheme_link = SchemeLink()
scheme_link.url = 'ws://google.com'
scheme_link.url = "ws://google.com"
scheme_link.validate()
def test_underscore_allowed_in_domains_names(self):
@ -55,5 +59,5 @@ class TestURLField(MongoDBTestCase):
url = URLField()
link = Link()
link.url = 'https://san_leandro-ca.geebo.com'
link.url = "https://san_leandro-ca.geebo.com"
link.validate()

View File

@ -15,11 +15,8 @@ class TestUUIDField(MongoDBTestCase):
uid = uuid.uuid4()
person = Person(api_key=uid).save()
self.assertEqual(
get_as_pymongo(person),
{'_id': person.id,
'api_key': str(uid)
}
)
get_as_pymongo(person), {"_id": person.id, "api_key": str(uid)}
)
def test_field_string(self):
"""Test UUID fields storing as String
@ -37,8 +34,10 @@ class TestUUIDField(MongoDBTestCase):
person.api_key = api_key
person.validate()
invalid = ('9d159858-549b-4975-9f98-dd2f987c113g',
'9d159858-549b-4975-9f98-dd2f987c113')
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)
@ -58,8 +57,10 @@ class TestUUIDField(MongoDBTestCase):
person.api_key = api_key
person.validate()
invalid = ('9d159858-549b-4975-9f98-dd2f987c113g',
'9d159858-549b-4975-9f98-dd2f987c113')
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

@ -11,7 +11,7 @@ class PickleEmbedded(EmbeddedDocument):
class PickleTest(Document):
number = IntField()
string = StringField(choices=(('One', '1'), ('Two', '2')))
string = StringField(choices=(("One", "1"), ("Two", "2")))
embedded = EmbeddedDocumentField(PickleEmbedded)
lists = ListField(StringField())
photo = FileField()
@ -19,7 +19,7 @@ class PickleTest(Document):
class NewDocumentPickleTest(Document):
number = IntField()
string = StringField(choices=(('One', '1'), ('Two', '2')))
string = StringField(choices=(("One", "1"), ("Two", "2")))
embedded = EmbeddedDocumentField(PickleEmbedded)
lists = ListField(StringField())
photo = FileField()
@ -36,7 +36,7 @@ class PickleDynamicTest(DynamicDocument):
class PickleSignalsTest(Document):
number = IntField()
string = StringField(choices=(('One', '1'), ('Two', '2')))
string = StringField(choices=(("One", "1"), ("Two", "2")))
embedded = EmbeddedDocumentField(PickleEmbedded)
lists = ListField(StringField())
@ -58,4 +58,4 @@ class Mixin(object):
class Base(Document):
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}

View File

@ -7,79 +7,78 @@ __all__ = ("QueryFieldListTest", "OnlyExcludeAllTest")
class QueryFieldListTest(unittest.TestCase):
def test_empty(self):
q = QueryFieldList()
self.assertFalse(q)
q = QueryFieldList(always_include=['_cls'])
q = QueryFieldList(always_include=["_cls"])
self.assertFalse(q)
def test_include_include(self):
q = QueryFieldList()
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.ONLY, _only_called=True)
self.assertEqual(q.as_dict(), {'a': 1, 'b': 1})
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {'a': 1, 'b': 1, 'c': 1})
q += QueryFieldList(
fields=["a", "b"], value=QueryFieldList.ONLY, _only_called=True
)
self.assertEqual(q.as_dict(), {"a": 1, "b": 1})
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {"a": 1, "b": 1, "c": 1})
def test_include_exclude(self):
q = QueryFieldList()
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {'a': 1, 'b': 1})
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {'a': 1})
q += QueryFieldList(fields=["a", "b"], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {"a": 1, "b": 1})
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {"a": 1})
def test_exclude_exclude(self):
q = QueryFieldList()
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {'a': 0, 'b': 0})
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {'a': 0, 'b': 0, 'c': 0})
q += QueryFieldList(fields=["a", "b"], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {"a": 0, "b": 0})
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {"a": 0, "b": 0, "c": 0})
def test_exclude_include(self):
q = QueryFieldList()
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {'a': 0, 'b': 0})
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {'c': 1})
q += QueryFieldList(fields=["a", "b"], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {"a": 0, "b": 0})
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {"c": 1})
def test_always_include(self):
q = QueryFieldList(always_include=['x', 'y'])
q += QueryFieldList(fields=['a', 'b', 'x'], value=QueryFieldList.EXCLUDE)
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {'x': 1, 'y': 1, 'c': 1})
q = QueryFieldList(always_include=["x", "y"])
q += QueryFieldList(fields=["a", "b", "x"], value=QueryFieldList.EXCLUDE)
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {"x": 1, "y": 1, "c": 1})
def test_reset(self):
q = QueryFieldList(always_include=['x', 'y'])
q += QueryFieldList(fields=['a', 'b', 'x'], value=QueryFieldList.EXCLUDE)
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {'x': 1, 'y': 1, 'c': 1})
q = QueryFieldList(always_include=["x", "y"])
q += QueryFieldList(fields=["a", "b", "x"], value=QueryFieldList.EXCLUDE)
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {"x": 1, "y": 1, "c": 1})
q.reset()
self.assertFalse(q)
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {'x': 1, 'y': 1, 'b': 1, 'c': 1})
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {"x": 1, "y": 1, "b": 1, "c": 1})
def test_using_a_slice(self):
q = QueryFieldList()
q += QueryFieldList(fields=['a'], value={"$slice": 5})
self.assertEqual(q.as_dict(), {'a': {"$slice": 5}})
q += QueryFieldList(fields=["a"], value={"$slice": 5})
self.assertEqual(q.as_dict(), {"a": {"$slice": 5}})
class OnlyExcludeAllTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
connect(db="mongoenginetest")
class Person(Document):
name = StringField()
age = IntField()
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
Person.drop_collection()
self.Person = Person
def test_mixing_only_exclude(self):
class MyDoc(Document):
a = StringField()
b = StringField()
@ -88,32 +87,32 @@ class OnlyExcludeAllTest(unittest.TestCase):
e = StringField()
f = StringField()
include = ['a', 'b', 'c', 'd', 'e']
exclude = ['d', 'e']
only = ['b', 'c']
include = ["a", "b", "c", "d", "e"]
exclude = ["d", "e"]
only = ["b", "c"]
qs = MyDoc.objects.fields(**{i: 1 for i in include})
self.assertEqual(qs._loaded_fields.as_dict(),
{'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1})
self.assertEqual(
qs._loaded_fields.as_dict(), {"a": 1, "b": 1, "c": 1, "d": 1, "e": 1}
)
qs = qs.only(*only)
self.assertEqual(qs._loaded_fields.as_dict(), {'b': 1, 'c': 1})
self.assertEqual(qs._loaded_fields.as_dict(), {"b": 1, "c": 1})
qs = qs.exclude(*exclude)
self.assertEqual(qs._loaded_fields.as_dict(), {'b': 1, 'c': 1})
self.assertEqual(qs._loaded_fields.as_dict(), {"b": 1, "c": 1})
qs = MyDoc.objects.fields(**{i: 1 for i in include})
qs = qs.exclude(*exclude)
self.assertEqual(qs._loaded_fields.as_dict(), {'a': 1, 'b': 1, 'c': 1})
self.assertEqual(qs._loaded_fields.as_dict(), {"a": 1, "b": 1, "c": 1})
qs = qs.only(*only)
self.assertEqual(qs._loaded_fields.as_dict(), {'b': 1, 'c': 1})
self.assertEqual(qs._loaded_fields.as_dict(), {"b": 1, "c": 1})
qs = MyDoc.objects.exclude(*exclude)
qs = qs.fields(**{i: 1 for i in include})
self.assertEqual(qs._loaded_fields.as_dict(), {'a': 1, 'b': 1, 'c': 1})
self.assertEqual(qs._loaded_fields.as_dict(), {"a": 1, "b": 1, "c": 1})
qs = qs.only(*only)
self.assertEqual(qs._loaded_fields.as_dict(), {'b': 1, 'c': 1})
self.assertEqual(qs._loaded_fields.as_dict(), {"b": 1, "c": 1})
def test_slicing(self):
class MyDoc(Document):
a = ListField()
b = ListField()
@ -122,24 +121,23 @@ class OnlyExcludeAllTest(unittest.TestCase):
e = ListField()
f = ListField()
include = ['a', 'b', 'c', 'd', 'e']
exclude = ['d', 'e']
only = ['b', 'c']
include = ["a", "b", "c", "d", "e"]
exclude = ["d", "e"]
only = ["b", "c"]
qs = MyDoc.objects.fields(**{i: 1 for i in include})
qs = qs.exclude(*exclude)
qs = qs.only(*only)
qs = qs.fields(slice__b=5)
self.assertEqual(qs._loaded_fields.as_dict(),
{'b': {'$slice': 5}, 'c': 1})
self.assertEqual(qs._loaded_fields.as_dict(), {"b": {"$slice": 5}, "c": 1})
qs = qs.fields(slice__c=[5, 1])
self.assertEqual(qs._loaded_fields.as_dict(),
{'b': {'$slice': 5}, 'c': {'$slice': [5, 1]}})
self.assertEqual(
qs._loaded_fields.as_dict(), {"b": {"$slice": 5}, "c": {"$slice": [5, 1]}}
)
qs = qs.exclude('c')
self.assertEqual(qs._loaded_fields.as_dict(),
{'b': {'$slice': 5}})
qs = qs.exclude("c")
self.assertEqual(qs._loaded_fields.as_dict(), {"b": {"$slice": 5}})
def test_mix_slice_with_other_fields(self):
class MyDoc(Document):
@ -148,43 +146,42 @@ class OnlyExcludeAllTest(unittest.TestCase):
c = ListField()
qs = MyDoc.objects.fields(a=1, b=0, slice__c=2)
self.assertEqual(qs._loaded_fields.as_dict(),
{'c': {'$slice': 2}, 'a': 1})
self.assertEqual(qs._loaded_fields.as_dict(), {"c": {"$slice": 2}, "a": 1})
def test_only(self):
"""Ensure that QuerySet.only only returns the requested fields.
"""
person = self.Person(name='test', age=25)
person = self.Person(name="test", age=25)
person.save()
obj = self.Person.objects.only('name').get()
obj = self.Person.objects.only("name").get()
self.assertEqual(obj.name, person.name)
self.assertEqual(obj.age, None)
obj = self.Person.objects.only('age').get()
obj = self.Person.objects.only("age").get()
self.assertEqual(obj.name, None)
self.assertEqual(obj.age, person.age)
obj = self.Person.objects.only('name', 'age').get()
obj = self.Person.objects.only("name", "age").get()
self.assertEqual(obj.name, person.name)
self.assertEqual(obj.age, person.age)
obj = self.Person.objects.only(*('id', 'name',)).get()
obj = self.Person.objects.only(*("id", "name")).get()
self.assertEqual(obj.name, person.name)
self.assertEqual(obj.age, None)
# Check polymorphism still works
class Employee(self.Person):
salary = IntField(db_field='wage')
salary = IntField(db_field="wage")
employee = Employee(name='test employee', age=40, salary=30000)
employee = Employee(name="test employee", age=40, salary=30000)
employee.save()
obj = self.Person.objects(id=employee.id).only('age').get()
obj = self.Person.objects(id=employee.id).only("age").get()
self.assertIsInstance(obj, Employee)
# Check field names are looked up properly
obj = Employee.objects(id=employee.id).only('salary').get()
obj = Employee.objects(id=employee.id).only("salary").get()
self.assertEqual(obj.salary, employee.salary)
self.assertEqual(obj.name, None)
@ -208,35 +205,41 @@ class OnlyExcludeAllTest(unittest.TestCase):
BlogPost.drop_collection()
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 = 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()
obj = BlogPost.objects.only('author.name',).get()
obj = BlogPost.objects.only("author.name").get()
self.assertEqual(obj.content, None)
self.assertEqual(obj.author.email, None)
self.assertEqual(obj.author.name, 'Test User')
self.assertEqual(obj.author.name, "Test User")
self.assertEqual(obj.comments, [])
obj = BlogPost.objects.only('various.test_dynamic.some').get()
obj = BlogPost.objects.only("various.test_dynamic.some").get()
self.assertEqual(obj.various["test_dynamic"].some, True)
obj = BlogPost.objects.only('content', 'comments.title',).get()
self.assertEqual(obj.content, 'Had a good coffee today...')
obj = BlogPost.objects.only("content", "comments.title").get()
self.assertEqual(obj.content, "Had a good coffee today...")
self.assertEqual(obj.author, None)
self.assertEqual(obj.comments[0].title, 'I aggree')
self.assertEqual(obj.comments[1].title, 'Coffee')
self.assertEqual(obj.comments[0].title, "I aggree")
self.assertEqual(obj.comments[1].title, "Coffee")
self.assertEqual(obj.comments[0].text, None)
self.assertEqual(obj.comments[1].text, None)
obj = BlogPost.objects.only('comments',).get()
obj = BlogPost.objects.only("comments").get()
self.assertEqual(obj.content, None)
self.assertEqual(obj.author, None)
self.assertEqual(obj.comments[0].title, 'I aggree')
self.assertEqual(obj.comments[1].title, 'Coffee')
self.assertEqual(obj.comments[0].text, 'Great post!')
self.assertEqual(obj.comments[1].text, 'I hate coffee')
self.assertEqual(obj.comments[0].title, "I aggree")
self.assertEqual(obj.comments[1].title, "Coffee")
self.assertEqual(obj.comments[0].text, "Great post!")
self.assertEqual(obj.comments[1].text, "I hate coffee")
BlogPost.drop_collection()
@ -256,15 +259,18 @@ class OnlyExcludeAllTest(unittest.TestCase):
BlogPost.drop_collection()
post = BlogPost(content='Had a good coffee today...')
post.author = User(name='Test User')
post.comments = [Comment(title='I aggree', text='Great post!'), Comment(title='Coffee', text='I hate coffee')]
post = BlogPost(content="Had a good coffee today...")
post.author = User(name="Test User")
post.comments = [
Comment(title="I aggree", text="Great post!"),
Comment(title="Coffee", text="I hate coffee"),
]
post.save()
obj = BlogPost.objects.exclude('author', 'comments.text').get()
obj = BlogPost.objects.exclude("author", "comments.text").get()
self.assertEqual(obj.author, None)
self.assertEqual(obj.content, 'Had a good coffee today...')
self.assertEqual(obj.comments[0].title, 'I aggree')
self.assertEqual(obj.content, "Had a good coffee today...")
self.assertEqual(obj.comments[0].title, "I aggree")
self.assertEqual(obj.comments[0].text, None)
BlogPost.drop_collection()
@ -283,32 +289,43 @@ class OnlyExcludeAllTest(unittest.TestCase):
attachments = ListField(EmbeddedDocumentField(Attachment))
Email.drop_collection()
email = Email(sender='me', to='you', subject='From Russia with Love', body='Hello!', content_type='text/plain')
email = Email(
sender="me",
to="you",
subject="From Russia with Love",
body="Hello!",
content_type="text/plain",
)
email.attachments = [
Attachment(name='file1.doc', content='ABC'),
Attachment(name='file2.doc', content='XYZ'),
Attachment(name="file1.doc", content="ABC"),
Attachment(name="file2.doc", content="XYZ"),
]
email.save()
obj = Email.objects.exclude('content_type').exclude('body').get()
self.assertEqual(obj.sender, 'me')
self.assertEqual(obj.to, 'you')
self.assertEqual(obj.subject, 'From Russia with Love')
obj = Email.objects.exclude("content_type").exclude("body").get()
self.assertEqual(obj.sender, "me")
self.assertEqual(obj.to, "you")
self.assertEqual(obj.subject, "From Russia with Love")
self.assertEqual(obj.body, None)
self.assertEqual(obj.content_type, None)
obj = Email.objects.only('sender', 'to').exclude('body', 'sender').get()
obj = Email.objects.only("sender", "to").exclude("body", "sender").get()
self.assertEqual(obj.sender, None)
self.assertEqual(obj.to, 'you')
self.assertEqual(obj.to, "you")
self.assertEqual(obj.subject, None)
self.assertEqual(obj.body, None)
self.assertEqual(obj.content_type, None)
obj = Email.objects.exclude('attachments.content').exclude('body').only('to', 'attachments.name').get()
self.assertEqual(obj.attachments[0].name, 'file1.doc')
obj = (
Email.objects.exclude("attachments.content")
.exclude("body")
.only("to", "attachments.name")
.get()
)
self.assertEqual(obj.attachments[0].name, "file1.doc")
self.assertEqual(obj.attachments[0].content, None)
self.assertEqual(obj.sender, None)
self.assertEqual(obj.to, 'you')
self.assertEqual(obj.to, "you")
self.assertEqual(obj.subject, None)
self.assertEqual(obj.body, None)
self.assertEqual(obj.content_type, None)
@ -316,7 +333,6 @@ class OnlyExcludeAllTest(unittest.TestCase):
Email.drop_collection()
def test_all_fields(self):
class Email(Document):
sender = StringField()
to = StringField()
@ -326,21 +342,33 @@ class OnlyExcludeAllTest(unittest.TestCase):
Email.drop_collection()
email = Email(sender='me', to='you', subject='From Russia with Love', body='Hello!', content_type='text/plain')
email = Email(
sender="me",
to="you",
subject="From Russia with Love",
body="Hello!",
content_type="text/plain",
)
email.save()
obj = Email.objects.exclude('content_type', 'body').only('to', 'body').all_fields().get()
self.assertEqual(obj.sender, 'me')
self.assertEqual(obj.to, 'you')
self.assertEqual(obj.subject, 'From Russia with Love')
self.assertEqual(obj.body, 'Hello!')
self.assertEqual(obj.content_type, 'text/plain')
obj = (
Email.objects.exclude("content_type", "body")
.only("to", "body")
.all_fields()
.get()
)
self.assertEqual(obj.sender, "me")
self.assertEqual(obj.to, "you")
self.assertEqual(obj.subject, "From Russia with Love")
self.assertEqual(obj.body, "Hello!")
self.assertEqual(obj.content_type, "text/plain")
Email.drop_collection()
def test_slicing_fields(self):
"""Ensure that query slicing an array works.
"""
class Numbers(Document):
n = ListField(IntField())
@ -414,11 +442,10 @@ class OnlyExcludeAllTest(unittest.TestCase):
self.assertEqual(numbers.embedded.n, [-5, -4, -3, -2, -1])
def test_exclude_from_subclasses_docs(self):
class Base(Document):
username = StringField()
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
class Anon(Base):
anon = BooleanField()
@ -436,5 +463,5 @@ class OnlyExcludeAllTest(unittest.TestCase):
self.assertRaises(LookUpError, Base.objects.exclude, "made_up")
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -10,9 +10,9 @@ __all__ = ("GeoQueriesTest",)
class GeoQueriesTest(MongoDBTestCase):
def _create_event_data(self, point_field_class=GeoPointField):
"""Create some sample data re-used in many of the tests below."""
class Event(Document):
title = StringField()
date = DateTimeField()
@ -28,15 +28,18 @@ class GeoQueriesTest(MongoDBTestCase):
event1 = Event.objects.create(
title="Coltrane Motion @ Double Door",
date=datetime.datetime.now() - datetime.timedelta(days=1),
location=[-87.677137, 41.909889])
location=[-87.677137, 41.909889],
)
event2 = Event.objects.create(
title="Coltrane Motion @ Bottom of the Hill",
date=datetime.datetime.now() - datetime.timedelta(days=10),
location=[-122.4194155, 37.7749295])
location=[-122.4194155, 37.7749295],
)
event3 = Event.objects.create(
title="Coltrane Motion @ Empty Bottle",
date=datetime.datetime.now(),
location=[-87.686638, 41.900474])
location=[-87.686638, 41.900474],
)
return event1, event2, event3
@ -65,8 +68,7 @@ class GeoQueriesTest(MongoDBTestCase):
# find events within 10 degrees of san francisco
point = [-122.415579, 37.7566023]
events = self.Event.objects(location__near=point,
location__max_distance=10)
events = self.Event.objects(location__near=point, location__max_distance=10)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
@ -78,8 +80,7 @@ class GeoQueriesTest(MongoDBTestCase):
# find events at least 10 degrees away of san francisco
point = [-122.415579, 37.7566023]
events = self.Event.objects(location__near=point,
location__min_distance=10)
events = self.Event.objects(location__near=point, location__min_distance=10)
self.assertEqual(events.count(), 2)
def test_within_distance(self):
@ -88,8 +89,7 @@ class GeoQueriesTest(MongoDBTestCase):
# find events within 5 degrees of pitchfork office, chicago
point_and_distance = [[-87.67892, 41.9120459], 5]
events = self.Event.objects(
location__within_distance=point_and_distance)
events = self.Event.objects(location__within_distance=point_and_distance)
self.assertEqual(events.count(), 2)
events = list(events)
self.assertNotIn(event2, events)
@ -98,21 +98,18 @@ class GeoQueriesTest(MongoDBTestCase):
# find events within 10 degrees of san francisco
point_and_distance = [[-122.415579, 37.7566023], 10]
events = self.Event.objects(
location__within_distance=point_and_distance)
events = self.Event.objects(location__within_distance=point_and_distance)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
# find events within 1 degree of greenpoint, broolyn, nyc, ny
point_and_distance = [[-73.9509714, 40.7237134], 1]
events = self.Event.objects(
location__within_distance=point_and_distance)
events = self.Event.objects(location__within_distance=point_and_distance)
self.assertEqual(events.count(), 0)
# ensure ordering is respected by "within_distance"
point_and_distance = [[-87.67892, 41.9120459], 10]
events = self.Event.objects(
location__within_distance=point_and_distance)
events = self.Event.objects(location__within_distance=point_and_distance)
events = events.order_by("-date")
self.assertEqual(events.count(), 2)
self.assertEqual(events[0], event3)
@ -145,7 +142,7 @@ class GeoQueriesTest(MongoDBTestCase):
polygon2 = [
(-1.742249, 54.033586),
(-1.225891, 52.792797),
(-4.40094, 53.389881)
(-4.40094, 53.389881),
]
events = self.Event.objects(location__within_polygon=polygon2)
self.assertEqual(events.count(), 0)
@ -154,9 +151,7 @@ class GeoQueriesTest(MongoDBTestCase):
"""Make sure the "near" operator works with a PointField, which
corresponds to a 2dsphere index.
"""
event1, event2, event3 = self._create_event_data(
point_field_class=PointField
)
event1, event2, event3 = self._create_event_data(point_field_class=PointField)
# find all events "near" pitchfork office, chicago.
# note that "near" will show the san francisco event, too,
@ -175,26 +170,23 @@ class GeoQueriesTest(MongoDBTestCase):
"""Ensure the "max_distance" operator works alongside the "near"
operator with a 2dsphere index.
"""
event1, event2, event3 = self._create_event_data(
point_field_class=PointField
)
event1, event2, event3 = self._create_event_data(point_field_class=PointField)
# find events within 10km of san francisco
point = [-122.415579, 37.7566023]
events = self.Event.objects(location__near=point,
location__max_distance=10000)
events = self.Event.objects(location__near=point, location__max_distance=10000)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
# find events within 1km of greenpoint, broolyn, nyc, ny
events = self.Event.objects(location__near=[-73.9509714, 40.7237134],
location__max_distance=1000)
events = self.Event.objects(
location__near=[-73.9509714, 40.7237134], location__max_distance=1000
)
self.assertEqual(events.count(), 0)
# ensure ordering is respected by "near"
events = self.Event.objects(
location__near=[-87.67892, 41.9120459],
location__max_distance=10000
location__near=[-87.67892, 41.9120459], location__max_distance=10000
).order_by("-date")
self.assertEqual(events.count(), 2)
self.assertEqual(events[0], event3)
@ -203,9 +195,7 @@ class GeoQueriesTest(MongoDBTestCase):
"""Ensure the "geo_within_box" operator works with a 2dsphere
index.
"""
event1, event2, event3 = self._create_event_data(
point_field_class=PointField
)
event1, event2, event3 = self._create_event_data(point_field_class=PointField)
# check that within_box works
box = [(-125.0, 35.0), (-100.0, 40.0)]
@ -217,9 +207,7 @@ class GeoQueriesTest(MongoDBTestCase):
"""Ensure the "geo_within_polygon" operator works with a
2dsphere index.
"""
event1, event2, event3 = self._create_event_data(
point_field_class=PointField
)
event1, event2, event3 = self._create_event_data(point_field_class=PointField)
polygon = [
(-87.694445, 41.912114),
@ -235,7 +223,7 @@ class GeoQueriesTest(MongoDBTestCase):
polygon2 = [
(-1.742249, 54.033586),
(-1.225891, 52.792797),
(-4.40094, 53.389881)
(-4.40094, 53.389881),
]
events = self.Event.objects(location__geo_within_polygon=polygon2)
self.assertEqual(events.count(), 0)
@ -244,23 +232,20 @@ class GeoQueriesTest(MongoDBTestCase):
"""Ensure "min_distace" and "max_distance" operators work well
together with the "near" operator in a 2dsphere index.
"""
event1, event2, event3 = self._create_event_data(
point_field_class=PointField
)
event1, event2, event3 = self._create_event_data(point_field_class=PointField)
# ensure min_distance and max_distance combine well
events = self.Event.objects(
location__near=[-87.67892, 41.9120459],
location__min_distance=1000,
location__max_distance=10000
location__max_distance=10000,
).order_by("-date")
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event3)
# ensure ordering is respected by "near" with "min_distance"
events = self.Event.objects(
location__near=[-87.67892, 41.9120459],
location__min_distance=10000
location__near=[-87.67892, 41.9120459], location__min_distance=10000
).order_by("-date")
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
@ -269,14 +254,11 @@ class GeoQueriesTest(MongoDBTestCase):
"""Make sure the "geo_within_center" operator works with a
2dsphere index.
"""
event1, event2, event3 = self._create_event_data(
point_field_class=PointField
)
event1, event2, event3 = self._create_event_data(point_field_class=PointField)
# find events within 5 degrees of pitchfork office, chicago
point_and_distance = [[-87.67892, 41.9120459], 2]
events = self.Event.objects(
location__geo_within_center=point_and_distance)
events = self.Event.objects(location__geo_within_center=point_and_distance)
self.assertEqual(events.count(), 2)
events = list(events)
self.assertNotIn(event2, events)
@ -287,6 +269,7 @@ class GeoQueriesTest(MongoDBTestCase):
"""Helper test method ensuring given point field class works
well in an embedded document.
"""
class Venue(EmbeddedDocument):
location = point_field_class()
name = StringField()
@ -300,12 +283,11 @@ class GeoQueriesTest(MongoDBTestCase):
venue1 = Venue(name="The Rock", location=[-87.677137, 41.909889])
venue2 = Venue(name="The Bridge", location=[-122.4194155, 37.7749295])
event1 = Event(title="Coltrane Motion @ Double Door",
venue=venue1).save()
event2 = Event(title="Coltrane Motion @ Bottom of the Hill",
venue=venue2).save()
event3 = Event(title="Coltrane Motion @ Empty Bottle",
venue=venue1).save()
event1 = Event(title="Coltrane Motion @ Double Door", venue=venue1).save()
event2 = Event(
title="Coltrane Motion @ Bottom of the Hill", venue=venue2
).save()
event3 = Event(title="Coltrane Motion @ Empty Bottle", venue=venue1).save()
# find all events "near" pitchfork office, chicago.
# note that "near" will show the san francisco event, too,
@ -324,6 +306,7 @@ class GeoQueriesTest(MongoDBTestCase):
def test_spherical_geospatial_operators(self):
"""Ensure that spherical geospatial queries are working."""
class Point(Document):
location = GeoPointField()
@ -343,26 +326,26 @@ class GeoQueriesTest(MongoDBTestCase):
# Same behavior for _within_spherical_distance
points = Point.objects(
location__within_spherical_distance=[
[-122, 37.5],
60 / earth_radius
]
location__within_spherical_distance=[[-122, 37.5], 60 / earth_radius]
)
self.assertEqual(points.count(), 2)
points = Point.objects(location__near_sphere=[-122, 37.5],
location__max_distance=60 / earth_radius)
points = Point.objects(
location__near_sphere=[-122, 37.5], location__max_distance=60 / earth_radius
)
self.assertEqual(points.count(), 2)
# Test query works with max_distance, being farer from one point
points = Point.objects(location__near_sphere=[-122, 37.8],
location__max_distance=60 / earth_radius)
points = Point.objects(
location__near_sphere=[-122, 37.8], location__max_distance=60 / earth_radius
)
close_point = points.first()
self.assertEqual(points.count(), 1)
# Test query works with min_distance, being farer from one point
points = Point.objects(location__near_sphere=[-122, 37.8],
location__min_distance=60 / earth_radius)
points = Point.objects(
location__near_sphere=[-122, 37.8], location__min_distance=60 / earth_radius
)
self.assertEqual(points.count(), 1)
far_point = points.first()
self.assertNotEqual(close_point, far_point)
@ -384,10 +367,7 @@ class GeoQueriesTest(MongoDBTestCase):
# Finds only one point because only the first point is within 60km of
# the reference point to the south.
points = Point.objects(
location__within_spherical_distance=[
[-122, 36.5],
60 / earth_radius
]
location__within_spherical_distance=[[-122, 36.5], 60 / earth_radius]
)
self.assertEqual(points.count(), 1)
self.assertEqual(points[0].id, south_point.id)
@ -413,8 +393,10 @@ class GeoQueriesTest(MongoDBTestCase):
self.assertEqual(1, roads)
# Within
polygon = {"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]}
polygon = {
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]],
}
roads = Road.objects.filter(line__geo_within=polygon["coordinates"]).count()
self.assertEqual(1, roads)
@ -425,8 +407,7 @@ class GeoQueriesTest(MongoDBTestCase):
self.assertEqual(1, roads)
# Intersects
line = {"type": "LineString",
"coordinates": [[40, 5], [40, 6]]}
line = {"type": "LineString", "coordinates": [[40, 5], [40, 6]]}
roads = Road.objects.filter(line__geo_intersects=line["coordinates"]).count()
self.assertEqual(1, roads)
@ -436,8 +417,10 @@ class GeoQueriesTest(MongoDBTestCase):
roads = Road.objects.filter(line__geo_intersects={"$geometry": line}).count()
self.assertEqual(1, roads)
polygon = {"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]}
polygon = {
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]],
}
roads = Road.objects.filter(line__geo_intersects=polygon["coordinates"]).count()
self.assertEqual(1, roads)
@ -468,8 +451,10 @@ class GeoQueriesTest(MongoDBTestCase):
self.assertEqual(1, roads)
# Within
polygon = {"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]}
polygon = {
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]],
}
roads = Road.objects.filter(poly__geo_within=polygon["coordinates"]).count()
self.assertEqual(1, roads)
@ -480,8 +465,7 @@ class GeoQueriesTest(MongoDBTestCase):
self.assertEqual(1, roads)
# Intersects
line = {"type": "LineString",
"coordinates": [[40, 5], [41, 6]]}
line = {"type": "LineString", "coordinates": [[40, 5], [41, 6]]}
roads = Road.objects.filter(poly__geo_intersects=line["coordinates"]).count()
self.assertEqual(1, roads)
@ -491,8 +475,10 @@ class GeoQueriesTest(MongoDBTestCase):
roads = Road.objects.filter(poly__geo_intersects={"$geometry": line}).count()
self.assertEqual(1, roads)
polygon = {"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]}
polygon = {
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]],
}
roads = Road.objects.filter(poly__geo_intersects=polygon["coordinates"]).count()
self.assertEqual(1, roads)
@ -504,20 +490,20 @@ class GeoQueriesTest(MongoDBTestCase):
def test_aspymongo_with_only(self):
"""Ensure as_pymongo works with only"""
class Place(Document):
location = PointField()
Place.drop_collection()
p = Place(location=[24.946861267089844, 60.16311983618494])
p.save()
qs = Place.objects().only('location')
qs = Place.objects().only("location")
self.assertDictEqual(
qs.as_pymongo()[0]['location'],
{u'type': u'Point',
u'coordinates': [
24.946861267089844,
60.16311983618494]
}
qs.as_pymongo()[0]["location"],
{
u"type": u"Point",
u"coordinates": [24.946861267089844, 60.16311983618494],
},
)
def test_2dsphere_point_sets_correctly(self):
@ -542,11 +528,15 @@ class GeoQueriesTest(MongoDBTestCase):
Location(line=[[1, 2], [2, 2]]).save()
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["line"], {"type": "LineString", "coordinates": [[1, 2], [2, 2]]})
self.assertEqual(
loc["line"], {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}
)
Location.objects.update(set__line=[[2, 1], [1, 2]])
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["line"], {"type": "LineString", "coordinates": [[2, 1], [1, 2]]})
self.assertEqual(
loc["line"], {"type": "LineString", "coordinates": [[2, 1], [1, 2]]}
)
def test_geojson_PolygonField(self):
class Location(Document):
@ -556,12 +546,18 @@ class GeoQueriesTest(MongoDBTestCase):
Location(poly=[[[40, 5], [40, 6], [41, 6], [40, 5]]]).save()
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["poly"], {"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]})
self.assertEqual(
loc["poly"],
{"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]},
)
Location.objects.update(set__poly=[[[40, 4], [40, 6], [41, 6], [40, 4]]])
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["poly"], {"type": "Polygon", "coordinates": [[[40, 4], [40, 6], [41, 6], [40, 4]]]})
self.assertEqual(
loc["poly"],
{"type": "Polygon", "coordinates": [[[40, 4], [40, 6], [41, 6], [40, 4]]]},
)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -11,7 +11,6 @@ class Doc(Document):
class FindAndModifyTest(unittest.TestCase):
def setUp(self):
connect(db="mongoenginetest")
Doc.drop_collection()
@ -82,9 +81,14 @@ class FindAndModifyTest(unittest.TestCase):
old_doc = Doc.objects().order_by("-id").modify(set__value=-1)
self.assertEqual(old_doc.to_json(), doc.to_json())
self.assertDbEqual([
{"_id": 0, "value": 3}, {"_id": 1, "value": 2},
{"_id": 2, "value": 1}, {"_id": 3, "value": -1}])
self.assertDbEqual(
[
{"_id": 0, "value": 3},
{"_id": 1, "value": 2},
{"_id": 2, "value": 1},
{"_id": 3, "value": -1},
]
)
def test_modify_with_fields(self):
Doc(id=0, value=0).save()
@ -103,27 +107,25 @@ class FindAndModifyTest(unittest.TestCase):
blog = BlogPost.objects.create()
# Push a new tag via modify with new=False (default).
BlogPost(id=blog.id).modify(push__tags='code')
BlogPost(id=blog.id).modify(push__tags="code")
self.assertEqual(blog.tags, [])
blog.reload()
self.assertEqual(blog.tags, ['code'])
self.assertEqual(blog.tags, ["code"])
# Push a new tag via modify with new=True.
blog = BlogPost.objects(id=blog.id).modify(push__tags='java', new=True)
self.assertEqual(blog.tags, ['code', 'java'])
blog = BlogPost.objects(id=blog.id).modify(push__tags="java", new=True)
self.assertEqual(blog.tags, ["code", "java"])
# Push a new tag with a positional argument.
blog = BlogPost.objects(id=blog.id).modify(
push__tags__0='python',
new=True)
self.assertEqual(blog.tags, ['python', 'code', 'java'])
blog = BlogPost.objects(id=blog.id).modify(push__tags__0="python", new=True)
self.assertEqual(blog.tags, ["python", "code", "java"])
# Push multiple new tags with a positional argument.
blog = BlogPost.objects(id=blog.id).modify(
push__tags__1=['go', 'rust'],
new=True)
self.assertEqual(blog.tags, ['python', 'go', 'rust', 'code', 'java'])
push__tags__1=["go", "rust"], new=True
)
self.assertEqual(blog.tags, ["python", "go", "rust", "code", "java"])
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -4,7 +4,7 @@ from pymongo.mongo_client import MongoClient
from mongoengine import Document, StringField, IntField
from mongoengine.connection import connect
__author__ = 'stas'
__author__ = "stas"
class Person(Document):
@ -17,6 +17,7 @@ class TestQuerysetPickable(unittest.TestCase):
Test for adding pickling support for QuerySet instances
See issue https://github.com/MongoEngine/mongoengine/issues/442
"""
def setUp(self):
super(TestQuerysetPickable, self).setUp()
@ -24,10 +25,7 @@ class TestQuerysetPickable(unittest.TestCase):
connection.drop_database("test")
self.john = Person.objects.create(
name="John",
age=21
)
self.john = Person.objects.create(name="John", age=21)
def test_picke_simple_qs(self):
@ -54,15 +52,9 @@ class TestQuerysetPickable(unittest.TestCase):
self.assertEqual(Person.objects.first().age, 23)
def test_pickle_support_filtration(self):
Person.objects.create(
name="Alice",
age=22
)
Person.objects.create(name="Alice", age=22)
Person.objects.create(
name="Bob",
age=23
)
Person.objects.create(name="Bob", age=23)
qs = Person.objects.filter(age__gte=22)
self.assertEqual(qs.count(), 2)
@ -71,9 +63,3 @@ class TestQuerysetPickable(unittest.TestCase):
self.assertEqual(loaded.count(), 2)
self.assertEqual(loaded.filter(name="Bob").first().age, 23)

File diff suppressed because it is too large Load Diff

View File

@ -9,25 +9,29 @@ __all__ = ("TransformTest",)
class TransformTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
connect(db="mongoenginetest")
def test_transform_query(self):
"""Ensure that the _transform_query function operates correctly.
"""
self.assertEqual(transform.query(name='test', age=30),
{'name': 'test', 'age': 30})
self.assertEqual(transform.query(age__lt=30),
{'age': {'$lt': 30}})
self.assertEqual(transform.query(age__gt=20, age__lt=50),
{'age': {'$gt': 20, '$lt': 50}})
self.assertEqual(transform.query(age=20, age__gt=50),
{'$and': [{'age': {'$gt': 50}}, {'age': 20}]})
self.assertEqual(transform.query(friend__age__gte=30),
{'friend.age': {'$gte': 30}})
self.assertEqual(transform.query(name__exists=True),
{'name': {'$exists': True}})
self.assertEqual(
transform.query(name="test", age=30), {"name": "test", "age": 30}
)
self.assertEqual(transform.query(age__lt=30), {"age": {"$lt": 30}})
self.assertEqual(
transform.query(age__gt=20, age__lt=50), {"age": {"$gt": 20, "$lt": 50}}
)
self.assertEqual(
transform.query(age=20, age__gt=50),
{"$and": [{"age": {"$gt": 50}}, {"age": 20}]},
)
self.assertEqual(
transform.query(friend__age__gte=30), {"friend.age": {"$gte": 30}}
)
self.assertEqual(
transform.query(name__exists=True), {"name": {"$exists": True}}
)
def test_transform_update(self):
class LisDoc(Document):
@ -46,7 +50,11 @@ class TransformTest(unittest.TestCase):
DicDoc().save()
doc = Doc().save()
for k, v in (("set", "$set"), ("set_on_insert", "$setOnInsert"), ("push", "$push")):
for k, v in (
("set", "$set"),
("set_on_insert", "$setOnInsert"),
("push", "$push"),
):
update = transform.update(DicDoc, **{"%s__dictField__test" % k: doc})
self.assertIsInstance(update[v]["dictField.test"], dict)
@ -57,55 +65,61 @@ class TransformTest(unittest.TestCase):
update = transform.update(DicDoc, pull__dictField__test=doc)
self.assertIsInstance(update["$pull"]["dictField"]["test"], dict)
update = transform.update(LisDoc, pull__foo__in=['a'])
self.assertEqual(update, {'$pull': {'foo': {'$in': ['a']}}})
update = transform.update(LisDoc, pull__foo__in=["a"])
self.assertEqual(update, {"$pull": {"foo": {"$in": ["a"]}}})
def test_transform_update_push(self):
"""Ensure the differences in behvaior between 'push' and 'push_all'"""
class BlogPost(Document):
tags = ListField(StringField())
update = transform.update(BlogPost, push__tags=['mongo', 'db'])
self.assertEqual(update, {'$push': {'tags': ['mongo', 'db']}})
update = transform.update(BlogPost, push__tags=["mongo", "db"])
self.assertEqual(update, {"$push": {"tags": ["mongo", "db"]}})
update = transform.update(BlogPost, push_all__tags=['mongo', 'db'])
self.assertEqual(update, {'$push': {'tags': {'$each': ['mongo', 'db']}}})
update = transform.update(BlogPost, push_all__tags=["mongo", "db"])
self.assertEqual(update, {"$push": {"tags": {"$each": ["mongo", "db"]}}})
def test_transform_update_no_operator_default_to_set(self):
"""Ensure the differences in behvaior between 'push' and 'push_all'"""
class BlogPost(Document):
tags = ListField(StringField())
update = transform.update(BlogPost, tags=['mongo', 'db'])
self.assertEqual(update, {'$set': {'tags': ['mongo', 'db']}})
update = transform.update(BlogPost, tags=["mongo", "db"])
self.assertEqual(update, {"$set": {"tags": ["mongo", "db"]}})
def test_query_field_name(self):
"""Ensure that the correct field name is used when querying.
"""
class Comment(EmbeddedDocument):
content = StringField(db_field='commentContent')
content = StringField(db_field="commentContent")
class BlogPost(Document):
title = StringField(db_field='postTitle')
comments = ListField(EmbeddedDocumentField(Comment),
db_field='postComments')
title = StringField(db_field="postTitle")
comments = ListField(
EmbeddedDocumentField(Comment), db_field="postComments"
)
BlogPost.drop_collection()
data = {'title': 'Post 1', 'comments': [Comment(content='test')]}
data = {"title": "Post 1", "comments": [Comment(content="test")]}
post = BlogPost(**data)
post.save()
self.assertIn('postTitle', BlogPost.objects(title=data['title'])._query)
self.assertFalse('title' in
BlogPost.objects(title=data['title'])._query)
self.assertEqual(BlogPost.objects(title=data['title']).count(), 1)
self.assertIn("postTitle", BlogPost.objects(title=data["title"])._query)
self.assertFalse("title" in BlogPost.objects(title=data["title"])._query)
self.assertEqual(BlogPost.objects(title=data["title"]).count(), 1)
self.assertIn('_id', BlogPost.objects(pk=post.id)._query)
self.assertIn("_id", BlogPost.objects(pk=post.id)._query)
self.assertEqual(BlogPost.objects(pk=post.id).count(), 1)
self.assertIn('postComments.commentContent', BlogPost.objects(comments__content='test')._query)
self.assertEqual(BlogPost.objects(comments__content='test').count(), 1)
self.assertIn(
"postComments.commentContent",
BlogPost.objects(comments__content="test")._query,
)
self.assertEqual(BlogPost.objects(comments__content="test").count(), 1)
BlogPost.drop_collection()
@ -113,18 +127,19 @@ class TransformTest(unittest.TestCase):
"""Ensure that the correct "primary key" field name is used when
querying
"""
class BlogPost(Document):
title = StringField(primary_key=True, db_field='postTitle')
title = StringField(primary_key=True, db_field="postTitle")
BlogPost.drop_collection()
data = {'title': 'Post 1'}
data = {"title": "Post 1"}
post = BlogPost(**data)
post.save()
self.assertIn('_id', BlogPost.objects(pk=data['title'])._query)
self.assertIn('_id', BlogPost.objects(title=data['title'])._query)
self.assertEqual(BlogPost.objects(pk=data['title']).count(), 1)
self.assertIn("_id", BlogPost.objects(pk=data["title"])._query)
self.assertIn("_id", BlogPost.objects(title=data["title"])._query)
self.assertEqual(BlogPost.objects(pk=data["title"]).count(), 1)
BlogPost.drop_collection()
@ -156,78 +171,125 @@ class TransformTest(unittest.TestCase):
"""
Test raw plays nicely
"""
class Foo(Document):
name = StringField()
a = StringField()
b = StringField()
c = StringField()
meta = {
'allow_inheritance': False
}
meta = {"allow_inheritance": False}
query = Foo.objects(__raw__={'$nor': [{'name': 'bar'}]})._query
self.assertEqual(query, {'$nor': [{'name': 'bar'}]})
query = Foo.objects(__raw__={"$nor": [{"name": "bar"}]})._query
self.assertEqual(query, {"$nor": [{"name": "bar"}]})
q1 = {'$or': [{'a': 1}, {'b': 1}]}
q1 = {"$or": [{"a": 1}, {"b": 1}]}
query = Foo.objects(Q(__raw__=q1) & Q(c=1))._query
self.assertEqual(query, {'$or': [{'a': 1}, {'b': 1}], 'c': 1})
self.assertEqual(query, {"$or": [{"a": 1}, {"b": 1}], "c": 1})
def test_raw_and_merging(self):
class Doc(Document):
meta = {'allow_inheritance': False}
meta = {"allow_inheritance": False}
raw_query = Doc.objects(__raw__={
'deleted': False,
'scraped': 'yes',
'$nor': [
{'views.extracted': 'no'},
{'attachments.views.extracted': 'no'}
]
})._query
raw_query = Doc.objects(
__raw__={
"deleted": False,
"scraped": "yes",
"$nor": [
{"views.extracted": "no"},
{"attachments.views.extracted": "no"},
],
}
)._query
self.assertEqual(raw_query, {
'deleted': False,
'scraped': 'yes',
'$nor': [
{'views.extracted': 'no'},
{'attachments.views.extracted': 'no'}
]
})
self.assertEqual(
raw_query,
{
"deleted": False,
"scraped": "yes",
"$nor": [
{"views.extracted": "no"},
{"attachments.views.extracted": "no"},
],
},
)
def test_geojson_PointField(self):
class Location(Document):
loc = PointField()
update = transform.update(Location, set__loc=[1, 2])
self.assertEqual(update, {'$set': {'loc': {"type": "Point", "coordinates": [1, 2]}}})
self.assertEqual(
update, {"$set": {"loc": {"type": "Point", "coordinates": [1, 2]}}}
)
update = transform.update(Location, set__loc={"type": "Point", "coordinates": [1, 2]})
self.assertEqual(update, {'$set': {'loc': {"type": "Point", "coordinates": [1, 2]}}})
update = transform.update(
Location, set__loc={"type": "Point", "coordinates": [1, 2]}
)
self.assertEqual(
update, {"$set": {"loc": {"type": "Point", "coordinates": [1, 2]}}}
)
def test_geojson_LineStringField(self):
class Location(Document):
line = LineStringField()
update = transform.update(Location, set__line=[[1, 2], [2, 2]])
self.assertEqual(update, {'$set': {'line': {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}})
self.assertEqual(
update,
{"$set": {"line": {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}},
)
update = transform.update(Location, set__line={"type": "LineString", "coordinates": [[1, 2], [2, 2]]})
self.assertEqual(update, {'$set': {'line': {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}})
update = transform.update(
Location, set__line={"type": "LineString", "coordinates": [[1, 2], [2, 2]]}
)
self.assertEqual(
update,
{"$set": {"line": {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}},
)
def test_geojson_PolygonField(self):
class Location(Document):
poly = PolygonField()
update = transform.update(Location, set__poly=[[[40, 5], [40, 6], [41, 6], [40, 5]]])
self.assertEqual(update, {'$set': {'poly': {"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]}}})
update = transform.update(
Location, set__poly=[[[40, 5], [40, 6], [41, 6], [40, 5]]]
)
self.assertEqual(
update,
{
"$set": {
"poly": {
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]],
}
}
},
)
update = transform.update(Location, set__poly={"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]})
self.assertEqual(update, {'$set': {'poly': {"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]}}})
update = transform.update(
Location,
set__poly={
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]],
},
)
self.assertEqual(
update,
{
"$set": {
"poly": {
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]],
}
}
},
)
def test_type(self):
class Doc(Document):
df = DynamicField()
Doc(df=True).save()
Doc(df=7).save()
Doc(df="df").save()
@ -252,7 +314,7 @@ class TransformTest(unittest.TestCase):
self.assertEqual(1, Doc.objects(item__type__="axe").count())
self.assertEqual(1, Doc.objects(item__name__="Heroic axe").count())
Doc.objects(id=doc.id).update(set__item__type__='sword')
Doc.objects(id=doc.id).update(set__item__type__="sword")
self.assertEqual(1, Doc.objects(item__type__="sword").count())
self.assertEqual(0, Doc.objects(item__type__="axe").count())
@ -272,6 +334,7 @@ class TransformTest(unittest.TestCase):
Test added to check pull operation in update for
EmbeddedDocumentListField which is inside a EmbeddedDocumentField
"""
class Word(EmbeddedDocument):
word = StringField()
index = IntField()
@ -284,18 +347,27 @@ class TransformTest(unittest.TestCase):
title = StringField()
content = EmbeddedDocumentField(SubDoc)
word = Word(word='abc', index=1)
word = Word(word="abc", index=1)
update = transform.update(MainDoc, pull__content__text=word)
self.assertEqual(update, {'$pull': {'content.text': SON([('word', u'abc'), ('index', 1)])}})
self.assertEqual(
update, {"$pull": {"content.text": SON([("word", u"abc"), ("index", 1)])}}
)
update = transform.update(MainDoc, pull__content__heading='xyz')
self.assertEqual(update, {'$pull': {'content.heading': 'xyz'}})
update = transform.update(MainDoc, pull__content__heading="xyz")
self.assertEqual(update, {"$pull": {"content.heading": "xyz"}})
update = transform.update(MainDoc, pull__content__text__word__in=['foo', 'bar'])
self.assertEqual(update, {'$pull': {'content.text': {'word': {'$in': ['foo', 'bar']}}}})
update = transform.update(MainDoc, pull__content__text__word__in=["foo", "bar"])
self.assertEqual(
update, {"$pull": {"content.text": {"word": {"$in": ["foo", "bar"]}}}}
)
update = transform.update(MainDoc, pull__content__text__word__nin=['foo', 'bar'])
self.assertEqual(update, {'$pull': {'content.text': {'word': {'$nin': ['foo', 'bar']}}}})
update = transform.update(
MainDoc, pull__content__text__word__nin=["foo", "bar"]
)
self.assertEqual(
update, {"$pull": {"content.text": {"word": {"$nin": ["foo", "bar"]}}}}
)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -12,14 +12,13 @@ __all__ = ("QTest",)
class QTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
connect(db="mongoenginetest")
class Person(Document):
name = StringField()
age = IntField()
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
Person.drop_collection()
self.Person = Person
@ -30,22 +29,22 @@ class QTest(unittest.TestCase):
q1 = Q()
q2 = Q(age__gte=18)
q3 = Q()
q4 = Q(name='test')
q4 = Q(name="test")
q5 = Q()
class Person(Document):
name = StringField()
age = IntField()
query = {'$or': [{'age': {'$gte': 18}}, {'name': 'test'}]}
query = {"$or": [{"age": {"$gte": 18}}, {"name": "test"}]}
self.assertEqual((q1 | q2 | q3 | q4 | q5).to_query(Person), query)
query = {'age': {'$gte': 18}, 'name': 'test'}
query = {"age": {"$gte": 18}, "name": "test"}
self.assertEqual((q1 & q2 & q3 & q4 & q5).to_query(Person), query)
def test_q_with_dbref(self):
"""Ensure Q objects handle DBRefs correctly"""
connect(db='mongoenginetest')
connect(db="mongoenginetest")
class User(Document):
pass
@ -62,15 +61,18 @@ class QTest(unittest.TestCase):
def test_and_combination(self):
"""Ensure that Q-objects correctly AND together.
"""
class TestDoc(Document):
x = IntField()
y = StringField()
query = (Q(x__lt=7) & Q(x__lt=3)).to_query(TestDoc)
self.assertEqual(query, {'$and': [{'x': {'$lt': 7}}, {'x': {'$lt': 3}}]})
self.assertEqual(query, {"$and": [{"x": {"$lt": 7}}, {"x": {"$lt": 3}}]})
query = (Q(y="a") & Q(x__lt=7) & Q(x__lt=3)).to_query(TestDoc)
self.assertEqual(query, {'$and': [{'y': "a"}, {'x': {'$lt': 7}}, {'x': {'$lt': 3}}]})
self.assertEqual(
query, {"$and": [{"y": "a"}, {"x": {"$lt": 7}}, {"x": {"$lt": 3}}]}
)
# Check normal cases work without an error
query = Q(x__lt=7) & Q(x__gt=3)
@ -78,69 +80,74 @@ class QTest(unittest.TestCase):
q1 = Q(x__lt=7)
q2 = Q(x__gt=3)
query = (q1 & q2).to_query(TestDoc)
self.assertEqual(query, {'x': {'$lt': 7, '$gt': 3}})
self.assertEqual(query, {"x": {"$lt": 7, "$gt": 3}})
# More complex nested example
query = Q(x__lt=100) & Q(y__ne='NotMyString')
query &= Q(y__in=['a', 'b', 'c']) & Q(x__gt=-100)
query = Q(x__lt=100) & Q(y__ne="NotMyString")
query &= Q(y__in=["a", "b", "c"]) & Q(x__gt=-100)
mongo_query = {
'x': {'$lt': 100, '$gt': -100},
'y': {'$ne': 'NotMyString', '$in': ['a', 'b', 'c']},
"x": {"$lt": 100, "$gt": -100},
"y": {"$ne": "NotMyString", "$in": ["a", "b", "c"]},
}
self.assertEqual(query.to_query(TestDoc), mongo_query)
def test_or_combination(self):
"""Ensure that Q-objects correctly OR together.
"""
class TestDoc(Document):
x = IntField()
q1 = Q(x__lt=3)
q2 = Q(x__gt=7)
query = (q1 | q2).to_query(TestDoc)
self.assertEqual(query, {
'$or': [
{'x': {'$lt': 3}},
{'x': {'$gt': 7}},
]
})
self.assertEqual(query, {"$or": [{"x": {"$lt": 3}}, {"x": {"$gt": 7}}]})
def test_and_or_combination(self):
"""Ensure that Q-objects handle ANDing ORed components.
"""
class TestDoc(Document):
x = IntField()
y = BooleanField()
TestDoc.drop_collection()
query = (Q(x__gt=0) | Q(x__exists=False))
query = Q(x__gt=0) | Q(x__exists=False)
query &= Q(x__lt=100)
self.assertEqual(query.to_query(TestDoc), {'$and': [
{'$or': [{'x': {'$gt': 0}},
{'x': {'$exists': False}}]},
{'x': {'$lt': 100}}]
})
self.assertEqual(
query.to_query(TestDoc),
{
"$and": [
{"$or": [{"x": {"$gt": 0}}, {"x": {"$exists": False}}]},
{"x": {"$lt": 100}},
]
},
)
q1 = (Q(x__gt=0) | Q(x__exists=False))
q2 = (Q(x__lt=100) | Q(y=True))
q1 = Q(x__gt=0) | Q(x__exists=False)
q2 = Q(x__lt=100) | Q(y=True)
query = (q1 & q2).to_query(TestDoc)
TestDoc(x=101).save()
TestDoc(x=10).save()
TestDoc(y=True).save()
self.assertEqual(query, {
'$and': [
{'$or': [{'x': {'$gt': 0}}, {'x': {'$exists': False}}]},
{'$or': [{'x': {'$lt': 100}}, {'y': True}]}
]
})
self.assertEqual(
query,
{
"$and": [
{"$or": [{"x": {"$gt": 0}}, {"x": {"$exists": False}}]},
{"$or": [{"x": {"$lt": 100}}, {"y": True}]},
]
},
)
self.assertEqual(2, TestDoc.objects(q1 & q2).count())
def test_or_and_or_combination(self):
"""Ensure that Q-objects handle ORing ANDed ORed components. :)
"""
class TestDoc(Document):
x = IntField()
y = BooleanField()
@ -151,18 +158,29 @@ class QTest(unittest.TestCase):
TestDoc(x=99, y=False).save()
TestDoc(x=101, y=False).save()
q1 = (Q(x__gt=0) & (Q(y=True) | Q(y__exists=False)))
q2 = (Q(x__lt=100) & (Q(y=False) | Q(y__exists=False)))
q1 = Q(x__gt=0) & (Q(y=True) | Q(y__exists=False))
q2 = Q(x__lt=100) & (Q(y=False) | Q(y__exists=False))
query = (q1 | q2).to_query(TestDoc)
self.assertEqual(query, {
'$or': [
{'$and': [{'x': {'$gt': 0}},
{'$or': [{'y': True}, {'y': {'$exists': False}}]}]},
{'$and': [{'x': {'$lt': 100}},
{'$or': [{'y': False}, {'y': {'$exists': False}}]}]}
]
})
self.assertEqual(
query,
{
"$or": [
{
"$and": [
{"x": {"$gt": 0}},
{"$or": [{"y": True}, {"y": {"$exists": False}}]},
]
},
{
"$and": [
{"x": {"$lt": 100}},
{"$or": [{"y": False}, {"y": {"$exists": False}}]},
]
},
]
},
)
self.assertEqual(2, TestDoc.objects(q1 | q2).count())
def test_multiple_occurence_in_field(self):
@ -170,8 +188,8 @@ class QTest(unittest.TestCase):
name = StringField(max_length=40)
title = StringField(max_length=40)
q1 = Q(name__contains='te') | Q(title__contains='te')
q2 = Q(name__contains='12') | Q(title__contains='12')
q1 = Q(name__contains="te") | Q(title__contains="te")
q2 = Q(name__contains="12") | Q(title__contains="12")
q3 = q1 & q2
@ -180,7 +198,6 @@ class QTest(unittest.TestCase):
self.assertEqual(query["$and"][1], q2.to_query(Test))
def test_q_clone(self):
class TestDoc(Document):
x = IntField()
@ -205,6 +222,7 @@ class QTest(unittest.TestCase):
def test_q(self):
"""Ensure that Q objects may be used to query for documents.
"""
class BlogPost(Document):
title = StringField()
publish_date = DateTimeField()
@ -212,22 +230,26 @@ class QTest(unittest.TestCase):
BlogPost.drop_collection()
post1 = BlogPost(title='Test 1', publish_date=datetime.datetime(2010, 1, 8), published=False)
post1 = BlogPost(
title="Test 1", publish_date=datetime.datetime(2010, 1, 8), published=False
)
post1.save()
post2 = BlogPost(title='Test 2', publish_date=datetime.datetime(2010, 1, 15), published=True)
post2 = BlogPost(
title="Test 2", publish_date=datetime.datetime(2010, 1, 15), published=True
)
post2.save()
post3 = BlogPost(title='Test 3', published=True)
post3 = BlogPost(title="Test 3", published=True)
post3.save()
post4 = BlogPost(title='Test 4', publish_date=datetime.datetime(2010, 1, 8))
post4 = BlogPost(title="Test 4", publish_date=datetime.datetime(2010, 1, 8))
post4.save()
post5 = BlogPost(title='Test 1', publish_date=datetime.datetime(2010, 1, 15))
post5 = BlogPost(title="Test 1", publish_date=datetime.datetime(2010, 1, 15))
post5.save()
post6 = BlogPost(title='Test 1', published=False)
post6 = BlogPost(title="Test 1", published=False)
post6.save()
# Check ObjectId lookup works
@ -235,13 +257,13 @@ class QTest(unittest.TestCase):
self.assertEqual(obj, post1)
# Check Q object combination with one does not exist
q = BlogPost.objects(Q(title='Test 5') | Q(published=True))
q = BlogPost.objects(Q(title="Test 5") | Q(published=True))
posts = [post.id for post in q]
published_posts = (post2, post3)
self.assertTrue(all(obj.id in posts for obj in published_posts))
q = BlogPost.objects(Q(title='Test 1') | Q(published=True))
q = BlogPost.objects(Q(title="Test 1") | Q(published=True))
posts = [post.id for post in q]
published_posts = (post1, post2, post3, post5, post6)
self.assertTrue(all(obj.id in posts for obj in published_posts))
@ -259,85 +281,91 @@ class QTest(unittest.TestCase):
BlogPost.drop_collection()
# Check the 'in' operator
self.Person(name='user1', age=20).save()
self.Person(name='user2', age=20).save()
self.Person(name='user3', age=30).save()
self.Person(name='user4', age=40).save()
self.Person(name="user1", age=20).save()
self.Person(name="user2", age=20).save()
self.Person(name="user3", age=30).save()
self.Person(name="user4", age=40).save()
self.assertEqual(self.Person.objects(Q(age__in=[20])).count(), 2)
self.assertEqual(self.Person.objects(Q(age__in=[20, 30])).count(), 3)
# Test invalid query objs
with self.assertRaises(InvalidQueryError):
self.Person.objects('user1')
self.Person.objects("user1")
# filter should fail, too
with self.assertRaises(InvalidQueryError):
self.Person.objects.filter('user1')
self.Person.objects.filter("user1")
def test_q_regex(self):
"""Ensure that Q objects can be queried using regexes.
"""
person = self.Person(name='Guido van Rossum')
person = self.Person(name="Guido van Rossum")
person.save()
obj = self.Person.objects(Q(name=re.compile('^Gui'))).first()
obj = self.Person.objects(Q(name=re.compile("^Gui"))).first()
self.assertEqual(obj, person)
obj = self.Person.objects(Q(name=re.compile('^gui'))).first()
obj = self.Person.objects(Q(name=re.compile("^gui"))).first()
self.assertEqual(obj, None)
obj = self.Person.objects(Q(name=re.compile('^gui', re.I))).first()
obj = self.Person.objects(Q(name=re.compile("^gui", re.I))).first()
self.assertEqual(obj, person)
obj = self.Person.objects(Q(name__not=re.compile('^bob'))).first()
obj = self.Person.objects(Q(name__not=re.compile("^bob"))).first()
self.assertEqual(obj, person)
obj = self.Person.objects(Q(name__not=re.compile('^Gui'))).first()
obj = self.Person.objects(Q(name__not=re.compile("^Gui"))).first()
self.assertEqual(obj, None)
def test_q_repr(self):
self.assertEqual(repr(Q()), 'Q(**{})')
self.assertEqual(repr(Q(name='test')), "Q(**{'name': 'test'})")
self.assertEqual(repr(Q()), "Q(**{})")
self.assertEqual(repr(Q(name="test")), "Q(**{'name': 'test'})")
self.assertEqual(
repr(Q(name='test') & Q(age__gte=18)),
"(Q(**{'name': 'test'}) & Q(**{'age__gte': 18}))")
repr(Q(name="test") & Q(age__gte=18)),
"(Q(**{'name': 'test'}) & Q(**{'age__gte': 18}))",
)
self.assertEqual(
repr(Q(name='test') | Q(age__gte=18)),
"(Q(**{'name': 'test'}) | Q(**{'age__gte': 18}))")
repr(Q(name="test") | Q(age__gte=18)),
"(Q(**{'name': 'test'}) | Q(**{'age__gte': 18}))",
)
def test_q_lists(self):
"""Ensure that Q objects query ListFields correctly.
"""
class BlogPost(Document):
tags = ListField(StringField())
BlogPost.drop_collection()
BlogPost(tags=['python', 'mongo']).save()
BlogPost(tags=['python']).save()
BlogPost(tags=["python", "mongo"]).save()
BlogPost(tags=["python"]).save()
self.assertEqual(BlogPost.objects(Q(tags='mongo')).count(), 1)
self.assertEqual(BlogPost.objects(Q(tags='python')).count(), 2)
self.assertEqual(BlogPost.objects(Q(tags="mongo")).count(), 1)
self.assertEqual(BlogPost.objects(Q(tags="python")).count(), 2)
BlogPost.drop_collection()
def test_q_merge_queries_edge_case(self):
class User(Document):
email = EmailField(required=False)
name = StringField()
User.drop_collection()
pk = ObjectId()
User(email='example@example.com', pk=pk).save()
User(email="example@example.com", pk=pk).save()
self.assertEqual(1, User.objects.filter(Q(email='example@example.com') |
Q(name='John Doe')).limit(2).filter(pk=pk).count())
self.assertEqual(
1,
User.objects.filter(Q(email="example@example.com") | Q(name="John Doe"))
.limit(2)
.filter(pk=pk)
.count(),
)
def test_chained_q_or_filtering(self):
class Post(EmbeddedDocument):
name = StringField(required=True)
@ -350,9 +378,16 @@ class QTest(unittest.TestCase):
Item(postables=[Post(name="a"), Post(name="c")]).save()
Item(postables=[Post(name="a"), Post(name="b"), Post(name="c")]).save()
self.assertEqual(Item.objects(Q(postables__name="a") & Q(postables__name="b")).count(), 2)
self.assertEqual(Item.objects.filter(postables__name="a").filter(postables__name="b").count(), 2)
self.assertEqual(
Item.objects(Q(postables__name="a") & Q(postables__name="b")).count(), 2
)
self.assertEqual(
Item.objects.filter(postables__name="a")
.filter(postables__name="b")
.count(),
2,
)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -5,7 +5,6 @@ from mongoengine import Document
class TestCommon(unittest.TestCase):
def test__import_class(self):
doc_cls = _import_class("Document")
self.assertIs(doc_cls, Document)

View File

@ -4,6 +4,8 @@ from pymongo import MongoClient
from pymongo.errors import OperationFailure, InvalidName
from pymongo import ReadPreference
from mongoengine import Document
try:
import unittest2 as unittest
except ImportError:
@ -14,12 +16,21 @@ import pymongo
from bson.tz_util import utc
from mongoengine import (
connect, register_connection,
Document, DateTimeField,
disconnect_all, StringField)
connect,
register_connection,
Document,
DateTimeField,
disconnect_all,
StringField,
)
import mongoengine.connection
from mongoengine.connection import (MongoEngineConnectionError, get_db,
get_connection, disconnect, DEFAULT_DATABASE_NAME)
from mongoengine.connection import (
ConnectionFailure,
get_db,
get_connection,
disconnect,
DEFAULT_DATABASE_NAME,
)
def get_tz_awareness(connection):
@ -27,7 +38,6 @@ def get_tz_awareness(connection):
class ConnectionTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
disconnect_all()
@ -43,58 +53,62 @@ class ConnectionTest(unittest.TestCase):
def test_connect(self):
"""Ensure that the connect() method works properly."""
connect('mongoenginetest')
connect("mongoenginetest")
conn = get_connection()
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, 'mongoenginetest')
self.assertEqual(db.name, "mongoenginetest")
connect('mongoenginetest2', alias='testdb')
conn = get_connection('testdb')
connect("mongoenginetest2", alias="testdb")
conn = get_connection("testdb")
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
def test_connect_disconnect_works_properly(self):
class History1(Document):
name = StringField()
meta = {'db_alias': 'db1'}
meta = {"db_alias": "db1"}
class History2(Document):
name = StringField()
meta = {'db_alias': 'db2'}
meta = {"db_alias": "db2"}
connect('db1', alias='db1')
connect('db2', alias='db2')
connect("db1", alias="db1")
connect("db2", alias="db2")
History1.drop_collection()
History2.drop_collection()
h = History1(name='default').save()
h1 = History2(name='db1').save()
h = History1(name="default").save()
h1 = History2(name="db1").save()
self.assertEqual(list(History1.objects().as_pymongo()),
[{'_id': h.id, 'name': 'default'}])
self.assertEqual(list(History2.objects().as_pymongo()),
[{'_id': h1.id, 'name': 'db1'}])
self.assertEqual(
list(History1.objects().as_pymongo()), [{"_id": h.id, "name": "default"}]
)
self.assertEqual(
list(History2.objects().as_pymongo()), [{"_id": h1.id, "name": "db1"}]
)
disconnect('db1')
disconnect('db2')
disconnect("db1")
disconnect("db2")
with self.assertRaises(MongoEngineConnectionError):
with self.assertRaises(ConnectionFailure):
list(History1.objects().as_pymongo())
with self.assertRaises(MongoEngineConnectionError):
with self.assertRaises(ConnectionFailure):
list(History2.objects().as_pymongo())
connect('db1', alias='db1')
connect('db2', alias='db2')
connect("db1", alias="db1")
connect("db2", alias="db2")
self.assertEqual(list(History1.objects().as_pymongo()),
[{'_id': h.id, 'name': 'default'}])
self.assertEqual(list(History2.objects().as_pymongo()),
[{'_id': h1.id, 'name': 'db1'}])
self.assertEqual(
list(History1.objects().as_pymongo()), [{"_id": h.id, "name": "default"}]
)
self.assertEqual(
list(History2.objects().as_pymongo()), [{"_id": h1.id, "name": "db1"}]
)
def test_connect_different_documents_to_different_database(self):
class History(Document):
@ -102,99 +116,110 @@ class ConnectionTest(unittest.TestCase):
class History1(Document):
name = StringField()
meta = {'db_alias': 'db1'}
meta = {"db_alias": "db1"}
class History2(Document):
name = StringField()
meta = {'db_alias': 'db2'}
meta = {"db_alias": "db2"}
connect()
connect('db1', alias='db1')
connect('db2', alias='db2')
connect("db1", alias="db1")
connect("db2", alias="db2")
History.drop_collection()
History1.drop_collection()
History2.drop_collection()
h = History(name='default').save()
h1 = History1(name='db1').save()
h2 = History2(name='db2').save()
h = History(name="default").save()
h1 = History1(name="db1").save()
h2 = History2(name="db2").save()
self.assertEqual(History._collection.database.name, DEFAULT_DATABASE_NAME)
self.assertEqual(History1._collection.database.name, 'db1')
self.assertEqual(History2._collection.database.name, 'db2')
self.assertEqual(History1._collection.database.name, "db1")
self.assertEqual(History2._collection.database.name, "db2")
self.assertEqual(list(History.objects().as_pymongo()),
[{'_id': h.id, 'name': 'default'}])
self.assertEqual(list(History1.objects().as_pymongo()),
[{'_id': h1.id, 'name': 'db1'}])
self.assertEqual(list(History2.objects().as_pymongo()),
[{'_id': h2.id, 'name': 'db2'}])
self.assertEqual(
list(History.objects().as_pymongo()), [{"_id": h.id, "name": "default"}]
)
self.assertEqual(
list(History1.objects().as_pymongo()), [{"_id": h1.id, "name": "db1"}]
)
self.assertEqual(
list(History2.objects().as_pymongo()), [{"_id": h2.id, "name": "db2"}]
)
def test_connect_fails_if_connect_2_times_with_default_alias(self):
connect('mongoenginetest')
connect("mongoenginetest")
with self.assertRaises(MongoEngineConnectionError) as ctx_err:
connect('mongoenginetest2')
self.assertEqual("A different connection with alias `default` was already registered. Use disconnect() first", str(ctx_err.exception))
with self.assertRaises(ConnectionFailure) as ctx_err:
connect("mongoenginetest2")
self.assertEqual(
"A different connection with alias `default` was already registered. Use disconnect() first",
str(ctx_err.exception),
)
def test_connect_fails_if_connect_2_times_with_custom_alias(self):
connect('mongoenginetest', alias='alias1')
connect("mongoenginetest", alias="alias1")
with self.assertRaises(MongoEngineConnectionError) as ctx_err:
connect('mongoenginetest2', alias='alias1')
with self.assertRaises(ConnectionFailure) as ctx_err:
connect("mongoenginetest2", alias="alias1")
self.assertEqual("A different connection with alias `alias1` was already registered. Use disconnect() first", str(ctx_err.exception))
self.assertEqual(
"A different connection with alias `alias1` was already registered. Use disconnect() first",
str(ctx_err.exception),
)
def test_connect_fails_if_similar_connection_settings_arent_defined_the_same_way(self):
def test_connect_fails_if_similar_connection_settings_arent_defined_the_same_way(
self
):
"""Intended to keep the detecton function simple but robust"""
db_name = 'mongoenginetest'
db_alias = 'alias1'
connect(db=db_name, alias=db_alias, host='localhost', port=27017)
db_name = "mongoenginetest"
db_alias = "alias1"
connect(db=db_name, alias=db_alias, host="localhost", port=27017)
with self.assertRaises(MongoEngineConnectionError):
connect(host='mongodb://localhost:27017/%s' % db_name, alias=db_alias)
with self.assertRaises(ConnectionFailure):
connect(host="mongodb://localhost:27017/%s" % db_name, alias=db_alias)
def test_connect_passes_silently_connect_multiple_times_with_same_config(self):
# test default connection to `test`
connect()
connect()
self.assertEqual(len(mongoengine.connection._connections), 1)
connect('test01', alias='test01')
connect('test01', alias='test01')
connect("test01", alias="test01")
connect("test01", alias="test01")
self.assertEqual(len(mongoengine.connection._connections), 2)
connect(host='mongodb://localhost:27017/mongoenginetest02', alias='test02')
connect(host='mongodb://localhost:27017/mongoenginetest02', alias='test02')
connect(host="mongodb://localhost:27017/mongoenginetest02", alias="test02")
connect(host="mongodb://localhost:27017/mongoenginetest02", alias="test02")
self.assertEqual(len(mongoengine.connection._connections), 3)
def test_connect_with_invalid_db_name(self):
"""Ensure that connect() method fails fast if db name is invalid
"""
with self.assertRaises(InvalidName):
connect('mongomock://localhost')
connect("mongomock://localhost")
def test_connect_with_db_name_external(self):
"""Ensure that connect() works if db name is $external
"""
"""Ensure that the connect() method works properly."""
connect('$external')
connect("$external")
conn = get_connection()
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, '$external')
self.assertEqual(db.name, "$external")
connect('$external', alias='testdb')
conn = get_connection('testdb')
connect("$external", alias="testdb")
conn = get_connection("testdb")
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
def test_connect_with_invalid_db_name_type(self):
"""Ensure that connect() method fails fast if db name has invalid type
"""
with self.assertRaises(TypeError):
non_string_db_name = ['e. g. list instead of a string']
non_string_db_name = ["e. g. list instead of a string"]
connect(non_string_db_name)
def test_connect_in_mocking(self):
@ -203,36 +228,69 @@ class ConnectionTest(unittest.TestCase):
try:
import mongomock
except ImportError:
raise SkipTest('you need mongomock installed to run this testcase')
raise SkipTest("you need mongomock installed to run this testcase")
connect('mongoenginetest', host='mongomock://localhost')
connect("mongoenginetest", host="mongomock://localhost")
conn = get_connection()
self.assertIsInstance(conn, mongomock.MongoClient)
connect('mongoenginetest2', host='mongomock://localhost', alias='testdb2')
conn = get_connection('testdb2')
connect("mongoenginetest2", host="mongomock://localhost", alias="testdb2")
conn = get_connection("testdb2")
self.assertIsInstance(conn, mongomock.MongoClient)
connect('mongoenginetest3', host='mongodb://localhost', is_mock=True, alias='testdb3')
conn = get_connection('testdb3')
connect(
"mongoenginetest3",
host="mongodb://localhost",
is_mock=True,
alias="testdb3",
)
conn = get_connection("testdb3")
self.assertIsInstance(conn, mongomock.MongoClient)
connect('mongoenginetest4', is_mock=True, alias='testdb4')
conn = get_connection('testdb4')
connect("mongoenginetest4", is_mock=True, alias="testdb4")
conn = get_connection("testdb4")
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host='mongodb://localhost:27017/mongoenginetest5', is_mock=True, alias='testdb5')
conn = get_connection('testdb5')
connect(
host="mongodb://localhost:27017/mongoenginetest5",
is_mock=True,
alias="testdb5",
)
conn = get_connection("testdb5")
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host='mongomock://localhost:27017/mongoenginetest6', alias='testdb6')
conn = get_connection('testdb6')
connect(host="mongomock://localhost:27017/mongoenginetest6", alias="testdb6")
conn = get_connection("testdb6")
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host='mongomock://localhost:27017/mongoenginetest7', is_mock=True, alias='testdb7')
conn = get_connection('testdb7')
connect(
host="mongomock://localhost:27017/mongoenginetest7",
is_mock=True,
alias="testdb7",
)
conn = get_connection("testdb7")
self.assertIsInstance(conn, mongomock.MongoClient)
def test_default_database_with_mocking(self):
"""Ensure that the default database is correctly set when using mongomock.
"""
try:
import mongomock
except ImportError:
raise SkipTest("you need mongomock installed to run this testcase")
disconnect_all()
class SomeDocument(Document):
pass
conn = connect(host="mongomock://localhost:27017/mongoenginetest")
some_document = SomeDocument()
# database won't exist until we save a document
some_document.save()
self.assertEqual(conn.get_default_database().name, "mongoenginetest")
self.assertEqual(conn.database_names()[0], "mongoenginetest")
def test_connect_with_host_list(self):
"""Ensure that the connect() method works when host is a list
@ -241,30 +299,39 @@ class ConnectionTest(unittest.TestCase):
try:
import mongomock
except ImportError:
raise SkipTest('you need mongomock installed to run this testcase')
raise SkipTest("you need mongomock installed to run this testcase")
connect(host=['mongomock://localhost'])
connect(host=["mongomock://localhost"])
conn = get_connection()
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host=['mongodb://localhost'], is_mock=True, alias='testdb2')
conn = get_connection('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')
conn = get_connection('testdb3')
connect(host=["localhost"], is_mock=True, alias="testdb3")
conn = get_connection("testdb3")
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host=['mongomock://localhost:27017', 'mongomock://localhost:27018'], alias='testdb4')
conn = get_connection('testdb4')
connect(
host=["mongomock://localhost:27017", "mongomock://localhost:27018"],
alias="testdb4",
)
conn = get_connection("testdb4")
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host=['mongodb://localhost:27017', 'mongodb://localhost:27018'], is_mock=True, alias='testdb5')
conn = get_connection('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')
conn = get_connection('testdb6')
connect(
host=["localhost:27017", "localhost:27018"], is_mock=True, alias="testdb6"
)
conn = get_connection("testdb6")
self.assertIsInstance(conn, mongomock.MongoClient)
def test_disconnect_cleans_globals(self):
@ -273,7 +340,7 @@ class ConnectionTest(unittest.TestCase):
dbs = mongoengine.connection._dbs
connection_settings = mongoengine.connection._connection_settings
connect('mongoenginetest')
connect("mongoenginetest")
self.assertEqual(len(connections), 1)
self.assertEqual(len(dbs), 0)
@ -292,7 +359,7 @@ class ConnectionTest(unittest.TestCase):
def test_disconnect_cleans_cached_collection_attribute_in_document(self):
"""Ensure that the disconnect() method works properly"""
conn1 = connect('mongoenginetest')
conn1 = connect("mongoenginetest")
class History(Document):
pass
@ -301,24 +368,26 @@ class ConnectionTest(unittest.TestCase):
History.drop_collection()
History.objects.first() # will trigger the caching of _collection attribute
History.objects.first() # will trigger the caching of _collection attribute
self.assertIsNotNone(History._collection)
disconnect()
self.assertIsNone(History._collection)
with self.assertRaises(MongoEngineConnectionError) as ctx_err:
with self.assertRaises(ConnectionFailure) as ctx_err:
History.objects.first()
self.assertEqual("You have not defined a default connection", str(ctx_err.exception))
self.assertEqual(
"You have not defined a default connection", str(ctx_err.exception)
)
def test_connect_disconnect_works_on_same_document(self):
"""Ensure that the connect/disconnect works properly with a single Document"""
db1 = 'db1'
db2 = 'db2'
db1 = "db1"
db2 = "db2"
# Ensure freshness of the 2 databases through pymongo
client = MongoClient('localhost', 27017)
client = MongoClient("localhost", 27017)
client.drop_database(db1)
client.drop_database(db2)
@ -328,44 +397,44 @@ class ConnectionTest(unittest.TestCase):
class User(Document):
name = StringField(required=True)
user1 = User(name='John is in db1').save()
user1 = User(name="John is in db1").save()
disconnect()
# Make sure save doesnt work at this stage
with self.assertRaises(MongoEngineConnectionError):
User(name='Wont work').save()
with self.assertRaises(ConnectionFailure):
User(name="Wont work").save()
# Save in db2
connect(db2)
user2 = User(name='Bob is in db2').save()
user2 = User(name="Bob is in db2").save()
disconnect()
db1_users = list(client[db1].user.find())
self.assertEqual(db1_users, [{'_id': user1.id, 'name': 'John is in db1'}])
self.assertEqual(db1_users, [{"_id": user1.id, "name": "John is in db1"}])
db2_users = list(client[db2].user.find())
self.assertEqual(db2_users, [{'_id': user2.id, 'name': 'Bob is in db2'}])
self.assertEqual(db2_users, [{"_id": user2.id, "name": "Bob is in db2"}])
def test_disconnect_silently_pass_if_alias_does_not_exist(self):
connections = mongoengine.connection._connections
self.assertEqual(len(connections), 0)
disconnect(alias='not_exist')
disconnect(alias="not_exist")
def test_disconnect_all(self):
connections = mongoengine.connection._connections
dbs = mongoengine.connection._dbs
connection_settings = mongoengine.connection._connection_settings
connect('mongoenginetest')
connect('mongoenginetest2', alias='db1')
connect("mongoenginetest")
connect("mongoenginetest2", alias="db1")
class History(Document):
pass
class History1(Document):
name = StringField()
meta = {'db_alias': 'db1'}
meta = {"db_alias": "db1"}
History.drop_collection() # will trigger the caching of _collection attribute
History.drop_collection() # will trigger the caching of _collection attribute
History.objects.first()
History1.drop_collection()
History1.objects.first()
@ -386,10 +455,10 @@ class ConnectionTest(unittest.TestCase):
self.assertEqual(len(dbs), 0)
self.assertEqual(len(connection_settings), 0)
with self.assertRaises(MongoEngineConnectionError):
with self.assertRaises(ConnectionFailure):
History.objects.first()
with self.assertRaises(MongoEngineConnectionError):
with self.assertRaises(ConnectionFailure):
History1.objects.first()
def test_disconnect_all_silently_pass_if_no_connection_exist(self):
@ -398,11 +467,11 @@ class ConnectionTest(unittest.TestCase):
def test_sharing_connections(self):
"""Ensure that connections are shared when the connection settings are exactly the same
"""
connect('mongoenginetests', alias='testdb1')
expected_connection = get_connection('testdb1')
connect("mongoenginetests", alias="testdb1")
expected_connection = get_connection("testdb1")
connect('mongoenginetests', alias='testdb2')
actual_connection = get_connection('testdb2')
connect("mongoenginetests", alias="testdb2")
actual_connection = get_connection("testdb2")
expected_connection.server_info()
@ -410,7 +479,7 @@ 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 = connect(db="mongoenginetest", alias="admin")
c.admin.system.users.delete_many({})
c.mongoenginetest.system.users.delete_many({})
@ -418,14 +487,16 @@ class ConnectionTest(unittest.TestCase):
c.admin.authenticate("admin", "password")
c.admin.command("createUser", "username", pwd="password", roles=["dbOwner"])
connect("testdb_uri", host='mongodb://username:password@localhost/mongoenginetest')
connect(
"testdb_uri", host="mongodb://username:password@localhost/mongoenginetest"
)
conn = get_connection()
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, 'mongoenginetest')
self.assertEqual(db.name, "mongoenginetest")
c.admin.system.users.delete_many({})
c.mongoenginetest.system.users.delete_many({})
@ -434,35 +505,35 @@ class ConnectionTest(unittest.TestCase):
"""Ensure connect() method works properly if the URI doesn't
include a database name.
"""
connect("mongoenginetest", host='mongodb://localhost/')
connect("mongoenginetest", host="mongodb://localhost/")
conn = get_connection()
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, 'mongoenginetest')
self.assertEqual(db.name, "mongoenginetest")
def test_connect_uri_default_db(self):
"""Ensure connect() defaults to the right database name if
the URI and the database_name don't explicitly specify it.
"""
connect(host='mongodb://localhost/')
connect(host="mongodb://localhost/")
conn = get_connection()
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, 'test')
self.assertEqual(db.name, "test")
def test_uri_without_credentials_doesnt_override_conn_settings(self):
"""Ensure connect() uses the username & password params if the URI
doesn't explicitly specify them.
"""
c = connect(host='mongodb://localhost/mongoenginetest',
username='user',
password='pass')
c = connect(
host="mongodb://localhost/mongoenginetest", username="user", password="pass"
)
# OperationFailure means that mongoengine attempted authentication
# w/ the provided username/password and failed - that's the desired
@ -474,27 +545,31 @@ class ConnectionTest(unittest.TestCase):
option in the URI.
"""
# Create users
c = connect('mongoenginetest')
c = connect("mongoenginetest")
c.admin.system.users.delete_many({})
c.admin.command("createUser", "username2", pwd="password", roles=["dbOwner"])
# Authentication fails without "authSource"
test_conn = connect(
'mongoenginetest', alias='test1',
host='mongodb://username2:password@localhost/mongoenginetest'
"mongoenginetest",
alias="test1",
host="mongodb://username2:password@localhost/mongoenginetest",
)
self.assertRaises(OperationFailure, test_conn.server_info)
# Authentication succeeds with "authSource"
authd_conn = connect(
'mongoenginetest', alias='test2',
host=('mongodb://username2:password@localhost/'
'mongoenginetest?authSource=admin')
"mongoenginetest",
alias="test2",
host=(
"mongodb://username2:password@localhost/"
"mongoenginetest?authSource=admin"
),
)
db = get_db('test2')
db = get_db("test2")
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, 'mongoenginetest')
self.assertEqual(db.name, "mongoenginetest")
# Clear all users
authd_conn.admin.system.users.delete_many({})
@ -502,82 +577,86 @@ class ConnectionTest(unittest.TestCase):
def test_register_connection(self):
"""Ensure that connections with different aliases may be registered.
"""
register_connection('testdb', 'mongoenginetest2')
register_connection("testdb", "mongoenginetest2")
self.assertRaises(MongoEngineConnectionError, get_connection)
conn = get_connection('testdb')
self.assertRaises(ConnectionFailure, get_connection)
conn = get_connection("testdb")
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
db = get_db('testdb')
db = get_db("testdb")
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, 'mongoenginetest2')
self.assertEqual(db.name, "mongoenginetest2")
def test_register_connection_defaults(self):
"""Ensure that defaults are used when the host and port are None.
"""
register_connection('testdb', 'mongoenginetest', host=None, port=None)
register_connection("testdb", "mongoenginetest", host=None, port=None)
conn = get_connection('testdb')
conn = get_connection("testdb")
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
def test_connection_kwargs(self):
"""Ensure that connection kwargs get passed to pymongo."""
connect('mongoenginetest', alias='t1', tz_aware=True)
conn = get_connection('t1')
connect("mongoenginetest", alias="t1", tz_aware=True)
conn = get_connection("t1")
self.assertTrue(get_tz_awareness(conn))
connect('mongoenginetest2', alias='t2')
conn = get_connection('t2')
connect("mongoenginetest2", alias="t2")
conn = get_connection("t2")
self.assertFalse(get_tz_awareness(conn))
def test_connection_pool_via_kwarg(self):
"""Ensure we can specify a max connection pool size using
a connection kwarg.
"""
pool_size_kwargs = {'maxpoolsize': 100}
pool_size_kwargs = {"maxpoolsize": 100}
conn = connect('mongoenginetest', alias='max_pool_size_via_kwarg', **pool_size_kwargs)
conn = connect(
"mongoenginetest", alias="max_pool_size_via_kwarg", **pool_size_kwargs
)
self.assertEqual(conn.max_pool_size, 100)
def test_connection_pool_via_uri(self):
"""Ensure we can specify a max connection pool size using
an option in a connection URI.
"""
conn = connect(host='mongodb://localhost/test?maxpoolsize=100', alias='max_pool_size_via_uri')
conn = connect(
host="mongodb://localhost/test?maxpoolsize=100",
alias="max_pool_size_via_uri",
)
self.assertEqual(conn.max_pool_size, 100)
def test_write_concern(self):
"""Ensure write concern can be specified in connect() via
a kwarg or as part of the connection URI.
"""
conn1 = connect(alias='conn1', host='mongodb://localhost/testing?w=1&j=true')
conn2 = connect('testing', alias='conn2', w=1, j=True)
self.assertEqual(conn1.write_concern.document, {'w': 1, 'j': True})
self.assertEqual(conn2.write_concern.document, {'w': 1, 'j': True})
conn1 = connect(alias="conn1", host="mongodb://localhost/testing?w=1&j=true")
conn2 = connect("testing", alias="conn2", w=1, j=True)
self.assertEqual(conn1.write_concern.document, {"w": 1, "j": True})
self.assertEqual(conn2.write_concern.document, {"w": 1, "j": True})
def test_connect_with_replicaset_via_uri(self):
"""Ensure connect() works when specifying a replicaSet via the
MongoDB URI.
"""
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
c = connect(host="mongodb://localhost/test?replicaSet=local-rs")
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, 'test')
self.assertEqual(db.name, "test")
def test_connect_with_replicaset_via_kwargs(self):
"""Ensure connect() works when specifying a replicaSet via the
connection kwargs
"""
c = connect(replicaset='local-rs')
self.assertEqual(c._MongoClient__options.replica_set_name,
'local-rs')
c = connect(replicaset="local-rs")
self.assertEqual(c._MongoClient__options.replica_set_name, "local-rs")
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, 'test')
self.assertEqual(db.name, "test")
def test_connect_tz_aware(self):
connect('mongoenginetest', tz_aware=True)
connect("mongoenginetest", tz_aware=True)
d = datetime.datetime(2010, 5, 5, tzinfo=utc)
class DateDoc(Document):
@ -590,37 +669,39 @@ class ConnectionTest(unittest.TestCase):
self.assertEqual(d, date_doc.the_date)
def test_read_preference_from_parse(self):
conn = connect(host="mongodb://a1.vpc,a2.vpc,a3.vpc/prod?readPreference=secondaryPreferred")
conn = connect(
host="mongodb://a1.vpc,a2.vpc,a3.vpc/prod?readPreference=secondaryPreferred"
)
self.assertEqual(conn.read_preference, ReadPreference.SECONDARY_PREFERRED)
def test_multiple_connection_settings(self):
connect('mongoenginetest', alias='t1', host="localhost")
connect("mongoenginetest", alias="t1", host="localhost")
connect('mongoenginetest2', alias='t2', host="127.0.0.1")
connect("mongoenginetest2", alias="t2", host="127.0.0.1")
mongo_connections = mongoengine.connection._connections
self.assertEqual(len(mongo_connections.items()), 2)
self.assertIn('t1', mongo_connections.keys())
self.assertIn('t2', mongo_connections.keys())
self.assertIn("t1", mongo_connections.keys())
self.assertIn("t2", mongo_connections.keys())
# Handle PyMongo 3+ Async Connection
# Ensure we are connected, throws ServerSelectionTimeoutError otherwise.
# Purposely not catching exception to fail test if thrown.
mongo_connections['t1'].server_info()
mongo_connections['t2'].server_info()
self.assertEqual(mongo_connections['t1'].address[0], 'localhost')
self.assertEqual(mongo_connections['t2'].address[0], '127.0.0.1')
mongo_connections["t1"].server_info()
mongo_connections["t2"].server_info()
self.assertEqual(mongo_connections["t1"].address[0], "localhost")
self.assertEqual(mongo_connections["t2"].address[0], "127.0.0.1")
def test_connect_2_databases_uses_same_client_if_only_dbname_differs(self):
c1 = connect(alias='testdb1', db='testdb1')
c2 = connect(alias='testdb2', db='testdb2')
c1 = connect(alias="testdb1", db="testdb1")
c2 = connect(alias="testdb2", db="testdb2")
self.assertIs(c1, c2)
def test_connect_2_databases_uses_different_client_if_different_parameters(self):
c1 = connect(alias='testdb1', db='testdb1', username='u1')
c2 = connect(alias='testdb2', db='testdb2', username='u2')
c1 = connect(alias="testdb1", db="testdb1", username="u1")
c2 = connect(alias="testdb2", db="testdb2", username="u2")
self.assertIsNot(c1, c2)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -2,17 +2,20 @@ import unittest
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.context_managers import (switch_db, switch_collection,
no_sub_classes, no_dereference,
query_counter)
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):
def test_switch_db_context_manager(self):
connect('mongoenginetest')
register_connection('testdb-1', 'mongoenginetest2')
connect("mongoenginetest")
register_connection("testdb-1", "mongoenginetest2")
class Group(Document):
name = StringField()
@ -22,7 +25,7 @@ class ContextManagersTest(unittest.TestCase):
Group(name="hello - default").save()
self.assertEqual(1, Group.objects.count())
with switch_db(Group, 'testdb-1') as Group:
with switch_db(Group, "testdb-1") as Group:
self.assertEqual(0, Group.objects.count())
@ -36,21 +39,21 @@ class ContextManagersTest(unittest.TestCase):
self.assertEqual(1, Group.objects.count())
def test_switch_collection_context_manager(self):
connect('mongoenginetest')
register_connection(alias='testdb-1', db='mongoenginetest2')
connect("mongoenginetest")
register_connection(alias="testdb-1", db="mongoenginetest2")
class Group(Document):
name = StringField()
Group.drop_collection() # drops in default
Group.drop_collection() # drops in default
with switch_collection(Group, 'group1') as Group:
Group.drop_collection() # drops in group1
with switch_collection(Group, "group1") as Group:
Group.drop_collection() # drops in group1
Group(name="hello - group").save()
self.assertEqual(1, Group.objects.count())
with switch_collection(Group, 'group1') as Group:
with switch_collection(Group, "group1") as Group:
self.assertEqual(0, Group.objects.count())
@ -66,7 +69,7 @@ class ContextManagersTest(unittest.TestCase):
def test_no_dereference_context_manager_object_id(self):
"""Ensure that DBRef items in ListFields aren't dereferenced.
"""
connect('mongoenginetest')
connect("mongoenginetest")
class User(Document):
name = StringField()
@ -80,14 +83,14 @@ class ContextManagersTest(unittest.TestCase):
Group.drop_collection()
for i in range(1, 51):
User(name='user %s' % i).save()
User(name="user %s" % i).save()
user = User.objects.first()
Group(ref=user, members=User.objects, generic=user).save()
with no_dereference(Group) as NoDeRefGroup:
self.assertTrue(Group._fields['members']._auto_dereference)
self.assertFalse(NoDeRefGroup._fields['members']._auto_dereference)
self.assertTrue(Group._fields["members"]._auto_dereference)
self.assertFalse(NoDeRefGroup._fields["members"]._auto_dereference)
with no_dereference(Group) as Group:
group = Group.objects.first()
@ -104,7 +107,7 @@ class ContextManagersTest(unittest.TestCase):
def test_no_dereference_context_manager_dbref(self):
"""Ensure that DBRef items in ListFields aren't dereferenced.
"""
connect('mongoenginetest')
connect("mongoenginetest")
class User(Document):
name = StringField()
@ -118,31 +121,29 @@ class ContextManagersTest(unittest.TestCase):
Group.drop_collection()
for i in range(1, 51):
User(name='user %s' % i).save()
User(name="user %s" % i).save()
user = User.objects.first()
Group(ref=user, members=User.objects, generic=user).save()
with no_dereference(Group) as NoDeRefGroup:
self.assertTrue(Group._fields['members']._auto_dereference)
self.assertFalse(NoDeRefGroup._fields['members']._auto_dereference)
self.assertTrue(Group._fields["members"]._auto_dereference)
self.assertFalse(NoDeRefGroup._fields["members"]._auto_dereference)
with no_dereference(Group) as Group:
group = Group.objects.first()
self.assertTrue(all([not isinstance(m, User)
for m in group.members]))
self.assertTrue(all([not isinstance(m, User) for m in group.members]))
self.assertNotIsInstance(group.ref, User)
self.assertNotIsInstance(group.generic, User)
self.assertTrue(all([isinstance(m, User)
for m in group.members]))
self.assertTrue(all([isinstance(m, User) for m in group.members]))
self.assertIsInstance(group.ref, User)
self.assertIsInstance(group.generic, User)
def test_no_sub_classes(self):
class A(Document):
x = IntField()
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
class B(A):
z = IntField()
@ -188,20 +189,20 @@ class ContextManagersTest(unittest.TestCase):
def test_no_sub_classes_modification_to_document_class_are_temporary(self):
class A(Document):
x = IntField()
meta = {'allow_inheritance': True}
meta = {"allow_inheritance": True}
class B(A):
z = IntField()
self.assertEqual(A._subclasses, ('A', 'A.B'))
self.assertEqual(A._subclasses, ("A", "A.B"))
with no_sub_classes(A):
self.assertEqual(A._subclasses, ('A',))
self.assertEqual(A._subclasses, ('A', 'A.B'))
self.assertEqual(A._subclasses, ("A",))
self.assertEqual(A._subclasses, ("A", "A.B"))
self.assertEqual(B._subclasses, ('A.B',))
self.assertEqual(B._subclasses, ("A.B",))
with no_sub_classes(B):
self.assertEqual(B._subclasses, ('A.B',))
self.assertEqual(B._subclasses, ('A.B',))
self.assertEqual(B._subclasses, ("A.B",))
self.assertEqual(B._subclasses, ("A.B",))
def test_no_subclass_context_manager_does_not_swallow_exception(self):
class User(Document):
@ -218,7 +219,7 @@ class ContextManagersTest(unittest.TestCase):
raise TypeError()
def test_query_counter_temporarily_modifies_profiling_level(self):
connect('mongoenginetest')
connect("mongoenginetest")
db = get_db()
initial_profiling_level = db.profiling_level()
@ -231,11 +232,13 @@ class ContextManagersTest(unittest.TestCase):
self.assertEqual(db.profiling_level(), 2)
self.assertEqual(db.profiling_level(), NEW_LEVEL)
except Exception:
db.set_profiling_level(initial_profiling_level) # Ensures it gets reseted no matter the outcome of the test
db.set_profiling_level(
initial_profiling_level
) # Ensures it gets reseted no matter the outcome of the test
raise
def test_query_counter(self):
connect('mongoenginetest')
connect("mongoenginetest")
db = get_db()
collection = db.query_counter
@ -245,7 +248,7 @@ class ContextManagersTest(unittest.TestCase):
count_documents(collection, {})
def issue_1_insert_query():
collection.insert_one({'test': 'garbage'})
collection.insert_one({"test": "garbage"})
def issue_1_find_query():
collection.find_one()
@ -253,7 +256,9 @@ class ContextManagersTest(unittest.TestCase):
counter = 0
with query_counter() as q:
self.assertEqual(q, counter)
self.assertEqual(q, counter) # Ensures previous count query did not get counted
self.assertEqual(
q, counter
) # Ensures previous count query did not get counted
for _ in range(10):
issue_1_insert_query()
@ -270,23 +275,25 @@ class ContextManagersTest(unittest.TestCase):
counter += 1
self.assertEqual(q, counter)
self.assertEqual(int(q), counter) # test __int__
self.assertEqual(int(q), counter) # test __int__
self.assertEqual(repr(q), str(int(q))) # test __repr__
self.assertGreater(q, -1) # test __gt__
self.assertGreaterEqual(q, int(q)) # test __gte__
self.assertGreater(q, -1) # test __gt__
self.assertGreaterEqual(q, int(q)) # test __gte__
self.assertNotEqual(q, -1)
self.assertLess(q, 1000)
self.assertLessEqual(q, int(q))
def test_query_counter_counts_getmore_queries(self):
connect('mongoenginetest')
connect("mongoenginetest")
db = get_db()
collection = db.query_counter
collection.drop()
many_docs = [{'test': 'garbage %s' % i} for i in range(150)]
collection.insert_many(many_docs) # first batch of documents contains 101 documents
many_docs = [{"test": "garbage %s" % i} for i in range(150)]
collection.insert_many(
many_docs
) # first batch of documents contains 101 documents
with query_counter() as q:
self.assertEqual(q, 0)
@ -294,24 +301,26 @@ class ContextManagersTest(unittest.TestCase):
self.assertEqual(q, 2) # 1st select + 1 getmore
def test_query_counter_ignores_particular_queries(self):
connect('mongoenginetest')
connect("mongoenginetest")
db = get_db()
collection = db.query_counter
collection.insert_many([{'test': 'garbage %s' % i} for i in range(10)])
collection.insert_many([{"test": "garbage %s" % i} for i in range(10)])
with query_counter() as q:
self.assertEqual(q, 0)
cursor = collection.find()
self.assertEqual(q, 0) # cursor wasn't opened yet
_ = next(cursor) # opens the cursor and fires the find query
self.assertEqual(q, 0) # cursor wasn't opened yet
_ = next(cursor) # opens the cursor and fires the find query
self.assertEqual(q, 1)
cursor.close() # issues a `killcursors` query that is ignored by the context
cursor.close() # issues a `killcursors` query that is ignored by the context
self.assertEqual(q, 1)
_ = db.system.indexes.find_one() # queries on db.system.indexes are ignored as well
_ = (
db.system.indexes.find_one()
) # queries on db.system.indexes are ignored as well
self.assertEqual(q, 1)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -14,128 +14,129 @@ class DocumentStub(object):
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
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'}
dict_items = {"k": "v"}
doc = MyDoc()
base_dict = BaseDict(dict_items, instance=doc, name='my_name')
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._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')
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'))
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')
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'})
base_dict = self._get_basedict({"k": "v"})
with self.assertRaises(KeyError):
base_dict.pop('X')
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 = self._get_basedict({"k": "v"})
base_dict.clear()
self.assertEqual(base_dict._instance._changed_fields, ['my_name'])
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'])
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']
base_dict["new"]
def test___getitem____simple_value(self):
base_dict = self._get_basedict({'k': 'v'})
base_dict['k'] = 'v'
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']
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(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'])
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'})
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(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'])
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')
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']
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)
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'})
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'])
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'
base_dict.a_new_attr = "test"
self.assertEqual(base_dict._instance._changed_fields, [])
def test___delattr____tracked_by_changes(self):
@ -143,19 +144,20 @@ class TestBaseDict(unittest.TestCase):
# 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'
base_dict.a_new_attr = "test"
del base_dict.a_new_attr
self.assertEqual(base_dict._instance._changed_fields, ['my_name.a_new_attr'])
self.assertEqual(base_dict._instance._changed_fields, ["my_name.a_new_attr"])
class TestBaseList(unittest.TestCase):
@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
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):
@ -164,19 +166,19 @@ class TestBaseList(unittest.TestCase):
list_items = [True]
doc = MyDoc()
base_list = BaseList(list_items, instance=doc, name='my_name')
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._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')
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):
# regular list allows for this, thus this subclass must comply to that
base_list = BaseList([True, False, True, False], instance=None, name='my_name')
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)
@ -185,7 +187,7 @@ class TestBaseList(unittest.TestCase):
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'])
self.assertEqual(base_list._instance._changed_fields, ["my_name"])
def test_subclass_append(self):
# Due to the way mark_as_changed_wrapper is implemented
@ -193,7 +195,7 @@ class TestBaseList(unittest.TestCase):
class SubBaseList(BaseList):
pass
base_list = SubBaseList([], instance=None, name='my_name')
base_list = SubBaseList([], instance=None, name="my_name")
base_list.append(True)
def test___getitem__using_simple_index(self):
@ -217,54 +219,45 @@ class TestBaseList(unittest.TestCase):
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]
]
)
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(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'])
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'}
]
)
base_list = self._get_baselist([{"subk": "subv"}])
sub_dict = base_list[0]
self.assertEqual(sub_dict, {'subk': 'subv'})
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(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'])
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'])
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'])
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'])
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])
@ -275,70 +268,76 @@ class TestBaseList(unittest.TestCase):
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'])
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'])
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'])
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'])
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'])
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'])
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._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._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._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._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'])
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'])
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])
@ -350,7 +349,7 @@ class TestBaseList(unittest.TestCase):
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'])
self.assertEqual(base_list._instance._changed_fields, ["my_name"])
def test_sort_calls_with_key(self):
base_list = self._get_baselist([1, 2, 11])
@ -371,7 +370,7 @@ class TestStrictDict(unittest.TestCase):
def test_iterkeys(self):
d = self.dtype(a=1)
self.assertEqual(list(iterkeys(d)), ['a'])
self.assertEqual(list(iterkeys(d)), ["a"])
def test_len(self):
d = self.dtype(a=1)
@ -379,9 +378,9 @@ class TestStrictDict(unittest.TestCase):
def test_pop(self):
d = self.dtype(a=1)
self.assertIn('a', d)
d.pop('a')
self.assertNotIn('a', d)
self.assertIn("a", d)
d.pop("a")
self.assertNotIn("a", d)
def test_repr(self):
d = self.dtype(a=1, b=2, c=3)
@ -416,7 +415,7 @@ class TestStrictDict(unittest.TestCase):
d = self.dtype()
d.a = 1
self.assertEqual(d.a, 1)
self.assertRaises(AttributeError, getattr, d, 'b')
self.assertRaises(AttributeError, getattr, d, "b")
def test_setattr_raises_on_nonexisting_attr(self):
d = self.dtype()
@ -430,20 +429,20 @@ class TestStrictDict(unittest.TestCase):
def test_get(self):
d = self.dtype(a=1)
self.assertEqual(d.get('a'), 1)
self.assertEqual(d.get('b', 'bla'), 'bla')
self.assertEqual(d.get("a"), 1)
self.assertEqual(d.get("b", "bla"), "bla")
def test_items(self):
d = self.dtype(a=1)
self.assertEqual(d.items(), [('a', 1)])
self.assertEqual(d.items(), [("a", 1)])
d = self.dtype(a=1, b=2)
self.assertEqual(d.items(), [('a', 1), ('b', 2)])
self.assertEqual(d.items(), [("a", 1), ("b", 2)])
def test_mappings_protocol(self):
d = self.dtype(a=1, b=2)
self.assertEqual(dict(d), {'a': 1, 'b': 2})
self.assertEqual(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__':
if __name__ == "__main__":
unittest.main()

View File

@ -10,18 +10,18 @@ from mongoengine.context_managers import query_counter
class FieldTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.db = connect(db='mongoenginetest')
cls.db = connect(db="mongoenginetest")
@classmethod
def tearDownClass(cls):
cls.db.drop_database('mongoenginetest')
cls.db.drop_database("mongoenginetest")
def test_list_item_dereference(self):
"""Ensure that DBRef items in ListFields are dereferenced.
"""
class User(Document):
name = StringField()
@ -32,7 +32,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
for i in range(1, 51):
user = User(name='user %s' % i)
user = User(name="user %s" % i)
user.save()
group = Group(members=User.objects)
@ -47,7 +47,7 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first()
self.assertEqual(q, 1)
len(group_obj._data['members'])
len(group_obj._data["members"])
self.assertEqual(q, 1)
len(group_obj.members)
@ -80,6 +80,7 @@ class FieldTest(unittest.TestCase):
def test_list_item_dereference_dref_false(self):
"""Ensure that DBRef items in ListFields are dereferenced.
"""
class User(Document):
name = StringField()
@ -90,7 +91,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
for i in range(1, 51):
user = User(name='user %s' % i)
user = User(name="user %s" % i)
user.save()
group = Group(members=User.objects)
@ -105,14 +106,14 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 2)
self.assertTrue(group_obj._data['members']._dereferenced)
self.assertTrue(group_obj._data["members"]._dereferenced)
# verifies that no additional queries gets executed
# if we re-iterate over the ListField once it is
# dereferenced
[m for m in group_obj.members]
self.assertEqual(q, 2)
self.assertTrue(group_obj._data['members']._dereferenced)
self.assertTrue(group_obj._data["members"]._dereferenced)
# Document select_related
with query_counter() as q:
@ -136,6 +137,7 @@ class FieldTest(unittest.TestCase):
def test_list_item_dereference_orphan_dbref(self):
"""Ensure that orphan DBRef items in ListFields are dereferenced.
"""
class User(Document):
name = StringField()
@ -146,7 +148,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
for i in range(1, 51):
user = User(name='user %s' % i)
user = User(name="user %s" % i)
user.save()
group = Group(members=User.objects)
@ -164,14 +166,14 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 2)
self.assertTrue(group_obj._data['members']._dereferenced)
self.assertTrue(group_obj._data["members"]._dereferenced)
# verifies that no additional queries gets executed
# if we re-iterate over the ListField once it is
# dereferenced
[m for m in group_obj.members]
self.assertEqual(q, 2)
self.assertTrue(group_obj._data['members']._dereferenced)
self.assertTrue(group_obj._data["members"]._dereferenced)
User.drop_collection()
Group.drop_collection()
@ -179,6 +181,7 @@ class FieldTest(unittest.TestCase):
def test_list_item_dereference_dref_false_stores_as_type(self):
"""Ensure that DBRef items are stored as their type
"""
class User(Document):
my_id = IntField(primary_key=True)
name = StringField()
@ -189,17 +192,18 @@ class FieldTest(unittest.TestCase):
User.drop_collection()
Group.drop_collection()
user = User(my_id=1, name='user 1').save()
user = User(my_id=1, name="user 1").save()
Group(members=User.objects).save()
group = Group.objects.first()
self.assertEqual(Group._get_collection().find_one()['members'], [1])
self.assertEqual(Group._get_collection().find_one()["members"], [1])
self.assertEqual(group.members, [user])
def test_handle_old_style_references(self):
"""Ensure that DBRef items in ListFields are dereferenced.
"""
class User(Document):
name = StringField()
@ -210,7 +214,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
for i in range(1, 26):
user = User(name='user %s' % i)
user = User(name="user %s" % i)
user.save()
group = Group(members=User.objects)
@ -227,8 +231,8 @@ class FieldTest(unittest.TestCase):
group.save()
group = Group.objects.first()
self.assertEqual(group.members[0].name, 'user 1')
self.assertEqual(group.members[-1].name, 'String!')
self.assertEqual(group.members[0].name, "user 1")
self.assertEqual(group.members[-1].name, "String!")
def test_migrate_references(self):
"""Example of migrating ReferenceField storage
@ -249,8 +253,8 @@ class FieldTest(unittest.TestCase):
group = Group(author=user, members=[user]).save()
raw_data = Group._get_collection().find_one()
self.assertIsInstance(raw_data['author'], DBRef)
self.assertIsInstance(raw_data['members'][0], DBRef)
self.assertIsInstance(raw_data["author"], DBRef)
self.assertIsInstance(raw_data["members"][0], DBRef)
group = Group.objects.first()
self.assertEqual(group.author, user)
@ -264,8 +268,8 @@ class FieldTest(unittest.TestCase):
# Migrate the data
for g in Group.objects():
# Explicitly mark as changed so resets
g._mark_as_changed('author')
g._mark_as_changed('members')
g._mark_as_changed("author")
g._mark_as_changed("members")
g.save()
group = Group.objects.first()
@ -273,35 +277,36 @@ class FieldTest(unittest.TestCase):
self.assertEqual(group.members, [user])
raw_data = Group._get_collection().find_one()
self.assertIsInstance(raw_data['author'], ObjectId)
self.assertIsInstance(raw_data['members'][0], ObjectId)
self.assertIsInstance(raw_data["author"], ObjectId)
self.assertIsInstance(raw_data["members"][0], ObjectId)
def test_recursive_reference(self):
"""Ensure that ReferenceFields can reference their own documents.
"""
class Employee(Document):
name = StringField()
boss = ReferenceField('self')
friends = ListField(ReferenceField('self'))
boss = ReferenceField("self")
friends = ListField(ReferenceField("self"))
Employee.drop_collection()
bill = Employee(name='Bill Lumbergh')
bill = Employee(name="Bill Lumbergh")
bill.save()
michael = Employee(name='Michael Bolton')
michael = Employee(name="Michael Bolton")
michael.save()
samir = Employee(name='Samir Nagheenanajar')
samir = Employee(name="Samir Nagheenanajar")
samir.save()
friends = [michael, samir]
peter = Employee(name='Peter Gibbons', boss=bill, friends=friends)
peter = Employee(name="Peter Gibbons", boss=bill, friends=friends)
peter.save()
Employee(name='Funky Gibbon', boss=bill, friends=friends).save()
Employee(name='Funky Gibbon', boss=bill, friends=friends).save()
Employee(name='Funky Gibbon', boss=bill, friends=friends).save()
Employee(name="Funky Gibbon", boss=bill, friends=friends).save()
Employee(name="Funky Gibbon", boss=bill, friends=friends).save()
Employee(name="Funky Gibbon", boss=bill, friends=friends).save()
with query_counter() as q:
self.assertEqual(q, 0)
@ -343,7 +348,6 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 2)
def test_list_of_lists_of_references(self):
class User(Document):
name = StringField()
@ -357,9 +361,9 @@ class FieldTest(unittest.TestCase):
Post.drop_collection()
SimpleList.drop_collection()
u1 = User.objects.create(name='u1')
u2 = User.objects.create(name='u2')
u3 = User.objects.create(name='u3')
u1 = User.objects.create(name="u1")
u2 = User.objects.create(name="u2")
u3 = User.objects.create(name="u3")
SimpleList.objects.create(users=[u1, u2, u3])
self.assertEqual(SimpleList.objects.all()[0].users, [u1, u2, u3])
@ -370,13 +374,14 @@ class FieldTest(unittest.TestCase):
def test_circular_reference(self):
"""Ensure you can handle circular references
"""
class Relation(EmbeddedDocument):
name = StringField()
person = ReferenceField('Person')
person = ReferenceField("Person")
class Person(Document):
name = StringField()
relations = ListField(EmbeddedDocumentField('Relation'))
relations = ListField(EmbeddedDocumentField("Relation"))
def __repr__(self):
return "<Person: %s>" % self.name
@ -398,14 +403,17 @@ class FieldTest(unittest.TestCase):
daughter.relations.append(self_rel)
daughter.save()
self.assertEqual("[<Person: Mother>, <Person: Daughter>]", "%s" % Person.objects())
self.assertEqual(
"[<Person: Mother>, <Person: Daughter>]", "%s" % Person.objects()
)
def test_circular_reference_on_self(self):
"""Ensure you can handle circular references
"""
class Person(Document):
name = StringField()
relations = ListField(ReferenceField('self'))
relations = ListField(ReferenceField("self"))
def __repr__(self):
return "<Person: %s>" % self.name
@ -424,14 +432,17 @@ class FieldTest(unittest.TestCase):
daughter.relations.append(daughter)
daughter.save()
self.assertEqual("[<Person: Mother>, <Person: Daughter>]", "%s" % Person.objects())
self.assertEqual(
"[<Person: Mother>, <Person: Daughter>]", "%s" % Person.objects()
)
def test_circular_tree_reference(self):
"""Ensure you can handle circular references with more than one level
"""
class Other(EmbeddedDocument):
name = StringField()
friends = ListField(ReferenceField('Person'))
friends = ListField(ReferenceField("Person"))
class Person(Document):
name = StringField()
@ -443,8 +454,8 @@ class FieldTest(unittest.TestCase):
Person.drop_collection()
paul = Person(name="Paul").save()
maria = Person(name="Maria").save()
julia = Person(name='Julia').save()
anna = Person(name='Anna').save()
julia = Person(name="Julia").save()
anna = Person(name="Anna").save()
paul.other.friends = [maria, julia, anna]
paul.other.name = "Paul's friends"
@ -464,11 +475,10 @@ class FieldTest(unittest.TestCase):
self.assertEqual(
"[<Person: Paul>, <Person: Maria>, <Person: Julia>, <Person: Anna>]",
"%s" % Person.objects()
"%s" % Person.objects(),
)
def test_generic_reference(self):
class UserA(Document):
name = StringField()
@ -488,13 +498,13 @@ class FieldTest(unittest.TestCase):
members = []
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a = UserA(name="User A %s" % i)
a.save()
b = UserB(name='User B %s' % i)
b = UserB(name="User B %s" % i)
b.save()
c = UserC(name='User C %s' % i)
c = UserC(name="User C %s" % i)
c.save()
members += [a, b, c]
@ -518,7 +528,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertIn('User', m.__class__.__name__)
self.assertIn("User", m.__class__.__name__)
# Document select_related
with query_counter() as q:
@ -534,7 +544,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertIn('User', m.__class__.__name__)
self.assertIn("User", m.__class__.__name__)
# Queryset select_related
with query_counter() as q:
@ -551,8 +561,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertIn('User', m.__class__.__name__)
self.assertIn("User", m.__class__.__name__)
def test_generic_reference_orphan_dbref(self):
"""Ensure that generic orphan DBRef items in ListFields are dereferenced.
@ -577,13 +586,13 @@ class FieldTest(unittest.TestCase):
members = []
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a = UserA(name="User A %s" % i)
a.save()
b = UserB(name='User B %s' % i)
b = UserB(name="User B %s" % i)
b.save()
c = UserC(name='User C %s' % i)
c = UserC(name="User C %s" % i)
c.save()
members += [a, b, c]
@ -602,11 +611,11 @@ class FieldTest(unittest.TestCase):
[m for m in group_obj.members]
self.assertEqual(q, 4)
self.assertTrue(group_obj._data['members']._dereferenced)
self.assertTrue(group_obj._data["members"]._dereferenced)
[m for m in group_obj.members]
self.assertEqual(q, 4)
self.assertTrue(group_obj._data['members']._dereferenced)
self.assertTrue(group_obj._data["members"]._dereferenced)
UserA.drop_collection()
UserB.drop_collection()
@ -614,7 +623,6 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
def test_list_field_complex(self):
class UserA(Document):
name = StringField()
@ -634,13 +642,13 @@ class FieldTest(unittest.TestCase):
members = []
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a = UserA(name="User A %s" % i)
a.save()
b = UserB(name='User B %s' % i)
b = UserB(name="User B %s" % i)
b.save()
c = UserC(name='User C %s' % i)
c = UserC(name="User C %s" % i)
c.save()
members += [a, b, c]
@ -664,7 +672,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertIn('User', m.__class__.__name__)
self.assertIn("User", m.__class__.__name__)
# Document select_related
with query_counter() as q:
@ -680,7 +688,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertIn('User', m.__class__.__name__)
self.assertIn("User", m.__class__.__name__)
# Queryset select_related
with query_counter() as q:
@ -697,7 +705,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertIn('User', m.__class__.__name__)
self.assertIn("User", m.__class__.__name__)
UserA.drop_collection()
UserB.drop_collection()
@ -705,7 +713,6 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
def test_map_field_reference(self):
class User(Document):
name = StringField()
@ -717,7 +724,7 @@ class FieldTest(unittest.TestCase):
members = []
for i in range(1, 51):
user = User(name='user %s' % i)
user = User(name="user %s" % i)
user.save()
members.append(user)
@ -752,7 +759,7 @@ class FieldTest(unittest.TestCase):
for k, m in iteritems(group_obj.members):
self.assertIsInstance(m, User)
# Queryset select_related
# Queryset select_related
with query_counter() as q:
self.assertEqual(q, 0)
@ -770,7 +777,6 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
def test_dict_field(self):
class UserA(Document):
name = StringField()
@ -790,13 +796,13 @@ class FieldTest(unittest.TestCase):
members = []
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a = UserA(name="User A %s" % i)
a.save()
b = UserB(name='User B %s' % i)
b = UserB(name="User B %s" % i)
b.save()
c = UserC(name='User C %s' % i)
c = UserC(name="User C %s" % i)
c.save()
members += [a, b, c]
@ -819,7 +825,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for k, m in iteritems(group_obj.members):
self.assertIn('User', m.__class__.__name__)
self.assertIn("User", m.__class__.__name__)
# Document select_related
with query_counter() as q:
@ -835,7 +841,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for k, m in iteritems(group_obj.members):
self.assertIn('User', m.__class__.__name__)
self.assertIn("User", m.__class__.__name__)
# Queryset select_related
with query_counter() as q:
@ -852,7 +858,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for k, m in iteritems(group_obj.members):
self.assertIn('User', m.__class__.__name__)
self.assertIn("User", m.__class__.__name__)
Group.objects.delete()
Group().save()
@ -873,10 +879,9 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
def test_dict_field_no_field_inheritance(self):
class UserA(Document):
name = StringField()
meta = {'allow_inheritance': False}
meta = {"allow_inheritance": False}
class Group(Document):
members = DictField()
@ -886,7 +891,7 @@ class FieldTest(unittest.TestCase):
members = []
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a = UserA(name="User A %s" % i)
a.save()
members += [a]
@ -949,7 +954,6 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
def test_generic_reference_map_field(self):
class UserA(Document):
name = StringField()
@ -969,13 +973,13 @@ class FieldTest(unittest.TestCase):
members = []
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a = UserA(name="User A %s" % i)
a.save()
b = UserB(name='User B %s' % i)
b = UserB(name="User B %s" % i)
b.save()
c = UserC(name='User C %s' % i)
c = UserC(name="User C %s" % i)
c.save()
members += [a, b, c]
@ -998,7 +1002,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for k, m in iteritems(group_obj.members):
self.assertIn('User', m.__class__.__name__)
self.assertIn("User", m.__class__.__name__)
# Document select_related
with query_counter() as q:
@ -1014,7 +1018,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for k, m in iteritems(group_obj.members):
self.assertIn('User', m.__class__.__name__)
self.assertIn("User", m.__class__.__name__)
# Queryset select_related
with query_counter() as q:
@ -1031,7 +1035,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for k, m in iteritems(group_obj.members):
self.assertIn('User', m.__class__.__name__)
self.assertIn("User", m.__class__.__name__)
Group.objects.delete()
Group().save()
@ -1051,7 +1055,6 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
def test_multidirectional_lists(self):
class Asset(Document):
name = StringField(max_length=250, required=True)
path = StringField()
@ -1062,10 +1065,10 @@ class FieldTest(unittest.TestCase):
Asset.drop_collection()
root = Asset(name='', path="/", title="Site Root")
root = Asset(name="", path="/", title="Site Root")
root.save()
company = Asset(name='company', title='Company', parent=root, parents=[root])
company = Asset(name="company", title="Company", parent=root, parents=[root])
company.save()
root.children = [company]
@ -1076,7 +1079,6 @@ class FieldTest(unittest.TestCase):
self.assertEqual(company.parents, [root])
def test_dict_in_dbref_instance(self):
class Person(Document):
name = StringField(max_length=250, required=True)
@ -1087,34 +1089,35 @@ class FieldTest(unittest.TestCase):
Person.drop_collection()
Room.drop_collection()
bob = Person.objects.create(name='Bob')
bob = Person.objects.create(name="Bob")
bob.save()
sarah = Person.objects.create(name='Sarah')
sarah = Person.objects.create(name="Sarah")
sarah.save()
room_101 = Room.objects.create(number="101")
room_101.staffs_with_position = [
{'position_key': 'window', 'staff': sarah},
{'position_key': 'door', 'staff': bob.to_dbref()}]
{"position_key": "window", "staff": sarah},
{"position_key": "door", "staff": bob.to_dbref()},
]
room_101.save()
room = Room.objects.first().select_related()
self.assertEqual(room.staffs_with_position[0]['staff'], sarah)
self.assertEqual(room.staffs_with_position[1]['staff'], bob)
self.assertEqual(room.staffs_with_position[0]["staff"], sarah)
self.assertEqual(room.staffs_with_position[1]["staff"], bob)
def test_document_reload_no_inheritance(self):
class Foo(Document):
meta = {'allow_inheritance': False}
bar = ReferenceField('Bar')
baz = ReferenceField('Baz')
meta = {"allow_inheritance": False}
bar = ReferenceField("Bar")
baz = ReferenceField("Baz")
class Bar(Document):
meta = {'allow_inheritance': False}
msg = StringField(required=True, default='Blammo!')
meta = {"allow_inheritance": False}
msg = StringField(required=True, default="Blammo!")
class Baz(Document):
meta = {'allow_inheritance': False}
msg = StringField(required=True, default='Kaboom!')
meta = {"allow_inheritance": False}
msg = StringField(required=True, default="Kaboom!")
Foo.drop_collection()
Bar.drop_collection()
@ -1138,11 +1141,14 @@ class FieldTest(unittest.TestCase):
Ensure reloading a document with multiple similar id
in different collections doesn't mix them.
"""
class Topic(Document):
id = IntField(primary_key=True)
class User(Document):
id = IntField(primary_key=True)
name = StringField()
class Message(Document):
id = IntField(primary_key=True)
topic = ReferenceField(Topic)
@ -1154,23 +1160,24 @@ class FieldTest(unittest.TestCase):
# All objects share the same id, but each in a different collection
topic = Topic(id=1).save()
user = User(id=1, name='user-name').save()
user = User(id=1, name="user-name").save()
Message(id=1, topic=topic, author=user).save()
concurrent_change_user = User.objects.get(id=1)
concurrent_change_user.name = 'new-name'
concurrent_change_user.name = "new-name"
concurrent_change_user.save()
self.assertNotEqual(user.name, 'new-name')
self.assertNotEqual(user.name, "new-name")
msg = Message.objects.get(id=1)
msg.reload()
self.assertEqual(msg.topic, topic)
self.assertEqual(msg.author, user)
self.assertEqual(msg.author.name, 'new-name')
self.assertEqual(msg.author.name, "new-name")
def test_list_lookup_not_checked_in_map(self):
"""Ensure we dereference list data correctly
"""
class Comment(Document):
id = IntField(primary_key=True)
text = StringField()
@ -1182,8 +1189,8 @@ class FieldTest(unittest.TestCase):
Comment.drop_collection()
Message.drop_collection()
c1 = Comment(id=0, text='zero').save()
c2 = Comment(id=1, text='one').save()
c1 = Comment(id=0, text="zero").save()
c2 = Comment(id=1, text="one").save()
Message(id=1, comments=[c1, c2]).save()
msg = Message.objects.get(id=1)
@ -1193,6 +1200,7 @@ class FieldTest(unittest.TestCase):
def test_list_item_dereference_dref_false_save_doesnt_cause_extra_queries(self):
"""Ensure that DBRef items in ListFields are dereferenced.
"""
class User(Document):
name = StringField()
@ -1204,7 +1212,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
for i in range(1, 51):
User(name='user %s' % i).save()
User(name="user %s" % i).save()
Group(name="Test", members=User.objects).save()
@ -1222,6 +1230,7 @@ class FieldTest(unittest.TestCase):
def test_list_item_dereference_dref_true_save_doesnt_cause_extra_queries(self):
"""Ensure that DBRef items in ListFields are dereferenced.
"""
class User(Document):
name = StringField()
@ -1233,7 +1242,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
for i in range(1, 51):
User(name='user %s' % i).save()
User(name="user %s" % i).save()
Group(name="Test", members=User.objects).save()
@ -1249,7 +1258,6 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 2)
def test_generic_reference_save_doesnt_cause_extra_queries(self):
class UserA(Document):
name = StringField()
@ -1270,9 +1278,9 @@ class FieldTest(unittest.TestCase):
members = []
for i in range(1, 51):
a = UserA(name='User A %s' % i).save()
b = UserB(name='User B %s' % i).save()
c = UserC(name='User C %s' % i).save()
a = UserA(name="User A %s" % i).save()
b = UserB(name="User B %s" % i).save()
c = UserC(name="User C %s" % i).save()
members += [a, b, c]
@ -1292,7 +1300,7 @@ class FieldTest(unittest.TestCase):
def test_objectid_reference_across_databases(self):
# mongoenginetest - Is default connection alias from setUp()
# Register Aliases
register_connection('testdb-1', 'mongoenginetest2')
register_connection("testdb-1", "mongoenginetest2")
class User(Document):
name = StringField()
@ -1311,16 +1319,17 @@ class FieldTest(unittest.TestCase):
# Can't use query_counter across databases - so test the _data object
book = Book.objects.first()
self.assertNotIsInstance(book._data['author'], User)
self.assertNotIsInstance(book._data["author"], User)
book.select_related()
self.assertIsInstance(book._data['author'], User)
self.assertIsInstance(book._data["author"], User)
def test_non_ascii_pk(self):
"""
Ensure that dbref conversion to string does not fail when
non-ascii characters are used in primary key
"""
class Brand(Document):
title = StringField(max_length=255, primary_key=True)
@ -1341,7 +1350,7 @@ class FieldTest(unittest.TestCase):
def test_dereferencing_embedded_listfield_referencefield(self):
class Tag(Document):
meta = {'collection': 'tags'}
meta = {"collection": "tags"}
name = StringField()
class Post(EmbeddedDocument):
@ -1349,22 +1358,21 @@ class FieldTest(unittest.TestCase):
tags = ListField(ReferenceField("Tag", dbref=True))
class Page(Document):
meta = {'collection': 'pages'}
meta = {"collection": "pages"}
tags = ListField(ReferenceField("Tag", dbref=True))
posts = ListField(EmbeddedDocumentField(Post))
Tag.drop_collection()
Page.drop_collection()
tag = Tag(name='test').save()
post = Post(body='test body', tags=[tag])
tag = Tag(name="test").save()
post = Post(body="test body", tags=[tag])
Page(tags=[tag], posts=[post]).save()
page = Page.objects.first()
self.assertEqual(page.tags[0], page.posts[0].tags[0])
def test_select_related_follows_embedded_referencefields(self):
class Song(Document):
title = StringField()
@ -1390,5 +1398,5 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 2)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -4,7 +4,7 @@ from pymongo import ReadPreference
from pymongo import MongoClient
import mongoengine
from mongoengine.connection import MongoEngineConnectionError
from mongoengine.connection import ConnectionFailure
CONN_CLASS = MongoClient
@ -12,7 +12,6 @@ READ_PREF = ReadPreference.SECONDARY
class ConnectionTest(unittest.TestCase):
def setUp(self):
mongoengine.connection._connection_settings = {}
mongoengine.connection._connections = {}
@ -28,10 +27,12 @@ class ConnectionTest(unittest.TestCase):
"""
try:
conn = mongoengine.connect(db='mongoenginetest',
host="mongodb://localhost/mongoenginetest?replicaSet=rs",
read_preference=READ_PREF)
except MongoEngineConnectionError as e:
conn = mongoengine.connect(
db="mongoenginetest",
host="mongodb://localhost/mongoenginetest?replicaSet=rs",
read_preference=READ_PREF,
)
except ConnectionFailure as e:
return
if not isinstance(conn, CONN_CLASS):
@ -41,5 +42,5 @@ class ConnectionTest(unittest.TestCase):
self.assertEqual(conn.read_preference, READ_PREF)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -20,7 +20,7 @@ class SignalTests(unittest.TestCase):
return signal_output
def setUp(self):
connect(db='mongoenginetest')
connect(db="mongoenginetest")
class Author(Document):
# Make the id deterministic for easier testing
@ -32,60 +32,63 @@ class SignalTests(unittest.TestCase):
@classmethod
def pre_init(cls, sender, document, *args, **kwargs):
signal_output.append('pre_init signal, %s' % cls.__name__)
signal_output.append(kwargs['values'])
signal_output.append("pre_init signal, %s" % cls.__name__)
signal_output.append(kwargs["values"])
@classmethod
def post_init(cls, sender, document, **kwargs):
signal_output.append('post_init signal, %s, document._created = %s' % (document, document._created))
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)
signal_output.append("pre_save signal, %s" % document)
signal_output.append(kwargs)
@classmethod
def pre_save_post_validation(cls, sender, document, **kwargs):
signal_output.append('pre_save_post_validation signal, %s' % document)
if kwargs.pop('created', False):
signal_output.append('Is created')
signal_output.append("pre_save_post_validation signal, %s" % document)
if kwargs.pop("created", False):
signal_output.append("Is created")
else:
signal_output.append('Is updated')
signal_output.append("Is updated")
signal_output.append(kwargs)
@classmethod
def post_save(cls, sender, document, **kwargs):
dirty_keys = document._delta()[0].keys() + document._delta()[1].keys()
signal_output.append('post_save signal, %s' % document)
signal_output.append('post_save dirty keys, %s' % dirty_keys)
if kwargs.pop('created', False):
signal_output.append('Is created')
signal_output.append("post_save signal, %s" % document)
signal_output.append("post_save dirty keys, %s" % dirty_keys)
if kwargs.pop("created", False):
signal_output.append("Is created")
else:
signal_output.append('Is updated')
signal_output.append("Is updated")
signal_output.append(kwargs)
@classmethod
def pre_delete(cls, sender, document, **kwargs):
signal_output.append('pre_delete signal, %s' % document)
signal_output.append("pre_delete signal, %s" % document)
signal_output.append(kwargs)
@classmethod
def post_delete(cls, sender, document, **kwargs):
signal_output.append('post_delete signal, %s' % document)
signal_output.append("post_delete signal, %s" % document)
signal_output.append(kwargs)
@classmethod
def pre_bulk_insert(cls, sender, documents, **kwargs):
signal_output.append('pre_bulk_insert signal, %s' % documents)
signal_output.append("pre_bulk_insert signal, %s" % documents)
signal_output.append(kwargs)
@classmethod
def post_bulk_insert(cls, sender, documents, **kwargs):
signal_output.append('post_bulk_insert signal, %s' % documents)
if kwargs.pop('loaded', False):
signal_output.append('Is loaded')
signal_output.append("post_bulk_insert signal, %s" % documents)
if kwargs.pop("loaded", False):
signal_output.append("Is loaded")
else:
signal_output.append('Not loaded')
signal_output.append("Not loaded")
signal_output.append(kwargs)
self.Author = Author
@ -101,12 +104,12 @@ class SignalTests(unittest.TestCase):
@classmethod
def pre_delete(cls, sender, document, **kwargs):
signal_output.append('pre_delete signal, %s' % document)
signal_output.append("pre_delete signal, %s" % document)
signal_output.append(kwargs)
@classmethod
def post_delete(cls, sender, document, **kwargs):
signal_output.append('post_delete signal, %s' % document)
signal_output.append("post_delete signal, %s" % document)
signal_output.append(kwargs)
self.Another = Another
@ -117,11 +120,11 @@ class SignalTests(unittest.TestCase):
@classmethod
def post_save(cls, sender, document, **kwargs):
if 'created' in kwargs:
if kwargs['created']:
signal_output.append('Is created')
if "created" in kwargs:
if kwargs["created"]:
signal_output.append("Is created")
else:
signal_output.append('Is updated')
signal_output.append("Is updated")
self.ExplicitId = ExplicitId
ExplicitId.drop_collection()
@ -136,9 +139,13 @@ class SignalTests(unittest.TestCase):
@classmethod
def pre_bulk_insert(cls, sender, documents, **kwargs):
signal_output.append('pre_bulk_insert signal, %s' %
[(doc, {'active': documents[n].active})
for n, doc in enumerate(documents)])
signal_output.append(
"pre_bulk_insert signal, %s"
% [
(doc, {"active": documents[n].active})
for n, doc in enumerate(documents)
]
)
# make changes here, this is just an example -
# it could be anything that needs pre-validation or looks-ups before bulk bulk inserting
@ -149,13 +156,17 @@ class SignalTests(unittest.TestCase):
@classmethod
def post_bulk_insert(cls, sender, documents, **kwargs):
signal_output.append('post_bulk_insert signal, %s' %
[(doc, {'active': documents[n].active})
for n, doc in enumerate(documents)])
if kwargs.pop('loaded', False):
signal_output.append('Is loaded')
signal_output.append(
"post_bulk_insert signal, %s"
% [
(doc, {"active": documents[n].active})
for n, doc in enumerate(documents)
]
)
if kwargs.pop("loaded", False):
signal_output.append("Is loaded")
else:
signal_output.append('Not loaded')
signal_output.append("Not loaded")
signal_output.append(kwargs)
self.Post = Post
@ -178,7 +189,9 @@ class SignalTests(unittest.TestCase):
signals.pre_init.connect(Author.pre_init, sender=Author)
signals.post_init.connect(Author.post_init, sender=Author)
signals.pre_save.connect(Author.pre_save, sender=Author)
signals.pre_save_post_validation.connect(Author.pre_save_post_validation, sender=Author)
signals.pre_save_post_validation.connect(
Author.pre_save_post_validation, sender=Author
)
signals.post_save.connect(Author.post_save, sender=Author)
signals.pre_delete.connect(Author.pre_delete, sender=Author)
signals.post_delete.connect(Author.post_delete, sender=Author)
@ -199,7 +212,9 @@ class SignalTests(unittest.TestCase):
signals.post_delete.disconnect(self.Author.post_delete)
signals.pre_delete.disconnect(self.Author.pre_delete)
signals.post_save.disconnect(self.Author.post_save)
signals.pre_save_post_validation.disconnect(self.Author.pre_save_post_validation)
signals.pre_save_post_validation.disconnect(
self.Author.pre_save_post_validation
)
signals.pre_save.disconnect(self.Author.pre_save)
signals.pre_bulk_insert.disconnect(self.Author.pre_bulk_insert)
signals.post_bulk_insert.disconnect(self.Author.post_bulk_insert)
@ -236,203 +251,236 @@ class SignalTests(unittest.TestCase):
""" Model saves should throw some signals. """
def create_author():
self.Author(name='Bill Shakespeare')
self.Author(name="Bill Shakespeare")
def bulk_create_author_with_load():
a1 = self.Author(name='Bill Shakespeare')
a1 = self.Author(name="Bill Shakespeare")
self.Author.objects.insert([a1], load_bulk=True)
def bulk_create_author_without_load():
a1 = self.Author(name='Bill Shakespeare')
a1 = self.Author(name="Bill Shakespeare")
self.Author.objects.insert([a1], load_bulk=False)
def load_existing_author():
a = self.Author(name='Bill Shakespeare')
a = self.Author(name="Bill Shakespeare")
a.save()
self.get_signal_output(lambda: None) # eliminate signal output
a1 = self.Author.objects(name='Bill Shakespeare')[0]
a1 = self.Author.objects(name="Bill Shakespeare")[0]
self.assertEqual(self.get_signal_output(create_author), [
"pre_init signal, Author",
{'name': 'Bill Shakespeare'},
"post_init signal, Bill Shakespeare, document._created = True",
])
self.assertEqual(
self.get_signal_output(create_author),
[
"pre_init signal, Author",
{"name": "Bill Shakespeare"},
"post_init signal, Bill Shakespeare, document._created = True",
],
)
a1 = self.Author(name='Bill Shakespeare')
self.assertEqual(self.get_signal_output(a1.save), [
"pre_save signal, Bill Shakespeare",
{},
"pre_save_post_validation signal, Bill Shakespeare",
"Is created",
{},
"post_save signal, Bill Shakespeare",
"post_save dirty keys, ['name']",
"Is created",
{}
])
a1 = self.Author(name="Bill Shakespeare")
self.assertEqual(
self.get_signal_output(a1.save),
[
"pre_save signal, Bill Shakespeare",
{},
"pre_save_post_validation signal, Bill Shakespeare",
"Is created",
{},
"post_save signal, Bill Shakespeare",
"post_save dirty keys, ['name']",
"Is created",
{},
],
)
a1.reload()
a1.name = 'William Shakespeare'
self.assertEqual(self.get_signal_output(a1.save), [
"pre_save signal, William Shakespeare",
{},
"pre_save_post_validation signal, William Shakespeare",
"Is updated",
{},
"post_save signal, William Shakespeare",
"post_save dirty keys, ['name']",
"Is updated",
{}
])
a1.name = "William Shakespeare"
self.assertEqual(
self.get_signal_output(a1.save),
[
"pre_save signal, William Shakespeare",
{},
"pre_save_post_validation signal, William Shakespeare",
"Is updated",
{},
"post_save signal, William Shakespeare",
"post_save dirty keys, ['name']",
"Is updated",
{},
],
)
self.assertEqual(self.get_signal_output(a1.delete), [
'pre_delete signal, William Shakespeare',
{},
'post_delete signal, William Shakespeare',
{}
])
self.assertEqual(
self.get_signal_output(a1.delete),
[
"pre_delete signal, William Shakespeare",
{},
"post_delete signal, William Shakespeare",
{},
],
)
self.assertEqual(self.get_signal_output(load_existing_author), [
"pre_init signal, Author",
{'id': 2, 'name': 'Bill Shakespeare'},
"post_init signal, Bill Shakespeare, document._created = False"
])
self.assertEqual(
self.get_signal_output(load_existing_author),
[
"pre_init signal, Author",
{"id": 2, "name": "Bill Shakespeare"},
"post_init signal, Bill Shakespeare, document._created = False",
],
)
self.assertEqual(self.get_signal_output(bulk_create_author_with_load), [
'pre_init signal, Author',
{'name': 'Bill Shakespeare'},
'post_init signal, Bill Shakespeare, document._created = True',
'pre_bulk_insert signal, [<Author: Bill Shakespeare>]',
{},
'pre_init signal, Author',
{'id': 3, 'name': 'Bill Shakespeare'},
'post_init signal, Bill Shakespeare, document._created = False',
'post_bulk_insert signal, [<Author: Bill Shakespeare>]',
'Is loaded',
{}
])
self.assertEqual(
self.get_signal_output(bulk_create_author_with_load),
[
"pre_init signal, Author",
{"name": "Bill Shakespeare"},
"post_init signal, Bill Shakespeare, document._created = True",
"pre_bulk_insert signal, [<Author: Bill Shakespeare>]",
{},
"pre_init signal, Author",
{"id": 3, "name": "Bill Shakespeare"},
"post_init signal, Bill Shakespeare, document._created = False",
"post_bulk_insert signal, [<Author: Bill Shakespeare>]",
"Is loaded",
{},
],
)
self.assertEqual(self.get_signal_output(bulk_create_author_without_load), [
"pre_init signal, Author",
{'name': 'Bill Shakespeare'},
"post_init signal, Bill Shakespeare, document._created = True",
"pre_bulk_insert signal, [<Author: Bill Shakespeare>]",
{},
"post_bulk_insert signal, [<Author: Bill Shakespeare>]",
"Not loaded",
{}
])
self.assertEqual(
self.get_signal_output(bulk_create_author_without_load),
[
"pre_init signal, Author",
{"name": "Bill Shakespeare"},
"post_init signal, Bill Shakespeare, document._created = True",
"pre_bulk_insert signal, [<Author: Bill Shakespeare>]",
{},
"post_bulk_insert signal, [<Author: Bill Shakespeare>]",
"Not loaded",
{},
],
)
def test_signal_kwargs(self):
""" Make sure signal_kwargs is passed to signals calls. """
def live_and_let_die():
a = self.Author(name='Bill Shakespeare')
a.save(signal_kwargs={'live': True, 'die': False})
a.delete(signal_kwargs={'live': False, 'die': True})
a = self.Author(name="Bill Shakespeare")
a.save(signal_kwargs={"live": True, "die": False})
a.delete(signal_kwargs={"live": False, "die": True})
self.assertEqual(self.get_signal_output(live_and_let_die), [
"pre_init signal, Author",
{'name': 'Bill Shakespeare'},
"post_init signal, Bill Shakespeare, document._created = True",
"pre_save signal, Bill Shakespeare",
{'die': False, 'live': True},
"pre_save_post_validation signal, Bill Shakespeare",
"Is created",
{'die': False, 'live': True},
"post_save signal, Bill Shakespeare",
"post_save dirty keys, ['name']",
"Is created",
{'die': False, 'live': True},
'pre_delete signal, Bill Shakespeare',
{'die': True, 'live': False},
'post_delete signal, Bill Shakespeare',
{'die': True, 'live': False}
])
self.assertEqual(
self.get_signal_output(live_and_let_die),
[
"pre_init signal, Author",
{"name": "Bill Shakespeare"},
"post_init signal, Bill Shakespeare, document._created = True",
"pre_save signal, Bill Shakespeare",
{"die": False, "live": True},
"pre_save_post_validation signal, Bill Shakespeare",
"Is created",
{"die": False, "live": True},
"post_save signal, Bill Shakespeare",
"post_save dirty keys, ['name']",
"Is created",
{"die": False, "live": True},
"pre_delete signal, Bill Shakespeare",
{"die": True, "live": False},
"post_delete signal, Bill Shakespeare",
{"die": True, "live": False},
],
)
def bulk_create_author():
a1 = self.Author(name='Bill Shakespeare')
self.Author.objects.insert([a1], signal_kwargs={'key': True})
a1 = self.Author(name="Bill Shakespeare")
self.Author.objects.insert([a1], signal_kwargs={"key": True})
self.assertEqual(self.get_signal_output(bulk_create_author), [
'pre_init signal, Author',
{'name': 'Bill Shakespeare'},
'post_init signal, Bill Shakespeare, document._created = True',
'pre_bulk_insert signal, [<Author: Bill Shakespeare>]',
{'key': True},
'pre_init signal, Author',
{'id': 2, 'name': 'Bill Shakespeare'},
'post_init signal, Bill Shakespeare, document._created = False',
'post_bulk_insert signal, [<Author: Bill Shakespeare>]',
'Is loaded',
{'key': True}
])
self.assertEqual(
self.get_signal_output(bulk_create_author),
[
"pre_init signal, Author",
{"name": "Bill Shakespeare"},
"post_init signal, Bill Shakespeare, document._created = True",
"pre_bulk_insert signal, [<Author: Bill Shakespeare>]",
{"key": True},
"pre_init signal, Author",
{"id": 2, "name": "Bill Shakespeare"},
"post_init signal, Bill Shakespeare, document._created = False",
"post_bulk_insert signal, [<Author: Bill Shakespeare>]",
"Is loaded",
{"key": True},
],
)
def test_queryset_delete_signals(self):
""" Queryset delete should throw some signals. """
self.Another(name='Bill Shakespeare').save()
self.assertEqual(self.get_signal_output(self.Another.objects.delete), [
'pre_delete signal, Bill Shakespeare',
{},
'post_delete signal, Bill Shakespeare',
{}
])
self.Another(name="Bill Shakespeare").save()
self.assertEqual(
self.get_signal_output(self.Another.objects.delete),
[
"pre_delete signal, Bill Shakespeare",
{},
"post_delete signal, Bill Shakespeare",
{},
],
)
def test_signals_with_explicit_doc_ids(self):
""" Model saves must have a created flag the first time."""
ei = self.ExplicitId(id=123)
# post save must received the created flag, even if there's already
# an object id present
self.assertEqual(self.get_signal_output(ei.save), ['Is created'])
self.assertEqual(self.get_signal_output(ei.save), ["Is created"])
# second time, it must be an update
self.assertEqual(self.get_signal_output(ei.save), ['Is updated'])
self.assertEqual(self.get_signal_output(ei.save), ["Is updated"])
def test_signals_with_switch_collection(self):
ei = self.ExplicitId(id=123)
ei.switch_collection("explicit__1")
self.assertEqual(self.get_signal_output(ei.save), ['Is created'])
self.assertEqual(self.get_signal_output(ei.save), ["Is created"])
ei.switch_collection("explicit__1")
self.assertEqual(self.get_signal_output(ei.save), ['Is updated'])
self.assertEqual(self.get_signal_output(ei.save), ["Is updated"])
ei.switch_collection("explicit__1", keep_created=False)
self.assertEqual(self.get_signal_output(ei.save), ['Is created'])
self.assertEqual(self.get_signal_output(ei.save), ["Is created"])
ei.switch_collection("explicit__1", keep_created=False)
self.assertEqual(self.get_signal_output(ei.save), ['Is created'])
self.assertEqual(self.get_signal_output(ei.save), ["Is created"])
def test_signals_with_switch_db(self):
connect('mongoenginetest')
register_connection('testdb-1', 'mongoenginetest2')
connect("mongoenginetest")
register_connection("testdb-1", "mongoenginetest2")
ei = self.ExplicitId(id=123)
ei.switch_db("testdb-1")
self.assertEqual(self.get_signal_output(ei.save), ['Is created'])
self.assertEqual(self.get_signal_output(ei.save), ["Is created"])
ei.switch_db("testdb-1")
self.assertEqual(self.get_signal_output(ei.save), ['Is updated'])
self.assertEqual(self.get_signal_output(ei.save), ["Is updated"])
ei.switch_db("testdb-1", keep_created=False)
self.assertEqual(self.get_signal_output(ei.save), ['Is created'])
self.assertEqual(self.get_signal_output(ei.save), ["Is created"])
ei.switch_db("testdb-1", keep_created=False)
self.assertEqual(self.get_signal_output(ei.save), ['Is created'])
self.assertEqual(self.get_signal_output(ei.save), ["Is created"])
def test_signals_bulk_insert(self):
def bulk_set_active_post():
posts = [
self.Post(title='Post 1'),
self.Post(title='Post 2'),
self.Post(title='Post 3')
self.Post(title="Post 1"),
self.Post(title="Post 2"),
self.Post(title="Post 3"),
]
self.Post.objects.insert(posts)
results = self.get_signal_output(bulk_set_active_post)
self.assertEqual(results, [
"pre_bulk_insert signal, [(<Post: Post 1>, {'active': False}), (<Post: Post 2>, {'active': False}), (<Post: Post 3>, {'active': False})]",
{},
"post_bulk_insert signal, [(<Post: Post 1>, {'active': True}), (<Post: Post 2>, {'active': True}), (<Post: Post 3>, {'active': True})]",
'Is loaded',
{}
])
self.assertEqual(
results,
[
"pre_bulk_insert signal, [(<Post: Post 1>, {'active': False}), (<Post: Post 2>, {'active': False}), (<Post: Post 3>, {'active': False})]",
{},
"post_bulk_insert signal, [(<Post: Post 1>, {'active': True}), (<Post: Post 2>, {'active': True}), (<Post: Post 3>, {'active': True})]",
"Is loaded",
{},
],
)
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()

View File

@ -7,32 +7,33 @@ signal_output = []
class LazyRegexCompilerTest(unittest.TestCase):
def test_lazy_regex_compiler_verify_laziness_of_descriptor(self):
class UserEmail(object):
EMAIL_REGEX = LazyRegexCompiler('@', flags=32)
EMAIL_REGEX = LazyRegexCompiler("@", flags=32)
descriptor = UserEmail.__dict__['EMAIL_REGEX']
descriptor = UserEmail.__dict__["EMAIL_REGEX"]
self.assertIsNone(descriptor._compiled_regex)
regex = UserEmail.EMAIL_REGEX
self.assertEqual(regex, re.compile('@', flags=32))
self.assertEqual(regex.search('user@domain.com').group(), '@')
self.assertEqual(regex, re.compile("@", flags=32))
self.assertEqual(regex.search("user@domain.com").group(), "@")
user_email = UserEmail()
self.assertIs(user_email.EMAIL_REGEX, UserEmail.EMAIL_REGEX)
def test_lazy_regex_compiler_verify_cannot_set_descriptor_on_instance(self):
class UserEmail(object):
EMAIL_REGEX = LazyRegexCompiler('@')
EMAIL_REGEX = LazyRegexCompiler("@")
user_email = UserEmail()
with self.assertRaises(AttributeError):
user_email.EMAIL_REGEX = re.compile('@')
user_email.EMAIL_REGEX = re.compile("@")
def test_lazy_regex_compiler_verify_can_override_class_attr(self):
class UserEmail(object):
EMAIL_REGEX = LazyRegexCompiler('@')
EMAIL_REGEX = LazyRegexCompiler("@")
UserEmail.EMAIL_REGEX = re.compile('cookies')
self.assertEqual(UserEmail.EMAIL_REGEX.search('Cake & cookies').group(), 'cookies')
UserEmail.EMAIL_REGEX = re.compile("cookies")
self.assertEqual(
UserEmail.EMAIL_REGEX.search("Cake & cookies").group(), "cookies"
)

View File

@ -8,7 +8,7 @@ from mongoengine.connection import get_db, disconnect_all
from mongoengine.mongodb_support import get_mongodb_version
MONGO_TEST_DB = 'mongoenginetest' # standard name for the test database
MONGO_TEST_DB = "mongoenginetest" # standard name for the test database
class MongoDBTestCase(unittest.TestCase):
@ -53,12 +53,15 @@ def _decorated_with_ver_requirement(func, mongo_version_req, oper):
: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 oper(mongodb_v, mongo_version_req):
return func(*args, **kwargs)
raise SkipTest('Needs MongoDB v{}+'.format('.'.join(str(n) for n in mongo_version_req)))
raise SkipTest(
"Needs MongoDB v{}+".format(".".join(str(n) for n in mongo_version_req))
)
_inner.__name__ = func.__name__
_inner.__doc__ = func.__doc__

View File

@ -1,12 +1,12 @@
[tox]
envlist = {py27,py35,pypy,pypy3}-{mg35,mg3x}
envlist = {py27,py35,pypy,pypy3}-{mg34,mg36}
[testenv]
commands =
python setup.py nosetests {posargs}
deps =
nose
mg34x: PyMongo>=3.4,<3.5
mg3x: PyMongo>=3.0,<3.7
mg34: pymongo>=3.4,<3.5
mg36: pymongo>=3.6,<3.7
setenv =
PYTHON_EGG_CACHE = {envdir}/python-eggs