Compare commits

..

No commits in common. "master" and "v0.22.0" have entirely different histories.

73 changed files with 537 additions and 1169 deletions

View File

@ -1,143 +0,0 @@
name: MongoengineCI
on:
# All PR
pull_request:
# master branch merge
push:
branches:
- master
# release tags
create:
tags:
- 'v[0-9]+\.[0-9]+\.[0-9]+*'
env:
MONGODB_3_6: 3.6.14
MONGODB_4_0: 4.0.23
MONGODB_4_2: 4.2
MONGODB_4_4: 4.4
PYMONGO_3_4: 3.4
PYMONGO_3_6: 3.6
PYMONGO_3_9: 3.9
PYMONGO_3_11: 3.11
MAIN_PYTHON_VERSION: 3.7
jobs:
linting:
# Run pre-commit (https://pre-commit.com/)
# which runs pre-configured linter & autoformatter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: 3.7
- run: bash .github/workflows/install_ci_python_dep.sh
- run: pre-commit run -a
test:
# Test suite run against recent python versions
# and against a few combination of MongoDB and pymongo
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3]
MONGODB: [$MONGODB_4_0]
PYMONGO: [$PYMONGO_3_11]
include:
- python-version: 3.7
MONGODB: $MONGODB_3_6
PYMONGO: $PYMONGO_3_9
- python-version: 3.7
MONGODB: $MONGODB_4_2
PYMONGO: $PYMONGO_3_6
- python-version: 3.7
MONGODB: $MONGODB_4_4
PYMONGO: $PYMONGO_3_11
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: install mongo and ci dependencies
run: |
bash .github/workflows/install_mongo.sh ${{ matrix.MONGODB }}
bash .github/workflows/install_ci_python_dep.sh
bash .github/workflows/start_mongo.sh ${{ matrix.MONGODB }}
- name: tox dry-run (to pre-install venv)
run: tox -e $(echo py${{ matrix.python-version }}-mg${{ matrix.PYMONGO }} | tr -d . | sed -e 's/pypypy/pypy/') -- -a "-k=test_ci_placeholder"
- name: Run test suite
run: tox -e $(echo py${{ matrix.python-version }}-mg${{ matrix.PYMONGO }} | tr -d . | sed -e 's/pypypy/pypy/') -- -a "--cov=mongoengine"
- name: Send coverage to Coveralls
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_SERVICE_NAME: github
if: ${{ matrix.python-version == env.MAIN_PYTHON_VERSION }}
run: coveralls
build_doc_dryrun:
# ensures that readthedocs can be built continuously
# to avoid that it breaks when new releases are being created
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: install python dep
run: |
pip install -e .
pip install -r docs/requirements.txt
- name: build doc
run: |
cd docs
make html-readthedocs
build-n-publish-dummy:
runs-on: ubuntu-latest
needs: [linting, test, build_doc_dryrun]
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@master
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: build dummy wheel for test-pypi
run: |
pip install wheel
python setup.py egg_info -b ".dev`date '+%Y%m%d%H%M%S'`" build sdist bdist_wheel
# - name: publish test-pypi
# # Although working and recommended, test-pypi has a limit
# # in the size of projects so it's better to avoid publishing
# # until there is a way to garbage collect these dummy releases
# uses: pypa/gh-action-pypi-publish@master
# with:
# password: ${{ secrets.test_pypi_token }}
# repository_url: https://test.pypi.org/legacy/
build-n-publish:
runs-on: ubuntu-latest
needs: [linting, test, build_doc_dryrun, build-n-publish-dummy]
if: github.event_name == 'create' && startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@master
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7
# todo separate build from publish
# https://stackoverflow.com/questions/59349905/which-properties-does-github-event-in-a-github-workflow-have
- name: build dummy wheel for test-pypi
run: |
pip install wheel
python setup.py sdist bdist_wheel
- name: publish pypi
uses: pypa/gh-action-pypi-publish@master
with:
password: ${{ secrets.pypi_token }}

View File

@ -1,5 +0,0 @@
#!/bin/bash
pip install --upgrade pip
pip install coveralls
pip install pre-commit
pip install tox

View File

@ -1,18 +0,0 @@
#!/bin/bash
MONGODB=$1
# Mongo > 4.0 follows different name convention for download links
mongo_build=mongodb-linux-x86_64-${MONGODB}
if [[ "$MONGODB" == *"4.2"* ]]; then
mongo_build=mongodb-linux-x86_64-ubuntu1804-v${MONGODB}-latest
elif [[ "$MONGODB" == *"4.4"* ]]; then
mongo_build=mongodb-linux-x86_64-ubuntu1804-v${MONGODB}-latest
fi
wget http://fastdl.mongodb.org/linux/$mongo_build.tgz
tar xzf $mongo_build.tgz
mongodb_dir=$(find ${PWD}/ -type d -name "mongodb-linux-x86_64*")
$mongodb_dir/bin/mongod --version

View File

@ -1,9 +0,0 @@
#!/bin/bash
MONGODB=$1
mongodb_dir=$(find ${PWD}/ -type d -name "mongodb-linux-x86_64*")
mkdir $mongodb_dir/data
$mongodb_dir/bin/mongod --dbpath $mongodb_dir/data --logpath $mongodb_dir/mongodb.log --fork
mongo --eval 'db.version();' # Make sure mongo is awake

View File

@ -1,26 +1,17 @@
fail_fast: false fail_fast: false
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: check-merge-conflict
- id: debug-statements
- id: trailing-whitespace
- id: end-of-file-fixer
- repo: https://github.com/ambv/black - repo: https://github.com/ambv/black
rev: 21.5b2 rev: 20.8b1
hooks: hooks:
- id: black - id: black
- repo: https://gitlab.com/pycqa/flake8 - repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2 rev: 3.8.4
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies:
- flake8-import-order
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v2.19.1 rev: v2.7.4
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py36-plus] args: [--py36-plus]
- repo: https://github.com/pycqa/isort
rev: 5.8.0
hooks:
- id: isort

107
.travis.yml Normal file
View File

@ -0,0 +1,107 @@
# 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.
# * Python3.7, MongoDB v3.4 & the latest PyMongo v3.x is currently the "main" setup,
# Other combinations are tested. See below for the details or check the travis jobs
# We should periodically check MongoDB Server versions supported by MongoDB
# Inc., add newly released versions to the test matrix, and remove versions
# which have reached their End of Life. See:
# 1. https://www.mongodb.com/support-policy.
# 2. https://docs.mongodb.com/ecosystem/drivers/driver-compatibility-reference/#python-driver-compatibility
#
# Reminder: Update README.rst if you change MongoDB versions we test.
language: python
dist: xenial
python:
- 3.6
- 3.7
- 3.8
- 3.9
- pypy3
env:
global:
- MONGODB_3_4=3.4.19
- MONGODB_3_6=3.6.13
- MONGODB_4_0=4.0.13
- PYMONGO_3_4=3.4
- PYMONGO_3_6=3.6
- PYMONGO_3_9=3.9
- PYMONGO_3_11=3.11
- MAIN_PYTHON_VERSION=3.7
matrix:
- MONGODB=${MONGODB_3_4} PYMONGO=${PYMONGO_3_11}
matrix:
# Finish the build as soon as one job fails
fast_finish: true
include:
- python: 3.7
env: MONGODB=${MONGODB_3_6} PYMONGO=${PYMONGO_3_6}
- python: 3.7
env: MONGODB=${MONGODB_3_6} PYMONGO=${PYMONGO_3_9}
- python: 3.7
env: MONGODB=${MONGODB_3_6} PYMONGO=${PYMONGO_3_11}
- python: 3.8
env: MONGODB=${MONGODB_4_0} PYMONGO=${PYMONGO_3_11}
install:
# Install Mongo
- wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-${MONGODB}.tgz
- tar xzf mongodb-linux-x86_64-${MONGODB}.tgz
- ${PWD}/mongodb-linux-x86_64-${MONGODB}/bin/mongod --version
# Install Python dependencies.
- pip install --upgrade pip
- pip install coveralls
- pip install pre-commit
- pip install tox
# tox dryrun to setup the tox venv (we run a mock test).
- tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -a "-k=test_ci_placeholder"
before_script:
- mkdir ${PWD}/mongodb-linux-x86_64-${MONGODB}/data
- ${PWD}/mongodb-linux-x86_64-${MONGODB}/bin/mongod --dbpath ${PWD}/mongodb-linux-x86_64-${MONGODB}/data --logpath ${PWD}/mongodb-linux-x86_64-${MONGODB}/mongodb.log --fork
# Run pre-commit hooks (black, flake8, etc) on entire codebase
- if [[ $TRAVIS_PYTHON_VERSION == $MAIN_PYTHON_VERSION ]]; then pre-commit run -a; else echo "pre-commit checks only runs on py37"; fi
- mongo --eval 'db.version();' # Make sure mongo is awake
script:
- tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -a "--cov=mongoengine"
after_success:
- if [[ $TRAVIS_PYTHON_VERSION == $MAIN_PYTHON_VERSION ]]; then coveralls --verbose; else echo "coveralls only sent for py37"; fi
notifications:
irc: irc.freenode.org#mongoengine
# Only run builds on the master branch and GitHub releases (tagged as vX.Y.Z)
branches:
only:
- master
- /^v.*$/
# Whenever a new release is created via GitHub, publish it on PyPI.
deploy:
provider: pypi
user: the_drow
password:
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 v3.7 along with PyMongo v3.x and MongoDB v3.4.
# We run Travis against many different Python, PyMongo, and MongoDB versions
# and we don't want the deploy to occur multiple times).
on:
tags: true
repo: MongoEngine/mongoengine
condition: ($PYMONGO = ${PYMONGO_3_11}) && ($MONGODB = ${MONGODB_3_4})
python: 3.7

View File

@ -259,7 +259,3 @@ that much better:
* Agustin Barto (https://github.com/abarto) * Agustin Barto (https://github.com/abarto)
* Stankiewicz Mateusz (https://github.com/mas15) * Stankiewicz Mateusz (https://github.com/mas15)
* Felix Schultheiß (https://github.com/felix-smashdocs) * Felix Schultheiß (https://github.com/felix-smashdocs)
* Jan Stein (https://github.com/janste63)
* Timothé Perez (https://github.com/AchilleAsh)
* oleksandr-l5 (https://github.com/oleksandr-l5)
* Ido Shraga (https://github.com/idoshr)

View File

@ -35,8 +35,8 @@ Travis runs the tests against the main Python 3.x versions.
Style Guide Style Guide
----------- -----------
MongoEngine's codebase is auto-formatted with `black <https://github.com/python/black>`_, imports are ordered with `isort <https://pycqa.github.io/isort/>`_ MongoEngine's codebase is formatted with `black <https://github.com/python/black>`_, other tools like
and other tools like flake8 are also used. Those tools will run as part of the CI and will fail in case the code is not formatted properly. flake8 are also used. Those tools will run as part of the CI and will fail in case the code is not formatted properly.
To install all development tools, simply run the following commands: To install all development tools, simply run the following commands:
@ -58,10 +58,6 @@ To enable ``pre-commit`` simply run:
See the ``.pre-commit-config.yaml`` configuration file for more information See the ``.pre-commit-config.yaml`` configuration file for more information
on how it works. on how it works.
pre-commit will now run upon every commit and will reject anything that doesn't comply.
You can also run all the checks with ``pre-commit run -a``, this is what is used in the CI.
Testing Testing
------- -------

View File

@ -31,7 +31,7 @@ myNoddys = noddy.find()
print("-" * 100) print("-" * 100)
print("PyMongo: Creating 10000 dictionaries.") print("PyMongo: Creating 10000 dictionaries.")
t = timeit.Timer(stmt=stmt, setup=setup) t = timeit.Timer(stmt=stmt, setup=setup)
print(f"{t.timeit(1)}s") print("{}s".format(t.timeit(1)))
stmt = """ stmt = """
from pymongo import MongoClient, WriteConcern from pymongo import MongoClient, WriteConcern
@ -54,7 +54,7 @@ myNoddys = noddy.find()
print("-" * 100) print("-" * 100)
print('PyMongo: Creating 10000 dictionaries (write_concern={"w": 0}).') print('PyMongo: Creating 10000 dictionaries (write_concern={"w": 0}).')
t = timeit.Timer(stmt=stmt, setup=setup) t = timeit.Timer(stmt=stmt, setup=setup)
print(f"{t.timeit(1)}s") print("{}s".format(t.timeit(1)))
setup = """ setup = """
from pymongo import MongoClient from pymongo import MongoClient
@ -84,7 +84,7 @@ myNoddys = Noddy.objects()
print("-" * 100) print("-" * 100)
print("MongoEngine: Creating 10000 dictionaries.") print("MongoEngine: Creating 10000 dictionaries.")
t = timeit.Timer(stmt=stmt, setup=setup) t = timeit.Timer(stmt=stmt, setup=setup)
print(f"{t.timeit(1)}s") print("{}s".format(t.timeit(1)))
stmt = """ stmt = """
for i in range(10000): for i in range(10000):
@ -102,7 +102,7 @@ myNoddys = Noddy.objects()
print("-" * 100) print("-" * 100)
print("MongoEngine: Creating 10000 dictionaries (using a single field assignment).") print("MongoEngine: Creating 10000 dictionaries (using a single field assignment).")
t = timeit.Timer(stmt=stmt, setup=setup) t = timeit.Timer(stmt=stmt, setup=setup)
print(f"{t.timeit(1)}s") print("{}s".format(t.timeit(1)))
stmt = """ stmt = """
for i in range(10000): for i in range(10000):
@ -118,7 +118,7 @@ myNoddys = Noddy.objects()
print("-" * 100) print("-" * 100)
print('MongoEngine: Creating 10000 dictionaries (write_concern={"w": 0}).') print('MongoEngine: Creating 10000 dictionaries (write_concern={"w": 0}).')
t = timeit.Timer(stmt=stmt, setup=setup) t = timeit.Timer(stmt=stmt, setup=setup)
print(f"{t.timeit(1)}s") print("{}s".format(t.timeit(1)))
stmt = """ stmt = """
for i in range(10000): for i in range(10000):
@ -136,7 +136,7 @@ myNoddys = Noddy.objects()
'MongoEngine: Creating 10000 dictionaries (write_concern={"w": 0}, validate=False).' 'MongoEngine: Creating 10000 dictionaries (write_concern={"w": 0}, validate=False).'
) )
t = timeit.Timer(stmt=stmt, setup=setup) t = timeit.Timer(stmt=stmt, setup=setup)
print(f"{t.timeit(1)}s") print("{}s".format(t.timeit(1)))
stmt = """ stmt = """
for i in range(10000): for i in range(10000):
@ -154,7 +154,7 @@ myNoddys = Noddy.objects()
'MongoEngine: Creating 10000 dictionaries (force_insert=True, write_concern={"w": 0}, validate=False).' 'MongoEngine: Creating 10000 dictionaries (force_insert=True, write_concern={"w": 0}, validate=False).'
) )
t = timeit.Timer(stmt=stmt, setup=setup) t = timeit.Timer(stmt=stmt, setup=setup)
print(f"{t.timeit(1)}s") print("{}s".format(t.timeit(1)))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -35,12 +35,6 @@ html:
@echo @echo
@echo "Build finished. Check $(BUILDDIR)/html/index.html" @echo "Build finished. Check $(BUILDDIR)/html/index.html"
html-readthedocs:
$(SPHINXBUILD) -T -E -b readthedocs $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml: dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo @echo

View File

@ -75,7 +75,6 @@ Fields
.. autoclass:: mongoengine.fields.StringField .. autoclass:: mongoengine.fields.StringField
.. autoclass:: mongoengine.fields.URLField .. autoclass:: mongoengine.fields.URLField
.. autoclass:: mongoengine.fields.EmailField .. autoclass:: mongoengine.fields.EmailField
.. autoclass:: mongoengine.fields.EnumField
.. autoclass:: mongoengine.fields.IntField .. autoclass:: mongoengine.fields.IntField
.. autoclass:: mongoengine.fields.LongField .. autoclass:: mongoengine.fields.LongField
.. autoclass:: mongoengine.fields.FloatField .. autoclass:: mongoengine.fields.FloatField

View File

@ -1,5 +1,4 @@
========= =========
Changelog Changelog
========= =========
@ -7,26 +6,6 @@ Changelog
Development Development
=========== ===========
- (Fill this out as you fix issues and develop your features). - (Fill this out as you fix issues and develop your features).
- EnumField improvements: now `choices` limits the values of an enum to allow
- Fix deepcopy of EmbeddedDocument #2202
- Fix error when using precision=0 with DecimalField #2535
- Add support for regex and whole word text search query #2568
Changes in 0.23.1
===========
- Bug fix: ignore LazyReferenceFields when clearing _changed_fields #2484
- Improve connection doc #2481
Changes in 0.23.0
=================
- Bugfix: manually setting SequenceField in DynamicDocument doesn't increment the counter #2471
- Add MongoDB 4.2 and 4.4 to CI
- Add support for allowDiskUse on querysets #2468
Changes in 0.22.1
=================
- Declare that Py3.5 is not supported in package metadata #2449
- Moved CI from Travis to Github-Actions
Changes in 0.22.0 Changes in 0.22.0
================= =================

View File

@ -26,7 +26,7 @@ sys.path.insert(0, os.path.abspath(".."))
# Add any Sphinx extension module names here, as strings. They can be extensions # Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ["sphinx.ext.autodoc", "sphinx.ext.todo", "readthedocs_ext.readthedocs"] extensions = ["sphinx.ext.autodoc", "sphinx.ext.todo"]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"] templates_path = ["_templates"]

View File

@ -5,7 +5,7 @@ Connecting to MongoDB
===================== =====================
Connections in MongoEngine are registered globally and are identified with aliases. Connections in MongoEngine are registered globally and are identified with aliases.
If no ``alias`` is provided during the connection, it will use "default" as alias. If no `alias` is provided during the connection, it will use "default" as alias.
To connect to a running instance of :program:`mongod`, use the :func:`~mongoengine.connect` To connect to a running instance of :program:`mongod`, use the :func:`~mongoengine.connect`
function. The first argument is the name of the database to connect to:: function. The first argument is the name of the database to connect to::
@ -14,66 +14,27 @@ function. The first argument is the name of the database to connect to::
connect('project1') connect('project1')
By default, MongoEngine assumes that the :program:`mongod` instance is running By default, MongoEngine assumes that the :program:`mongod` instance is running
on **localhost** on port **27017**. on **localhost** on port **27017**. If MongoDB is running elsewhere, you should
provide the :attr:`host` and :attr:`port` arguments to
:func:`~mongoengine.connect`::
If MongoDB is running elsewhere, you need to provide details on how to connect. There are two ways of connect('project1', host='192.168.1.35', port=12345)
doing this. Using a connection string in URI format (**this is the preferred method**) or individual attributes
provided as keyword arguments.
Connect with URI string
=======================
When using a connection string in URI format you should specify the connection details
as the :attr:`host` to :func:`~mongoengine.connect`. In a web application context for instance, the URI
is typically read from the config file::
connect(host="mongodb://127.0.0.1:27017/my_db")
If the database requires authentication, you can specify it in the
URI. As each database can have its own users configured, you need to tell MongoDB
where to look for the user you are working with, that's what the ``?authSource=admin`` bit
of the MongoDB connection string is for::
# Connects to 'my_db' database by authenticating
# with given credentials against the 'admin' database (by default as authSource isn't provided)
connect(host="mongodb://my_user:my_password@127.0.0.1:27017/my_db")
# Equivalent to previous connection but explicitly states that
# it should use admin as the authentication source database
connect(host="mongodb://my_user:my_password@hostname:port/my_db?authSource=admin")
# Connects to 'my_db' database by authenticating
# with given credentials against that same database
connect(host="mongodb://my_user:my_password@127.0.0.1:27017/my_db?authSource=my_db")
The URI string can also be used to configure advanced parameters like ssl, replicaSet, etc. For more
information or example about URI string, you can refer to the `official doc <https://docs.mongodb.com/manual/reference/connection-string/>`_::
connect(host="mongodb://my_user:my_password@127.0.0.1:27017/my_db?authSource=admin&ssl=true&replicaSet=globaldb")
.. note:: URI containing SRV records (e.g "mongodb+srv://server.example.com/") can be used as well
Connect with keyword attributes
===============================
The second option for specifying the connection details is to provide the information as keyword
attributes to :func:`~mongoengine.connect`::
connect('my_db', host='127.0.0.1', port=27017)
If the database requires authentication, :attr:`username`, :attr:`password` If the database requires authentication, :attr:`username`, :attr:`password`
and :attr:`authentication_source` arguments should be provided:: and :attr:`authentication_source` arguments should be provided::
connect('my_db', username='my_user', password='my_password', authentication_source='admin') connect('project1', username='webapp', password='pwd123', authentication_source='admin')
The set of attributes that :func:`~mongoengine.connect` recognizes includes but is not limited to: URI style connections are also supported -- just supply the URI as
:attr:`host`, :attr:`port`, :attr:`read_preference`, :attr:`username`, :attr:`password`, :attr:`authentication_source`, :attr:`authentication_mechanism`, the :attr:`host` to
:attr:`replicaset`, :attr:`tls`, etc. Most of the parameters accepted by `pymongo.MongoClient <https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient>`_ :func:`~mongoengine.connect`::
can be used with :func:`~mongoengine.connect` and will simply be forwarded when instantiating the `pymongo.MongoClient`.
connect('project1', host='mongodb://localhost/database_name')
.. note:: URI containing SRV records (e.g mongodb+srv://server.example.com/) can be used as well as the :attr:`host`
.. note:: Database, username and password from URI string overrides .. note:: Database, username and password from URI string overrides
corresponding parameters in :func:`~mongoengine.connect`, this should corresponding parameters in :func:`~mongoengine.connect`: ::
obviously be avoided: ::
connect( connect(
db='test', db='test',
@ -82,19 +43,28 @@ can be used with :func:`~mongoengine.connect` and will simply be forwarded when
host='mongodb://admin:qwerty@localhost/production' host='mongodb://admin:qwerty@localhost/production'
) )
will establish connection to ``production`` database using ``admin`` username and ``qwerty`` password. will establish connection to ``production`` database using
``admin`` username and ``qwerty`` password.
.. note:: Calling :func:`~mongoengine.connect` without argument will establish .. note:: Calling :func:`~mongoengine.connect` without argument will establish
a connection to the "test" database by default a connection to the "test" database by default
Read Preferences Replica Sets
================ ============
As stated above, Read preferences are supported through the connection but also via individual MongoEngine supports connecting to replica sets::
from mongoengine import connect
# 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
queries by passing the read_preference :: queries by passing the read_preference ::
from pymongo import ReadPreference
Bar.objects().read_preference(ReadPreference.PRIMARY) Bar.objects().read_preference(ReadPreference.PRIMARY)
Bar.objects(read_preference=ReadPreference.PRIMARY) Bar.objects(read_preference=ReadPreference.PRIMARY)

View File

@ -27,8 +27,6 @@ objects** as class attributes to the document class::
As BSON (the binary format for storing data in mongodb) is order dependent, As BSON (the binary format for storing data in mongodb) is order dependent,
documents are serialized based on their field order. documents are serialized based on their field order.
.. _dynamic-document-schemas:
Dynamic document schemas Dynamic document schemas
======================== ========================
One of the benefits of MongoDB is dynamic schemas for a collection, whilst data One of the benefits of MongoDB is dynamic schemas for a collection, whilst data
@ -233,9 +231,6 @@ document class as the first argument::
comment2 = Comment(content='Nice article!') comment2 = Comment(content='Nice article!')
page = Page(comments=[comment1, comment2]) page = Page(comments=[comment1, comment2])
Embedded documents can also leverage the flexibility of :ref:`dynamic-document-schemas:`
by inheriting :class:`~mongoengine.DynamicEmbeddedDocument`.
Dictionary Fields Dictionary Fields
----------------- -----------------
Often, an embedded document may be used instead of a dictionary generally Often, an embedded document may be used instead of a dictionary generally
@ -295,12 +290,12 @@ as the constructor's argument::
content = StringField() content = StringField()
.. _many-to-many-with-listfields: .. _one-to-many-with-listfields:
Many to Many with ListFields One to Many with ListFields
''''''''''''''''''''''''''' '''''''''''''''''''''''''''
If you are implementing a many to many relationship via a list of references, If you are implementing a one to many relationship via a list of references,
then the references are stored as DBRefs and to query you need to pass an then the references are stored as DBRefs and to query you need to pass an
instance of the object to the query:: instance of the object to the query::
@ -341,6 +336,7 @@ supplying the :attr:`reverse_delete_rule` attributes on the
:class:`ReferenceField` definition, like this:: :class:`ReferenceField` definition, like this::
class ProfilePage(Document): class ProfilePage(Document):
...
employee = ReferenceField('Employee', reverse_delete_rule=mongoengine.CASCADE) employee = ReferenceField('Employee', reverse_delete_rule=mongoengine.CASCADE)
The declaration in this example means that when an :class:`Employee` object is The declaration in this example means that when an :class:`Employee` object is
@ -436,10 +432,10 @@ Document collections
==================== ====================
Document classes that inherit **directly** from :class:`~mongoengine.Document` Document classes that inherit **directly** from :class:`~mongoengine.Document`
will have their own **collection** in the database. The name of the collection will have their own **collection** in the database. The name of the collection
is by default the name of the class converted to snake_case (e.g if your Document class is by default the name of the class, converted to lowercase (so in the example
is named `CompanyUser`, the corresponding collection would be `company_user`). If you need above, the collection would be called `page`). If you need to change the name
to change the name of the collection (e.g. to use MongoEngine with an existing database), of the collection (e.g. to use MongoEngine with an existing database), then
then create a class dictionary attribute called :attr:`meta` on your document, and create a class dictionary attribute called :attr:`meta` on your document, and
set :attr:`collection` to the name of the collection that you want your set :attr:`collection` to the name of the collection that you want your
document class to use:: document class to use::
@ -477,7 +473,7 @@ dictionary containing a full index definition.
A direction may be specified on fields by prefixing the field name with a A direction may be specified on fields by prefixing the field name with a
**+** (for ascending) or a **-** sign (for descending). Note that direction **+** (for ascending) or a **-** sign (for descending). Note that direction
only matters on compound indexes. Text indexes may be specified by prefixing only matters on multi-field indexes. Text indexes may be specified by prefixing
the field name with a **$**. Hashed indexes may be specified by prefixing the field name with a **$**. Hashed indexes may be specified by prefixing
the field name with a **#**:: the field name with a **#**::
@ -488,14 +484,14 @@ the field name with a **#**::
created = DateTimeField() created = DateTimeField()
meta = { meta = {
'indexes': [ 'indexes': [
'title', # single-field index 'title',
'$title', # text index '$title', # text index
'#title', # hashed index '#title', # hashed index
('title', '-rating'), # compound index ('title', '-rating'),
('category', '_cls'), # compound index ('category', '_cls'),
{ {
'fields': ['created'], 'fields': ['created'],
'expireAfterSeconds': 3600 # ttl index 'expireAfterSeconds': 3600
} }
] ]
} }
@ -628,8 +624,8 @@ point. To create a geospatial index you must prefix the field with the
], ],
} }
Time To Live (TTL) indexes Time To Live indexes
-------------------------- --------------------
A special index type that allows you to automatically expire data from a A special index type that allows you to automatically expire data from a
collection after a given period. See the official collection after a given period. See the official

View File

@ -35,7 +35,7 @@ existing ``User`` model with a `default=True`. Thus you simply update the ``User
class User(Document): class User(Document):
name = StringField(required=True) name = StringField(required=True)
enabled = BooleanField(default=True) enabled = BooleaField(default=True)
Without applying any migration, we now reload an object from the database into the ``User`` class Without applying any migration, we now reload an object from the database into the ``User`` class
and checks its `enabled` attribute: and checks its `enabled` attribute:
@ -223,47 +223,6 @@ it is often useful for complex migrations of Document models.
.. warning:: Be aware of this `flaw <https://groups.google.com/g/mongodb-user/c/AFC1ia7MHzk>`_ if you modify documents while iterating .. warning:: Be aware of this `flaw <https://groups.google.com/g/mongodb-user/c/AFC1ia7MHzk>`_ if you modify documents while iterating
Example 4: Index removal
========================
If you remove an index from your Document class, or remove an indexed Field from your Document class,
you'll need to manually drop the corresponding index. MongoEngine will not do that for you.
The way to deal with this case is to identify the name of the index to drop with `index_information()`, and then drop
it with `drop_index()`
Let's for instance assume that you start with the following Document class
.. code-block:: python
class User(Document):
name = StringField(index=True)
meta = {"indexes": ["name"]}
User(name="John Doe").save()
As soon as you start interacting with the Document collection (when `.save()` is called in this case),
it would create the following indexes:
.. code-block:: python
print(User._get_collection().index_information())
# {
# '_id_': {'key': [('_id', 1)], 'v': 2},
# 'name_1': {'background': False, 'key': [('name', 1)], 'v': 2},
# }
Thus: '_id' which is the default index and 'name_1' which is our custom index.
If you would remove the 'name' field or its index, you would have to call:
.. code-block:: python
User._get_collection().drop_index('name_1')
.. note:: When adding new fields or new indexes, MongoEngine will take care of creating them
(unless `auto_create_index` is disabled) ::
Recommendations Recommendations
=============== ===============

View File

@ -86,10 +86,6 @@ expressions:
* ``istartswith`` -- string field starts with value (case insensitive) * ``istartswith`` -- string field starts with value (case insensitive)
* ``endswith`` -- string field ends with value * ``endswith`` -- string field ends with value
* ``iendswith`` -- string field ends with value (case insensitive) * ``iendswith`` -- string field ends with value (case insensitive)
* ``wholeword`` -- string field contains whole word
* ``iwholeword`` -- string field contains whole word (case insensitive)
* ``regex`` -- string field match by regex
* ``iregex`` -- string field match by regex (case insensitive)
* ``match`` -- performs an $elemMatch so you can match an entire document within an array * ``match`` -- performs an $elemMatch so you can match an entire document within an array
@ -243,7 +239,7 @@ Limiting and skipping results
Just as with traditional ORMs, you may limit the number of results returned or Just as with traditional ORMs, you may limit the number of results returned or
skip a number or results in you query. skip a number or results in you query.
:meth:`~mongoengine.queryset.QuerySet.limit` and :meth:`~mongoengine.queryset.QuerySet.limit` and
:meth:`~mongoengine.queryset.QuerySet.skip` methods are available on :meth:`~mongoengine.queryset.QuerySet.skip` and methods are available on
:class:`~mongoengine.queryset.QuerySet` objects, but the `array-slicing` syntax :class:`~mongoengine.queryset.QuerySet` objects, but the `array-slicing` syntax
is preferred for achieving this:: is preferred for achieving this::
@ -547,10 +543,7 @@ Documents may be updated atomically by using the
There are several different "modifiers" that you may use with these methods: There are several different "modifiers" that you may use with these methods:
* ``set`` -- set a particular value * ``set`` -- set a particular value
* ``set_on_insert`` -- set only if this is new document `need to add upsert=True`_
* ``unset`` -- delete a particular value (since MongoDB v1.3) * ``unset`` -- delete a particular value (since MongoDB v1.3)
* ``max`` -- update only if value is bigger
* ``min`` -- update only if value is smaller
* ``inc`` -- increment a value by a given amount * ``inc`` -- increment a value by a given amount
* ``dec`` -- decrement a value by a given amount * ``dec`` -- decrement a value by a given amount
* ``push`` -- append a value to a list * ``push`` -- append a value to a list
@ -559,7 +552,6 @@ There are several different "modifiers" that you may use with these methods:
* ``pull`` -- remove a value from a list * ``pull`` -- remove a value from a list
* ``pull_all`` -- remove several values from a list * ``pull_all`` -- remove several values from a list
* ``add_to_set`` -- add value to a list only if its not in the list already * ``add_to_set`` -- add value to a list only if its not in the list already
* ``rename`` -- rename the key name
.. _depending on the value: http://docs.mongodb.org/manual/reference/operator/update/pop/ .. _depending on the value: http://docs.mongodb.org/manual/reference/operator/update/pop/

View File

@ -120,3 +120,4 @@ the validation and cleaning of a document when you call :meth:`~mongoengine.docu
Person(age=1000).save(validate=False) Person(age=1000).save(validate=False)
person = Person.objects.first() person = Person.objects.first()
assert person.age == 1000 assert person.age == 1000

View File

@ -1,3 +1,3 @@
Sphinx==3.3.0 pymongo>=3.11
Sphinx==3.2.1
sphinx-rtd-theme==0.5.0 sphinx-rtd-theme==0.5.0
readthedocs-sphinx-ext==2.1.1

View File

@ -1,12 +1,10 @@
# Import submodules so that we can expose their __all__ # Import submodules so that we can expose their __all__
from mongoengine import ( from mongoengine import connection
connection, from mongoengine import document
document, from mongoengine import errors
errors, from mongoengine import fields
fields, from mongoengine import queryset
queryset, from mongoengine import signals
signals,
)
# Import everything from each submodule so that it can be accessed via # Import everything from each submodule so that it can be accessed via
# mongoengine, e.g. instead of `from mongoengine.connection import connect`, # mongoengine, e.g. instead of `from mongoengine.connection import connect`,
@ -19,6 +17,7 @@ from mongoengine.fields import * # noqa: F401
from mongoengine.queryset import * # noqa: F401 from mongoengine.queryset import * # noqa: F401
from mongoengine.signals import * # noqa: F401 from mongoengine.signals import * # noqa: F401
__all__ = ( __all__ = (
list(document.__all__) list(document.__all__)
+ list(fields.__all__) + list(fields.__all__)
@ -29,7 +28,7 @@ __all__ = (
) )
VERSION = (0, 23, 1) VERSION = (0, 22, 0)
def get_version(): def get_version():

View File

@ -1,9 +1,10 @@
import copy import copy
import numbers import numbers
from functools import partial from functools import partial
from bson import DBRef, ObjectId, SON, json_util
import pymongo import pymongo
from bson import SON, DBRef, ObjectId, json_util
from mongoengine import signals from mongoengine import signals
from mongoengine.base.common import get_document from mongoengine.base.common import get_document
@ -155,7 +156,7 @@ class BaseDocument:
# Handle dynamic data only if an initialised dynamic document # Handle dynamic data only if an initialised dynamic document
if self._dynamic and not self._dynamic_lock: if self._dynamic and not self._dynamic_lock:
if name not in self._fields_ordered and not name.startswith("_"): if not hasattr(self, name) and not name.startswith("_"):
DynamicField = _import_class("DynamicField") DynamicField = _import_class("DynamicField")
field = DynamicField(db_field=name, null=True) field = DynamicField(db_field=name, null=True)
field.name = name field.name = name
@ -614,9 +615,7 @@ class BaseDocument:
def _get_changed_fields(self): def _get_changed_fields(self):
"""Return a list of all fields that have explicitly been changed.""" """Return a list of all fields that have explicitly been changed."""
EmbeddedDocument = _import_class("EmbeddedDocument") EmbeddedDocument = _import_class("EmbeddedDocument")
LazyReferenceField = _import_class("LazyReferenceField")
ReferenceField = _import_class("ReferenceField") ReferenceField = _import_class("ReferenceField")
GenericLazyReferenceField = _import_class("GenericLazyReferenceField")
GenericReferenceField = _import_class("GenericReferenceField") GenericReferenceField = _import_class("GenericReferenceField")
SortedListField = _import_class("SortedListField") SortedListField = _import_class("SortedListField")
@ -642,13 +641,7 @@ class BaseDocument:
changed_fields += [f"{key}{k}" for k in changed if k] changed_fields += [f"{key}{k}" for k in changed if k]
elif isinstance(data, (list, tuple, dict)): elif isinstance(data, (list, tuple, dict)):
if hasattr(field, "field") and isinstance( if hasattr(field, "field") and isinstance(
field.field, field.field, (ReferenceField, GenericReferenceField)
(
LazyReferenceField,
ReferenceField,
GenericLazyReferenceField,
GenericReferenceField,
),
): ):
continue continue
elif isinstance(field, SortedListField) and field._ordering: elif isinstance(field, SortedListField) and field._ordering:

View File

@ -1,15 +1,11 @@
import operator import operator
import weakref import weakref
from bson import DBRef, ObjectId, SON
import pymongo import pymongo
from bson import SON, DBRef, ObjectId
from mongoengine.base.common import UPDATE_OPERATORS from mongoengine.base.common import UPDATE_OPERATORS
from mongoengine.base.datastructures import ( from mongoengine.base.datastructures import BaseDict, BaseList, EmbeddedDocumentList
BaseDict,
BaseList,
EmbeddedDocumentList,
)
from mongoengine.common import _import_class from mongoengine.common import _import_class
from mongoengine.errors import DeprecatedError, ValidationError from mongoengine.errors import DeprecatedError, ValidationError
@ -271,17 +267,6 @@ class ComplexBaseField(BaseField):
self.field = field self.field = field
super().__init__(**kwargs) super().__init__(**kwargs)
@staticmethod
def _lazy_load_refs(instance, name, ref_values, *, max_depth):
_dereference = _import_class("DeReference")()
documents = _dereference(
ref_values,
max_depth=max_depth,
instance=instance,
name=name,
)
return documents
def __get__(self, instance, owner): def __get__(self, instance, owner):
"""Descriptor to automatically dereference references.""" """Descriptor to automatically dereference references."""
if instance is None: if instance is None:
@ -299,15 +284,19 @@ class ComplexBaseField(BaseField):
or isinstance(self.field, (GenericReferenceField, ReferenceField)) or isinstance(self.field, (GenericReferenceField, ReferenceField))
) )
_dereference = _import_class("DeReference")()
if ( if (
instance._initialised instance._initialised
and dereference and dereference
and instance._data.get(self.name) and instance._data.get(self.name)
and not getattr(instance._data[self.name], "_dereferenced", False) and not getattr(instance._data[self.name], "_dereferenced", False)
): ):
ref_values = instance._data.get(self.name) instance._data[self.name] = _dereference(
instance._data[self.name] = self._lazy_load_refs( instance._data.get(self.name),
ref_values=ref_values, instance=instance, name=self.name, max_depth=1 max_depth=1,
instance=instance,
name=self.name,
) )
if hasattr(instance._data[self.name], "_dereferenced"): if hasattr(instance._data[self.name], "_dereferenced"):
instance._data[self.name]._dereferenced = True instance._data[self.name]._dereferenced = True
@ -333,9 +322,7 @@ class ComplexBaseField(BaseField):
and isinstance(value, (BaseList, BaseDict)) and isinstance(value, (BaseList, BaseDict))
and not value._dereferenced and not value._dereferenced
): ):
value = self._lazy_load_refs( value = _dereference(value, max_depth=1, instance=instance, name=self.name)
ref_values=value, instance=instance, name=self.name, max_depth=1
)
value._dereferenced = True value._dereferenced = True
instance._data[self.name] = value instance._data[self.name] = value

View File

@ -2,11 +2,7 @@ import itertools
import warnings import warnings
from mongoengine.base.common import _document_registry from mongoengine.base.common import _document_registry
from mongoengine.base.fields import ( from mongoengine.base.fields import BaseField, ComplexBaseField, ObjectIdField
BaseField,
ComplexBaseField,
ObjectIdField,
)
from mongoengine.common import _import_class from mongoengine.common import _import_class
from mongoengine.errors import InvalidDocumentError from mongoengine.errors import InvalidDocumentError
from mongoengine.queryset import ( from mongoengine.queryset import (
@ -16,6 +12,7 @@ from mongoengine.queryset import (
QuerySetManager, QuerySetManager,
) )
__all__ = ("DocumentMetaclass", "TopLevelDocumentMetaclass") __all__ = ("DocumentMetaclass", "TopLevelDocumentMetaclass")

View File

@ -58,20 +58,20 @@ def _get_connection_settings(
): ):
"""Get the connection settings as a dict """Get the connection settings as a dict
:param db: the name of the database to use, for compatibility with connect : param db: the name of the database to use, for compatibility with connect
:param name: the name of the specific database to use : param name: the name of the specific database to use
:param host: the host name of the: program: `mongod` instance to connect to : param host: the host name of the: program: `mongod` instance to connect to
:param port: the port that the: program: `mongod` instance is running on : param port: the port that the: program: `mongod` instance is running on
:param read_preference: The read preference for the collection : param read_preference: The read preference for the collection
:param username: username to authenticate with : param username: username to authenticate with
:param password: password to authenticate with : param password: password to authenticate with
:param authentication_source: database to authenticate against : param authentication_source: database to authenticate against
:param authentication_mechanism: database authentication mechanisms. : param authentication_mechanism: database authentication mechanisms.
By default, use SCRAM-SHA-1 with MongoDB 3.0 and later, By default, use SCRAM-SHA-1 with MongoDB 3.0 and later,
MONGODB-CR (MongoDB Challenge Response protocol) for older servers. MONGODB-CR (MongoDB Challenge Response protocol) for older servers.
:param is_mock: explicitly use mongomock for this connection : param is_mock: explicitly use mongomock for this connection
(can also be done by using `mongomock: // ` as db host prefix) (can also be done by using `mongomock: // ` as db host prefix)
:param kwargs: ad-hoc parameters to be passed into the pymongo driver, : param kwargs: ad-hoc parameters to be passed into the pymongo driver,
for example maxpoolsize, tz_aware, etc. See the documentation for example maxpoolsize, tz_aware, etc. See the documentation
for pymongo's `MongoClient` for a full list. for pymongo's `MongoClient` for a full list.
""" """
@ -181,21 +181,22 @@ def register_connection(
): ):
"""Register the connection settings. """Register the connection settings.
:param alias: the name that will be used to refer to this connection throughout MongoEngine : param alias: the name that will be used to refer to this connection
:param db: the name of the database to use, for compatibility with connect throughout MongoEngine
:param name: the name of the specific database to use : param db: the name of the database to use, for compatibility with connect
:param host: the host name of the: program: `mongod` instance to connect to : param name: the name of the specific database to use
:param port: the port that the: program: `mongod` instance is running on : param host: the host name of the: program: `mongod` instance to connect to
:param read_preference: The read preference for the collection : param port: the port that the: program: `mongod` instance is running on
:param username: username to authenticate with : param read_preference: The read preference for the collection
:param password: password to authenticate with : param username: username to authenticate with
:param authentication_source: database to authenticate against : param password: password to authenticate with
:param authentication_mechanism: database authentication mechanisms. : param authentication_source: database to authenticate against
: param authentication_mechanism: database authentication mechanisms.
By default, use SCRAM-SHA-1 with MongoDB 3.0 and later, By default, use SCRAM-SHA-1 with MongoDB 3.0 and later,
MONGODB-CR (MongoDB Challenge Response protocol) for older servers. MONGODB-CR (MongoDB Challenge Response protocol) for older servers.
:param is_mock: explicitly use mongomock for this connection : param is_mock: explicitly use mongomock for this connection
(can also be done by using `mongomock: // ` as db host prefix) (can also be done by using `mongomock: // ` as db host prefix)
:param kwargs: ad-hoc parameters to be passed into the pymongo driver, : param kwargs: ad-hoc parameters to be passed into the pymongo driver,
for example maxpoolsize, tz_aware, etc. See the documentation for example maxpoolsize, tz_aware, etc. See the documentation
for pymongo's `MongoClient` for a full list. for pymongo's `MongoClient` for a full list.
""" """
@ -216,8 +217,8 @@ def register_connection(
def disconnect(alias=DEFAULT_CONNECTION_NAME): def disconnect(alias=DEFAULT_CONNECTION_NAME):
"""Close the connection with a given alias.""" """Close the connection with a given alias."""
from mongoengine import Document
from mongoengine.base.common import _get_documents_by_db from mongoengine.base.common import _get_documents_by_db
from mongoengine import Document
if alias in _connections: if alias in _connections:
get_connection(alias=alias).close() get_connection(alias=alias).close()

View File

@ -177,28 +177,14 @@ class query_counter:
This was designed for debugging purpose. In fact it is a global counter so queries issued by other threads/processes This was designed for debugging purpose. In fact it is a global counter so queries issued by other threads/processes
can interfere with it can interfere with it
Usage:
.. code-block:: python
class User(Document):
name = StringField()
with query_counter() as q:
user = User(name='Bob')
assert q == 0 # no query fired yet
user.save()
assert q == 1 # 1 query was fired, an 'insert'
user_bis = User.objects().first()
assert q == 2 # a 2nd query was fired, a 'find_one'
Be aware that: Be aware that:
- Iterating over large amount of documents (>101) makes pymongo issue `getmore` queries to fetch the next batch of
- Iterating over large amount of documents (>101) makes pymongo issue `getmore` queries to fetch the next batch of documents (https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/#cursor-batches) documents (https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/#cursor-batches)
- Some queries are ignored by default by the counter (killcursors, db.system.indexes) - Some queries are ignored by default by the counter (killcursors, db.system.indexes)
""" """
def __init__(self, alias=DEFAULT_CONNECTION_NAME): def __init__(self, alias=DEFAULT_CONNECTION_NAME):
"""Construct the query_counter"""
self.db = get_db(alias=alias) self.db = get_db(alias=alias)
self.initial_profiling_level = None self.initial_profiling_level = None
self._ctx_query_counter = 0 # number of queries issued by the context self._ctx_query_counter = 0 # number of queries issued by the context

View File

@ -1,4 +1,4 @@
from bson import SON, DBRef from bson import DBRef, SON
from mongoengine.base import ( from mongoengine.base import (
BaseDict, BaseDict,
@ -10,12 +10,7 @@ from mongoengine.base import (
from mongoengine.base.datastructures import LazyReference from mongoengine.base.datastructures import LazyReference
from mongoengine.connection import get_db from mongoengine.connection import get_db
from mongoengine.document import Document, EmbeddedDocument from mongoengine.document import Document, EmbeddedDocument
from mongoengine.fields import ( from mongoengine.fields import DictField, ListField, MapField, ReferenceField
DictField,
ListField,
MapField,
ReferenceField,
)
from mongoengine.queryset import QuerySet from mongoengine.queryset import QuerySet

View File

@ -1,7 +1,7 @@
import re import re
import pymongo
from bson.dbref import DBRef from bson.dbref import DBRef
import pymongo
from pymongo.read_preferences import ReadPreference from pymongo.read_preferences import ReadPreference
from mongoengine import signals from mongoengine import signals
@ -16,23 +16,14 @@ from mongoengine.base import (
) )
from mongoengine.common import _import_class from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.context_managers import ( from mongoengine.context_managers import set_write_concern, switch_collection, switch_db
set_write_concern,
switch_collection,
switch_db,
)
from mongoengine.errors import ( from mongoengine.errors import (
InvalidDocumentError, InvalidDocumentError,
InvalidQueryError, InvalidQueryError,
SaveConditionError, SaveConditionError,
) )
from mongoengine.pymongo_support import list_collection_names from mongoengine.pymongo_support import list_collection_names
from mongoengine.queryset import ( from mongoengine.queryset import NotUniqueError, OperationError, QuerySet, transform
NotUniqueError,
OperationError,
QuerySet,
transform,
)
__all__ = ( __all__ = (
"Document", "Document",
@ -99,15 +90,6 @@ class EmbeddedDocument(BaseDocument, metaclass=DocumentMetaclass):
def __ne__(self, other): def __ne__(self, other):
return not self.__eq__(other) return not self.__eq__(other)
def __getstate__(self):
data = super().__getstate__()
data["_instance"] = None
return data
def __setstate__(self, state):
super().__setstate__(state)
self._instance = state["_instance"]
def to_mongo(self, *args, **kwargs): def to_mongo(self, *args, **kwargs):
data = super().to_mongo(*args, **kwargs) data = super().to_mongo(*args, **kwargs)
@ -127,7 +109,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
By default, the MongoDB collection used to store documents created using a By default, the MongoDB collection used to store documents created using a
:class:`~mongoengine.Document` subclass will be the name of the subclass :class:`~mongoengine.Document` subclass will be the name of the subclass
converted to snake_case. A different collection may be specified by converted to lowercase. A different collection may be specified by
providing :attr:`collection` to the :attr:`meta` dictionary in the class providing :attr:`collection` to the :attr:`meta` dictionary in the class
definition. definition.
@ -135,7 +117,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
create a specialised version of the document that will be stored in the create a specialised version of the document that will be stored in the
same collection. To facilitate this behaviour a `_cls` same collection. To facilitate this behaviour a `_cls`
field is added to documents (hidden though the MongoEngine interface). field is added to documents (hidden though the MongoEngine interface).
To enable this behaviour set :attr:`allow_inheritance` to ``True`` in the To enable this behaviourset :attr:`allow_inheritance` to ``True`` in the
:attr:`meta` dictionary. :attr:`meta` dictionary.
A :class:`~mongoengine.Document` may use a **Capped Collection** by A :class:`~mongoengine.Document` may use a **Capped Collection** by
@ -341,7 +323,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
_refs=None, _refs=None,
save_condition=None, save_condition=None,
signal_kwargs=None, signal_kwargs=None,
**kwargs, **kwargs
): ):
"""Save the :class:`~mongoengine.Document` to the database. If the """Save the :class:`~mongoengine.Document` to the database. If the
document already exists, it will be updated, otherwise it will be document already exists, it will be updated, otherwise it will be
@ -572,7 +554,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
if not getattr(ref, "_changed_fields", True): if not getattr(ref, "_changed_fields", True):
continue continue
ref_id = f"{ref.__class__.__name__},{str(ref._data)}" ref_id = "{},{}".format(ref.__class__.__name__, str(ref._data))
if ref and ref_id not in _refs: if ref and ref_id not in _refs:
_refs.append(ref_id) _refs.append(ref_id)
kwargs["_refs"] = _refs kwargs["_refs"] = _refs
@ -583,7 +565,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
def _qs(self): def _qs(self):
"""Return the default queryset corresponding to this document.""" """Return the default queryset corresponding to this document."""
if not hasattr(self, "__objects"): if not hasattr(self, "__objects"):
self.__objects = QuerySet(self.__class__, self._get_collection()) self.__objects = QuerySet(self, self._get_collection())
return self.__objects return self.__objects
@property @property
@ -875,10 +857,6 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
Global defaults can be set in the meta - see :doc:`guide/defining-documents` Global defaults can be set in the meta - see :doc:`guide/defining-documents`
By default, this will get called automatically upon first interaction with the
Document collection (query, save, etc) so unless you disabled `auto_create_index`, you
shouldn't have to call this manually.
.. note:: You can disable automatic index creation by setting .. note:: You can disable automatic index creation by setting
`auto_create_index` to False in the documents meta data `auto_create_index` to False in the documents meta data
""" """
@ -928,10 +906,8 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
@classmethod @classmethod
def list_indexes(cls): def list_indexes(cls):
"""Lists all indexes that should be created for the Document collection. """Lists all of the indexes that should be created for given
It includes all the indexes from super- and sub-classes. collection. It includes all the indexes from super- and sub-classes.
Note that it will only return the indexes' fields, not the indexes' options
""" """
if cls._meta.get("abstract"): if cls._meta.get("abstract"):
return [] return []

View File

@ -1,5 +1,6 @@
from collections import defaultdict from collections import defaultdict
__all__ = ( __all__ = (
"NotRegistered", "NotRegistered",
"InvalidDocumentError", "InvalidDocumentError",
@ -17,15 +18,11 @@ __all__ = (
) )
class MongoEngineException(Exception): class NotRegistered(Exception):
pass pass
class NotRegistered(MongoEngineException): class InvalidDocumentError(Exception):
pass
class InvalidDocumentError(MongoEngineException):
pass pass
@ -33,19 +30,19 @@ class LookUpError(AttributeError):
pass pass
class DoesNotExist(MongoEngineException): class DoesNotExist(Exception):
pass pass
class MultipleObjectsReturned(MongoEngineException): class MultipleObjectsReturned(Exception):
pass pass
class InvalidQueryError(MongoEngineException): class InvalidQueryError(Exception):
pass pass
class OperationError(MongoEngineException): class OperationError(Exception):
pass pass
@ -61,7 +58,7 @@ class SaveConditionError(OperationError):
pass pass
class FieldDoesNotExist(MongoEngineException): class FieldDoesNotExist(Exception):
"""Raised when trying to set a field """Raised when trying to set a field
not declared in a :class:`~mongoengine.Document` not declared in a :class:`~mongoengine.Document`
or an :class:`~mongoengine.EmbeddedDocument`. or an :class:`~mongoengine.EmbeddedDocument`.
@ -159,7 +156,7 @@ class ValidationError(AssertionError):
return " ".join([f"{k}: {v}" for k, v in error_dict.items()]) return " ".join([f"{k}: {v}" for k, v in error_dict.items()])
class DeprecatedError(MongoEngineException): class DeprecatedError(Exception):
"""Raise when a user uses a feature that has been Deprecated""" """Raise when a user uses a feature that has been Deprecated"""
pass pass

View File

@ -1,6 +1,5 @@
import datetime import datetime
import decimal import decimal
import inspect
import itertools import itertools
import re import re
import socket import socket
@ -9,10 +8,10 @@ import uuid
from io import BytesIO from io import BytesIO
from operator import itemgetter from operator import itemgetter
from bson import Binary, DBRef, ObjectId, SON
from bson.int64 import Int64
import gridfs import gridfs
import pymongo import pymongo
from bson import SON, Binary, DBRef, ObjectId
from bson.int64 import Int64
from pymongo import ReturnDocument from pymongo import ReturnDocument
try: try:
@ -22,6 +21,7 @@ except ImportError:
else: else:
import dateutil.parser import dateutil.parser
from mongoengine.base import ( from mongoengine.base import (
BaseDocument, BaseDocument,
BaseField, BaseField,
@ -35,11 +35,7 @@ from mongoengine.base.utils import LazyRegexCompiler
from mongoengine.common import _import_class from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.document import Document, EmbeddedDocument from mongoengine.document import Document, EmbeddedDocument
from mongoengine.errors import ( from mongoengine.errors import DoesNotExist, InvalidQueryError, ValidationError
DoesNotExist,
InvalidQueryError,
ValidationError,
)
from mongoengine.queryset import DO_NOTHING from mongoengine.queryset import DO_NOTHING
from mongoengine.queryset.base import BaseQuerySet from mongoengine.queryset.base import BaseQuerySet
from mongoengine.queryset.transform import STRING_OPERATORS from mongoengine.queryset.transform import STRING_OPERATORS
@ -157,14 +153,7 @@ class StringField(BaseField):
regex = r"%s$" regex = r"%s$"
elif op == "exact": elif op == "exact":
regex = r"^%s$" regex = r"^%s$"
elif op == "wholeword":
regex = r"\b%s\b"
elif op == "regex":
regex = value
if op == "regex":
value = re.compile(regex, flags)
else:
# escape unsafe characters which could lead to a re.error # escape unsafe characters which could lead to a re.error
value = re.escape(value) value = re.escape(value)
value = re.compile(regex % value, flags) value = re.compile(regex % value, flags)
@ -475,10 +464,6 @@ class DecimalField(BaseField):
self.min_value = min_value self.min_value = min_value
self.max_value = max_value self.max_value = max_value
self.force_string = force_string self.force_string = force_string
if precision < 0 or not isinstance(precision, int):
self.error("precision must be a positive integer")
self.precision = precision self.precision = precision
self.rounding = rounding self.rounding = rounding
@ -493,12 +478,9 @@ class DecimalField(BaseField):
value = decimal.Decimal("%s" % value) value = decimal.Decimal("%s" % value)
except (TypeError, ValueError, decimal.InvalidOperation): except (TypeError, ValueError, decimal.InvalidOperation):
return value return value
if self.precision > 0:
return value.quantize( return value.quantize(
decimal.Decimal(".%s" % ("0" * self.precision)), rounding=self.rounding decimal.Decimal(".%s" % ("0" * self.precision)), rounding=self.rounding
) )
else:
return value.quantize(decimal.Decimal(), rounding=self.rounding)
def to_mongo(self, value): def to_mongo(self, value):
if value is None: if value is None:
@ -532,7 +514,7 @@ class BooleanField(BaseField):
def to_python(self, value): def to_python(self, value):
try: try:
value = bool(value) value = bool(value)
except (ValueError, TypeError): except ValueError:
pass pass
return value return value
@ -932,7 +914,7 @@ class ListField(ComplexBaseField):
"""A list field that wraps a standard field, allowing multiple instances """A list field that wraps a standard field, allowing multiple instances
of the field to be used as a list in the database. of the field to be used as a list in the database.
If using with ReferenceFields see: :ref:`many-to-many-with-listfields` If using with ReferenceFields see: :ref:`one-to-many-with-listfields`
.. note:: .. note::
Required means it cannot be empty - as the default for ListFields is [] Required means it cannot be empty - as the default for ListFields is []
@ -1046,6 +1028,17 @@ def key_not_string(d):
return True return True
def key_has_dot_or_dollar(d):
"""Helper function to recursively determine if any key in a
dictionary contains a dot or a dollar sign.
"""
for k, v in d.items():
if ("." in k or k.startswith("$")) or (
isinstance(v, dict) and key_has_dot_or_dollar(v)
):
return True
def key_starts_with_dollar(d): def key_starts_with_dollar(d):
"""Helper function to recursively determine if any key in a """Helper function to recursively determine if any key in a
dictionary starts with a dollar dictionary starts with a dollar
@ -1093,7 +1086,16 @@ class DictField(ComplexBaseField):
return DictField(db_field=member_name) return DictField(db_field=member_name)
def prepare_query_value(self, op, value): def prepare_query_value(self, op, value):
match_operators = [*STRING_OPERATORS] match_operators = [
"contains",
"icontains",
"startswith",
"istartswith",
"endswith",
"iendswith",
"exact",
"iexact",
]
if op in match_operators and isinstance(value, str): if op in match_operators and isinstance(value, str):
return StringField().prepare_query_value(op, value) return StringField().prepare_query_value(op, value)
@ -1170,7 +1172,7 @@ class ReferenceField(BaseField):
:param document_type: The type of Document that will be referenced :param document_type: The type of Document that will be referenced
:param dbref: Store the reference as :class:`~pymongo.dbref.DBRef` :param dbref: Store the reference as :class:`~pymongo.dbref.DBRef`
or as the :class:`~pymongo.objectid.ObjectId`. or as the :class:`~pymongo.objectid.ObjectId`.id .
:param reverse_delete_rule: Determines what to do when the referring :param reverse_delete_rule: Determines what to do when the referring
object is deleted object is deleted
:param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.BaseField` :param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.BaseField`
@ -1202,14 +1204,6 @@ class ReferenceField(BaseField):
self.document_type_obj = get_document(self.document_type_obj) self.document_type_obj = get_document(self.document_type_obj)
return self.document_type_obj return self.document_type_obj
@staticmethod
def _lazy_load_ref(ref_cls, dbref):
dereferenced_son = ref_cls._get_db().dereference(dbref)
if dereferenced_son is None:
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
return ref_cls._from_son(dereferenced_son)
def __get__(self, instance, owner): def __get__(self, instance, owner):
"""Descriptor to allow lazy dereferencing.""" """Descriptor to allow lazy dereferencing."""
if instance is None: if instance is None:
@ -1217,17 +1211,20 @@ class ReferenceField(BaseField):
return self return self
# Get value from document instance if available # Get value from document instance if available
ref_value = instance._data.get(self.name) value = instance._data.get(self.name)
auto_dereference = instance._fields[self.name]._auto_dereference auto_dereference = instance._fields[self.name]._auto_dereference
# Dereference DBRefs # Dereference DBRefs
if auto_dereference and isinstance(ref_value, DBRef): if auto_dereference and isinstance(value, DBRef):
if hasattr(ref_value, "cls"): if hasattr(value, "cls"):
# Dereference using the class type specified in the reference # Dereference using the class type specified in the reference
cls = get_document(ref_value.cls) cls = get_document(value.cls)
else: else:
cls = self.document_type cls = self.document_type
dereferenced = cls._get_db().dereference(value)
instance._data[self.name] = self._lazy_load_ref(cls, ref_value) if dereferenced is None:
raise DoesNotExist("Trying to dereference unknown document %s" % value)
else:
instance._data[self.name] = cls._from_son(dereferenced)
return super().__get__(instance, owner) return super().__get__(instance, owner)
@ -1314,8 +1311,8 @@ class CachedReferenceField(BaseField):
fields = [] fields = []
# XXX ValidationError raised outside of the "validate" method. # XXX ValidationError raised outside of the "validate" method.
if not isinstance(document_type, str) and not ( if not isinstance(document_type, str) and not issubclass(
inspect.isclass(document_type) and issubclass(document_type, Document) document_type, Document
): ):
self.error( self.error(
"Argument to CachedReferenceField constructor must be a" "Argument to CachedReferenceField constructor must be a"
@ -1366,14 +1363,6 @@ class CachedReferenceField(BaseField):
self.document_type_obj = get_document(self.document_type_obj) self.document_type_obj = get_document(self.document_type_obj)
return self.document_type_obj return self.document_type_obj
@staticmethod
def _lazy_load_ref(ref_cls, dbref):
dereferenced_son = ref_cls._get_db().dereference(dbref)
if dereferenced_son is None:
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
return ref_cls._from_son(dereferenced_son)
def __get__(self, instance, owner): def __get__(self, instance, owner):
if instance is None: if instance is None:
# Document class being used rather than a document object # Document class being used rather than a document object
@ -1385,7 +1374,11 @@ class CachedReferenceField(BaseField):
# Dereference DBRefs # Dereference DBRefs
if auto_dereference and isinstance(value, DBRef): if auto_dereference and isinstance(value, DBRef):
instance._data[self.name] = self._lazy_load_ref(self.document_type, value) dereferenced = self.document_type._get_db().dereference(value)
if dereferenced is None:
raise DoesNotExist("Trying to dereference unknown document %s" % value)
else:
instance._data[self.name] = self.document_type._from_son(dereferenced)
return super().__get__(instance, owner) return super().__get__(instance, owner)
@ -1510,14 +1503,6 @@ class GenericReferenceField(BaseField):
value = value._class_name value = value._class_name
super()._validate_choices(value) super()._validate_choices(value)
@staticmethod
def _lazy_load_ref(ref_cls, dbref):
dereferenced_son = ref_cls._get_db().dereference(dbref)
if dereferenced_son is None:
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
return ref_cls._from_son(dereferenced_son)
def __get__(self, instance, owner): def __get__(self, instance, owner):
if instance is None: if instance is None:
return self return self
@ -1525,9 +1510,12 @@ class GenericReferenceField(BaseField):
value = instance._data.get(self.name) value = instance._data.get(self.name)
auto_dereference = instance._fields[self.name]._auto_dereference auto_dereference = instance._fields[self.name]._auto_dereference
if auto_dereference and isinstance(value, dict): if auto_dereference and isinstance(value, (dict, SON)):
doc_cls = get_document(value["_cls"]) dereferenced = self.dereference(value)
instance._data[self.name] = self._lazy_load_ref(doc_cls, value["_ref"]) if dereferenced is None:
raise DoesNotExist("Trying to dereference unknown document %s" % value)
else:
instance._data[self.name] = dereferenced
return super().__get__(instance, owner) return super().__get__(instance, owner)
@ -1546,6 +1534,14 @@ class GenericReferenceField(BaseField):
" saved to the database" " saved to the database"
) )
def dereference(self, value):
doc_cls = get_document(value["_cls"])
reference = value["_ref"]
doc = doc_cls._get_db().dereference(reference)
if doc is not None:
doc = doc_cls._from_son(doc)
return doc
def to_mongo(self, document): def to_mongo(self, document):
if document is None: if document is None:
return None return None
@ -1616,14 +1612,11 @@ class EnumField(BaseField):
"""Enumeration Field. Values are stored underneath as is, """Enumeration Field. Values are stored underneath as is,
so it will only work with simple types (str, int, etc) that so it will only work with simple types (str, int, etc) that
are bson encodable are bson encodable
Example usage: Example usage:
.. code-block:: python .. code-block:: python
class Status(Enum): class Status(Enum):
NEW = 'new' NEW = 'new'
ONGOING = 'ongoing'
DONE = 'done' DONE = 'done'
class ModelWithEnum(Document): class ModelWithEnum(Document):
@ -1633,31 +1626,23 @@ class EnumField(BaseField):
ModelWithEnum(status=Status.DONE) ModelWithEnum(status=Status.DONE)
Enum fields can be searched using enum or its value: Enum fields can be searched using enum or its value:
.. code-block:: python .. code-block:: python
ModelWithEnum.objects(status='new').count() ModelWithEnum.objects(status='new').count()
ModelWithEnum.objects(status=Status.NEW).count() ModelWithEnum.objects(status=Status.NEW).count()
The values can be restricted to a subset of the enum by using the ``choices`` parameter: Note that choices cannot be set explicitly, they are derived
from the provided enum class.
.. code-block:: python
class ModelWithEnum(Document):
status = EnumField(Status, choices=[Status.NEW, Status.DONE])
""" """
def __init__(self, enum, **kwargs): def __init__(self, enum, **kwargs):
self._enum_cls = enum self._enum_cls = enum
if kwargs.get("choices"): if "choices" in kwargs:
invalid_choices = [] raise ValueError(
for choice in kwargs["choices"]: "'choices' can't be set on EnumField, "
if not isinstance(choice, enum): "it is implicitly set as the enum class"
invalid_choices.append(choice) )
if invalid_choices: kwargs["choices"] = list(self._enum_cls)
raise ValueError("Invalid choices: %r" % invalid_choices)
else:
kwargs["choices"] = list(self._enum_cls) # Implicit validator
super().__init__(**kwargs) super().__init__(**kwargs)
def __set__(self, instance, value): def __set__(self, instance, value):
@ -1674,6 +1659,13 @@ class EnumField(BaseField):
return value.value return value.value
return value return value
def validate(self, value):
if value and not isinstance(value, self._enum_cls):
try:
self._enum_cls(value)
except Exception as e:
self.error(str(e))
def prepare_query_value(self, op, value): def prepare_query_value(self, op, value):
if value is None: if value is None:
return value return value
@ -2439,7 +2431,7 @@ class LazyReferenceField(BaseField):
object is deleted object is deleted
:param passthrough: When trying to access unknown fields, the :param passthrough: When trying to access unknown fields, the
:class:`~mongoengine.base.datastructure.LazyReference` instance will :class:`~mongoengine.base.datastructure.LazyReference` instance will
automatically call `fetch()` and try to retrieve the field on the fetched automatically call `fetch()` and try to retrive the field on the fetched
document. Note this only work getting field (not setting or deleting). document. Note this only work getting field (not setting or deleting).
""" """
# XXX ValidationError raised outside of the "validate" method. # XXX ValidationError raised outside of the "validate" method.

View File

@ -3,12 +3,11 @@ Helper functions, constants, and types to aid with MongoDB version support
""" """
from mongoengine.connection import get_connection from mongoengine.connection import get_connection
# Constant that can be used to compare the version retrieved with # Constant that can be used to compare the version retrieved with
# get_mongodb_version() # get_mongodb_version()
MONGODB_34 = (3, 4) MONGODB_34 = (3, 4)
MONGODB_36 = (3, 6) MONGODB_36 = (3, 6)
MONGODB_42 = (4, 2)
MONGODB_44 = (4, 4)
def get_mongodb_version(): def get_mongodb_version():

View File

@ -2,12 +2,13 @@ import copy
import itertools import itertools
import re import re
import warnings import warnings
from collections.abc import Mapping from collections.abc import Mapping
import pymongo
import pymongo.errors
from bson import SON, json_util from bson import SON, json_util
from bson.code import Code from bson.code import Code
import pymongo
import pymongo.errors
from pymongo.collection import ReturnDocument from pymongo.collection import ReturnDocument
from pymongo.common import validate_read_preference from pymongo.common import validate_read_preference
from pymongo.read_concern import ReadConcern from pymongo.read_concern import ReadConcern
@ -33,6 +34,7 @@ from mongoengine.queryset import transform
from mongoengine.queryset.field_list import QueryFieldList from mongoengine.queryset.field_list import QueryFieldList
from mongoengine.queryset.visitor import Q, QNode from mongoengine.queryset.visitor import Q, QNode
__all__ = ("BaseQuerySet", "DO_NOTHING", "NULLIFY", "CASCADE", "DENY", "PULL") __all__ = ("BaseQuerySet", "DO_NOTHING", "NULLIFY", "CASCADE", "DENY", "PULL")
# Delete rules # Delete rules
@ -62,7 +64,6 @@ class BaseQuerySet:
self._ordering = None self._ordering = None
self._snapshot = False self._snapshot = False
self._timeout = True self._timeout = True
self._allow_disk_use = False
self._read_preference = None self._read_preference = None
self._read_concern = None self._read_concern = None
self._iter = False self._iter = False
@ -720,7 +721,7 @@ class BaseQuerySet:
return queryset.filter(pk=object_id).first() return queryset.filter(pk=object_id).first()
def in_bulk(self, object_ids): def in_bulk(self, object_ids):
"""Retrieve a set of documents by their ids. """ "Retrieve a set of documents by their ids.
:param object_ids: a list or tuple of ObjectId's :param object_ids: a list or tuple of ObjectId's
:rtype: dict of ObjectId's as keys and collection-specific :rtype: dict of ObjectId's as keys and collection-specific
@ -798,7 +799,6 @@ class BaseQuerySet:
"_ordering", "_ordering",
"_snapshot", "_snapshot",
"_timeout", "_timeout",
"_allow_disk_use",
"_read_preference", "_read_preference",
"_read_concern", "_read_concern",
"_iter", "_iter",
@ -1165,16 +1165,6 @@ class BaseQuerySet:
queryset._snapshot = enabled queryset._snapshot = enabled
return queryset return queryset
def allow_disk_use(self, enabled):
"""Enable or disable the use of temporary files on disk while processing a blocking sort operation.
(To store data exceeding the 100 megabyte system memory limit)
:param enabled: whether or not temporary files on disk are used
"""
queryset = self.clone()
queryset._allow_disk_use = enabled
return queryset
def timeout(self, enabled): def timeout(self, enabled):
"""Enable or disable the default mongod timeout when querying. (no_cursor_timeout option) """Enable or disable the default mongod timeout when querying. (no_cursor_timeout option)
@ -1353,18 +1343,21 @@ class BaseQuerySet:
MapReduceDocument = _import_class("MapReduceDocument") MapReduceDocument = _import_class("MapReduceDocument")
if not hasattr(self._collection, "map_reduce"):
raise NotImplementedError("Requires MongoDB >= 1.7.1")
map_f_scope = {} map_f_scope = {}
if isinstance(map_f, Code): if isinstance(map_f, Code):
map_f_scope = map_f.scope map_f_scope = map_f.scope
map_f = str(map_f) map_f = str(map_f)
map_f = Code(queryset._sub_js_fields(map_f), map_f_scope or None) map_f = Code(queryset._sub_js_fields(map_f), map_f_scope)
reduce_f_scope = {} reduce_f_scope = {}
if isinstance(reduce_f, Code): if isinstance(reduce_f, Code):
reduce_f_scope = reduce_f.scope reduce_f_scope = reduce_f.scope
reduce_f = str(reduce_f) reduce_f = str(reduce_f)
reduce_f_code = queryset._sub_js_fields(reduce_f) reduce_f_code = queryset._sub_js_fields(reduce_f)
reduce_f = Code(reduce_f_code, reduce_f_scope or None) reduce_f = Code(reduce_f_code, reduce_f_scope)
mr_args = {"query": queryset._query} mr_args = {"query": queryset._query}
@ -1374,7 +1367,7 @@ class BaseQuerySet:
finalize_f_scope = finalize_f.scope finalize_f_scope = finalize_f.scope
finalize_f = str(finalize_f) finalize_f = str(finalize_f)
finalize_f_code = queryset._sub_js_fields(finalize_f) finalize_f_code = queryset._sub_js_fields(finalize_f)
finalize_f = Code(finalize_f_code, finalize_f_scope or None) finalize_f = Code(finalize_f_code, finalize_f_scope)
mr_args["finalize"] = finalize_f mr_args["finalize"] = finalize_f
if scope: if scope:
@ -1611,9 +1604,6 @@ class BaseQuerySet:
if not self._timeout: if not self._timeout:
cursor_args["no_cursor_timeout"] = True cursor_args["no_cursor_timeout"] = True
if self._allow_disk_use:
cursor_args["allow_disk_use"] = True
if self._loaded_fields: if self._loaded_fields:
cursor_args[fields_name] = self._loaded_fields.as_dict() cursor_args[fields_name] = self._loaded_fields.as_dict()

View File

@ -1,5 +1,4 @@
from functools import partial from functools import partial
from mongoengine.queryset.queryset import QuerySet from mongoengine.queryset.queryset import QuerySet
__all__ = ("queryset_manager", "QuerySetManager") __all__ = ("queryset_manager", "QuerySetManager")

View File

@ -1,11 +1,11 @@
from mongoengine.errors import OperationError from mongoengine.errors import OperationError
from mongoengine.queryset.base import ( from mongoengine.queryset.base import (
BaseQuerySet,
CASCADE, CASCADE,
DENY, DENY,
DO_NOTHING, DO_NOTHING,
NULLIFY, NULLIFY,
PULL, PULL,
BaseQuerySet,
) )
__all__ = ( __all__ = (

View File

@ -1,8 +1,8 @@
from collections import defaultdict from collections import defaultdict
import pymongo from bson import ObjectId, SON
from bson import SON, ObjectId
from bson.dbref import DBRef from bson.dbref import DBRef
import pymongo
from mongoengine.base import UPDATE_OPERATORS from mongoengine.base import UPDATE_OPERATORS
from mongoengine.common import _import_class from mongoengine.common import _import_class
@ -51,10 +51,6 @@ STRING_OPERATORS = (
"iendswith", "iendswith",
"exact", "exact",
"iexact", "iexact",
"regex",
"iregex",
"wholeword",
"iwholeword",
) )
CUSTOM_OPERATORS = ("match",) CUSTOM_OPERATORS = ("match",)
MATCH_OPERATORS = ( MATCH_OPERATORS = (

View File

@ -1,5 +1,6 @@
black black
flake8 flake8
flake8-import-order
pre-commit pre-commit
pytest pytest
ipdb ipdb

View File

@ -2,17 +2,9 @@
ignore=E501,F403,F405,I201,I202,W504,W605,W503,B007 ignore=E501,F403,F405,I201,I202,W504,W605,W503,B007
exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests
max-complexity=47 max-complexity=47
application-import-names=mongoengine,tests
[tool:pytest] [tool:pytest]
# Limits the discovery to tests directory # Limits the discovery to tests directory
# avoids that it runs for instance the benchmark # avoids that it runs for instance the benchmark
testpaths = tests testpaths = tests
[isort]
known_first_party = mongoengine,tests
default_section = THIRDPARTY
multi_line_output = 3
include_trailing_comma = True
combine_as_imports = True
line_length = 70
ensure_newline_before_comments = 1

View File

@ -53,8 +53,8 @@ class PyTest(TestCommand):
def run_tests(self): def run_tests(self):
# import here, cause outside the eggs aren't loaded # import here, cause outside the eggs aren't loaded
import pytest
from pkg_resources import _namespace_packages from pkg_resources import _namespace_packages
import pytest
# Purge modules under test from sys.modules. The test loader will # Purge modules under test from sys.modules. The test loader will
# re-import them from the build location. Required when 2to3 is used # re-import them from the build location. Required when 2to3 is used
@ -98,11 +98,10 @@ CLASSIFIERS = [
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Database", "Topic :: Database",
@ -112,9 +111,9 @@ CLASSIFIERS = [
extra_opts = { extra_opts = {
"packages": find_packages(exclude=["tests", "tests.*"]), "packages": find_packages(exclude=["tests", "tests.*"]),
"tests_require": [ "tests_require": [
"pytest", "pytest<5.0",
"pytest-cov", "pytest-cov",
"coverage", "coverage<5.0", # recent coverage switched to sqlite format for the .coverage file which isn't handled properly by coveralls
"blinker", "blinker",
"Pillow>=7.0.0", "Pillow>=7.0.0",
], ],
@ -141,7 +140,7 @@ setup(
long_description=LONG_DESCRIPTION, long_description=LONG_DESCRIPTION,
platforms=["any"], platforms=["any"],
classifiers=CLASSIFIERS, classifiers=CLASSIFIERS,
python_requires=">=3.6", python_requires=">=3.5",
install_requires=["pymongo>=3.4, <4.0"], install_requires=["pymongo>=3.4, <4.0"],
cmdclass={"test": PyTest}, cmdclass={"test": PyTest},
**extra_opts **extra_opts

View File

@ -177,7 +177,7 @@ class TestClassMethods(unittest.TestCase):
assert BlogPostWithCustomField.compare_indexes() == {"missing": [], "extra": []} assert BlogPostWithCustomField.compare_indexes() == {"missing": [], "extra": []}
def test_compare_indexes_for_text_indexes(self): def test_compare_indexes_for_text_indexes(self):
"""Ensure that compare_indexes behaves correctly for text indexes""" """ Ensure that compare_indexes behaves correctly for text indexes """
class Doc(Document): class Doc(Document):
a = StringField() a = StringField()

View File

@ -1,7 +1,6 @@
import unittest import unittest
from bson import SON from bson import SON
from mongoengine import * from mongoengine import *
from mongoengine.pymongo_support import list_collection_names from mongoengine.pymongo_support import list_collection_names
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase

View File

@ -1,16 +1,12 @@
import unittest import unittest
from datetime import datetime from datetime import datetime
import pytest
from pymongo.collation import Collation from pymongo.collation import Collation
from pymongo.errors import OperationFailure from pymongo.errors import OperationFailure
import pytest
from mongoengine import * from mongoengine import *
from mongoengine.connection import get_db from mongoengine.connection import get_db
from mongoengine.mongodb_support import (
MONGODB_42,
get_mongodb_version,
)
class TestIndexes(unittest.TestCase): class TestIndexes(unittest.TestCase):
@ -456,11 +452,9 @@ class TestIndexes(unittest.TestCase):
.get("stage") .get("stage")
== "IXSCAN" == "IXSCAN"
) )
mongo_db = get_mongodb_version()
PROJECTION_STR = "PROJECTION" if mongo_db < MONGODB_42 else "PROJECTION_COVERED"
assert ( assert (
query_plan.get("queryPlanner").get("winningPlan").get("stage") query_plan.get("queryPlanner").get("winningPlan").get("stage")
== PROJECTION_STR == "PROJECTION"
) )
query_plan = Test.objects(a=1).explain() query_plan = Test.objects(a=1).explain()

View File

@ -6,9 +6,9 @@ import weakref
from datetime import datetime from datetime import datetime
import bson import bson
import pytest
from bson import DBRef, ObjectId from bson import DBRef, ObjectId
from pymongo.errors import DuplicateKeyError from pymongo.errors import DuplicateKeyError
import pytest
from mongoengine import * from mongoengine import *
from mongoengine import signals from mongoengine import signals
@ -23,11 +23,7 @@ from mongoengine.errors import (
NotUniqueError, NotUniqueError,
SaveConditionError, SaveConditionError,
) )
from mongoengine.mongodb_support import ( from mongoengine.mongodb_support import MONGODB_34, MONGODB_36, get_mongodb_version
MONGODB_34,
MONGODB_36,
get_mongodb_version,
)
from mongoengine.pymongo_support import list_collection_names from mongoengine.pymongo_support import list_collection_names
from mongoengine.queryset import NULLIFY, Q from mongoengine.queryset import NULLIFY, Q
from tests import fixtures from tests import fixtures
@ -65,12 +61,12 @@ class TestDocumentInstance(MongoDBTestCase):
for collection in list_collection_names(self.db): for collection in list_collection_names(self.db):
self.db.drop_collection(collection) self.db.drop_collection(collection)
def _assert_db_equal(self, docs): def assertDbEqual(self, docs):
assert list(self.Person._get_collection().find().sort("id")) == sorted( assert list(self.Person._get_collection().find().sort("id")) == sorted(
docs, key=lambda doc: doc["_id"] docs, key=lambda doc: doc["_id"]
) )
def _assert_has_instance(self, field, instance): def assertHasInstance(self, field, instance):
assert hasattr(field, "_instance") assert hasattr(field, "_instance")
assert field._instance is not None assert field._instance is not None
if isinstance(field._instance, weakref.ProxyType): if isinstance(field._instance, weakref.ProxyType):
@ -407,16 +403,6 @@ class TestDocumentInstance(MongoDBTestCase):
assert person.name == "Test User" assert person.name == "Test User"
assert person.age == 30 assert person.age == 30
def test__qs_property_does_not_raise(self):
# ensures no regression of #2500
class MyDocument(Document):
pass
MyDocument.drop_collection()
object = MyDocument()
object._qs().insert([MyDocument()])
assert MyDocument.objects.count() == 1
def test_to_dbref(self): def test_to_dbref(self):
"""Ensure that you can get a dbref of a document.""" """Ensure that you can get a dbref of a document."""
person = self.Person(name="Test User", age=30) person = self.Person(name="Test User", age=30)
@ -740,11 +726,11 @@ class TestDocumentInstance(MongoDBTestCase):
Doc.drop_collection() Doc.drop_collection()
doc = Doc(embedded_field=Embedded(string="Hi")) doc = Doc(embedded_field=Embedded(string="Hi"))
self._assert_has_instance(doc.embedded_field, doc) self.assertHasInstance(doc.embedded_field, doc)
doc.save() doc.save()
doc = Doc.objects.get() doc = Doc.objects.get()
self._assert_has_instance(doc.embedded_field, doc) self.assertHasInstance(doc.embedded_field, doc)
def test_embedded_document_complex_instance(self): def test_embedded_document_complex_instance(self):
"""Ensure that embedded documents in complex fields can reference """Ensure that embedded documents in complex fields can reference
@ -759,11 +745,11 @@ class TestDocumentInstance(MongoDBTestCase):
Doc.drop_collection() Doc.drop_collection()
doc = Doc(embedded_field=[Embedded(string="Hi")]) doc = Doc(embedded_field=[Embedded(string="Hi")])
self._assert_has_instance(doc.embedded_field[0], doc) self.assertHasInstance(doc.embedded_field[0], doc)
doc.save() doc.save()
doc = Doc.objects.get() doc = Doc.objects.get()
self._assert_has_instance(doc.embedded_field[0], doc) self.assertHasInstance(doc.embedded_field[0], doc)
def test_embedded_document_complex_instance_no_use_db_field(self): def test_embedded_document_complex_instance_no_use_db_field(self):
"""Ensure that use_db_field is propagated to list of Emb Docs.""" """Ensure that use_db_field is propagated to list of Emb Docs."""
@ -792,11 +778,11 @@ class TestDocumentInstance(MongoDBTestCase):
acc = Account() acc = Account()
acc.email = Email(email="test@example.com") acc.email = Email(email="test@example.com")
self._assert_has_instance(acc._data["email"], acc) self.assertHasInstance(acc._data["email"], acc)
acc.save() acc.save()
acc1 = Account.objects.first() acc1 = Account.objects.first()
self._assert_has_instance(acc1._data["email"], acc1) self.assertHasInstance(acc1._data["email"], acc1)
def test_instance_is_set_on_setattr_on_embedded_document_list(self): def test_instance_is_set_on_setattr_on_embedded_document_list(self):
class Email(EmbeddedDocument): class Email(EmbeddedDocument):
@ -808,11 +794,11 @@ class TestDocumentInstance(MongoDBTestCase):
Account.drop_collection() Account.drop_collection()
acc = Account() acc = Account()
acc.emails = [Email(email="test@example.com")] acc.emails = [Email(email="test@example.com")]
self._assert_has_instance(acc._data["emails"][0], acc) self.assertHasInstance(acc._data["emails"][0], acc)
acc.save() acc.save()
acc1 = Account.objects.first() acc1 = Account.objects.first()
self._assert_has_instance(acc1._data["emails"][0], acc1) self.assertHasInstance(acc1._data["emails"][0], acc1)
def test_save_checks_that_clean_is_called(self): def test_save_checks_that_clean_is_called(self):
class CustomError(Exception): class CustomError(Exception):
@ -921,7 +907,7 @@ class TestDocumentInstance(MongoDBTestCase):
with pytest.raises(InvalidDocumentError): with pytest.raises(InvalidDocumentError):
self.Person().modify(set__age=10) self.Person().modify(set__age=10)
self._assert_db_equal([dict(doc.to_mongo())]) self.assertDbEqual([dict(doc.to_mongo())])
def test_modify_invalid_query(self): def test_modify_invalid_query(self):
doc1 = self.Person(name="bob", age=10).save() doc1 = self.Person(name="bob", age=10).save()
@ -931,7 +917,7 @@ class TestDocumentInstance(MongoDBTestCase):
with pytest.raises(InvalidQueryError): with pytest.raises(InvalidQueryError):
doc1.modify({"id": doc2.id}, set__value=20) doc1.modify({"id": doc2.id}, set__value=20)
self._assert_db_equal(docs) self.assertDbEqual(docs)
def test_modify_match_another_document(self): def test_modify_match_another_document(self):
doc1 = self.Person(name="bob", age=10).save() doc1 = self.Person(name="bob", age=10).save()
@ -941,7 +927,7 @@ class TestDocumentInstance(MongoDBTestCase):
n_modified = doc1.modify({"name": doc2.name}, set__age=100) n_modified = doc1.modify({"name": doc2.name}, set__age=100)
assert n_modified == 0 assert n_modified == 0
self._assert_db_equal(docs) self.assertDbEqual(docs)
def test_modify_not_exists(self): def test_modify_not_exists(self):
doc1 = self.Person(name="bob", age=10).save() doc1 = self.Person(name="bob", age=10).save()
@ -951,7 +937,7 @@ class TestDocumentInstance(MongoDBTestCase):
n_modified = doc2.modify({"name": doc2.name}, set__age=100) n_modified = doc2.modify({"name": doc2.name}, set__age=100)
assert n_modified == 0 assert n_modified == 0
self._assert_db_equal(docs) self.assertDbEqual(docs)
def test_modify_update(self): def test_modify_update(self):
other_doc = self.Person(name="bob", age=10).save() other_doc = self.Person(name="bob", age=10).save()
@ -977,7 +963,7 @@ class TestDocumentInstance(MongoDBTestCase):
assert doc.to_json() == doc_copy.to_json() assert doc.to_json() == doc_copy.to_json()
assert doc._get_changed_fields() == [] assert doc._get_changed_fields() == []
self._assert_db_equal([dict(other_doc.to_mongo()), dict(doc.to_mongo())]) self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
def test_modify_with_positional_push(self): def test_modify_with_positional_push(self):
class Content(EmbeddedDocument): class Content(EmbeddedDocument):

View File

@ -1,7 +1,7 @@
import unittest import unittest
import uuid import uuid
from datetime import datetime
from datetime import datetime
from bson import ObjectId from bson import ObjectId
from mongoengine import * from mongoengine import *

View File

@ -1,7 +1,7 @@
import uuid import uuid
import pytest
from bson import Binary from bson import Binary
import pytest
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase

View File

@ -13,17 +13,6 @@ class TestBooleanField(MongoDBTestCase):
person.save() person.save()
assert get_as_pymongo(person) == {"_id": person.id, "admin": True} assert get_as_pymongo(person) == {"_id": person.id, "admin": True}
def test_construction_does_not_fail_uncastable_value(self):
class BoolFail:
def __bool__(self):
return "bogus"
class Person(Document):
admin = BooleanField()
person = Person(admin=BoolFail())
person.admin == "bogus"
def test_validation(self): def test_validation(self):
"""Ensure that invalid values cannot be assigned to boolean """Ensure that invalid values cannot be assigned to boolean
fields. fields.

View File

@ -2,28 +2,11 @@ from decimal import Decimal
import pytest import pytest
from mongoengine import ( from mongoengine import *
CachedReferenceField,
DecimalField,
Document,
EmbeddedDocument,
EmbeddedDocumentField,
InvalidDocumentError,
ListField,
ReferenceField,
StringField,
ValidationError,
)
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
class TestCachedReferenceField(MongoDBTestCase): class TestCachedReferenceField(MongoDBTestCase):
def test_constructor_fail_bad_document_type(self):
with pytest.raises(
ValidationError, match="must be a document class or a string"
):
CachedReferenceField(document_type=0)
def test_get_and_save(self): def test_get_and_save(self):
""" """
Tests #1047: CachedReferenceField creates DBRefs on to_python, Tests #1047: CachedReferenceField creates DBRefs on to_python,

View File

@ -6,6 +6,7 @@ import re
import pytest import pytest
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase

View File

@ -9,6 +9,7 @@ except ImportError:
from mongoengine import * from mongoengine import *
from mongoengine import connection from mongoengine import connection
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase

View File

@ -2,11 +2,59 @@ from decimal import Decimal
import pytest import pytest
from mongoengine import DecimalField, Document, ValidationError from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
class TestDecimalField(MongoDBTestCase): class TestDecimalField(MongoDBTestCase):
def test_validation(self):
"""Ensure that invalid values cannot be assigned to decimal fields."""
class Person(Document):
height = DecimalField(min_value=Decimal("0.1"), max_value=Decimal("3.5"))
Person.drop_collection()
Person(height=Decimal("1.89")).save()
person = Person.objects.first()
assert person.height == Decimal("1.89")
person.height = "2.0"
person.save()
person.height = 0.01
with pytest.raises(ValidationError):
person.validate()
person.height = Decimal("0.01")
with pytest.raises(ValidationError):
person.validate()
person.height = Decimal("4.0")
with pytest.raises(ValidationError):
person.validate()
person.height = "something invalid"
with pytest.raises(ValidationError):
person.validate()
person_2 = Person(height="something invalid")
with pytest.raises(ValidationError):
person_2.validate()
def test_comparison(self):
class Person(Document):
money = DecimalField()
Person.drop_collection()
Person(money=6).save()
Person(money=7).save()
Person(money=8).save()
Person(money=10).save()
assert 2 == Person.objects(money__gt=Decimal("7")).count()
assert 2 == Person.objects(money__gt=7).count()
assert 2 == Person.objects(money__gt="7").count()
assert 3 == Person.objects(money__gte="7").count()
def test_storage(self): def test_storage(self):
class Person(Document): class Person(Document):
float_value = DecimalField(precision=4) float_value = DecimalField(precision=4)
@ -58,83 +106,3 @@ class TestDecimalField(MongoDBTestCase):
for field_name in ["float_value", "string_value"]: for field_name in ["float_value", "string_value"]:
actual = list(Person.objects().scalar(field_name)) actual = list(Person.objects().scalar(field_name))
assert expected == actual assert expected == actual
def test_save_none(self):
class Person(Document):
value = DecimalField()
Person.drop_collection()
person = Person(value=None)
assert person.value is None
person.save()
fetched_person = Person.objects.first()
fetched_person.value is None
def test_validation(self):
"""Ensure that invalid values cannot be assigned to decimal fields."""
class Person(Document):
height = DecimalField(min_value=Decimal("0.1"), max_value=Decimal("3.5"))
Person.drop_collection()
Person(height=Decimal("1.89")).save()
person = Person.objects.first()
assert person.height == Decimal("1.89")
person.height = "2.0"
person.save()
person.height = 0.01
with pytest.raises(ValidationError):
person.validate()
person.height = Decimal("0.01")
with pytest.raises(ValidationError):
person.validate()
person.height = Decimal("4.0")
with pytest.raises(ValidationError):
person.validate()
person.height = "something invalid"
with pytest.raises(ValidationError):
person.validate()
person_2 = Person(height="something invalid")
with pytest.raises(ValidationError):
person_2.validate()
def test_comparison(self):
class Person(Document):
money = DecimalField()
Person.drop_collection()
Person(money=6).save()
Person(money=7).save()
Person(money=8).save()
Person(money=10).save()
assert 2 == Person.objects(money__gt=Decimal("7")).count()
assert 2 == Person.objects(money__gt=7).count()
assert 2 == Person.objects(money__gt="7").count()
assert 3 == Person.objects(money__gte="7").count()
def test_precision_0(self):
"""prevent regression of a bug that was raising an exception when using precision=0"""
class TestDoc(Document):
d = DecimalField(precision=0)
TestDoc.drop_collection()
td = TestDoc(d=Decimal("12.00032678131263"))
assert td.d == Decimal("12")
def test_precision_negative_raise(self):
"""prevent regression of a bug that was raising an exception when using precision=0"""
with pytest.raises(
ValidationError, match="precision must be a positive integer"
):
class TestDoc(Document):
dneg = DecimalField(precision=-1)

View File

@ -1,12 +1,10 @@
import pytest
from bson import InvalidDocument from bson import InvalidDocument
import pytest
from mongoengine import * from mongoengine import *
from mongoengine.base import BaseDict from mongoengine.base import BaseDict
from mongoengine.mongodb_support import ( from mongoengine.mongodb_support import MONGODB_36, get_mongodb_version
MONGODB_36,
get_mongodb_version,
)
from tests.utils import MongoDBTestCase, get_as_pymongo from tests.utils import MongoDBTestCase, get_as_pymongo

View File

@ -1,7 +1,4 @@
from copy import deepcopy
import pytest import pytest
from bson import ObjectId
from mongoengine import ( from mongoengine import (
Document, Document,
@ -12,10 +9,10 @@ from mongoengine import (
InvalidQueryError, InvalidQueryError,
ListField, ListField,
LookUpError, LookUpError,
MapField,
StringField, StringField,
ValidationError, ValidationError,
) )
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -354,30 +351,3 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
# Test existing attribute # Test existing attribute
assert Person.objects(settings__base_foo="basefoo").first().id == p.id assert Person.objects(settings__base_foo="basefoo").first().id == p.id
assert Person.objects(settings__sub_foo="subfoo").first().id == p.id assert Person.objects(settings__sub_foo="subfoo").first().id == p.id
def test_deepcopy_set__instance(self):
"""Ensure that the _instance attribute on EmbeddedDocument exists after a deepcopy"""
class Wallet(EmbeddedDocument):
money = IntField()
class Person(Document):
wallet = EmbeddedDocumentField(Wallet)
wallet_map = MapField(EmbeddedDocumentField(Wallet))
# Test on fresh EmbeddedDoc
emb_doc = Wallet(money=1)
assert emb_doc._instance is None
copied_emb_doc = deepcopy(emb_doc)
assert copied_emb_doc._instance is None
# Test on attached EmbeddedDoc
doc = Person(
id=ObjectId(), wallet=Wallet(money=2), wallet_map={"test": Wallet(money=2)}
)
assert doc.wallet._instance == doc
copied_emb_doc = deepcopy(doc.wallet)
assert copied_emb_doc._instance is None
copied_map_emb_doc = deepcopy(doc.wallet_map)
assert copied_map_emb_doc["test"]._instance is None

View File

@ -1,9 +1,9 @@
from enum import Enum from enum import Enum
import pytest
from bson import InvalidDocument from bson import InvalidDocument
import pytest
from mongoengine import Document, EnumField, ValidationError from mongoengine import *
from tests.utils import MongoDBTestCase, get_as_pymongo from tests.utils import MongoDBTestCase, get_as_pymongo
@ -12,11 +12,6 @@ class Status(Enum):
DONE = "done" DONE = "done"
class Color(Enum):
RED = 1
BLUE = 2
class ModelWithEnum(Document): class ModelWithEnum(Document):
status = EnumField(Status) status = EnumField(Status)
@ -50,11 +45,6 @@ class TestStringEnumField(MongoDBTestCase):
m.save() m.save()
assert m.status == Status.DONE assert m.status == Status.DONE
m.status = "wrong"
assert m.status == "wrong"
with pytest.raises(ValidationError):
m.validate()
def test_set_default(self): def test_set_default(self):
class ModelWithDefault(Document): class ModelWithDefault(Document):
status = EnumField(Status, default=Status.DONE) status = EnumField(Status, default=Status.DONE)
@ -79,27 +69,14 @@ class TestStringEnumField(MongoDBTestCase):
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
m.validate() m.validate()
def test_partial_choices(self): def test_user_is_informed_when_tries_to_set_choices(self):
partial = [Status.DONE] with pytest.raises(ValueError, match="'choices' can't be set on EnumField"):
enum_field = EnumField(Status, choices=partial)
assert enum_field.choices == partial
class FancyDoc(Document):
z = enum_field
FancyDoc(z=Status.DONE).validate()
with pytest.raises(
ValidationError, match=r"Value must be one of .*Status.DONE"
):
FancyDoc(z=Status.NEW).validate()
def test_wrong_choices(self):
with pytest.raises(ValueError, match="Invalid choices"):
EnumField(Status, choices=["my", "custom", "options"]) EnumField(Status, choices=["my", "custom", "options"])
with pytest.raises(ValueError, match="Invalid choices"):
EnumField(Status, choices=[Color.RED])
with pytest.raises(ValueError, match="Invalid choices"): class Color(Enum):
EnumField(Status, choices=[Status.DONE, Color.RED]) RED = 1
BLUE = 2
class ModelWithColor(Document): class ModelWithColor(Document):

View File

@ -1,8 +1,8 @@
import datetime import datetime
import unittest import unittest
from bson import DBRef, ObjectId, SON
import pytest import pytest
from bson import SON, DBRef, ObjectId
from mongoengine import ( from mongoengine import (
BooleanField, BooleanField,
@ -34,12 +34,9 @@ from mongoengine import (
StringField, StringField,
ValidationError, ValidationError,
) )
from mongoengine.base import ( from mongoengine.base import BaseField, EmbeddedDocumentList, _document_registry
BaseField,
EmbeddedDocumentList,
_document_registry,
)
from mongoengine.errors import DeprecatedError from mongoengine.errors import DeprecatedError
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -309,7 +306,7 @@ class TestField(MongoDBTestCase):
) )
assert res == 1 assert res == 1
# Retrieve data from db and verify it. # Retrive data from db and verify it.
ret = HandleNoneFields.objects.all()[0] ret = HandleNoneFields.objects.all()[0]
assert ret.str_fld is None assert ret.str_fld is None
assert ret.int_fld is None assert ret.int_fld is None
@ -343,7 +340,7 @@ class TestField(MongoDBTestCase):
{"$unset": {"str_fld": 1, "int_fld": 1, "flt_fld": 1, "comp_dt_fld": 1}}, {"$unset": {"str_fld": 1, "int_fld": 1, "flt_fld": 1, "comp_dt_fld": 1}},
) )
# Retrieve data from db and verify it. # Retrive data from db and verify it.
ret = HandleNoneFields.objects.first() ret = HandleNoneFields.objects.first()
assert ret.str_fld is None assert ret.str_fld is None
assert ret.int_fld is None assert ret.int_fld is None
@ -377,6 +374,34 @@ class TestField(MongoDBTestCase):
person.id = str(ObjectId()) person.id = str(ObjectId())
person.validate() person.validate()
def test_string_validation(self):
"""Ensure that invalid values cannot be assigned to string fields."""
class Person(Document):
name = StringField(max_length=20)
userid = StringField(r"[0-9a-z_]+$")
person = Person(name=34)
with pytest.raises(ValidationError):
person.validate()
# Test regex validation on userid
person = Person(userid="test.User")
with pytest.raises(ValidationError):
person.validate()
person.userid = "test_user"
assert person.userid == "test_user"
person.validate()
# Test max length validation on name
person = Person(name="Name that is more than twenty characters")
with pytest.raises(ValidationError):
person.validate()
person.name = "Shorter name"
person.validate()
def test_db_field_validation(self): def test_db_field_validation(self):
"""Ensure that db_field doesn't accept invalid values.""" """Ensure that db_field doesn't accept invalid values."""
@ -2077,7 +2102,7 @@ class TestField(MongoDBTestCase):
a ComplexBaseField. a ComplexBaseField.
""" """
class SomeField(BaseField): class EnumField(BaseField):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@ -2088,7 +2113,7 @@ class TestField(MongoDBTestCase):
return tuple(value) return tuple(value)
class TestDoc(Document): class TestDoc(Document):
items = ListField(SomeField()) items = ListField(EnumField())
TestDoc.drop_collection() TestDoc.drop_collection()

View File

@ -308,7 +308,7 @@ class TestFileField(MongoDBTestCase):
assert test_file.the_file not in [{"test": 1}] assert test_file.the_file not in [{"test": 1}]
def test_file_disk_space(self): def test_file_disk_space(self):
"""Test disk space usage when we delete/replace a file""" """ Test disk space usage when we delete/replace a file """
class TestFile(Document): class TestFile(Document):
the_file = FileField() the_file = FileField()

View File

@ -1,6 +1,7 @@
import pytest import pytest
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase

View File

@ -1,6 +1,7 @@
import pytest import pytest
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase

View File

@ -1,9 +1,10 @@
import pytest
from bson import DBRef, ObjectId from bson import DBRef, ObjectId
import pytest
from mongoengine import * from mongoengine import *
from mongoengine.base import LazyReference from mongoengine.base import LazyReference
from mongoengine.context_managers import query_counter from mongoengine.context_managers import query_counter
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -374,26 +375,6 @@ class TestLazyReferenceField(MongoDBTestCase):
assert isinstance(ref.author, LazyReference) assert isinstance(ref.author, LazyReference)
assert isinstance(ref.author.id, ObjectId) assert isinstance(ref.author.id, ObjectId)
def test_lazy_reference_in_list_with_changed_element(self):
class Animal(Document):
name = StringField()
tag = StringField()
class Ocurrence(Document):
in_list = ListField(LazyReferenceField(Animal))
Animal.drop_collection()
Ocurrence.drop_collection()
animal1 = Animal(name="doggo").save()
animal1.tag = "blue"
occ = Ocurrence(in_list=[animal1]).save()
animal1.save()
assert isinstance(occ.in_list[0], LazyReference)
assert occ.in_list[0].pk == animal1.pk
class TestGenericLazyReferenceField(MongoDBTestCase): class TestGenericLazyReferenceField(MongoDBTestCase):
def test_generic_lazy_reference_simple(self): def test_generic_lazy_reference_simple(self):

View File

@ -1,28 +1,13 @@
import pytest
from bson.int64 import Int64 from bson.int64 import Int64
import pytest
from mongoengine import * from mongoengine import *
from mongoengine.connection import get_db from mongoengine.connection import get_db
from tests.utils import MongoDBTestCase, get_as_pymongo
from tests.utils import MongoDBTestCase
class TestLongField(MongoDBTestCase): class TestLongField(MongoDBTestCase):
def test_storage(self):
class Person(Document):
value = LongField()
Person.drop_collection()
person = Person(value=5000)
person.save()
assert get_as_pymongo(person) == {"_id": person.id, "value": 5000}
def test_construction_does_not_fail_with_invalid_value(self):
class Person(Document):
value = LongField()
person = Person(value="not_an_int")
assert person.value == "not_an_int"
def test_long_field_is_considered_as_int64(self): def test_long_field_is_considered_as_int64(self):
""" """
Tests that long fields are stored as long in mongo, even if long Tests that long fields are stored as long in mongo, even if long
@ -45,16 +30,19 @@ class TestLongField(MongoDBTestCase):
class TestDocument(Document): class TestDocument(Document):
value = LongField(min_value=0, max_value=110) value = LongField(min_value=0, max_value=110)
TestDocument(value=50).validate() doc = TestDocument()
doc.value = 50
doc.validate()
doc.value = -1
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
TestDocument(value=-1).validate() doc.validate()
doc.value = 120
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
TestDocument(value=120).validate() doc.validate()
doc.value = "ten"
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
TestDocument(value="ten").validate() doc.validate()
def test_long_ne_operator(self): def test_long_ne_operator(self):
class TestDocument(Document): class TestDocument(Document):
@ -65,5 +53,4 @@ class TestLongField(MongoDBTestCase):
TestDocument(long_fld=None).save() TestDocument(long_fld=None).save()
TestDocument(long_fld=1).save() TestDocument(long_fld=1).save()
assert TestDocument.objects(long_fld__ne=None).count() == 1 assert 1 == TestDocument.objects(long_fld__ne=None).count()
assert TestDocument.objects(long_fld__ne=1).count() == 1

View File

@ -1,5 +1,5 @@
from bson import DBRef, SON
import pytest import pytest
from bson import SON, DBRef
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase

View File

@ -1,4 +1,5 @@
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -273,25 +274,3 @@ class TestSequenceField(MongoDBTestCase):
assert foo.counter == bar.counter assert foo.counter == bar.counter
assert foo._fields["counter"].owner_document == Foo assert foo._fields["counter"].owner_document == Foo
assert bar._fields["counter"].owner_document == Bar assert bar._fields["counter"].owner_document == Bar
def test_sequence_setattr_not_incrementing_counter(self):
class Person(DynamicDocument):
id = SequenceField(primary_key=True)
name = StringField()
self.db["mongoengine.counters"].drop()
Person.drop_collection()
for x in range(10):
Person(name="Person %s" % x).save()
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
assert c["next"] == 10
# Setting SequenceField field value should not increment counter:
new_person = Person()
new_person.id = 1100
# Counter should still be at 10
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
assert c["next"] == 10

View File

@ -1,43 +0,0 @@
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase, get_as_pymongo
class TestStringField(MongoDBTestCase):
def test_storage(self):
class Person(Document):
name = StringField()
Person.drop_collection()
person = Person(name="test123")
person.save()
assert get_as_pymongo(person) == {"_id": person.id, "name": "test123"}
def test_validation(self):
class Person(Document):
name = StringField(max_length=20, min_length=2)
userid = StringField(r"[0-9a-z_]+$")
with pytest.raises(ValidationError, match="only accepts string values"):
Person(name=34).validate()
with pytest.raises(ValidationError, match="value is too short"):
Person(name="s").validate()
# Test regex validation on userid
person = Person(userid="test.User")
with pytest.raises(ValidationError):
person.validate()
person.userid = "test_user"
assert person.userid == "test_user"
person.validate()
# Test max length validation on name
person = Person(name="Name that is more than twenty characters")
with pytest.raises(ValidationError):
person.validate()
person = Person(name="a friendl name", userid="7a757668sqjdkqlsdkq")
person.validate()

View File

@ -2,6 +2,7 @@ import datetime
import unittest import unittest
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase

View File

@ -1,12 +1,6 @@
import unittest import unittest
from mongoengine import ( from mongoengine import Document, IntField, ListField, StringField, connect
Document,
IntField,
ListField,
StringField,
connect,
)
class Doc(Document): class Doc(Document):
@ -19,7 +13,7 @@ class TestFindAndModify(unittest.TestCase):
connect(db="mongoenginetest") connect(db="mongoenginetest")
Doc.drop_collection() Doc.drop_collection()
def _assert_db_equal(self, docs): def assertDbEqual(self, docs):
assert list(Doc._collection.find().sort("id")) == docs assert list(Doc._collection.find().sort("id")) == docs
def test_modify(self): def test_modify(self):
@ -28,7 +22,7 @@ class TestFindAndModify(unittest.TestCase):
old_doc = Doc.objects(id=1).modify(set__value=-1) old_doc = Doc.objects(id=1).modify(set__value=-1)
assert old_doc.to_json() == doc.to_json() assert old_doc.to_json() == doc.to_json()
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}]) self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
def test_modify_with_new(self): def test_modify_with_new(self):
Doc(id=0, value=0).save() Doc(id=0, value=0).save()
@ -37,18 +31,18 @@ class TestFindAndModify(unittest.TestCase):
new_doc = Doc.objects(id=1).modify(set__value=-1, new=True) new_doc = Doc.objects(id=1).modify(set__value=-1, new=True)
doc.value = -1 doc.value = -1
assert new_doc.to_json() == doc.to_json() assert new_doc.to_json() == doc.to_json()
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}]) self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
def test_modify_not_existing(self): def test_modify_not_existing(self):
Doc(id=0, value=0).save() Doc(id=0, value=0).save()
assert Doc.objects(id=1).modify(set__value=-1) is None assert Doc.objects(id=1).modify(set__value=-1) is None
self._assert_db_equal([{"_id": 0, "value": 0}]) self.assertDbEqual([{"_id": 0, "value": 0}])
def test_modify_with_upsert(self): def test_modify_with_upsert(self):
Doc(id=0, value=0).save() Doc(id=0, value=0).save()
old_doc = Doc.objects(id=1).modify(set__value=1, upsert=True) old_doc = Doc.objects(id=1).modify(set__value=1, upsert=True)
assert old_doc is None assert old_doc is None
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}]) self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
def test_modify_with_upsert_existing(self): def test_modify_with_upsert_existing(self):
Doc(id=0, value=0).save() Doc(id=0, value=0).save()
@ -56,13 +50,13 @@ class TestFindAndModify(unittest.TestCase):
old_doc = Doc.objects(id=1).modify(set__value=-1, upsert=True) old_doc = Doc.objects(id=1).modify(set__value=-1, upsert=True)
assert old_doc.to_json() == doc.to_json() assert old_doc.to_json() == doc.to_json()
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}]) self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
def test_modify_with_upsert_with_new(self): def test_modify_with_upsert_with_new(self):
Doc(id=0, value=0).save() Doc(id=0, value=0).save()
new_doc = Doc.objects(id=1).modify(upsert=True, new=True, set__value=1) new_doc = Doc.objects(id=1).modify(upsert=True, new=True, set__value=1)
assert new_doc.to_mongo() == {"_id": 1, "value": 1} assert new_doc.to_mongo() == {"_id": 1, "value": 1}
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}]) self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
def test_modify_with_remove(self): def test_modify_with_remove(self):
Doc(id=0, value=0).save() Doc(id=0, value=0).save()
@ -70,12 +64,12 @@ class TestFindAndModify(unittest.TestCase):
old_doc = Doc.objects(id=1).modify(remove=True) old_doc = Doc.objects(id=1).modify(remove=True)
assert old_doc.to_json() == doc.to_json() assert old_doc.to_json() == doc.to_json()
self._assert_db_equal([{"_id": 0, "value": 0}]) self.assertDbEqual([{"_id": 0, "value": 0}])
def test_find_and_modify_with_remove_not_existing(self): def test_find_and_modify_with_remove_not_existing(self):
Doc(id=0, value=0).save() Doc(id=0, value=0).save()
assert Doc.objects(id=1).modify(remove=True) is None assert Doc.objects(id=1).modify(remove=True) is None
self._assert_db_equal([{"_id": 0, "value": 0}]) self.assertDbEqual([{"_id": 0, "value": 0}])
def test_modify_with_order_by(self): def test_modify_with_order_by(self):
Doc(id=0, value=3).save() Doc(id=0, value=3).save()
@ -85,7 +79,7 @@ class TestFindAndModify(unittest.TestCase):
old_doc = Doc.objects().order_by("-id").modify(set__value=-1) old_doc = Doc.objects().order_by("-id").modify(set__value=-1)
assert old_doc.to_json() == doc.to_json() assert old_doc.to_json() == doc.to_json()
self._assert_db_equal( self.assertDbEqual(
[ [
{"_id": 0, "value": 3}, {"_id": 0, "value": 3},
{"_id": 1, "value": 2}, {"_id": 1, "value": 2},
@ -100,7 +94,7 @@ class TestFindAndModify(unittest.TestCase):
old_doc = Doc.objects(id=1).only("id").modify(set__value=-1) old_doc = Doc.objects(id=1).only("id").modify(set__value=-1)
assert old_doc.to_mongo() == {"_id": 1} assert old_doc.to_mongo() == {"_id": 1}
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}]) self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
def test_modify_with_push(self): def test_modify_with_push(self):
class BlogPost(Document): class BlogPost(Document):

View File

@ -3,20 +3,17 @@ import unittest
import uuid import uuid
from decimal import Decimal from decimal import Decimal
import pymongo
import pytest
from bson import DBRef, ObjectId from bson import DBRef, ObjectId
import pymongo
from pymongo.read_preferences import ReadPreference from pymongo.read_preferences import ReadPreference
from pymongo.results import UpdateResult from pymongo.results import UpdateResult
import pytest
from mongoengine import * from mongoengine import *
from mongoengine.connection import get_db from mongoengine.connection import get_db
from mongoengine.context_managers import query_counter, switch_db from mongoengine.context_managers import query_counter, switch_db
from mongoengine.errors import InvalidQueryError from mongoengine.errors import InvalidQueryError
from mongoengine.mongodb_support import ( from mongoengine.mongodb_support import MONGODB_36, get_mongodb_version
MONGODB_36,
get_mongodb_version,
)
from mongoengine.queryset import ( from mongoengine.queryset import (
DoesNotExist, DoesNotExist,
MultipleObjectsReturned, MultipleObjectsReturned,
@ -24,10 +21,6 @@ from mongoengine.queryset import (
QuerySetManager, QuerySetManager,
queryset_manager, queryset_manager,
) )
from tests.utils import (
requires_mongodb_gte_44,
requires_mongodb_lt_42,
)
class db_ops_tracker(query_counter): class db_ops_tracker(query_counter):
@ -867,21 +860,6 @@ class TestQueryset(unittest.TestCase):
assert "Bob" == bob.name assert "Bob" == bob.name
assert 30 == bob.age assert 30 == bob.age
def test_rename(self):
self.Person.drop_collection()
self.Person.objects.create(name="Foo", age=11)
bob = self.Person.objects.as_pymongo().first()
assert "age" in bob
assert bob["age"] == 11
self.Person.objects(name="Foo").update(rename__age="person_age")
bob = self.Person.objects.as_pymongo().first()
assert "age" not in bob
assert "person_age" in bob
assert bob["person_age"] == 11
def test_save_and_only_on_fields_with_default(self): def test_save_and_only_on_fields_with_default(self):
class Embed(EmbeddedDocument): class Embed(EmbeddedDocument):
field = IntField() field = IntField()
@ -1255,34 +1233,6 @@ class TestQueryset(unittest.TestCase):
obj = self.Person.objects(name__iexact="gUIDO VAN rOSSU").first() obj = self.Person.objects(name__iexact="gUIDO VAN rOSSU").first()
assert obj is None assert obj is None
# Test wholeword
obj = self.Person.objects(name__wholeword="Guido").first()
assert obj == person
obj = self.Person.objects(name__wholeword="rossum").first()
assert obj is None
obj = self.Person.objects(name__wholeword="Rossu").first()
assert obj is None
# Test iwholeword
obj = self.Person.objects(name__iwholeword="rOSSUM").first()
assert obj == person
obj = self.Person.objects(name__iwholeword="rOSSU").first()
assert obj is None
# Test regex
obj = self.Person.objects(name__regex="^[Guido].*[Rossum]$").first()
assert obj == person
obj = self.Person.objects(name__regex="^[guido].*[rossum]$").first()
assert obj is None
obj = self.Person.objects(name__regex="^[uido].*[Rossum]$").first()
assert obj is None
# Test iregex
obj = self.Person.objects(name__iregex="^[guido].*[rossum]$").first()
assert obj == person
obj = self.Person.objects(name__iregex="^[Uido].*[Rossum]$").first()
assert obj is None
# Test unsafe expressions # Test unsafe expressions
person = self.Person(name="Guido van Rossum [.'Geek']") person = self.Person(name="Guido van Rossum [.'Geek']")
person.save() person.save()
@ -1367,14 +1317,7 @@ class TestQueryset(unittest.TestCase):
person.save() person.save()
people = self.Person.objects people = self.Person.objects
people = ( people = people.filter(name__startswith="Gui").filter(name__not__endswith="tum")
people.filter(name__startswith="Gui")
.filter(name__not__endswith="tum")
.filter(name__icontains="VAN")
.filter(name__regex="^Guido")
.filter(name__wholeword="Guido")
.filter(name__wholeword="van")
)
assert people.count() == 1 assert people.count() == 1
def assertSequence(self, qs, expected): def assertSequence(self, qs, expected):
@ -1546,7 +1489,6 @@ class TestQueryset(unittest.TestCase):
BlogPost.drop_collection() BlogPost.drop_collection()
@requires_mongodb_lt_42
def test_exec_js_query(self): def test_exec_js_query(self):
"""Ensure that queries are properly formed for use in exec_js.""" """Ensure that queries are properly formed for use in exec_js."""
@ -1584,7 +1526,6 @@ class TestQueryset(unittest.TestCase):
BlogPost.drop_collection() BlogPost.drop_collection()
@requires_mongodb_lt_42
def test_exec_js_field_sub(self): def test_exec_js_field_sub(self):
"""Ensure that field substitutions occur properly in exec_js functions.""" """Ensure that field substitutions occur properly in exec_js functions."""
@ -2718,8 +2659,6 @@ class TestQueryset(unittest.TestCase):
title = StringField(primary_key=True) title = StringField(primary_key=True)
tags = ListField(StringField()) tags = ListField(StringField())
BlogPost.drop_collection()
post1 = BlogPost(title="Post #1", tags=["mongodb", "mongoengine"]) post1 = BlogPost(title="Post #1", tags=["mongodb", "mongoengine"])
post2 = BlogPost(title="Post #2", tags=["django", "mongodb"]) post2 = BlogPost(title="Post #2", tags=["django", "mongodb"])
post3 = BlogPost(title="Post #3", tags=["hitchcock films"]) post3 = BlogPost(title="Post #3", tags=["hitchcock films"])
@ -2748,15 +2687,12 @@ class TestQueryset(unittest.TestCase):
} }
""" """
results = BlogPost.objects.order_by("_id").map_reduce( results = BlogPost.objects.map_reduce(map_f, reduce_f, "myresults")
map_f, reduce_f, "myresults2"
)
results = list(results) results = list(results)
assert len(results) == 3 assert results[0].object == post1
assert results[0].object.id == post1.id assert results[1].object == post2
assert results[1].object.id == post2.id assert results[2].object == post3
assert results[2].object.id == post3.id
BlogPost.drop_collection() BlogPost.drop_collection()
@ -2764,6 +2700,7 @@ class TestQueryset(unittest.TestCase):
""" """
Test map/reduce custom output Test map/reduce custom output
""" """
register_connection("test2", "mongoenginetest2")
class Family(Document): class Family(Document):
id = IntField(primary_key=True) id = IntField(primary_key=True)
@ -2836,7 +2773,6 @@ class TestQueryset(unittest.TestCase):
family.persons.push(person); family.persons.push(person);
family.totalAge += person.age; family.totalAge += person.age;
}); });
family.persons.sort((a, b) => (a.age > b.age))
} }
}); });
@ -2865,10 +2801,10 @@ class TestQueryset(unittest.TestCase):
"_id": 1, "_id": 1,
"value": { "value": {
"persons": [ "persons": [
{"age": 17, "name": "Tayza Mariana"},
{"age": 21, "name": "Wilson Jr"}, {"age": 21, "name": "Wilson Jr"},
{"age": 40, "name": "Eliana Costa"},
{"age": 45, "name": "Wilson Father"}, {"age": 45, "name": "Wilson Father"},
{"age": 40, "name": "Eliana Costa"},
{"age": 17, "name": "Tayza Mariana"},
], ],
"totalAge": 123, "totalAge": 123,
}, },
@ -2878,9 +2814,9 @@ class TestQueryset(unittest.TestCase):
"_id": 2, "_id": 2,
"value": { "value": {
"persons": [ "persons": [
{"age": 10, "name": "Igor Gabriel"},
{"age": 16, "name": "Isabella Luanna"}, {"age": 16, "name": "Isabella Luanna"},
{"age": 36, "name": "Sandra Mara"}, {"age": 36, "name": "Sandra Mara"},
{"age": 10, "name": "Igor Gabriel"},
], ],
"totalAge": 62, "totalAge": 62,
}, },
@ -2890,8 +2826,8 @@ class TestQueryset(unittest.TestCase):
"_id": 3, "_id": 3,
"value": { "value": {
"persons": [ "persons": [
{"age": 25, "name": "Paula Leonel"},
{"age": 30, "name": "Arthur WA"}, {"age": 30, "name": "Arthur WA"},
{"age": 25, "name": "Paula Leonel"},
], ],
"totalAge": 55, "totalAge": 55,
}, },
@ -3172,7 +3108,6 @@ class TestQueryset(unittest.TestCase):
freq = Person.objects.item_frequencies("city", normalize=True, map_reduce=True) freq = Person.objects.item_frequencies("city", normalize=True, map_reduce=True)
assert freq == {"CRB": 0.5, None: 0.5} assert freq == {"CRB": 0.5, None: 0.5}
@requires_mongodb_lt_42
def test_item_frequencies_with_null_embedded(self): def test_item_frequencies_with_null_embedded(self):
class Data(EmbeddedDocument): class Data(EmbeddedDocument):
name = StringField() name = StringField()
@ -3201,7 +3136,6 @@ class TestQueryset(unittest.TestCase):
ot = Person.objects.item_frequencies("extra.tag", map_reduce=True) ot = Person.objects.item_frequencies("extra.tag", map_reduce=True)
assert ot == {None: 1.0, "friend": 1.0} assert ot == {None: 1.0, "friend": 1.0}
@requires_mongodb_lt_42
def test_item_frequencies_with_0_values(self): def test_item_frequencies_with_0_values(self):
class Test(Document): class Test(Document):
val = IntField() val = IntField()
@ -3216,7 +3150,6 @@ class TestQueryset(unittest.TestCase):
ot = Test.objects.item_frequencies("val", map_reduce=False) ot = Test.objects.item_frequencies("val", map_reduce=False)
assert ot == {0: 1} assert ot == {0: 1}
@requires_mongodb_lt_42
def test_item_frequencies_with_False_values(self): def test_item_frequencies_with_False_values(self):
class Test(Document): class Test(Document):
val = BooleanField() val = BooleanField()
@ -3231,7 +3164,6 @@ class TestQueryset(unittest.TestCase):
ot = Test.objects.item_frequencies("val", map_reduce=False) ot = Test.objects.item_frequencies("val", map_reduce=False)
assert ot == {False: 1} assert ot == {False: 1}
@requires_mongodb_lt_42
def test_item_frequencies_normalize(self): def test_item_frequencies_normalize(self):
class Test(Document): class Test(Document):
val = IntField() val = IntField()
@ -3618,8 +3550,7 @@ class TestQueryset(unittest.TestCase):
Book.objects.create(title="The Stories", authors=[mark_twain, john_tolkien]) Book.objects.create(title="The Stories", authors=[mark_twain, john_tolkien])
authors = Book.objects.distinct("authors") authors = Book.objects.distinct("authors")
authors_names = {author.name for author in authors} assert authors == [mark_twain, john_tolkien]
assert authors_names == {mark_twain.name, john_tolkien.name}
def test_distinct_ListField_EmbeddedDocumentField_EmbeddedDocumentField(self): def test_distinct_ListField_EmbeddedDocumentField_EmbeddedDocumentField(self):
class Continent(EmbeddedDocument): class Continent(EmbeddedDocument):
@ -3656,8 +3587,7 @@ class TestQueryset(unittest.TestCase):
assert country_list == [scotland, tibet] assert country_list == [scotland, tibet]
continent_list = Book.objects.distinct("authors.country.continent") continent_list = Book.objects.distinct("authors.country.continent")
continent_list_names = {c.continent_name for c in continent_list} assert continent_list == [europe, asia]
assert continent_list_names == {europe.continent_name, asia.continent_name}
def test_distinct_ListField_ReferenceField(self): def test_distinct_ListField_ReferenceField(self):
class Bar(Document): class Bar(Document):
@ -5726,31 +5656,6 @@ class TestQueryset(unittest.TestCase):
qs = self.Person.objects().timeout(False) qs = self.Person.objects().timeout(False)
assert qs._cursor_args == {"no_cursor_timeout": True} assert qs._cursor_args == {"no_cursor_timeout": True}
@requires_mongodb_gte_44
def test_allow_disk_use(self):
qs = self.Person.objects()
assert qs._cursor_args == {}
qs = self.Person.objects().allow_disk_use(False)
assert qs._cursor_args == {}
qs = self.Person.objects().allow_disk_use(True)
assert qs._cursor_args == {"allow_disk_use": True}
# Test if allow_disk_use changes the results
self.Person.drop_collection()
self.Person.objects.create(name="Foo", age=12)
self.Person.objects.create(name="Baz", age=17)
self.Person.objects.create(name="Bar", age=13)
qs_disk = self.Person.objects().order_by("age").allow_disk_use(True)
qs = self.Person.objects().order_by("age")
assert qs_disk.count() == qs.count()
for index in range(qs_disk.count()):
assert qs_disk[index] == qs[index]
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -248,34 +248,6 @@ class TestQuerysetAggregate(MongoDBTestCase):
assert list(data) == [{"_id": p1.pk, "name": "ISABELLA LUANNA"}] assert list(data) == [{"_id": p1.pk, "name": "ISABELLA LUANNA"}]
def test_queryset_aggregation_geonear_aggregation_on_pointfield(self):
"""test ensures that $geonear can be used as a 1-stage pipeline and that
MongoEngine does not interfer with such pipeline (#2473)
"""
class Aggr(Document):
name = StringField()
c = PointField()
Aggr.drop_collection()
agg1 = Aggr(name="X", c=[10.634584, 35.8245029]).save()
agg2 = Aggr(name="Y", c=[10.634584, 35.8245029]).save()
pipeline = [
{
"$geoNear": {
"near": {"type": "Point", "coordinates": [10.634584, 35.8245029]},
"distanceField": "c",
"spherical": True,
}
}
]
assert list(Aggr.objects.aggregate(*pipeline)) == [
{"_id": agg1.id, "c": 0.0, "name": "X"},
{"_id": agg2.id, "c": 0.0, "name": "Y"},
]
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -1,7 +1,7 @@
import unittest import unittest
import pytest
from bson.son import SON from bson.son import SON
import pytest
from mongoengine import * from mongoengine import *
from mongoengine.queryset import Q, transform from mongoengine.queryset import Q, transform

View File

@ -2,8 +2,8 @@ import datetime
import re import re
import unittest import unittest
import pytest
from bson import ObjectId from bson import ObjectId
import pytest
from mongoengine import * from mongoengine import *
from mongoengine.errors import InvalidQueryError from mongoengine.errors import InvalidQueryError

View File

@ -1,11 +1,11 @@
import datetime import datetime
import unittest import unittest
import pymongo
import pytest
from bson.tz_util import utc from bson.tz_util import utc
import pymongo
from pymongo import MongoClient, ReadPreference from pymongo import MongoClient, ReadPreference
from pymongo.errors import InvalidName, OperationFailure from pymongo.errors import InvalidName, OperationFailure
import pytest
import mongoengine.connection import mongoengine.connection
from mongoengine import ( from mongoengine import (
@ -17,8 +17,8 @@ from mongoengine import (
register_connection, register_connection,
) )
from mongoengine.connection import ( from mongoengine.connection import (
DEFAULT_DATABASE_NAME,
ConnectionFailure, ConnectionFailure,
DEFAULT_DATABASE_NAME,
disconnect, disconnect,
get_connection, get_connection,
get_db, get_db,

View File

@ -3,9 +3,15 @@ import unittest
import pytest import pytest
import mongoengine.connection import mongoengine.connection
from mongoengine import Document, StringField, connect, disconnect_all from mongoengine import (
Document,
StringField,
connect,
disconnect_all,
)
from mongoengine.connection import get_connection from mongoengine.connection import get_connection
try: try:
import mongomock import mongomock

View File

@ -3,11 +3,7 @@ import unittest
import pytest import pytest
from mongoengine import Document from mongoengine import Document
from mongoengine.base.datastructures import ( from mongoengine.base.datastructures import BaseDict, BaseList, StrictDict
BaseDict,
BaseList,
StrictDict,
)
class DocumentStub: class DocumentStub:

View File

@ -5,6 +5,7 @@ from pymongo import MongoClient, ReadPreference
import mongoengine import mongoengine
from mongoengine.connection import ConnectionFailure from mongoengine.connection import ConnectionFailure
CONN_CLASS = MongoClient CONN_CLASS = MongoClient
READ_PREF = ReadPreference.SECONDARY READ_PREF = ReadPreference.SECONDARY

View File

@ -249,7 +249,7 @@ class TestSignal(unittest.TestCase):
assert self.pre_signals == post_signals assert self.pre_signals == post_signals
def test_model_signals(self): def test_model_signals(self):
"""Model saves should throw some signals.""" """ Model saves should throw some signals. """
def create_author(): def create_author():
self.Author(name="Bill Shakespeare") self.Author(name="Bill Shakespeare")
@ -340,7 +340,7 @@ class TestSignal(unittest.TestCase):
] ]
def test_signal_kwargs(self): def test_signal_kwargs(self):
"""Make sure signal_kwargs is passed to signals calls.""" """ Make sure signal_kwargs is passed to signals calls. """
def live_and_let_die(): def live_and_let_die():
a = self.Author(name="Bill Shakespeare") a = self.Author(name="Bill Shakespeare")
@ -385,7 +385,7 @@ class TestSignal(unittest.TestCase):
] ]
def test_queryset_delete_signals(self): def test_queryset_delete_signals(self):
"""Queryset delete should throw some signals.""" """ Queryset delete should throw some signals. """
self.Another(name="Bill Shakespeare").save() self.Another(name="Bill Shakespeare").save()
assert self.get_signal_output(self.Another.objects.delete) == [ assert self.get_signal_output(self.Another.objects.delete) == [
@ -396,7 +396,7 @@ class TestSignal(unittest.TestCase):
] ]
def test_signals_with_explicit_doc_ids(self): def test_signals_with_explicit_doc_ids(self):
"""Model saves must have a created flag the first time.""" """ Model saves must have a created flag the first time."""
ei = self.ExplicitId(id=123) ei = self.ExplicitId(id=123)
# post save must received the created flag, even if there's already # post save must received the created flag, even if there's already
# an object id present # an object id present

View File

@ -1,4 +1,3 @@
import operator
import unittest import unittest
import pytest import pytest
@ -7,6 +6,7 @@ from mongoengine import connect
from mongoengine.connection import disconnect_all, get_db from mongoengine.connection import disconnect_all, get_db
from mongoengine.mongodb_support import get_mongodb_version from mongoengine.mongodb_support import get_mongodb_version
MONGO_TEST_DB = "mongoenginetest" # standard name for the test database MONGO_TEST_DB = "mongoenginetest" # standard name for the test database
@ -33,14 +33,6 @@ def get_as_pymongo(doc):
return doc.__class__.objects.as_pymongo().get(id=doc.id) return doc.__class__.objects.as_pymongo().get(id=doc.id)
def requires_mongodb_lt_42(func):
return _decorated_with_ver_requirement(func, (4, 2), oper=operator.lt)
def requires_mongodb_gte_44(func):
return _decorated_with_ver_requirement(func, (4, 4), oper=operator.ge)
def _decorated_with_ver_requirement(func, mongo_version_req, oper): def _decorated_with_ver_requirement(func, mongo_version_req, oper):
"""Return a MongoDB version requirement decorator. """Return a MongoDB version requirement decorator.

View File

@ -1,5 +1,5 @@
[tox] [tox]
envlist = pypy3-{mg34,mg36,mg39,mg311} envlist = {py35,pypy3}-{mg34,mg36,mg39,mg311}
[testenv] [testenv]
commands = commands =