Compare commits
4 Commits
cleanup-fi
...
test-conne
Author | SHA1 | Date | |
---|---|---|---|
|
4e8bb14131 | ||
|
9cc4fad614 | ||
|
2a486ee537 | ||
|
2579ed754f |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,4 +17,3 @@ tests/test_bugfix.py
|
|||||||
htmlcov/
|
htmlcov/
|
||||||
venv
|
venv
|
||||||
venv3
|
venv3
|
||||||
scratchpad
|
|
||||||
|
@@ -1,23 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
|
|
||||||
|
|
||||||
if [ "$MONGODB" = "2.4" ]; then
|
|
||||||
echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | sudo tee /etc/apt/sources.list.d/mongodb.list
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install mongodb-10gen=2.4.14
|
|
||||||
sudo service mongodb start
|
|
||||||
elif [ "$MONGODB" = "2.6" ]; then
|
|
||||||
echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | sudo tee /etc/apt/sources.list.d/mongodb.list
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install mongodb-org-server=2.6.12
|
|
||||||
# service should be started automatically
|
|
||||||
elif [ "$MONGODB" = "3.0" ]; then
|
|
||||||
echo "deb http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb.list
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install mongodb-org-server=3.0.14
|
|
||||||
# service should be started automatically
|
|
||||||
else
|
|
||||||
echo "Invalid MongoDB version, expected 2.4, 2.6, or 3.0."
|
|
||||||
exit 1
|
|
||||||
fi;
|
|
67
.travis.yml
67
.travis.yml
@@ -1,48 +1,28 @@
|
|||||||
# For full coverage, we'd have to test all supported Python, MongoDB, and
|
|
||||||
# PyMongo combinations. However, that would result in an overly long build
|
|
||||||
# with a very large number of jobs, hence we only test a subset of all the
|
|
||||||
# combinations:
|
|
||||||
# * MongoDB v2.4 & v3.0 are only tested against Python v2.7 & v3.5.
|
|
||||||
# * MongoDB v2.4 is tested against PyMongo v2.7 & v3.x.
|
|
||||||
# * MongoDB v3.0 is tested against PyMongo v3.x.
|
|
||||||
# * MongoDB v2.6 is currently the "main" version tested against Python v2.7,
|
|
||||||
# v3.5, PyPy & PyPy3, and PyMongo v2.7, v2.8 & v3.x.
|
|
||||||
#
|
|
||||||
# Reminder: Update README.rst if you change MongoDB versions we test.
|
|
||||||
|
|
||||||
language: python
|
language: python
|
||||||
|
|
||||||
python:
|
python:
|
||||||
- 2.7
|
- '2.7'
|
||||||
- 3.5
|
- '3.3'
|
||||||
|
- '3.4'
|
||||||
|
- '3.5'
|
||||||
- pypy
|
- pypy
|
||||||
- pypy3
|
- pypy3
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- MONGODB=2.6 PYMONGO=2.7
|
- PYMONGO=2.7
|
||||||
- MONGODB=2.6 PYMONGO=2.8
|
- PYMONGO=2.8
|
||||||
- MONGODB=2.6 PYMONGO=3.0
|
- PYMONGO=3.0
|
||||||
|
- PYMONGO=dev
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
# Finish the build as soon as one job fails
|
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
|
||||||
include:
|
|
||||||
- python: 2.7
|
|
||||||
env: MONGODB=2.4 PYMONGO=2.7
|
|
||||||
- python: 2.7
|
|
||||||
env: MONGODB=2.4 PYMONGO=3.0
|
|
||||||
- python: 2.7
|
|
||||||
env: MONGODB=3.0 PYMONGO=3.0
|
|
||||||
- python: 3.5
|
|
||||||
env: MONGODB=2.4 PYMONGO=2.7
|
|
||||||
- python: 3.5
|
|
||||||
env: MONGODB=2.4 PYMONGO=3.0
|
|
||||||
- python: 3.5
|
|
||||||
env: MONGODB=3.0 PYMONGO=3.0
|
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- bash .install_mongodb_on_travis.sh
|
- travis_retry sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
|
||||||
|
- echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' |
|
||||||
|
sudo tee /etc/apt/sources.list.d/mongodb.list
|
||||||
|
- travis_retry sudo apt-get update
|
||||||
|
- travis_retry sudo apt-get install mongodb-org-server
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- sudo apt-get install python-dev python3-dev libopenjpeg-dev zlib1g-dev libjpeg-turbo8-dev
|
- sudo apt-get install python-dev python3-dev libopenjpeg-dev zlib1g-dev libjpeg-turbo8-dev
|
||||||
@@ -50,17 +30,14 @@ install:
|
|||||||
python-tk
|
python-tk
|
||||||
- travis_retry pip install --upgrade pip
|
- travis_retry pip install --upgrade pip
|
||||||
- travis_retry pip install coveralls
|
- travis_retry pip install coveralls
|
||||||
- travis_retry pip install flake8 flake8-import-order
|
- travis_retry pip install flake8
|
||||||
- travis_retry pip install tox>=1.9
|
- travis_retry pip install tox>=1.9
|
||||||
- travis_retry pip install "virtualenv<14.0.0" # virtualenv>=14.0.0 has dropped Python 3.2 support (and pypy3 is based on py32)
|
- travis_retry pip install "virtualenv<14.0.0" # virtualenv>=14.0.0 has dropped Python 3.2 support (and pypy3 is based on py32)
|
||||||
- travis_retry tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -e test
|
- travis_retry tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -e test
|
||||||
|
|
||||||
# Cache dependencies installed via pip
|
|
||||||
cache: pip
|
|
||||||
|
|
||||||
# Run flake8 for py27
|
# Run flake8 for py27
|
||||||
before_script:
|
before_script:
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then flake8 .; else echo "flake8 only runs on py27"; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then tox -e flake8; fi
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- --with-coverage
|
- tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- --with-coverage
|
||||||
@@ -68,34 +45,22 @@ script:
|
|||||||
# For now only submit coveralls for Python v2.7. Python v3.x currently shows
|
# For now only submit coveralls for Python v2.7. Python v3.x currently shows
|
||||||
# 0% coverage. That's caused by 'use_2to3', which builds the py3-compatible
|
# 0% coverage. That's caused by 'use_2to3', which builds the py3-compatible
|
||||||
# code in a separate dir and runs tests on that.
|
# code in a separate dir and runs tests on that.
|
||||||
after_success:
|
after_script:
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --verbose; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --verbose; fi
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
irc: irc.freenode.org#mongoengine
|
irc: irc.freenode.org#mongoengine
|
||||||
|
|
||||||
# Only run builds on the master branch and GitHub releases (tagged as vX.Y.Z)
|
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
- /^v.*$/
|
- /^v.*$/
|
||||||
|
|
||||||
# Whenever a new release is created via GitHub, publish it on PyPI.
|
|
||||||
deploy:
|
deploy:
|
||||||
provider: pypi
|
provider: pypi
|
||||||
user: the_drow
|
user: the_drow
|
||||||
password:
|
password:
|
||||||
secure: QMyatmWBnC6ZN3XLW2+fTBDU4LQcp1m/LjR2/0uamyeUzWKdlOoh/Wx5elOgLwt/8N9ppdPeG83ose1jOz69l5G0MUMjv8n/RIcMFSpCT59tGYqn3kh55b0cIZXFT9ar+5cxlif6a5rS72IHm5li7QQyxexJIII6Uxp0kpvUmek=
|
secure: QMyatmWBnC6ZN3XLW2+fTBDU4LQcp1m/LjR2/0uamyeUzWKdlOoh/Wx5elOgLwt/8N9ppdPeG83ose1jOz69l5G0MUMjv8n/RIcMFSpCT59tGYqn3kh55b0cIZXFT9ar+5cxlif6a5rS72IHm5li7QQyxexJIII6Uxp0kpvUmek=
|
||||||
|
|
||||||
# create a source distribution and a pure python wheel for faster installs
|
|
||||||
distributions: "sdist bdist_wheel"
|
|
||||||
|
|
||||||
# only deploy on tagged commits (aka GitHub releases) and only for the
|
|
||||||
# parent repo's builds running Python 2.7 along with dev PyMongo (we run
|
|
||||||
# Travis against many different Python and PyMongo versions and we don't
|
|
||||||
# want the deploy to occur multiple times).
|
|
||||||
on:
|
on:
|
||||||
tags: true
|
tags: true
|
||||||
repo: MongoEngine/mongoengine
|
repo: MongoEngine/mongoengine
|
||||||
condition: "$PYMONGO = 3.0"
|
|
||||||
python: 2.7
|
|
||||||
|
@@ -29,20 +29,19 @@ Style Guide
|
|||||||
-----------
|
-----------
|
||||||
|
|
||||||
MongoEngine aims to follow `PEP8 <http://www.python.org/dev/peps/pep-0008/>`_
|
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
|
including 4 space indents. When possible we try to stick to 79 character line limits.
|
||||||
limits. However, screens got bigger and an ORM has a strong focus on
|
However, screens got bigger and an ORM has a strong focus on readability and
|
||||||
readability and if it can help, we accept 119 as maximum line length, in a
|
if it can help, we accept 119 as maximum line length, in a similar way as
|
||||||
similar way as `django does
|
`django does <https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#python-style>`_
|
||||||
<https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#python-style>`_
|
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
-------
|
-------
|
||||||
|
|
||||||
All tests are run on `Travis <http://travis-ci.org/MongoEngine/mongoengine>`_
|
All tests are run on `Travis <http://travis-ci.org/MongoEngine/mongoengine>`_
|
||||||
and any pull requests are automatically tested. Any pull requests without
|
and any pull requests are automatically tested by Travis. Any pull requests
|
||||||
tests will take longer to be integrated and might be refused.
|
without tests will take longer to be integrated and might be refused.
|
||||||
|
|
||||||
You may also submit a simple failing test as a pull request if you don't know
|
You may also submit a simple failing test as a PullRequest if you don't know
|
||||||
how to fix it, it will be easier for other people to work on it and it may get
|
how to fix it, it will be easier for other people to work on it and it may get
|
||||||
fixed faster.
|
fixed faster.
|
||||||
|
|
||||||
@@ -50,18 +49,13 @@ General Guidelines
|
|||||||
------------------
|
------------------
|
||||||
|
|
||||||
- Avoid backward breaking changes if at all possible.
|
- Avoid backward breaking changes if at all possible.
|
||||||
- If you *have* to introduce a breaking change, make it very clear in your
|
|
||||||
pull request's description. Also, describe how users of this package
|
|
||||||
should adapt to the breaking change in docs/upgrade.rst.
|
|
||||||
- Write inline documentation for new classes and methods.
|
- Write inline documentation for new classes and methods.
|
||||||
- Write tests and make sure they pass (make sure you have a mongod
|
- Write tests and make sure they pass (make sure you have a mongod
|
||||||
running on the default port, then execute ``python setup.py nosetests``
|
running on the default port, then execute ``python setup.py nosetests``
|
||||||
from the cmd line to run the test suite).
|
from the cmd line to run the test suite).
|
||||||
- Ensure tests pass on all supported Python, PyMongo, and MongoDB versions.
|
- Ensure tests pass on every Python and PyMongo versions.
|
||||||
You can test various Python and PyMongo versions locally by executing
|
You can test on these versions locally by executing ``tox``
|
||||||
``tox``. For different MongoDB versions, you can rely on our automated
|
- Add enhancements or problematic bug fixes to docs/changelog.rst
|
||||||
Travis tests.
|
|
||||||
- Add enhancements or problematic bug fixes to docs/changelog.rst.
|
|
||||||
- Add yourself to AUTHORS :)
|
- Add yourself to AUTHORS :)
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
@@ -75,6 +69,3 @@ just make your changes to the inline documentation of the appropriate
|
|||||||
branch and submit a `pull request <https://help.github.com/articles/using-pull-requests>`_.
|
branch and submit a `pull request <https://help.github.com/articles/using-pull-requests>`_.
|
||||||
You might also use the github `Edit <https://github.com/blog/844-forking-with-the-edit-button>`_
|
You might also use the github `Edit <https://github.com/blog/844-forking-with-the-edit-button>`_
|
||||||
button.
|
button.
|
||||||
|
|
||||||
If you want to test your documentation changes locally, you need to install
|
|
||||||
the ``sphinx`` package.
|
|
||||||
|
45
README.rst
45
README.rst
@@ -19,31 +19,23 @@ MongoEngine
|
|||||||
About
|
About
|
||||||
=====
|
=====
|
||||||
MongoEngine is a Python Object-Document Mapper for working with MongoDB.
|
MongoEngine is a Python Object-Document Mapper for working with MongoDB.
|
||||||
Documentation is available at https://mongoengine-odm.readthedocs.io - there
|
Documentation available at https://mongoengine-odm.readthedocs.io - there is currently
|
||||||
is currently a `tutorial <https://mongoengine-odm.readthedocs.io/tutorial.html>`_,
|
a `tutorial <https://mongoengine-odm.readthedocs.io/tutorial.html>`_, a `user guide
|
||||||
a `user guide <https://mongoengine-odm.readthedocs.io/guide/index.html>`_, and
|
<https://mongoengine-odm.readthedocs.io/guide/index.html>`_ and an `API reference
|
||||||
an `API reference <https://mongoengine-odm.readthedocs.io/apireference.html>`_.
|
<https://mongoengine-odm.readthedocs.io/apireference.html>`_.
|
||||||
|
|
||||||
Supported MongoDB Versions
|
|
||||||
==========================
|
|
||||||
MongoEngine is currently tested against MongoDB v2.4, v2.6, and v3.0. Future
|
|
||||||
versions should be supported as well, but aren't actively tested at the moment.
|
|
||||||
Make sure to open an issue or submit a pull request if you experience any
|
|
||||||
problems with MongoDB v3.2+.
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
We recommend the use of `virtualenv <https://virtualenv.pypa.io/>`_ and of
|
We recommend the use of `virtualenv <https://virtualenv.pypa.io/>`_ and of
|
||||||
`pip <https://pip.pypa.io/>`_. You can then use ``pip install -U mongoengine``.
|
`pip <https://pip.pypa.io/>`_. You can then use ``pip install -U mongoengine``.
|
||||||
You may also have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
|
You may also have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_ and thus
|
||||||
and thus you can use ``easy_install -U mongoengine``. Otherwise, you can download the
|
you can use ``easy_install -U mongoengine``. Otherwise, you can download the
|
||||||
source from `GitHub <http://github.com/MongoEngine/mongoengine>`_ and run ``python
|
source from `GitHub <http://github.com/MongoEngine/mongoengine>`_ and run ``python
|
||||||
setup.py install``.
|
setup.py install``.
|
||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
============
|
============
|
||||||
All of the dependencies can easily be installed via `pip <https://pip.pypa.io/>`_.
|
All of the dependencies can easily be installed via `pip <https://pip.pypa.io/>`_. At the very least, you'll need these two packages to use MongoEngine:
|
||||||
At the very least, you'll need these two packages to use MongoEngine:
|
|
||||||
|
|
||||||
- pymongo>=2.7.1
|
- pymongo>=2.7.1
|
||||||
- six>=1.10.0
|
- six>=1.10.0
|
||||||
@@ -56,6 +48,10 @@ If you need to use an ``ImageField`` or ``ImageGridFsProxy``:
|
|||||||
|
|
||||||
- Pillow>=2.0.0
|
- Pillow>=2.0.0
|
||||||
|
|
||||||
|
If you want to generate the documentation (e.g. to contribute to it):
|
||||||
|
|
||||||
|
- sphinx
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
========
|
========
|
||||||
Some simple examples of what MongoEngine code looks like:
|
Some simple examples of what MongoEngine code looks like:
|
||||||
@@ -114,11 +110,11 @@ Some simple examples of what MongoEngine code looks like:
|
|||||||
Tests
|
Tests
|
||||||
=====
|
=====
|
||||||
To run the test suite, ensure you are running a local instance of MongoDB on
|
To run the test suite, ensure you are running a local instance of MongoDB on
|
||||||
the standard port and have ``nose`` installed. Then, run ``python setup.py nosetests``.
|
the standard port and have ``nose`` installed. Then, run: ``python setup.py nosetests``.
|
||||||
|
|
||||||
To run the test suite on every supported Python and PyMongo version, you can
|
To run the test suite on every supported Python version and every supported PyMongo version,
|
||||||
use ``tox``. You'll need to make sure you have each supported Python version
|
you can use ``tox``.
|
||||||
installed in your environment and then:
|
tox and each supported Python version should be installed in your environment:
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
@@ -127,16 +123,13 @@ installed in your environment and then:
|
|||||||
# Run the test suites
|
# Run the test suites
|
||||||
$ tox
|
$ tox
|
||||||
|
|
||||||
If you wish to run a subset of tests, use the nosetests convention:
|
If you wish to run one single or selected tests, use the nosetest convention. It will find the folder,
|
||||||
|
eventually the file, go to the TestClass specified after the colon and eventually right to the single test.
|
||||||
|
Also use the -s argument if you want to print out whatever or access pdb while testing.
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
# Run all the tests in a particular test file
|
$ python setup.py nosetests --tests tests/fields/fields.py:FieldTest.test_cls_field -s
|
||||||
$ python setup.py nosetests --tests tests/fields/fields.py
|
|
||||||
# Run only particular test class in that file
|
|
||||||
$ python setup.py nosetests --tests tests/fields/fields.py:FieldTest
|
|
||||||
# Use the -s option if you want to print some debug statements or use pdb
|
|
||||||
$ python setup.py nosetests --tests tests/fields/fields.py:FieldTest -s
|
|
||||||
|
|
||||||
Community
|
Community
|
||||||
=========
|
=========
|
||||||
|
@@ -42,18 +42,13 @@ the :attr:`host` to
|
|||||||
will establish connection to ``production`` database using
|
will establish connection to ``production`` database using
|
||||||
``admin`` username and ``qwerty`` password.
|
``admin`` username and ``qwerty`` password.
|
||||||
|
|
||||||
Replica Sets
|
ReplicaSets
|
||||||
============
|
===========
|
||||||
|
|
||||||
MongoEngine supports connecting to replica sets::
|
MongoEngine supports
|
||||||
|
:class:`~pymongo.mongo_replica_set_client.MongoReplicaSetClient`. To use them,
|
||||||
from mongoengine import connect
|
please use an URI style connection and provide the ``replicaSet`` name
|
||||||
|
in the connection kwargs.
|
||||||
# Regular connect
|
|
||||||
connect('dbname', replicaset='rs-name')
|
|
||||||
|
|
||||||
# MongoDB URI-style connect
|
|
||||||
connect(host='mongodb://localhost/dbname?replicaSet=rs-name')
|
|
||||||
|
|
||||||
Read preferences are supported through the connection or via individual
|
Read preferences are supported through the connection or via individual
|
||||||
queries by passing the read_preference ::
|
queries by passing the read_preference ::
|
||||||
@@ -64,74 +59,76 @@ queries by passing the read_preference ::
|
|||||||
Multiple Databases
|
Multiple Databases
|
||||||
==================
|
==================
|
||||||
|
|
||||||
To use multiple databases you can use :func:`~mongoengine.connect` and provide
|
Multiple database support was added in MongoEngine 0.6. To use multiple
|
||||||
an `alias` name for the connection - if no `alias` is provided then "default"
|
databases you can use :func:`~mongoengine.connect` and provide an `alias` name
|
||||||
is used.
|
for the connection - if no `alias` is provided then "default" is used.
|
||||||
|
|
||||||
In the background this uses :func:`~mongoengine.register_connection` to
|
In the background this uses :func:`~mongoengine.register_connection` to
|
||||||
store the data and you can register all aliases up front if required.
|
store the data and you can register all aliases up front if required.
|
||||||
|
|
||||||
Individual documents can also support multiple databases by providing a
|
Individual documents can also support multiple databases by providing a
|
||||||
`db_alias` in their meta data. This allows :class:`~pymongo.dbref.DBRef`
|
`db_alias` in their meta data. This allows :class:`~pymongo.dbref.DBRef` objects
|
||||||
objects to point across databases and collections. Below is an example schema,
|
to point across databases and collections. Below is an example schema, using
|
||||||
using 3 different databases to store data::
|
3 different databases to store data::
|
||||||
|
|
||||||
class User(Document):
|
class User(Document):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
|
|
||||||
meta = {'db_alias': 'user-db'}
|
meta = {"db_alias": "user-db"}
|
||||||
|
|
||||||
class Book(Document):
|
class Book(Document):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
|
|
||||||
meta = {'db_alias': 'book-db'}
|
meta = {"db_alias": "book-db"}
|
||||||
|
|
||||||
class AuthorBooks(Document):
|
class AuthorBooks(Document):
|
||||||
author = ReferenceField(User)
|
author = ReferenceField(User)
|
||||||
book = ReferenceField(Book)
|
book = ReferenceField(Book)
|
||||||
|
|
||||||
meta = {'db_alias': 'users-books-db'}
|
meta = {"db_alias": "users-books-db"}
|
||||||
|
|
||||||
|
|
||||||
Context Managers
|
Context Managers
|
||||||
================
|
================
|
||||||
Sometimes you may want to switch the database or collection to query against.
|
Sometimes you may want to switch the database or collection to query against
|
||||||
|
for a class.
|
||||||
For example, archiving older data into a separate database for performance
|
For example, archiving older data into a separate database for performance
|
||||||
reasons or writing functions that dynamically choose collections to write
|
reasons or writing functions that dynamically choose collections to write
|
||||||
a document to.
|
document to.
|
||||||
|
|
||||||
Switch Database
|
Switch Database
|
||||||
---------------
|
---------------
|
||||||
The :class:`~mongoengine.context_managers.switch_db` context manager allows
|
The :class:`~mongoengine.context_managers.switch_db` context manager allows
|
||||||
you to change the database alias for a given class allowing quick and easy
|
you to change the database alias for a given class allowing quick and easy
|
||||||
access to the same User document across databases::
|
access the same User document across databases::
|
||||||
|
|
||||||
from mongoengine.context_managers import switch_db
|
from mongoengine.context_managers import switch_db
|
||||||
|
|
||||||
class User(Document):
|
class User(Document):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
|
|
||||||
meta = {'db_alias': 'user-db'}
|
meta = {"db_alias": "user-db"}
|
||||||
|
|
||||||
with switch_db(User, 'archive-user-db') as User:
|
with switch_db(User, 'archive-user-db') as User:
|
||||||
User(name='Ross').save() # Saves the 'archive-user-db'
|
User(name="Ross").save() # Saves the 'archive-user-db'
|
||||||
|
|
||||||
|
|
||||||
Switch Collection
|
Switch Collection
|
||||||
-----------------
|
-----------------
|
||||||
The :class:`~mongoengine.context_managers.switch_collection` context manager
|
The :class:`~mongoengine.context_managers.switch_collection` context manager
|
||||||
allows you to change the collection for a given class allowing quick and easy
|
allows you to change the collection for a given class allowing quick and easy
|
||||||
access to the same Group document across collection::
|
access the same Group document across collection::
|
||||||
|
|
||||||
from mongoengine.context_managers import switch_collection
|
from mongoengine.context_managers import switch_collection
|
||||||
|
|
||||||
class Group(Document):
|
class Group(Document):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
|
|
||||||
Group(name='test').save() # Saves in the default db
|
Group(name="test").save() # Saves in the default db
|
||||||
|
|
||||||
with switch_collection(Group, 'group2000') as Group:
|
with switch_collection(Group, 'group2000') as Group:
|
||||||
Group(name='hello Group 2000 collection!').save() # Saves in group2000 collection
|
Group(name="hello Group 2000 collection!").save() # Saves in group2000 collection
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. note:: Make sure any aliases have been registered with
|
.. note:: Make sure any aliases have been registered with
|
||||||
|
@@ -2,13 +2,13 @@
|
|||||||
Installing MongoEngine
|
Installing MongoEngine
|
||||||
======================
|
======================
|
||||||
|
|
||||||
To use MongoEngine, you will need to download `MongoDB <http://mongodb.com/>`_
|
To use MongoEngine, you will need to download `MongoDB <http://mongodb.org/>`_
|
||||||
and ensure it is running in an accessible location. You will also need
|
and ensure it is running in an accessible location. You will also need
|
||||||
`PyMongo <http://api.mongodb.org/python>`_ to use MongoEngine, but if you
|
`PyMongo <http://api.mongodb.org/python>`_ to use MongoEngine, but if you
|
||||||
install MongoEngine using setuptools, then the dependencies will be handled for
|
install MongoEngine using setuptools, then the dependencies will be handled for
|
||||||
you.
|
you.
|
||||||
|
|
||||||
MongoEngine is available on PyPI, so you can use :program:`pip`:
|
MongoEngine is available on PyPI, so to use it you can use :program:`pip`:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
|
@@ -3,10 +3,11 @@ Tutorial
|
|||||||
========
|
========
|
||||||
|
|
||||||
This tutorial introduces **MongoEngine** by means of example --- we will walk
|
This tutorial introduces **MongoEngine** by means of example --- we will walk
|
||||||
through how to create a simple **Tumblelog** application. A tumblelog is a
|
through how to create a simple **Tumblelog** application. A Tumblelog is a type
|
||||||
blog that supports mixed media content, including text, images, links, video,
|
of blog where posts are not constrained to being conventional text-based posts.
|
||||||
audio, etc. For simplicity's sake, we'll stick to text, image, and link
|
As well as text-based entries, users may post images, links, videos, etc. For
|
||||||
entries. As the purpose of this tutorial is to introduce MongoEngine, we'll
|
simplicity's sake, we'll stick to text, image and link entries in our
|
||||||
|
application. As the purpose of this tutorial is to introduce MongoEngine, we'll
|
||||||
focus on the data-modelling side of the application, leaving out a user
|
focus on the data-modelling side of the application, leaving out a user
|
||||||
interface.
|
interface.
|
||||||
|
|
||||||
@@ -15,14 +16,14 @@ Getting started
|
|||||||
|
|
||||||
Before we start, make sure that a copy of MongoDB is running in an accessible
|
Before we start, make sure that a copy of MongoDB is running in an accessible
|
||||||
location --- running it locally will be easier, but if that is not an option
|
location --- running it locally will be easier, but if that is not an option
|
||||||
then it may be run on a remote server. If you haven't installed MongoEngine,
|
then it may be run on a remote server. If you haven't installed mongoengine,
|
||||||
simply use pip to install it like so::
|
simply use pip to install it like so::
|
||||||
|
|
||||||
$ pip install mongoengine
|
$ pip install mongoengine
|
||||||
|
|
||||||
Before we can start using MongoEngine, we need to tell it how to connect to our
|
Before we can start using MongoEngine, we need to tell it how to connect to our
|
||||||
instance of :program:`mongod`. For this we use the :func:`~mongoengine.connect`
|
instance of :program:`mongod`. For this we use the :func:`~mongoengine.connect`
|
||||||
function. If running locally, the only argument we need to provide is the name
|
function. If running locally the only argument we need to provide is the name
|
||||||
of the MongoDB database to use::
|
of the MongoDB database to use::
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
@@ -38,18 +39,18 @@ Defining our documents
|
|||||||
MongoDB is *schemaless*, which means that no schema is enforced by the database
|
MongoDB is *schemaless*, which means that no schema is enforced by the database
|
||||||
--- we may add and remove fields however we want and MongoDB won't complain.
|
--- we may add and remove fields however we want and MongoDB won't complain.
|
||||||
This makes life a lot easier in many regards, especially when there is a change
|
This makes life a lot easier in many regards, especially when there is a change
|
||||||
to the data model. However, defining schemas for our documents can help to iron
|
to the data model. However, defining schemata for our documents can help to
|
||||||
out bugs involving incorrect types or missing fields, and also allow us to
|
iron out bugs involving incorrect types or missing fields, and also allow us to
|
||||||
define utility methods on our documents in the same way that traditional
|
define utility methods on our documents in the same way that traditional
|
||||||
:abbr:`ORMs (Object-Relational Mappers)` do.
|
:abbr:`ORMs (Object-Relational Mappers)` do.
|
||||||
|
|
||||||
In our Tumblelog application we need to store several different types of
|
In our Tumblelog application we need to store several different types of
|
||||||
information. We will need to have a collection of **users**, so that we may
|
information. We will need to have a collection of **users**, so that we may
|
||||||
link posts to an individual. We also need to store our different types of
|
link posts to an individual. We also need to store our different types of
|
||||||
**posts** (eg: text, image and link) in the database. To aid navigation of our
|
**posts** (eg: text, image and link) in the database. To aid navigation of our
|
||||||
Tumblelog, posts may have **tags** associated with them, so that the list of
|
Tumblelog, posts may have **tags** associated with them, so that the list of
|
||||||
posts shown to the user may be limited to posts that have been assigned a
|
posts shown to the user may be limited to posts that have been assigned a
|
||||||
specific tag. Finally, it would be nice if **comments** could be added to
|
specific tag. Finally, it would be nice if **comments** could be added to
|
||||||
posts. We'll start with **users**, as the other document models are slightly
|
posts. We'll start with **users**, as the other document models are slightly
|
||||||
more involved.
|
more involved.
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ Now we'll think about how to store the rest of the information. If we were
|
|||||||
using a relational database, we would most likely have a table of **posts**, a
|
using a relational database, we would most likely have a table of **posts**, a
|
||||||
table of **comments** and a table of **tags**. To associate the comments with
|
table of **comments** and a table of **tags**. To associate the comments with
|
||||||
individual posts, we would put a column in the comments table that contained a
|
individual posts, we would put a column in the comments table that contained a
|
||||||
foreign key to the posts table. We'd also need a link table to provide the
|
foreign key to the posts table. We'd also need a link table to provide the
|
||||||
many-to-many relationship between posts and tags. Then we'd need to address the
|
many-to-many relationship between posts and tags. Then we'd need to address the
|
||||||
problem of storing the specialised post-types (text, image and link). There are
|
problem of storing the specialised post-types (text, image and link). There are
|
||||||
several ways we can achieve this, but each of them have their problems --- none
|
several ways we can achieve this, but each of them have their problems --- none
|
||||||
@@ -95,7 +96,7 @@ using* the new fields we need to support video posts. This fits with the
|
|||||||
Object-Oriented principle of *inheritance* nicely. We can think of
|
Object-Oriented principle of *inheritance* nicely. We can think of
|
||||||
:class:`Post` as a base class, and :class:`TextPost`, :class:`ImagePost` and
|
:class:`Post` as a base class, and :class:`TextPost`, :class:`ImagePost` and
|
||||||
:class:`LinkPost` as subclasses of :class:`Post`. In fact, MongoEngine supports
|
:class:`LinkPost` as subclasses of :class:`Post`. In fact, MongoEngine supports
|
||||||
this kind of modeling out of the box --- all you need do is turn on inheritance
|
this kind of modelling out of the box --- all you need do is turn on inheritance
|
||||||
by setting :attr:`allow_inheritance` to True in the :attr:`meta`::
|
by setting :attr:`allow_inheritance` to True in the :attr:`meta`::
|
||||||
|
|
||||||
class Post(Document):
|
class Post(Document):
|
||||||
@@ -127,8 +128,8 @@ link table, we can just store a list of tags in each post. So, for both
|
|||||||
efficiency and simplicity's sake, we'll store the tags as strings directly
|
efficiency and simplicity's sake, we'll store the tags as strings directly
|
||||||
within the post, rather than storing references to tags in a separate
|
within the post, rather than storing references to tags in a separate
|
||||||
collection. Especially as tags are generally very short (often even shorter
|
collection. Especially as tags are generally very short (often even shorter
|
||||||
than a document's id), this denormalization won't impact the size of the
|
than a document's id), this denormalisation won't impact very strongly on the
|
||||||
database very strongly. Let's take a look at the code of our modified
|
size of our database. So let's take a look that the code our modified
|
||||||
:class:`Post` class::
|
:class:`Post` class::
|
||||||
|
|
||||||
class Post(Document):
|
class Post(Document):
|
||||||
@@ -140,7 +141,7 @@ The :class:`~mongoengine.fields.ListField` object that is used to define a Post'
|
|||||||
takes a field object as its first argument --- this means that you can have
|
takes a field object as its first argument --- this means that you can have
|
||||||
lists of any type of field (including lists).
|
lists of any type of field (including lists).
|
||||||
|
|
||||||
.. note:: We don't need to modify the specialized post types as they all
|
.. note:: We don't need to modify the specialised post types as they all
|
||||||
inherit from :class:`Post`.
|
inherit from :class:`Post`.
|
||||||
|
|
||||||
Comments
|
Comments
|
||||||
@@ -148,7 +149,7 @@ Comments
|
|||||||
|
|
||||||
A comment is typically associated with *one* post. In a relational database, to
|
A comment is typically associated with *one* post. In a relational database, to
|
||||||
display a post with its comments, we would have to retrieve the post from the
|
display a post with its comments, we would have to retrieve the post from the
|
||||||
database and then query the database again for the comments associated with the
|
database, then query the database again for the comments associated with the
|
||||||
post. This works, but there is no real reason to be storing the comments
|
post. This works, but there is no real reason to be storing the comments
|
||||||
separately from their associated posts, other than to work around the
|
separately from their associated posts, other than to work around the
|
||||||
relational model. Using MongoDB we can store the comments as a list of
|
relational model. Using MongoDB we can store the comments as a list of
|
||||||
@@ -218,8 +219,8 @@ Now that we've got our user in the database, let's add a couple of posts::
|
|||||||
post2.tags = ['mongoengine']
|
post2.tags = ['mongoengine']
|
||||||
post2.save()
|
post2.save()
|
||||||
|
|
||||||
.. note:: If you change a field on an object that has already been saved and
|
.. note:: If you change a field on a object that has already been saved, then
|
||||||
then call :meth:`save` again, the document will be updated.
|
call :meth:`save` again, the document will be updated.
|
||||||
|
|
||||||
Accessing our data
|
Accessing our data
|
||||||
==================
|
==================
|
||||||
@@ -231,17 +232,17 @@ used to access the documents in the database collection associated with that
|
|||||||
class. So let's see how we can get our posts' titles::
|
class. So let's see how we can get our posts' titles::
|
||||||
|
|
||||||
for post in Post.objects:
|
for post in Post.objects:
|
||||||
print(post.title)
|
print post.title
|
||||||
|
|
||||||
Retrieving type-specific information
|
Retrieving type-specific information
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
This will print the titles of our posts, one on each line. But what if we want
|
This will print the titles of our posts, one on each line. But What if we want
|
||||||
to access the type-specific data (link_url, content, etc.)? One way is simply
|
to access the type-specific data (link_url, content, etc.)? One way is simply
|
||||||
to use the :attr:`objects` attribute of a subclass of :class:`Post`::
|
to use the :attr:`objects` attribute of a subclass of :class:`Post`::
|
||||||
|
|
||||||
for post in TextPost.objects:
|
for post in TextPost.objects:
|
||||||
print(post.content)
|
print post.content
|
||||||
|
|
||||||
Using TextPost's :attr:`objects` attribute only returns documents that were
|
Using TextPost's :attr:`objects` attribute only returns documents that were
|
||||||
created using :class:`TextPost`. Actually, there is a more general rule here:
|
created using :class:`TextPost`. Actually, there is a more general rule here:
|
||||||
@@ -258,14 +259,16 @@ instances of :class:`Post` --- they were instances of the subclass of
|
|||||||
practice::
|
practice::
|
||||||
|
|
||||||
for post in Post.objects:
|
for post in Post.objects:
|
||||||
print(post.title)
|
print post.title
|
||||||
print('=' * len(post.title))
|
print '=' * len(post.title)
|
||||||
|
|
||||||
if isinstance(post, TextPost):
|
if isinstance(post, TextPost):
|
||||||
print(post.content)
|
print post.content
|
||||||
|
|
||||||
if isinstance(post, LinkPost):
|
if isinstance(post, LinkPost):
|
||||||
print('Link: {}'.format(post.link_url))
|
print 'Link:', post.link_url
|
||||||
|
|
||||||
|
print
|
||||||
|
|
||||||
This would print the title of each post, followed by the content if it was a
|
This would print the title of each post, followed by the content if it was a
|
||||||
text post, and "Link: <url>" if it was a link post.
|
text post, and "Link: <url>" if it was a link post.
|
||||||
@@ -280,7 +283,7 @@ your query. Let's adjust our query so that only posts with the tag "mongodb"
|
|||||||
are returned::
|
are returned::
|
||||||
|
|
||||||
for post in Post.objects(tags='mongodb'):
|
for post in Post.objects(tags='mongodb'):
|
||||||
print(post.title)
|
print post.title
|
||||||
|
|
||||||
There are also methods available on :class:`~mongoengine.queryset.QuerySet`
|
There are also methods available on :class:`~mongoengine.queryset.QuerySet`
|
||||||
objects that allow different results to be returned, for example, calling
|
objects that allow different results to be returned, for example, calling
|
||||||
@@ -289,11 +292,11 @@ the first matched by the query you provide. Aggregation functions may also be
|
|||||||
used on :class:`~mongoengine.queryset.QuerySet` objects::
|
used on :class:`~mongoengine.queryset.QuerySet` objects::
|
||||||
|
|
||||||
num_posts = Post.objects(tags='mongodb').count()
|
num_posts = Post.objects(tags='mongodb').count()
|
||||||
print('Found {} posts with tag "mongodb"'.format(num_posts))
|
print 'Found %d posts with tag "mongodb"' % num_posts
|
||||||
|
|
||||||
Learning more about MongoEngine
|
Learning more about mongoengine
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
If you got this far you've made a great start, so well done! The next step on
|
If you got this far you've made a great start, so well done! The next step on
|
||||||
your MongoEngine journey is the `full user guide <guide/index.html>`_, where
|
your mongoengine journey is the `full user guide <guide/index.html>`_, where you
|
||||||
you can learn in-depth about how to use MongoEngine and MongoDB.
|
can learn indepth about how to use mongoengine and mongodb.
|
||||||
|
@@ -286,7 +286,7 @@ class BaseQuerySet(object):
|
|||||||
|
|
||||||
.. versionadded:: 0.4
|
.. versionadded:: 0.4
|
||||||
"""
|
"""
|
||||||
return self._document(**kwargs).save(force_insert=True)
|
return self._document(**kwargs).save()
|
||||||
|
|
||||||
def first(self):
|
def first(self):
|
||||||
"""Retrieve the first object matching the query."""
|
"""Retrieve the first object matching the query."""
|
||||||
|
@@ -2,14 +2,14 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from nose.plugins.skip import SkipTest
|
|
||||||
from datetime import datetime
|
|
||||||
import pymongo
|
import pymongo
|
||||||
|
|
||||||
from mongoengine import *
|
from nose.plugins.skip import SkipTest
|
||||||
from mongoengine.connection import get_db
|
from datetime import datetime
|
||||||
|
|
||||||
from tests.utils import get_mongodb_version, needs_mongodb_v26
|
from mongoengine import *
|
||||||
|
from mongoengine.connection import get_db, get_connection
|
||||||
|
|
||||||
__all__ = ("IndexesTest", )
|
__all__ = ("IndexesTest", )
|
||||||
|
|
||||||
@@ -412,6 +412,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
User.ensure_indexes()
|
User.ensure_indexes()
|
||||||
info = User.objects._collection.index_information()
|
info = User.objects._collection.index_information()
|
||||||
self.assertEqual(sorted(info.keys()), ['_cls_1_user_guid_1', '_id_'])
|
self.assertEqual(sorted(info.keys()), ['_cls_1_user_guid_1', '_id_'])
|
||||||
|
User.drop_collection()
|
||||||
|
|
||||||
def test_embedded_document_index(self):
|
def test_embedded_document_index(self):
|
||||||
"""Tests settings an index on an embedded document
|
"""Tests settings an index on an embedded document
|
||||||
@@ -433,6 +434,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
|
|
||||||
info = BlogPost.objects._collection.index_information()
|
info = BlogPost.objects._collection.index_information()
|
||||||
self.assertEqual(sorted(info.keys()), ['_id_', 'date.yr_-1'])
|
self.assertEqual(sorted(info.keys()), ['_id_', 'date.yr_-1'])
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
def test_list_embedded_document_index(self):
|
def test_list_embedded_document_index(self):
|
||||||
"""Ensure list embedded documents can be indexed
|
"""Ensure list embedded documents can be indexed
|
||||||
@@ -459,6 +461,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
post1 = BlogPost(title="Embedded Indexes tests in place",
|
post1 = BlogPost(title="Embedded Indexes tests in place",
|
||||||
tags=[Tag(name="about"), Tag(name="time")])
|
tags=[Tag(name="about"), Tag(name="time")])
|
||||||
post1.save()
|
post1.save()
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
def test_recursive_embedded_objects_dont_break_indexes(self):
|
def test_recursive_embedded_objects_dont_break_indexes(self):
|
||||||
|
|
||||||
@@ -491,7 +494,8 @@ class IndexesTest(unittest.TestCase):
|
|||||||
obj = Test(a=1)
|
obj = Test(a=1)
|
||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
IS_MONGODB_3 = get_mongodb_version()[0] >= 3
|
connection = get_connection()
|
||||||
|
IS_MONGODB_3 = connection.server_info()['versionArray'][0] >= 3
|
||||||
|
|
||||||
# Need to be explicit about covered indexes as mongoDB doesn't know if
|
# Need to be explicit about covered indexes as mongoDB doesn't know if
|
||||||
# the documents returned might have more keys in that here.
|
# the documents returned might have more keys in that here.
|
||||||
@@ -619,6 +623,8 @@ class IndexesTest(unittest.TestCase):
|
|||||||
post3 = BlogPost(title='test3', date=Date(year=2010), slug='test')
|
post3 = BlogPost(title='test3', date=Date(year=2010), slug='test')
|
||||||
self.assertRaises(OperationError, post3.save)
|
self.assertRaises(OperationError, post3.save)
|
||||||
|
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
def test_unique_embedded_document(self):
|
def test_unique_embedded_document(self):
|
||||||
"""Ensure that uniqueness constraints are applied to fields on embedded documents.
|
"""Ensure that uniqueness constraints are applied to fields on embedded documents.
|
||||||
"""
|
"""
|
||||||
@@ -646,6 +652,8 @@ class IndexesTest(unittest.TestCase):
|
|||||||
sub=SubDocument(year=2010, slug='test'))
|
sub=SubDocument(year=2010, slug='test'))
|
||||||
self.assertRaises(NotUniqueError, post3.save)
|
self.assertRaises(NotUniqueError, post3.save)
|
||||||
|
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
def test_unique_embedded_document_in_list(self):
|
def test_unique_embedded_document_in_list(self):
|
||||||
"""
|
"""
|
||||||
Ensure that the uniqueness constraints are applied to fields in
|
Ensure that the uniqueness constraints are applied to fields in
|
||||||
@@ -676,6 +684,8 @@ class IndexesTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertRaises(NotUniqueError, post2.save)
|
self.assertRaises(NotUniqueError, post2.save)
|
||||||
|
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
def test_unique_with_embedded_document_and_embedded_unique(self):
|
def test_unique_with_embedded_document_and_embedded_unique(self):
|
||||||
"""Ensure that uniqueness constraints are applied to fields on
|
"""Ensure that uniqueness constraints are applied to fields on
|
||||||
embedded documents. And work with unique_with as well.
|
embedded documents. And work with unique_with as well.
|
||||||
@@ -709,6 +719,8 @@ class IndexesTest(unittest.TestCase):
|
|||||||
sub=SubDocument(year=2009, slug='test-1'))
|
sub=SubDocument(year=2009, slug='test-1'))
|
||||||
self.assertRaises(NotUniqueError, post3.save)
|
self.assertRaises(NotUniqueError, post3.save)
|
||||||
|
|
||||||
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
def test_ttl_indexes(self):
|
def test_ttl_indexes(self):
|
||||||
|
|
||||||
class Log(Document):
|
class Log(Document):
|
||||||
@@ -721,6 +733,14 @@ class IndexesTest(unittest.TestCase):
|
|||||||
|
|
||||||
Log.drop_collection()
|
Log.drop_collection()
|
||||||
|
|
||||||
|
if pymongo.version_tuple[0] < 2 and pymongo.version_tuple[1] < 3:
|
||||||
|
raise SkipTest('pymongo needs to be 2.3 or higher for this test')
|
||||||
|
|
||||||
|
connection = get_connection()
|
||||||
|
version_array = connection.server_info()['versionArray']
|
||||||
|
if version_array[0] < 2 and version_array[1] < 2:
|
||||||
|
raise SkipTest('MongoDB needs to be 2.2 or higher for this test')
|
||||||
|
|
||||||
# Indexes are lazy so use list() to perform query
|
# Indexes are lazy so use list() to perform query
|
||||||
list(Log.objects)
|
list(Log.objects)
|
||||||
info = Log.objects._collection.index_information()
|
info = Log.objects._collection.index_information()
|
||||||
@@ -748,11 +768,13 @@ class IndexesTest(unittest.TestCase):
|
|||||||
raise AssertionError("We saved a dupe!")
|
raise AssertionError("We saved a dupe!")
|
||||||
except NotUniqueError:
|
except NotUniqueError:
|
||||||
pass
|
pass
|
||||||
|
Customer.drop_collection()
|
||||||
|
|
||||||
def test_unique_and_primary(self):
|
def test_unique_and_primary(self):
|
||||||
"""If you set a field as primary, then unexpected behaviour can occur.
|
"""If you set a field as primary, then unexpected behaviour can occur.
|
||||||
You won't create a duplicate but you will update an existing document.
|
You won't create a duplicate but you will update an existing document.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class User(Document):
|
class User(Document):
|
||||||
name = StringField(primary_key=True, unique=True)
|
name = StringField(primary_key=True, unique=True)
|
||||||
password = StringField()
|
password = StringField()
|
||||||
@@ -768,23 +790,8 @@ class IndexesTest(unittest.TestCase):
|
|||||||
self.assertEqual(User.objects.count(), 1)
|
self.assertEqual(User.objects.count(), 1)
|
||||||
self.assertEqual(User.objects.get().password, 'secret2')
|
self.assertEqual(User.objects.get().password, 'secret2')
|
||||||
|
|
||||||
def test_unique_and_primary_create(self):
|
|
||||||
"""Create a new record with a duplicate primary key
|
|
||||||
throws an exception
|
|
||||||
"""
|
|
||||||
class User(Document):
|
|
||||||
name = StringField(primary_key=True)
|
|
||||||
password = StringField()
|
|
||||||
|
|
||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
|
|
||||||
User.objects.create(name='huangz', password='secret')
|
|
||||||
with self.assertRaises(NotUniqueError):
|
|
||||||
User.objects.create(name='huangz', password='secret2')
|
|
||||||
|
|
||||||
self.assertEqual(User.objects.count(), 1)
|
|
||||||
self.assertEqual(User.objects.get().password, 'secret')
|
|
||||||
|
|
||||||
def test_index_with_pk(self):
|
def test_index_with_pk(self):
|
||||||
"""Ensure you can use `pk` as part of a query"""
|
"""Ensure you can use `pk` as part of a query"""
|
||||||
|
|
||||||
@@ -867,8 +874,8 @@ class IndexesTest(unittest.TestCase):
|
|||||||
info['provider_ids.foo_1_provider_ids.bar_1']['key'])
|
info['provider_ids.foo_1_provider_ids.bar_1']['key'])
|
||||||
self.assertTrue(info['provider_ids.foo_1_provider_ids.bar_1']['sparse'])
|
self.assertTrue(info['provider_ids.foo_1_provider_ids.bar_1']['sparse'])
|
||||||
|
|
||||||
@needs_mongodb_v26
|
|
||||||
def test_text_indexes(self):
|
def test_text_indexes(self):
|
||||||
|
|
||||||
class Book(Document):
|
class Book(Document):
|
||||||
title = DictField()
|
title = DictField()
|
||||||
meta = {
|
meta = {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,139 +1,105 @@
|
|||||||
import datetime
|
from datetime import datetime, timedelta
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from pymongo.errors import OperationFailure
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
|
from mongoengine.connection import get_connection
|
||||||
from tests.utils import MongoDBTestCase, needs_mongodb_v3
|
from nose.plugins.skip import SkipTest
|
||||||
|
|
||||||
|
|
||||||
__all__ = ("GeoQueriesTest",)
|
__all__ = ("GeoQueriesTest",)
|
||||||
|
|
||||||
|
|
||||||
class GeoQueriesTest(MongoDBTestCase):
|
class GeoQueriesTest(unittest.TestCase):
|
||||||
|
|
||||||
def _create_event_data(self, point_field_class=GeoPointField):
|
def setUp(self):
|
||||||
"""Create some sample data re-used in many of the tests below."""
|
connect(db='mongoenginetest')
|
||||||
|
|
||||||
|
def test_geospatial_operators(self):
|
||||||
|
"""Ensure that geospatial queries are working.
|
||||||
|
"""
|
||||||
class Event(Document):
|
class Event(Document):
|
||||||
title = StringField()
|
title = StringField()
|
||||||
date = DateTimeField()
|
date = DateTimeField()
|
||||||
location = point_field_class()
|
location = GeoPointField()
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
self.Event = Event
|
|
||||||
|
|
||||||
Event.drop_collection()
|
Event.drop_collection()
|
||||||
|
|
||||||
event1 = Event.objects.create(
|
event1 = Event(title="Coltrane Motion @ Double Door",
|
||||||
title="Coltrane Motion @ Double Door",
|
date=datetime.now() - timedelta(days=1),
|
||||||
date=datetime.datetime.now() - datetime.timedelta(days=1),
|
location=[-87.677137, 41.909889]).save()
|
||||||
location=[-87.677137, 41.909889])
|
event2 = Event(title="Coltrane Motion @ Bottom of the Hill",
|
||||||
event2 = Event.objects.create(
|
date=datetime.now() - timedelta(days=10),
|
||||||
title="Coltrane Motion @ Bottom of the Hill",
|
location=[-122.4194155, 37.7749295]).save()
|
||||||
date=datetime.datetime.now() - datetime.timedelta(days=10),
|
event3 = Event(title="Coltrane Motion @ Empty Bottle",
|
||||||
location=[-122.4194155, 37.7749295])
|
date=datetime.now(),
|
||||||
event3 = Event.objects.create(
|
location=[-87.686638, 41.900474]).save()
|
||||||
title="Coltrane Motion @ Empty Bottle",
|
|
||||||
date=datetime.datetime.now(),
|
|
||||||
location=[-87.686638, 41.900474])
|
|
||||||
|
|
||||||
return event1, event2, event3
|
|
||||||
|
|
||||||
def test_near(self):
|
|
||||||
"""Make sure the "near" operator works."""
|
|
||||||
event1, event2, event3 = self._create_event_data()
|
|
||||||
|
|
||||||
# find all events "near" pitchfork office, chicago.
|
# find all events "near" pitchfork office, chicago.
|
||||||
# note that "near" will show the san francisco event, too,
|
# note that "near" will show the san francisco event, too,
|
||||||
# although it sorts to last.
|
# although it sorts to last.
|
||||||
events = self.Event.objects(location__near=[-87.67892, 41.9120459])
|
events = Event.objects(location__near=[-87.67892, 41.9120459])
|
||||||
self.assertEqual(events.count(), 3)
|
self.assertEqual(events.count(), 3)
|
||||||
self.assertEqual(list(events), [event1, event3, event2])
|
self.assertEqual(list(events), [event1, event3, event2])
|
||||||
|
|
||||||
# ensure ordering is respected by "near"
|
|
||||||
events = self.Event.objects(location__near=[-87.67892, 41.9120459])
|
|
||||||
events = events.order_by("-date")
|
|
||||||
self.assertEqual(events.count(), 3)
|
|
||||||
self.assertEqual(list(events), [event3, event1, event2])
|
|
||||||
|
|
||||||
def test_near_and_max_distance(self):
|
|
||||||
"""Ensure the "max_distance" operator works alongside the "near"
|
|
||||||
operator.
|
|
||||||
"""
|
|
||||||
event1, event2, event3 = self._create_event_data()
|
|
||||||
|
|
||||||
# find events within 10 degrees of san francisco
|
|
||||||
point = [-122.415579, 37.7566023]
|
|
||||||
events = self.Event.objects(location__near=point,
|
|
||||||
location__max_distance=10)
|
|
||||||
self.assertEqual(events.count(), 1)
|
|
||||||
self.assertEqual(events[0], event2)
|
|
||||||
|
|
||||||
# $minDistance was added in MongoDB v2.6, but continued being buggy
|
|
||||||
# until v3.0; skip for older versions
|
|
||||||
@needs_mongodb_v3
|
|
||||||
def test_near_and_min_distance(self):
|
|
||||||
"""Ensure the "min_distance" operator works alongside the "near"
|
|
||||||
operator.
|
|
||||||
"""
|
|
||||||
event1, event2, event3 = self._create_event_data()
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
self.assertEqual(events.count(), 2)
|
|
||||||
|
|
||||||
def test_within_distance(self):
|
|
||||||
"""Make sure the "within_distance" operator works."""
|
|
||||||
event1, event2, event3 = self._create_event_data()
|
|
||||||
|
|
||||||
# find events within 5 degrees of pitchfork office, chicago
|
# find events within 5 degrees of pitchfork office, chicago
|
||||||
point_and_distance = [[-87.67892, 41.9120459], 5]
|
point_and_distance = [[-87.67892, 41.9120459], 5]
|
||||||
events = self.Event.objects(
|
events = Event.objects(location__within_distance=point_and_distance)
|
||||||
location__within_distance=point_and_distance)
|
|
||||||
self.assertEqual(events.count(), 2)
|
self.assertEqual(events.count(), 2)
|
||||||
events = list(events)
|
events = list(events)
|
||||||
self.assertTrue(event2 not in events)
|
self.assertTrue(event2 not in events)
|
||||||
self.assertTrue(event1 in events)
|
self.assertTrue(event1 in events)
|
||||||
self.assertTrue(event3 in events)
|
self.assertTrue(event3 in events)
|
||||||
|
|
||||||
|
# ensure ordering is respected by "near"
|
||||||
|
events = Event.objects(location__near=[-87.67892, 41.9120459])
|
||||||
|
events = events.order_by("-date")
|
||||||
|
self.assertEqual(events.count(), 3)
|
||||||
|
self.assertEqual(list(events), [event3, event1, event2])
|
||||||
|
|
||||||
|
# find events within 10 degrees of san francisco
|
||||||
|
point = [-122.415579, 37.7566023]
|
||||||
|
events = Event.objects(location__near=point, location__max_distance=10)
|
||||||
|
self.assertEqual(events.count(), 1)
|
||||||
|
self.assertEqual(events[0], event2)
|
||||||
|
|
||||||
|
# find events at least 10 degrees away of san francisco
|
||||||
|
point = [-122.415579, 37.7566023]
|
||||||
|
events = Event.objects(location__near=point, location__min_distance=10)
|
||||||
|
# The following real test passes on MongoDB 3 but minDistance seems
|
||||||
|
# buggy on older MongoDB versions
|
||||||
|
if get_connection().server_info()['versionArray'][0] > 2:
|
||||||
|
self.assertEqual(events.count(), 2)
|
||||||
|
else:
|
||||||
|
self.assertTrue(events.count() >= 2)
|
||||||
|
|
||||||
# find events within 10 degrees of san francisco
|
# find events within 10 degrees of san francisco
|
||||||
point_and_distance = [[-122.415579, 37.7566023], 10]
|
point_and_distance = [[-122.415579, 37.7566023], 10]
|
||||||
events = self.Event.objects(
|
events = Event.objects(location__within_distance=point_and_distance)
|
||||||
location__within_distance=point_and_distance)
|
|
||||||
self.assertEqual(events.count(), 1)
|
self.assertEqual(events.count(), 1)
|
||||||
self.assertEqual(events[0], event2)
|
self.assertEqual(events[0], event2)
|
||||||
|
|
||||||
# find events within 1 degree of greenpoint, broolyn, nyc, ny
|
# find events within 1 degree of greenpoint, broolyn, nyc, ny
|
||||||
point_and_distance = [[-73.9509714, 40.7237134], 1]
|
point_and_distance = [[-73.9509714, 40.7237134], 1]
|
||||||
events = self.Event.objects(
|
events = Event.objects(location__within_distance=point_and_distance)
|
||||||
location__within_distance=point_and_distance)
|
|
||||||
self.assertEqual(events.count(), 0)
|
self.assertEqual(events.count(), 0)
|
||||||
|
|
||||||
# ensure ordering is respected by "within_distance"
|
# ensure ordering is respected by "within_distance"
|
||||||
point_and_distance = [[-87.67892, 41.9120459], 10]
|
point_and_distance = [[-87.67892, 41.9120459], 10]
|
||||||
events = self.Event.objects(
|
events = Event.objects(location__within_distance=point_and_distance)
|
||||||
location__within_distance=point_and_distance)
|
|
||||||
events = events.order_by("-date")
|
events = events.order_by("-date")
|
||||||
self.assertEqual(events.count(), 2)
|
self.assertEqual(events.count(), 2)
|
||||||
self.assertEqual(events[0], event3)
|
self.assertEqual(events[0], event3)
|
||||||
|
|
||||||
def test_within_box(self):
|
|
||||||
"""Ensure the "within_box" operator works."""
|
|
||||||
event1, event2, event3 = self._create_event_data()
|
|
||||||
|
|
||||||
# check that within_box works
|
# check that within_box works
|
||||||
box = [(-125.0, 35.0), (-100.0, 40.0)]
|
box = [(-125.0, 35.0), (-100.0, 40.0)]
|
||||||
events = self.Event.objects(location__within_box=box)
|
events = Event.objects(location__within_box=box)
|
||||||
self.assertEqual(events.count(), 1)
|
self.assertEqual(events.count(), 1)
|
||||||
self.assertEqual(events[0].id, event2.id)
|
self.assertEqual(events[0].id, event2.id)
|
||||||
|
|
||||||
def test_within_polygon(self):
|
|
||||||
"""Ensure the "within_polygon" operator works."""
|
|
||||||
event1, event2, event3 = self._create_event_data()
|
|
||||||
|
|
||||||
polygon = [
|
polygon = [
|
||||||
(-87.694445, 41.912114),
|
(-87.694445, 41.912114),
|
||||||
(-87.69084, 41.919395),
|
(-87.69084, 41.919395),
|
||||||
@@ -141,7 +107,7 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
(-87.654276, 41.911731),
|
(-87.654276, 41.911731),
|
||||||
(-87.656164, 41.898061),
|
(-87.656164, 41.898061),
|
||||||
]
|
]
|
||||||
events = self.Event.objects(location__within_polygon=polygon)
|
events = Event.objects(location__within_polygon=polygon)
|
||||||
self.assertEqual(events.count(), 1)
|
self.assertEqual(events.count(), 1)
|
||||||
self.assertEqual(events[0].id, event1.id)
|
self.assertEqual(events[0].id, event1.id)
|
||||||
|
|
||||||
@@ -150,151 +116,13 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
(-1.225891, 52.792797),
|
(-1.225891, 52.792797),
|
||||||
(-4.40094, 53.389881)
|
(-4.40094, 53.389881)
|
||||||
]
|
]
|
||||||
events = self.Event.objects(location__within_polygon=polygon2)
|
events = Event.objects(location__within_polygon=polygon2)
|
||||||
self.assertEqual(events.count(), 0)
|
self.assertEqual(events.count(), 0)
|
||||||
|
|
||||||
def test_2dsphere_near(self):
|
def test_geo_spatial_embedded(self):
|
||||||
"""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
|
|
||||||
)
|
|
||||||
|
|
||||||
# find all events "near" pitchfork office, chicago.
|
|
||||||
# note that "near" will show the san francisco event, too,
|
|
||||||
# although it sorts to last.
|
|
||||||
events = self.Event.objects(location__near=[-87.67892, 41.9120459])
|
|
||||||
self.assertEqual(events.count(), 3)
|
|
||||||
self.assertEqual(list(events), [event1, event3, event2])
|
|
||||||
|
|
||||||
# ensure ordering is respected by "near"
|
|
||||||
events = self.Event.objects(location__near=[-87.67892, 41.9120459])
|
|
||||||
events = events.order_by("-date")
|
|
||||||
self.assertEqual(events.count(), 3)
|
|
||||||
self.assertEqual(list(events), [event3, event1, event2])
|
|
||||||
|
|
||||||
def test_2dsphere_near_and_max_distance(self):
|
|
||||||
"""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
|
|
||||||
)
|
|
||||||
|
|
||||||
# find events within 10km of san francisco
|
|
||||||
point = [-122.415579, 37.7566023]
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
).order_by("-date")
|
|
||||||
self.assertEqual(events.count(), 2)
|
|
||||||
self.assertEqual(events[0], event3)
|
|
||||||
|
|
||||||
def test_2dsphere_geo_within_box(self):
|
|
||||||
"""Ensure the "geo_within_box" operator works with a 2dsphere
|
|
||||||
index.
|
|
||||||
"""
|
|
||||||
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)]
|
|
||||||
events = self.Event.objects(location__geo_within_box=box)
|
|
||||||
self.assertEqual(events.count(), 1)
|
|
||||||
self.assertEqual(events[0].id, event2.id)
|
|
||||||
|
|
||||||
def test_2dsphere_geo_within_polygon(self):
|
|
||||||
"""Ensure the "geo_within_polygon" operator works with a
|
|
||||||
2dsphere index.
|
|
||||||
"""
|
|
||||||
event1, event2, event3 = self._create_event_data(
|
|
||||||
point_field_class=PointField
|
|
||||||
)
|
|
||||||
|
|
||||||
polygon = [
|
|
||||||
(-87.694445, 41.912114),
|
|
||||||
(-87.69084, 41.919395),
|
|
||||||
(-87.681742, 41.927186),
|
|
||||||
(-87.654276, 41.911731),
|
|
||||||
(-87.656164, 41.898061),
|
|
||||||
]
|
|
||||||
events = self.Event.objects(location__geo_within_polygon=polygon)
|
|
||||||
self.assertEqual(events.count(), 1)
|
|
||||||
self.assertEqual(events[0].id, event1.id)
|
|
||||||
|
|
||||||
polygon2 = [
|
|
||||||
(-1.742249, 54.033586),
|
|
||||||
(-1.225891, 52.792797),
|
|
||||||
(-4.40094, 53.389881)
|
|
||||||
]
|
|
||||||
events = self.Event.objects(location__geo_within_polygon=polygon2)
|
|
||||||
self.assertEqual(events.count(), 0)
|
|
||||||
|
|
||||||
# $minDistance was added in MongoDB v2.6, but continued being buggy
|
|
||||||
# until v3.0; skip for older versions
|
|
||||||
@needs_mongodb_v3
|
|
||||||
def test_2dsphere_near_and_min_max_distance(self):
|
|
||||||
"""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
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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
|
|
||||||
).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
|
|
||||||
).order_by("-date")
|
|
||||||
self.assertEqual(events.count(), 1)
|
|
||||||
self.assertEqual(events[0], event2)
|
|
||||||
|
|
||||||
def test_2dsphere_geo_within_center(self):
|
|
||||||
"""Make sure the "geo_within_center" operator works with a
|
|
||||||
2dsphere index.
|
|
||||||
"""
|
|
||||||
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)
|
|
||||||
self.assertEqual(events.count(), 2)
|
|
||||||
events = list(events)
|
|
||||||
self.assertTrue(event2 not in events)
|
|
||||||
self.assertTrue(event1 in events)
|
|
||||||
self.assertTrue(event3 in events)
|
|
||||||
|
|
||||||
def _test_embedded(self, point_field_class):
|
|
||||||
"""Helper test method ensuring given point field class works
|
|
||||||
well in an embedded document.
|
|
||||||
"""
|
|
||||||
class Venue(EmbeddedDocument):
|
class Venue(EmbeddedDocument):
|
||||||
location = point_field_class()
|
location = GeoPointField()
|
||||||
name = StringField()
|
name = StringField()
|
||||||
|
|
||||||
class Event(Document):
|
class Event(Document):
|
||||||
@@ -320,18 +148,16 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
self.assertEqual(events.count(), 3)
|
self.assertEqual(events.count(), 3)
|
||||||
self.assertEqual(list(events), [event1, event3, event2])
|
self.assertEqual(list(events), [event1, event3, event2])
|
||||||
|
|
||||||
def test_geo_spatial_embedded(self):
|
|
||||||
"""Make sure GeoPointField works properly in an embedded document."""
|
|
||||||
self._test_embedded(point_field_class=GeoPointField)
|
|
||||||
|
|
||||||
def test_2dsphere_point_embedded(self):
|
|
||||||
"""Make sure PointField works properly in an embedded document."""
|
|
||||||
self._test_embedded(point_field_class=PointField)
|
|
||||||
|
|
||||||
# Needs MongoDB > 2.6.4 https://jira.mongodb.org/browse/SERVER-14039
|
|
||||||
@needs_mongodb_v3
|
|
||||||
def test_spherical_geospatial_operators(self):
|
def test_spherical_geospatial_operators(self):
|
||||||
"""Ensure that spherical geospatial queries are working."""
|
"""Ensure that spherical geospatial queries are working
|
||||||
|
"""
|
||||||
|
# Needs MongoDB > 2.6.4 https://jira.mongodb.org/browse/SERVER-14039
|
||||||
|
connection = get_connection()
|
||||||
|
info = connection.test.command('buildInfo')
|
||||||
|
mongodb_version = tuple([int(i) for i in info['version'].split('.')])
|
||||||
|
if mongodb_version < (2, 6, 4):
|
||||||
|
raise SkipTest("Need MongoDB version 2.6.4+")
|
||||||
|
|
||||||
class Point(Document):
|
class Point(Document):
|
||||||
location = GeoPointField()
|
location = GeoPointField()
|
||||||
|
|
||||||
@@ -351,10 +177,7 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
|
|
||||||
# Same behavior for _within_spherical_distance
|
# Same behavior for _within_spherical_distance
|
||||||
points = Point.objects(
|
points = Point.objects(
|
||||||
location__within_spherical_distance=[
|
location__within_spherical_distance=[[-122, 37.5], 60 / earth_radius]
|
||||||
[-122, 37.5],
|
|
||||||
60 / earth_radius
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
self.assertEqual(points.count(), 2)
|
self.assertEqual(points.count(), 2)
|
||||||
|
|
||||||
@@ -371,9 +194,14 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
# Test query works with min_distance, being farer from one point
|
# Test query works with min_distance, being farer from one point
|
||||||
points = Point.objects(location__near_sphere=[-122, 37.8],
|
points = Point.objects(location__near_sphere=[-122, 37.8],
|
||||||
location__min_distance=60 / earth_radius)
|
location__min_distance=60 / earth_radius)
|
||||||
self.assertEqual(points.count(), 1)
|
# The following real test passes on MongoDB 3 but minDistance seems
|
||||||
far_point = points.first()
|
# buggy on older MongoDB versions
|
||||||
self.assertNotEqual(close_point, far_point)
|
if get_connection().server_info()['versionArray'][0] > 2:
|
||||||
|
self.assertEqual(points.count(), 1)
|
||||||
|
far_point = points.first()
|
||||||
|
self.assertNotEqual(close_point, far_point)
|
||||||
|
else:
|
||||||
|
self.assertTrue(points.count() >= 1)
|
||||||
|
|
||||||
# Finds both points, but orders the north point first because it's
|
# Finds both points, but orders the north point first because it's
|
||||||
# closer to the reference point to the north.
|
# closer to the reference point to the north.
|
||||||
@@ -392,15 +220,141 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
# Finds only one point because only the first point is within 60km of
|
# Finds only one point because only the first point is within 60km of
|
||||||
# the reference point to the south.
|
# the reference point to the south.
|
||||||
points = Point.objects(
|
points = Point.objects(
|
||||||
location__within_spherical_distance=[
|
location__within_spherical_distance=[[-122, 36.5], 60/earth_radius])
|
||||||
[-122, 36.5],
|
|
||||||
60 / earth_radius
|
|
||||||
]
|
|
||||||
)
|
|
||||||
self.assertEqual(points.count(), 1)
|
self.assertEqual(points.count(), 1)
|
||||||
self.assertEqual(points[0].id, south_point.id)
|
self.assertEqual(points[0].id, south_point.id)
|
||||||
|
|
||||||
|
def test_2dsphere_point(self):
|
||||||
|
|
||||||
|
class Event(Document):
|
||||||
|
title = StringField()
|
||||||
|
date = DateTimeField()
|
||||||
|
location = PointField()
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
Event.drop_collection()
|
||||||
|
|
||||||
|
event1 = Event(title="Coltrane Motion @ Double Door",
|
||||||
|
date=datetime.now() - timedelta(days=1),
|
||||||
|
location=[-87.677137, 41.909889])
|
||||||
|
event1.save()
|
||||||
|
event2 = Event(title="Coltrane Motion @ Bottom of the Hill",
|
||||||
|
date=datetime.now() - timedelta(days=10),
|
||||||
|
location=[-122.4194155, 37.7749295]).save()
|
||||||
|
event3 = Event(title="Coltrane Motion @ Empty Bottle",
|
||||||
|
date=datetime.now(),
|
||||||
|
location=[-87.686638, 41.900474]).save()
|
||||||
|
|
||||||
|
# find all events "near" pitchfork office, chicago.
|
||||||
|
# note that "near" will show the san francisco event, too,
|
||||||
|
# although it sorts to last.
|
||||||
|
events = Event.objects(location__near=[-87.67892, 41.9120459])
|
||||||
|
self.assertEqual(events.count(), 3)
|
||||||
|
self.assertEqual(list(events), [event1, event3, event2])
|
||||||
|
|
||||||
|
# find events within 5 degrees of pitchfork office, chicago
|
||||||
|
point_and_distance = [[-87.67892, 41.9120459], 2]
|
||||||
|
events = Event.objects(location__geo_within_center=point_and_distance)
|
||||||
|
self.assertEqual(events.count(), 2)
|
||||||
|
events = list(events)
|
||||||
|
self.assertTrue(event2 not in events)
|
||||||
|
self.assertTrue(event1 in events)
|
||||||
|
self.assertTrue(event3 in events)
|
||||||
|
|
||||||
|
# ensure ordering is respected by "near"
|
||||||
|
events = Event.objects(location__near=[-87.67892, 41.9120459])
|
||||||
|
events = events.order_by("-date")
|
||||||
|
self.assertEqual(events.count(), 3)
|
||||||
|
self.assertEqual(list(events), [event3, event1, event2])
|
||||||
|
|
||||||
|
# find events within 10km of san francisco
|
||||||
|
point = [-122.415579, 37.7566023]
|
||||||
|
events = 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 = Event.objects(location__near=[-73.9509714, 40.7237134], location__max_distance=1000)
|
||||||
|
self.assertEqual(events.count(), 0)
|
||||||
|
|
||||||
|
# ensure ordering is respected by "near"
|
||||||
|
events = Event.objects(location__near=[-87.67892, 41.9120459],
|
||||||
|
location__max_distance=10000).order_by("-date")
|
||||||
|
self.assertEqual(events.count(), 2)
|
||||||
|
self.assertEqual(events[0], event3)
|
||||||
|
|
||||||
|
# ensure min_distance and max_distance combine well
|
||||||
|
events = Event.objects(location__near=[-87.67892, 41.9120459],
|
||||||
|
location__min_distance=1000,
|
||||||
|
location__max_distance=10000).order_by("-date")
|
||||||
|
self.assertEqual(events.count(), 1)
|
||||||
|
self.assertEqual(events[0], event3)
|
||||||
|
|
||||||
|
# ensure ordering is respected by "near"
|
||||||
|
events = Event.objects(location__near=[-87.67892, 41.9120459],
|
||||||
|
# location__min_distance=10000
|
||||||
|
location__min_distance=10000).order_by("-date")
|
||||||
|
self.assertEqual(events.count(), 1)
|
||||||
|
self.assertEqual(events[0], event2)
|
||||||
|
|
||||||
|
# check that within_box works
|
||||||
|
box = [(-125.0, 35.0), (-100.0, 40.0)]
|
||||||
|
events = Event.objects(location__geo_within_box=box)
|
||||||
|
self.assertEqual(events.count(), 1)
|
||||||
|
self.assertEqual(events[0].id, event2.id)
|
||||||
|
|
||||||
|
polygon = [
|
||||||
|
(-87.694445, 41.912114),
|
||||||
|
(-87.69084, 41.919395),
|
||||||
|
(-87.681742, 41.927186),
|
||||||
|
(-87.654276, 41.911731),
|
||||||
|
(-87.656164, 41.898061),
|
||||||
|
]
|
||||||
|
events = Event.objects(location__geo_within_polygon=polygon)
|
||||||
|
self.assertEqual(events.count(), 1)
|
||||||
|
self.assertEqual(events[0].id, event1.id)
|
||||||
|
|
||||||
|
polygon2 = [
|
||||||
|
(-1.742249, 54.033586),
|
||||||
|
(-1.225891, 52.792797),
|
||||||
|
(-4.40094, 53.389881)
|
||||||
|
]
|
||||||
|
events = Event.objects(location__geo_within_polygon=polygon2)
|
||||||
|
self.assertEqual(events.count(), 0)
|
||||||
|
|
||||||
|
def test_2dsphere_point_embedded(self):
|
||||||
|
|
||||||
|
class Venue(EmbeddedDocument):
|
||||||
|
location = GeoPointField()
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Event(Document):
|
||||||
|
title = StringField()
|
||||||
|
venue = EmbeddedDocumentField(Venue)
|
||||||
|
|
||||||
|
Event.drop_collection()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
# find all events "near" pitchfork office, chicago.
|
||||||
|
# note that "near" will show the san francisco event, too,
|
||||||
|
# although it sorts to last.
|
||||||
|
events = Event.objects(venue__location__near=[-87.67892, 41.9120459])
|
||||||
|
self.assertEqual(events.count(), 3)
|
||||||
|
self.assertEqual(list(events), [event1, event3, event2])
|
||||||
|
|
||||||
def test_linestring(self):
|
def test_linestring(self):
|
||||||
|
|
||||||
class Road(Document):
|
class Road(Document):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
line = LineStringField()
|
line = LineStringField()
|
||||||
@@ -456,6 +410,7 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
self.assertEqual(1, roads)
|
self.assertEqual(1, roads)
|
||||||
|
|
||||||
def test_polygon(self):
|
def test_polygon(self):
|
||||||
|
|
||||||
class Road(Document):
|
class Road(Document):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
poly = PolygonField()
|
poly = PolygonField()
|
||||||
@@ -552,6 +507,5 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
loc = Location.objects.as_pymongo()[0]
|
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()
|
unittest.main()
|
||||||
|
@@ -19,9 +19,6 @@ from mongoengine.python_support import IS_PYMONGO_3
|
|||||||
from mongoengine.queryset import (DoesNotExist, MultipleObjectsReturned,
|
from mongoengine.queryset import (DoesNotExist, MultipleObjectsReturned,
|
||||||
QuerySet, QuerySetManager, queryset_manager)
|
QuerySet, QuerySetManager, queryset_manager)
|
||||||
|
|
||||||
from tests.utils import needs_mongodb_v26, skip_pymongo3
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ("QuerySetTest",)
|
__all__ = ("QuerySetTest",)
|
||||||
|
|
||||||
|
|
||||||
@@ -35,6 +32,37 @@ class db_ops_tracker(query_counter):
|
|||||||
return list(self.db.system.profile.find(ignore_query))
|
return list(self.db.system.profile.find(ignore_query))
|
||||||
|
|
||||||
|
|
||||||
|
def skip_older_mongodb(f):
|
||||||
|
def _inner(*args, **kwargs):
|
||||||
|
connection = get_connection()
|
||||||
|
info = connection.test.command('buildInfo')
|
||||||
|
mongodb_version = tuple([int(i) for i in info['version'].split('.')])
|
||||||
|
|
||||||
|
if mongodb_version < (2, 6):
|
||||||
|
raise SkipTest("Need MongoDB version 2.6+")
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
_inner.__name__ = f.__name__
|
||||||
|
_inner.__doc__ = f.__doc__
|
||||||
|
|
||||||
|
return _inner
|
||||||
|
|
||||||
|
|
||||||
|
def skip_pymongo3(f):
|
||||||
|
def _inner(*args, **kwargs):
|
||||||
|
|
||||||
|
if IS_PYMONGO_3:
|
||||||
|
raise SkipTest("Useless with PyMongo 3+")
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
_inner.__name__ = f.__name__
|
||||||
|
_inner.__doc__ = f.__doc__
|
||||||
|
|
||||||
|
return _inner
|
||||||
|
|
||||||
|
|
||||||
class QuerySetTest(unittest.TestCase):
|
class QuerySetTest(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -571,23 +599,16 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertEqual(post.comments[0].by, 'joe')
|
self.assertEqual(post.comments[0].by, 'joe')
|
||||||
self.assertEqual(post.comments[0].votes.score, 4)
|
self.assertEqual(post.comments[0].votes.score, 4)
|
||||||
|
|
||||||
@needs_mongodb_v26
|
|
||||||
def test_update_min_max(self):
|
def test_update_min_max(self):
|
||||||
class Scores(Document):
|
class Scores(Document):
|
||||||
high_score = IntField()
|
high_score = IntField()
|
||||||
low_score = IntField()
|
low_score = IntField()
|
||||||
|
scores = Scores(high_score=800, low_score=200)
|
||||||
scores = Scores.objects.create(high_score=800, low_score=200)
|
scores.save()
|
||||||
|
|
||||||
Scores.objects(id=scores.id).update(min__low_score=150)
|
Scores.objects(id=scores.id).update(min__low_score=150)
|
||||||
self.assertEqual(Scores.objects.get(id=scores.id).low_score, 150)
|
self.assertEqual(Scores.objects(id=scores.id).get().low_score, 150)
|
||||||
Scores.objects(id=scores.id).update(min__low_score=250)
|
Scores.objects(id=scores.id).update(min__low_score=250)
|
||||||
self.assertEqual(Scores.objects.get(id=scores.id).low_score, 150)
|
self.assertEqual(Scores.objects(id=scores.id).get().low_score, 150)
|
||||||
|
|
||||||
Scores.objects(id=scores.id).update(max__high_score=1000)
|
|
||||||
self.assertEqual(Scores.objects.get(id=scores.id).high_score, 1000)
|
|
||||||
Scores.objects(id=scores.id).update(max__high_score=500)
|
|
||||||
self.assertEqual(Scores.objects.get(id=scores.id).high_score, 1000)
|
|
||||||
|
|
||||||
def test_updates_can_have_match_operators(self):
|
def test_updates_can_have_match_operators(self):
|
||||||
|
|
||||||
@@ -991,7 +1012,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertEqual(person.name, "User A")
|
self.assertEqual(person.name, "User A")
|
||||||
self.assertEqual(person.age, 20)
|
self.assertEqual(person.age, 20)
|
||||||
|
|
||||||
@needs_mongodb_v26
|
@skip_older_mongodb
|
||||||
@skip_pymongo3
|
@skip_pymongo3
|
||||||
def test_cursor_args(self):
|
def test_cursor_args(self):
|
||||||
"""Ensures the cursor args can be set as expected
|
"""Ensures the cursor args can be set as expected
|
||||||
@@ -3108,7 +3129,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(Foo.objects.distinct("bar"), [bar])
|
self.assertEqual(Foo.objects.distinct("bar"), [bar])
|
||||||
|
|
||||||
@needs_mongodb_v26
|
@skip_older_mongodb
|
||||||
def test_text_indexes(self):
|
def test_text_indexes(self):
|
||||||
class News(Document):
|
class News(Document):
|
||||||
title = StringField()
|
title = StringField()
|
||||||
@@ -3195,7 +3216,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
'brasil').order_by('$text_score').first()
|
'brasil').order_by('$text_score').first()
|
||||||
self.assertEqual(item.get_text_score(), max_text_score)
|
self.assertEqual(item.get_text_score(), max_text_score)
|
||||||
|
|
||||||
@needs_mongodb_v26
|
@skip_older_mongodb
|
||||||
def test_distinct_handles_references_to_alias(self):
|
def test_distinct_handles_references_to_alias(self):
|
||||||
register_connection('testdb', 'mongoenginetest2')
|
register_connection('testdb', 'mongoenginetest2')
|
||||||
|
|
||||||
@@ -4870,7 +4891,6 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertTrue(Person.objects._has_data(),
|
self.assertTrue(Person.objects._has_data(),
|
||||||
'Cursor has data and returned False')
|
'Cursor has data and returned False')
|
||||||
|
|
||||||
@needs_mongodb_v26
|
|
||||||
def test_queryset_aggregation_framework(self):
|
def test_queryset_aggregation_framework(self):
|
||||||
class Person(Document):
|
class Person(Document):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
@@ -4905,13 +4925,17 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
{'_id': p1.pk, 'name': "ISABELLA LUANNA"}
|
{'_id': p1.pk, 'name': "ISABELLA LUANNA"}
|
||||||
])
|
])
|
||||||
|
|
||||||
data = Person.objects(age__gte=17, age__lte=40).order_by('-age').aggregate({
|
data = Person.objects(
|
||||||
'$group': {
|
age__gte=17, age__lte=40).order_by('-age').aggregate(
|
||||||
'_id': None,
|
{'$group': {
|
||||||
'total': {'$sum': 1},
|
'_id': None,
|
||||||
'avg': {'$avg': '$age'}
|
'total': {'$sum': 1},
|
||||||
}
|
'avg': {'$avg': '$age'}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(list(data), [
|
self.assertEqual(list(data), [
|
||||||
{'_id': None, 'avg': 29, 'total': 2}
|
{'_id': None, 'avg': 29, 'total': 2}
|
||||||
])
|
])
|
||||||
@@ -4952,13 +4976,11 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertEquals(Animal.objects(folded_ears=True).count(), 1)
|
self.assertEquals(Animal.objects(folded_ears=True).count(), 1)
|
||||||
self.assertEquals(Animal.objects(whiskers_length=5.1).count(), 1)
|
self.assertEquals(Animal.objects(whiskers_length=5.1).count(), 1)
|
||||||
|
|
||||||
def test_loop_over_invalid_id_does_not_crash(self):
|
def test_loop_via_invalid_id_does_not_crash(self):
|
||||||
class Person(Document):
|
class Person(Document):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
|
Person.objects.delete()
|
||||||
Person.drop_collection()
|
Person._get_collection().update({"name": "a"}, {"$set": {"_id": ""}}, upsert=True)
|
||||||
|
|
||||||
Person._get_collection().insert({'name': 'a', 'id': ''})
|
|
||||||
for p in Person.objects():
|
for p in Person.objects():
|
||||||
self.assertEqual(p.name, 'a')
|
self.assertEqual(p.name, 'a')
|
||||||
|
|
||||||
|
@@ -35,7 +35,8 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
mongoengine.connection._dbs = {}
|
mongoengine.connection._dbs = {}
|
||||||
|
|
||||||
def test_connect(self):
|
def test_connect(self):
|
||||||
"""Ensure that the connect() method works properly."""
|
"""Ensure that the connect() method works properly.
|
||||||
|
"""
|
||||||
connect('mongoenginetest')
|
connect('mongoenginetest')
|
||||||
|
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
@@ -145,7 +146,8 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
self.assertEqual(expected_connection, actual_connection)
|
self.assertEqual(expected_connection, actual_connection)
|
||||||
|
|
||||||
def test_connect_uri(self):
|
def test_connect_uri(self):
|
||||||
"""Ensure that the connect() method works properly with URIs."""
|
"""Ensure that the connect() method works properly with uri's
|
||||||
|
"""
|
||||||
c = connect(db='mongoenginetest', alias='admin')
|
c = connect(db='mongoenginetest', alias='admin')
|
||||||
c.admin.system.users.remove({})
|
c.admin.system.users.remove({})
|
||||||
c.mongoenginetest.system.users.remove({})
|
c.mongoenginetest.system.users.remove({})
|
||||||
@@ -198,6 +200,19 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
self.assertTrue(isinstance(db, pymongo.database.Database))
|
self.assertTrue(isinstance(db, pymongo.database.Database))
|
||||||
self.assertEqual(db.name, 'test')
|
self.assertEqual(db.name, 'test')
|
||||||
|
|
||||||
|
def test_connect_uri_with_replicaset(self):
|
||||||
|
"""Ensure connect() works when specifying a replicaSet."""
|
||||||
|
if IS_PYMONGO_3:
|
||||||
|
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
|
||||||
|
db = get_db()
|
||||||
|
self.assertTrue(isinstance(db, pymongo.database.Database))
|
||||||
|
self.assertEqual(db.name, 'test')
|
||||||
|
else:
|
||||||
|
# PyMongo < v3.x raises an exception:
|
||||||
|
# "localhost:27017 is not a member of replica set local-rs"
|
||||||
|
with self.assertRaises(MongoEngineConnectionError):
|
||||||
|
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
|
||||||
|
|
||||||
def test_uri_without_credentials_doesnt_override_conn_settings(self):
|
def test_uri_without_credentials_doesnt_override_conn_settings(self):
|
||||||
"""Ensure connect() uses the username & password params if the URI
|
"""Ensure connect() uses the username & password params if the URI
|
||||||
doesn't explicitly specify them.
|
doesn't explicitly specify them.
|
||||||
@@ -212,8 +227,9 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
self.assertRaises(OperationFailure, get_db)
|
self.assertRaises(OperationFailure, get_db)
|
||||||
|
|
||||||
def test_connect_uri_with_authsource(self):
|
def test_connect_uri_with_authsource(self):
|
||||||
"""Ensure that the connect() method works well with `authSource`
|
"""Ensure that the connect() method works well with
|
||||||
option in the URI.
|
the option `authSource` in URI.
|
||||||
|
This feature was introduced in MongoDB 2.4 and removed in 2.6
|
||||||
"""
|
"""
|
||||||
# Create users
|
# Create users
|
||||||
c = connect('mongoenginetest')
|
c = connect('mongoenginetest')
|
||||||
@@ -222,31 +238,30 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
|
|
||||||
# Authentication fails without "authSource"
|
# Authentication fails without "authSource"
|
||||||
if IS_PYMONGO_3:
|
if IS_PYMONGO_3:
|
||||||
test_conn = connect(
|
test_conn = connect('mongoenginetest', alias='test1',
|
||||||
'mongoenginetest', alias='test1',
|
host='mongodb://username2:password@localhost/mongoenginetest')
|
||||||
host='mongodb://username2:password@localhost/mongoenginetest'
|
|
||||||
)
|
|
||||||
self.assertRaises(OperationFailure, test_conn.server_info)
|
self.assertRaises(OperationFailure, test_conn.server_info)
|
||||||
else:
|
else:
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
MongoEngineConnectionError,
|
MongoEngineConnectionError, connect, 'mongoenginetest',
|
||||||
connect, 'mongoenginetest', alias='test1',
|
alias='test1',
|
||||||
host='mongodb://username2:password@localhost/mongoenginetest'
|
host='mongodb://username2:password@localhost/mongoenginetest'
|
||||||
)
|
)
|
||||||
self.assertRaises(MongoEngineConnectionError, get_db, 'test1')
|
self.assertRaises(MongoEngineConnectionError, get_db, 'test1')
|
||||||
|
|
||||||
# Authentication succeeds with "authSource"
|
# Authentication succeeds with "authSource"
|
||||||
authd_conn = connect(
|
connect(
|
||||||
'mongoenginetest', alias='test2',
|
'mongoenginetest', alias='test2',
|
||||||
host=('mongodb://username2:password@localhost/'
|
host=('mongodb://username2:password@localhost/'
|
||||||
'mongoenginetest?authSource=admin')
|
'mongoenginetest?authSource=admin')
|
||||||
)
|
)
|
||||||
|
# This will fail starting from MongoDB 2.6+
|
||||||
db = get_db('test2')
|
db = get_db('test2')
|
||||||
self.assertTrue(isinstance(db, pymongo.database.Database))
|
self.assertTrue(isinstance(db, pymongo.database.Database))
|
||||||
self.assertEqual(db.name, 'mongoenginetest')
|
self.assertEqual(db.name, 'mongoenginetest')
|
||||||
|
|
||||||
# Clear all users
|
# Clear all users
|
||||||
authd_conn.admin.system.users.remove({})
|
c.admin.system.users.remove({})
|
||||||
|
|
||||||
def test_register_connection(self):
|
def test_register_connection(self):
|
||||||
"""Ensure that connections with different aliases may be registered.
|
"""Ensure that connections with different aliases may be registered.
|
||||||
@@ -319,38 +334,6 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
self.assertEqual(dict(conn1.write_concern), {'w': 1, 'j': True})
|
self.assertEqual(dict(conn1.write_concern), {'w': 1, 'j': True})
|
||||||
self.assertEqual(dict(conn2.write_concern), {'w': 1, 'j': True})
|
self.assertEqual(dict(conn2.write_concern), {'w': 1, 'j': True})
|
||||||
|
|
||||||
def test_connect_with_replicaset_via_uri(self):
|
|
||||||
"""Ensure connect() works when specifying a replicaSet via the
|
|
||||||
MongoDB URI.
|
|
||||||
"""
|
|
||||||
if IS_PYMONGO_3:
|
|
||||||
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
|
|
||||||
db = get_db()
|
|
||||||
self.assertTrue(isinstance(db, pymongo.database.Database))
|
|
||||||
self.assertEqual(db.name, 'test')
|
|
||||||
else:
|
|
||||||
# PyMongo < v3.x raises an exception:
|
|
||||||
# "localhost:27017 is not a member of replica set local-rs"
|
|
||||||
with self.assertRaises(MongoEngineConnectionError):
|
|
||||||
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
|
|
||||||
|
|
||||||
def test_connect_with_replicaset_via_kwargs(self):
|
|
||||||
"""Ensure connect() works when specifying a replicaSet via the
|
|
||||||
connection kwargs
|
|
||||||
"""
|
|
||||||
if IS_PYMONGO_3:
|
|
||||||
c = connect(replicaset='local-rs')
|
|
||||||
self.assertEqual(c._MongoClient__options.replica_set_name,
|
|
||||||
'local-rs')
|
|
||||||
db = get_db()
|
|
||||||
self.assertTrue(isinstance(db, pymongo.database.Database))
|
|
||||||
self.assertEqual(db.name, 'test')
|
|
||||||
else:
|
|
||||||
# PyMongo < v3.x raises an exception:
|
|
||||||
# "localhost:27017 is not a member of replica set local-rs"
|
|
||||||
with self.assertRaises(MongoEngineConnectionError):
|
|
||||||
c = connect(replicaset='local-rs')
|
|
||||||
|
|
||||||
def test_datetime(self):
|
def test_datetime(self):
|
||||||
connect('mongoenginetest', tz_aware=True)
|
connect('mongoenginetest', tz_aware=True)
|
||||||
d = datetime.datetime(2010, 5, 5, tzinfo=utc)
|
d = datetime.datetime(2010, 5, 5, tzinfo=utc)
|
||||||
|
@@ -1,11 +1,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from nose.plugins.skip import SkipTest
|
|
||||||
|
|
||||||
from mongoengine import connect
|
from mongoengine import connect
|
||||||
from mongoengine.connection import get_db, get_connection
|
from mongoengine.connection import get_db
|
||||||
from mongoengine.python_support import IS_PYMONGO_3
|
|
||||||
|
|
||||||
|
|
||||||
MONGO_TEST_DB = 'mongoenginetest'
|
MONGO_TEST_DB = 'mongoenginetest'
|
||||||
|
|
||||||
@@ -24,55 +20,3 @@ class MongoDBTestCase(unittest.TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
cls._connection.drop_database(MONGO_TEST_DB)
|
cls._connection.drop_database(MONGO_TEST_DB)
|
||||||
|
|
||||||
|
|
||||||
def get_mongodb_version():
|
|
||||||
"""Return the version tuple of the MongoDB server that the default
|
|
||||||
connection is connected to.
|
|
||||||
"""
|
|
||||||
return tuple(get_connection().server_info()['versionArray'])
|
|
||||||
|
|
||||||
def _decorated_with_ver_requirement(func, ver_tuple):
|
|
||||||
"""Return a given function decorated with the version requirement
|
|
||||||
for a particular MongoDB version tuple.
|
|
||||||
"""
|
|
||||||
def _inner(*args, **kwargs):
|
|
||||||
mongodb_ver = get_mongodb_version()
|
|
||||||
if mongodb_ver >= ver_tuple:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
raise SkipTest('Needs MongoDB v{}+'.format(
|
|
||||||
'.'.join([str(v) for v in ver_tuple])
|
|
||||||
))
|
|
||||||
|
|
||||||
_inner.__name__ = func.__name__
|
|
||||||
_inner.__doc__ = func.__doc__
|
|
||||||
|
|
||||||
return _inner
|
|
||||||
|
|
||||||
def needs_mongodb_v26(func):
|
|
||||||
"""Raise a SkipTest exception if we're working with MongoDB version
|
|
||||||
lower than v2.6.
|
|
||||||
"""
|
|
||||||
return _decorated_with_ver_requirement(func, (2, 6))
|
|
||||||
|
|
||||||
def needs_mongodb_v3(func):
|
|
||||||
"""Raise a SkipTest exception if we're working with MongoDB version
|
|
||||||
lower than v3.0.
|
|
||||||
"""
|
|
||||||
return _decorated_with_ver_requirement(func, (3, 0))
|
|
||||||
|
|
||||||
def skip_pymongo3(f):
|
|
||||||
"""Raise a SkipTest exception if we're running a test against
|
|
||||||
PyMongo v3.x.
|
|
||||||
"""
|
|
||||||
def _inner(*args, **kwargs):
|
|
||||||
if IS_PYMONGO_3:
|
|
||||||
raise SkipTest("Useless with PyMongo 3+")
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
|
|
||||||
_inner.__name__ = f.__name__
|
|
||||||
_inner.__doc__ = f.__doc__
|
|
||||||
|
|
||||||
return _inner
|
|
||||||
|
|
||||||
|
13
tox.ini
13
tox.ini
@@ -1,5 +1,5 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = {py27,py35,pypy,pypy3}-{mg27,mg28,mg30}
|
envlist = {py26,py27,py33,py34,py35,pypy,pypy3}-{mg27,mg28},flake8
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands =
|
commands =
|
||||||
@@ -7,7 +7,16 @@ commands =
|
|||||||
deps =
|
deps =
|
||||||
nose
|
nose
|
||||||
mg27: PyMongo<2.8
|
mg27: PyMongo<2.8
|
||||||
mg28: PyMongo>=2.8,<2.9
|
mg28: PyMongo>=2.8,<3.0
|
||||||
mg30: PyMongo>=3.0
|
mg30: PyMongo>=3.0
|
||||||
|
mgdev: https://github.com/mongodb/mongo-python-driver/tarball/master
|
||||||
setenv =
|
setenv =
|
||||||
PYTHON_EGG_CACHE = {envdir}/python-eggs
|
PYTHON_EGG_CACHE = {envdir}/python-eggs
|
||||||
|
passenv = windir
|
||||||
|
|
||||||
|
[testenv:flake8]
|
||||||
|
deps =
|
||||||
|
flake8
|
||||||
|
flake8-import-order
|
||||||
|
commands =
|
||||||
|
flake8
|
||||||
|
Reference in New Issue
Block a user