Compare commits
No commits in common. "master" and "v0.22.0" have entirely different histories.
143
.github/workflows/github-actions.yml
vendored
143
.github/workflows/github-actions.yml
vendored
@ -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 }}
|
|
5
.github/workflows/install_ci_python_dep.sh
vendored
5
.github/workflows/install_ci_python_dep.sh
vendored
@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
pip install --upgrade pip
|
|
||||||
pip install coveralls
|
|
||||||
pip install pre-commit
|
|
||||||
pip install tox
|
|
18
.github/workflows/install_mongo.sh
vendored
18
.github/workflows/install_mongo.sh
vendored
@ -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
|
|
9
.github/workflows/start_mongo.sh
vendored
9
.github/workflows/start_mongo.sh
vendored
@ -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
|
|
@ -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
107
.travis.yml
Normal 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
|
4
AUTHORS
4
AUTHORS
@ -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)
|
|
||||||
|
@ -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
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
@ -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__":
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
=================
|
=================
|
||||||
|
@ -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"]
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
@ -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/
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
|
@ -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():
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 []
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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():
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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__ = (
|
||||||
|
@ -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 = (
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
black
|
black
|
||||||
flake8
|
flake8
|
||||||
|
flake8-import-order
|
||||||
pre-commit
|
pre-commit
|
||||||
pytest
|
pytest
|
||||||
ipdb
|
ipdb
|
||||||
|
10
setup.cfg
10
setup.cfg
@ -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
|
|
||||||
|
11
setup.py
11
setup.py
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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):
|
||||||
|
@ -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 *
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
|
|
||||||
from tests.utils import MongoDBTestCase
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
|
|
||||||
from tests.utils import MongoDBTestCase
|
from tests.utils import MongoDBTestCase
|
||||||
|
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
|
@ -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()
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user