Compare commits
122 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2f4b2aadb4 | ||
|
f4a87bff1d | ||
|
3a32c195d5 | ||
|
880a8efc63 | ||
|
9a414edb25 | ||
|
4b643ad01f | ||
|
811dc12db5 | ||
|
dbd72282bc | ||
|
686b42c29b | ||
|
4210ea06c3 | ||
|
8c3e2b340b | ||
|
7a0a58c163 | ||
|
0af1a1151f | ||
|
28226f81a8 | ||
|
558e3299fe | ||
|
cb0035f87a | ||
|
85b0c1c945 | ||
|
3d6b650592 | ||
|
6bc1b83695 | ||
|
7e10bb2894 | ||
|
8428da368d | ||
|
5fe9436ea7 | ||
|
1b249e336e | ||
|
533700583d | ||
|
639124c311 | ||
|
8b5cf9e2be | ||
|
0af96b1323 | ||
|
f6d864b6d1 | ||
|
bb9ba73a7b | ||
|
66978aea67 | ||
|
5a41998eda | ||
|
c32b308730 | ||
|
88642eb021 | ||
|
3b10236b5e | ||
|
bffe058726 | ||
|
09c415a416 | ||
|
4670508a1c | ||
|
e7f2d77a75 | ||
|
7bf7153e4f | ||
|
0df0585d07 | ||
|
6ae722d04b | ||
|
cc85165b0c | ||
|
2fd4169656 | ||
|
5897b1d042 | ||
|
756bffe868 | ||
|
5209547a89 | ||
|
ecb5dda281 | ||
|
34245fe258 | ||
|
04c26acdd6 | ||
|
d0a15a8924 | ||
|
2215e2746b | ||
|
0e6bcbc030 | ||
|
ecf84cbd5d | ||
|
232071f8f4 | ||
|
fc0fb31d43 | ||
|
1bd8cd803e | ||
|
ef57a58155 | ||
|
9680259904 | ||
|
49a4d23371 | ||
|
b9d370c885 | ||
|
e5a2714baf | ||
|
ff596fcb7e | ||
|
f0fad6df19 | ||
|
da173cf0e2 | ||
|
1669f0c5a4 | ||
|
b045925efe | ||
|
b3ce65453a | ||
|
50d891cb7b | ||
|
e31f9150d2 | ||
|
74ceb9703b | ||
|
58a3c6de03 | ||
|
86ad8d119d | ||
|
34d273015c | ||
|
7147043d63 | ||
|
b9b536133d | ||
|
8fd969aba9 | ||
|
f244207168 | ||
|
0620ac5641 | ||
|
3b9a167022 | ||
|
b479bb7c6b | ||
|
8ef771912d | ||
|
2d1c9afbb7 | ||
|
9ff5d8426c | ||
|
467e9c3ddf | ||
|
0d5e028c55 | ||
|
5858ea1bf0 | ||
|
1f220b4eaf | ||
|
97c99ca40d | ||
|
80a3b1c88c | ||
|
68447af127 | ||
|
d033e3b133 | ||
|
4428842e77 | ||
|
f38cc6edd3 | ||
|
aeb4f8f4da | ||
|
1b7c2085c9 | ||
|
48b979599f | ||
|
af3d3b7ee6 | ||
|
56fe126f3a | ||
|
04905d4b37 | ||
|
460df112f4 | ||
|
772096ec55 | ||
|
98d64f41c6 | ||
|
9a3bca8ab6 | ||
|
5781753cc8 | ||
|
fd3699a519 | ||
|
4f6a24411d | ||
|
de3888a48b | ||
|
700fe80a00 | ||
|
49e33b978d | ||
|
81197d6061 | ||
|
aa368be4d3 | ||
|
0f1fce4a7b | ||
|
cc591a634a | ||
|
6e332e782b | ||
|
0e9920b190 | ||
|
fd35df07c4 | ||
|
8f3d21c312 | ||
|
7b772e3a4a | ||
|
59438a4768 | ||
|
fe9f7f1f80 | ||
|
6b5231265c | ||
|
0014346de1 |
143
.github/workflows/github-actions.yml
vendored
Normal file
143
.github/workflows/github-actions.yml
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
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
Normal file
5
.github/workflows/install_ci_python_dep.sh
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
pip install --upgrade pip
|
||||
pip install coveralls
|
||||
pip install pre-commit
|
||||
pip install tox
|
18
.github/workflows/install_mongo.sh
vendored
Normal file
18
.github/workflows/install_mongo.sh
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
#!/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
Normal file
9
.github/workflows/start_mongo.sh
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
#!/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,17 +1,26 @@
|
||||
fail_fast: false
|
||||
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
|
||||
rev: 20.8b1
|
||||
rev: 21.5b2
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.8.4
|
||||
rev: 3.9.2
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-import-order
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.7.4
|
||||
rev: v2.19.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py36-plus]
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.8.0
|
||||
hooks:
|
||||
- id: isort
|
||||
|
107
.travis.yml
107
.travis.yml
@ -1,107 +0,0 @@
|
||||
# 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,3 +259,7 @@ that much better:
|
||||
* Agustin Barto (https://github.com/abarto)
|
||||
* Stankiewicz Mateusz (https://github.com/mas15)
|
||||
* 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
|
||||
-----------
|
||||
|
||||
MongoEngine's codebase is formatted with `black <https://github.com/python/black>`_, 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.
|
||||
MongoEngine's codebase is auto-formatted with `black <https://github.com/python/black>`_, imports are ordered with `isort <https://pycqa.github.io/isort/>`_
|
||||
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.
|
||||
|
||||
To install all development tools, simply run the following commands:
|
||||
|
||||
@ -58,6 +58,10 @@ To enable ``pre-commit`` simply run:
|
||||
See the ``.pre-commit-config.yaml`` configuration file for more information
|
||||
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
|
||||
-------
|
||||
|
||||
|
@ -31,7 +31,7 @@ myNoddys = noddy.find()
|
||||
print("-" * 100)
|
||||
print("PyMongo: Creating 10000 dictionaries.")
|
||||
t = timeit.Timer(stmt=stmt, setup=setup)
|
||||
print("{}s".format(t.timeit(1)))
|
||||
print(f"{t.timeit(1)}s")
|
||||
|
||||
stmt = """
|
||||
from pymongo import MongoClient, WriteConcern
|
||||
@ -54,7 +54,7 @@ myNoddys = noddy.find()
|
||||
print("-" * 100)
|
||||
print('PyMongo: Creating 10000 dictionaries (write_concern={"w": 0}).')
|
||||
t = timeit.Timer(stmt=stmt, setup=setup)
|
||||
print("{}s".format(t.timeit(1)))
|
||||
print(f"{t.timeit(1)}s")
|
||||
|
||||
setup = """
|
||||
from pymongo import MongoClient
|
||||
@ -84,7 +84,7 @@ myNoddys = Noddy.objects()
|
||||
print("-" * 100)
|
||||
print("MongoEngine: Creating 10000 dictionaries.")
|
||||
t = timeit.Timer(stmt=stmt, setup=setup)
|
||||
print("{}s".format(t.timeit(1)))
|
||||
print(f"{t.timeit(1)}s")
|
||||
|
||||
stmt = """
|
||||
for i in range(10000):
|
||||
@ -102,7 +102,7 @@ myNoddys = Noddy.objects()
|
||||
print("-" * 100)
|
||||
print("MongoEngine: Creating 10000 dictionaries (using a single field assignment).")
|
||||
t = timeit.Timer(stmt=stmt, setup=setup)
|
||||
print("{}s".format(t.timeit(1)))
|
||||
print(f"{t.timeit(1)}s")
|
||||
|
||||
stmt = """
|
||||
for i in range(10000):
|
||||
@ -118,7 +118,7 @@ myNoddys = Noddy.objects()
|
||||
print("-" * 100)
|
||||
print('MongoEngine: Creating 10000 dictionaries (write_concern={"w": 0}).')
|
||||
t = timeit.Timer(stmt=stmt, setup=setup)
|
||||
print("{}s".format(t.timeit(1)))
|
||||
print(f"{t.timeit(1)}s")
|
||||
|
||||
stmt = """
|
||||
for i in range(10000):
|
||||
@ -136,7 +136,7 @@ myNoddys = Noddy.objects()
|
||||
'MongoEngine: Creating 10000 dictionaries (write_concern={"w": 0}, validate=False).'
|
||||
)
|
||||
t = timeit.Timer(stmt=stmt, setup=setup)
|
||||
print("{}s".format(t.timeit(1)))
|
||||
print(f"{t.timeit(1)}s")
|
||||
|
||||
stmt = """
|
||||
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).'
|
||||
)
|
||||
t = timeit.Timer(stmt=stmt, setup=setup)
|
||||
print("{}s".format(t.timeit(1)))
|
||||
print(f"{t.timeit(1)}s")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -35,6 +35,12 @@ html:
|
||||
@echo
|
||||
@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:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
|
@ -75,6 +75,7 @@ Fields
|
||||
.. autoclass:: mongoengine.fields.StringField
|
||||
.. autoclass:: mongoengine.fields.URLField
|
||||
.. autoclass:: mongoengine.fields.EmailField
|
||||
.. autoclass:: mongoengine.fields.EnumField
|
||||
.. autoclass:: mongoengine.fields.IntField
|
||||
.. autoclass:: mongoengine.fields.LongField
|
||||
.. autoclass:: mongoengine.fields.FloatField
|
||||
|
@ -1,4 +1,5 @@
|
||||
|
||||
|
||||
=========
|
||||
Changelog
|
||||
=========
|
||||
@ -6,6 +7,26 @@ Changelog
|
||||
Development
|
||||
===========
|
||||
- (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
|
||||
=================
|
||||
|
@ -26,7 +26,7 @@ sys.path.insert(0, os.path.abspath(".."))
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ["sphinx.ext.autodoc", "sphinx.ext.todo"]
|
||||
extensions = ["sphinx.ext.autodoc", "sphinx.ext.todo", "readthedocs_ext.readthedocs"]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
|
@ -5,7 +5,7 @@ Connecting to MongoDB
|
||||
=====================
|
||||
|
||||
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`
|
||||
function. The first argument is the name of the database to connect to::
|
||||
@ -14,27 +14,66 @@ function. The first argument is the name of the database to connect to::
|
||||
connect('project1')
|
||||
|
||||
By default, MongoEngine assumes that the :program:`mongod` instance is running
|
||||
on **localhost** on port **27017**. If MongoDB is running elsewhere, you should
|
||||
provide the :attr:`host` and :attr:`port` arguments to
|
||||
:func:`~mongoengine.connect`::
|
||||
on **localhost** on port **27017**.
|
||||
|
||||
connect('project1', host='192.168.1.35', port=12345)
|
||||
If MongoDB is running elsewhere, you need to provide details on how to connect. There are two ways of
|
||||
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`
|
||||
and :attr:`authentication_source` arguments should be provided::
|
||||
|
||||
connect('project1', username='webapp', password='pwd123', authentication_source='admin')
|
||||
connect('my_db', username='my_user', password='my_password', authentication_source='admin')
|
||||
|
||||
URI style connections are also supported -- just supply the URI as
|
||||
the :attr:`host` to
|
||||
:func:`~mongoengine.connect`::
|
||||
|
||||
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`
|
||||
The set of attributes that :func:`~mongoengine.connect` recognizes includes but is not limited to:
|
||||
:attr:`host`, :attr:`port`, :attr:`read_preference`, :attr:`username`, :attr:`password`, :attr:`authentication_source`, :attr:`authentication_mechanism`,
|
||||
: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>`_
|
||||
can be used with :func:`~mongoengine.connect` and will simply be forwarded when instantiating the `pymongo.MongoClient`.
|
||||
|
||||
.. note:: Database, username and password from URI string overrides
|
||||
corresponding parameters in :func:`~mongoengine.connect`: ::
|
||||
corresponding parameters in :func:`~mongoengine.connect`, this should
|
||||
obviously be avoided: ::
|
||||
|
||||
connect(
|
||||
db='test',
|
||||
@ -43,28 +82,19 @@ the :attr:`host` to
|
||||
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
|
||||
a connection to the "test" database by default
|
||||
|
||||
Replica Sets
|
||||
============
|
||||
Read Preferences
|
||||
================
|
||||
|
||||
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
|
||||
As stated above, Read preferences are supported through the connection but also via individual
|
||||
queries by passing the read_preference ::
|
||||
|
||||
from pymongo import ReadPreference
|
||||
|
||||
Bar.objects().read_preference(ReadPreference.PRIMARY)
|
||||
Bar.objects(read_preference=ReadPreference.PRIMARY)
|
||||
|
||||
|
@ -27,6 +27,8 @@ objects** as class attributes to the document class::
|
||||
As BSON (the binary format for storing data in mongodb) is order dependent,
|
||||
documents are serialized based on their field order.
|
||||
|
||||
.. _dynamic-document-schemas:
|
||||
|
||||
Dynamic document schemas
|
||||
========================
|
||||
One of the benefits of MongoDB is dynamic schemas for a collection, whilst data
|
||||
@ -231,6 +233,9 @@ document class as the first argument::
|
||||
comment2 = Comment(content='Nice article!')
|
||||
page = Page(comments=[comment1, comment2])
|
||||
|
||||
Embedded documents can also leverage the flexibility of :ref:`dynamic-document-schemas:`
|
||||
by inheriting :class:`~mongoengine.DynamicEmbeddedDocument`.
|
||||
|
||||
Dictionary Fields
|
||||
-----------------
|
||||
Often, an embedded document may be used instead of a dictionary – generally
|
||||
@ -290,12 +295,12 @@ as the constructor's argument::
|
||||
content = StringField()
|
||||
|
||||
|
||||
.. _one-to-many-with-listfields:
|
||||
.. _many-to-many-with-listfields:
|
||||
|
||||
One to Many with ListFields
|
||||
Many to Many with ListFields
|
||||
'''''''''''''''''''''''''''
|
||||
|
||||
If you are implementing a one to many relationship via a list of references,
|
||||
If you are implementing a many to many relationship via a list of references,
|
||||
then the references are stored as DBRefs and to query you need to pass an
|
||||
instance of the object to the query::
|
||||
|
||||
@ -336,7 +341,6 @@ supplying the :attr:`reverse_delete_rule` attributes on the
|
||||
:class:`ReferenceField` definition, like this::
|
||||
|
||||
class ProfilePage(Document):
|
||||
...
|
||||
employee = ReferenceField('Employee', reverse_delete_rule=mongoengine.CASCADE)
|
||||
|
||||
The declaration in this example means that when an :class:`Employee` object is
|
||||
@ -432,10 +436,10 @@ Document collections
|
||||
====================
|
||||
Document classes that inherit **directly** from :class:`~mongoengine.Document`
|
||||
will have their own **collection** in the database. The name of the collection
|
||||
is by default the name of the class, converted to lowercase (so in the example
|
||||
above, the collection would be called `page`). If you need to change the name
|
||||
of the collection (e.g. to use MongoEngine with an existing database), then
|
||||
create a class dictionary attribute called :attr:`meta` on your document, and
|
||||
is by default the name of the class converted to snake_case (e.g if your Document class
|
||||
is named `CompanyUser`, the corresponding collection would be `company_user`). If you need
|
||||
to change the name of the collection (e.g. to use MongoEngine with an existing database),
|
||||
then 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
|
||||
document class to use::
|
||||
|
||||
@ -473,7 +477,7 @@ dictionary containing a full index definition.
|
||||
|
||||
A direction may be specified on fields by prefixing the field name with a
|
||||
**+** (for ascending) or a **-** sign (for descending). Note that direction
|
||||
only matters on multi-field indexes. Text indexes may be specified by prefixing
|
||||
only matters on compound 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 **#**::
|
||||
|
||||
@ -484,14 +488,14 @@ the field name with a **#**::
|
||||
created = DateTimeField()
|
||||
meta = {
|
||||
'indexes': [
|
||||
'title',
|
||||
'title', # single-field index
|
||||
'$title', # text index
|
||||
'#title', # hashed index
|
||||
('title', '-rating'),
|
||||
('category', '_cls'),
|
||||
('title', '-rating'), # compound index
|
||||
('category', '_cls'), # compound index
|
||||
{
|
||||
'fields': ['created'],
|
||||
'expireAfterSeconds': 3600
|
||||
'expireAfterSeconds': 3600 # ttl index
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -624,8 +628,8 @@ point. To create a geospatial index you must prefix the field with the
|
||||
],
|
||||
}
|
||||
|
||||
Time To Live indexes
|
||||
--------------------
|
||||
Time To Live (TTL) indexes
|
||||
--------------------------
|
||||
|
||||
A special index type that allows you to automatically expire data from a
|
||||
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):
|
||||
name = StringField(required=True)
|
||||
enabled = BooleaField(default=True)
|
||||
enabled = BooleanField(default=True)
|
||||
|
||||
Without applying any migration, we now reload an object from the database into the ``User`` class
|
||||
and checks its `enabled` attribute:
|
||||
@ -223,6 +223,47 @@ 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
|
||||
|
||||
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
|
||||
===============
|
||||
|
||||
|
@ -86,6 +86,10 @@ expressions:
|
||||
* ``istartswith`` -- string field starts with value (case insensitive)
|
||||
* ``endswith`` -- string field ends with value
|
||||
* ``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
|
||||
|
||||
|
||||
@ -239,7 +243,7 @@ Limiting and skipping results
|
||||
Just as with traditional ORMs, you may limit the number of results returned or
|
||||
skip a number or results in you query.
|
||||
:meth:`~mongoengine.queryset.QuerySet.limit` and
|
||||
:meth:`~mongoengine.queryset.QuerySet.skip` and methods are available on
|
||||
:meth:`~mongoengine.queryset.QuerySet.skip` methods are available on
|
||||
:class:`~mongoengine.queryset.QuerySet` objects, but the `array-slicing` syntax
|
||||
is preferred for achieving this::
|
||||
|
||||
@ -543,7 +547,10 @@ Documents may be updated atomically by using the
|
||||
There are several different "modifiers" that you may use with these methods:
|
||||
|
||||
* ``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)
|
||||
* ``max`` -- update only if value is bigger
|
||||
* ``min`` -- update only if value is smaller
|
||||
* ``inc`` -- increment a value by a given amount
|
||||
* ``dec`` -- decrement a value by a given amount
|
||||
* ``push`` -- append a value to a list
|
||||
@ -552,6 +559,7 @@ There are several different "modifiers" that you may use with these methods:
|
||||
* ``pull`` -- remove a value 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
|
||||
* ``rename`` -- rename the key name
|
||||
|
||||
.. _depending on the value: http://docs.mongodb.org/manual/reference/operator/update/pop/
|
||||
|
||||
|
@ -120,4 +120,3 @@ the validation and cleaning of a document when you call :meth:`~mongoengine.docu
|
||||
Person(age=1000).save(validate=False)
|
||||
person = Person.objects.first()
|
||||
assert person.age == 1000
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
pymongo>=3.11
|
||||
Sphinx==3.2.1
|
||||
Sphinx==3.3.0
|
||||
sphinx-rtd-theme==0.5.0
|
||||
readthedocs-sphinx-ext==2.1.1
|
||||
|
@ -1,10 +1,12 @@
|
||||
# Import submodules so that we can expose their __all__
|
||||
from mongoengine import connection
|
||||
from mongoengine import document
|
||||
from mongoengine import errors
|
||||
from mongoengine import fields
|
||||
from mongoengine import queryset
|
||||
from mongoengine import signals
|
||||
from mongoengine import (
|
||||
connection,
|
||||
document,
|
||||
errors,
|
||||
fields,
|
||||
queryset,
|
||||
signals,
|
||||
)
|
||||
|
||||
# Import everything from each submodule so that it can be accessed via
|
||||
# mongoengine, e.g. instead of `from mongoengine.connection import connect`,
|
||||
@ -17,7 +19,6 @@ from mongoengine.fields import * # noqa: F401
|
||||
from mongoengine.queryset import * # noqa: F401
|
||||
from mongoengine.signals import * # noqa: F401
|
||||
|
||||
|
||||
__all__ = (
|
||||
list(document.__all__)
|
||||
+ list(fields.__all__)
|
||||
@ -28,7 +29,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
VERSION = (0, 22, 0)
|
||||
VERSION = (0, 23, 1)
|
||||
|
||||
|
||||
def get_version():
|
||||
|
@ -1,10 +1,9 @@
|
||||
import copy
|
||||
|
||||
import numbers
|
||||
from functools import partial
|
||||
|
||||
from bson import DBRef, ObjectId, SON, json_util
|
||||
import pymongo
|
||||
from bson import SON, DBRef, ObjectId, json_util
|
||||
|
||||
from mongoengine import signals
|
||||
from mongoengine.base.common import get_document
|
||||
@ -156,7 +155,7 @@ class BaseDocument:
|
||||
# Handle dynamic data only if an initialised dynamic document
|
||||
if self._dynamic and not self._dynamic_lock:
|
||||
|
||||
if not hasattr(self, name) and not name.startswith("_"):
|
||||
if name not in self._fields_ordered and not name.startswith("_"):
|
||||
DynamicField = _import_class("DynamicField")
|
||||
field = DynamicField(db_field=name, null=True)
|
||||
field.name = name
|
||||
@ -615,7 +614,9 @@ class BaseDocument:
|
||||
def _get_changed_fields(self):
|
||||
"""Return a list of all fields that have explicitly been changed."""
|
||||
EmbeddedDocument = _import_class("EmbeddedDocument")
|
||||
LazyReferenceField = _import_class("LazyReferenceField")
|
||||
ReferenceField = _import_class("ReferenceField")
|
||||
GenericLazyReferenceField = _import_class("GenericLazyReferenceField")
|
||||
GenericReferenceField = _import_class("GenericReferenceField")
|
||||
SortedListField = _import_class("SortedListField")
|
||||
|
||||
@ -641,7 +642,13 @@ class BaseDocument:
|
||||
changed_fields += [f"{key}{k}" for k in changed if k]
|
||||
elif isinstance(data, (list, tuple, dict)):
|
||||
if hasattr(field, "field") and isinstance(
|
||||
field.field, (ReferenceField, GenericReferenceField)
|
||||
field.field,
|
||||
(
|
||||
LazyReferenceField,
|
||||
ReferenceField,
|
||||
GenericLazyReferenceField,
|
||||
GenericReferenceField,
|
||||
),
|
||||
):
|
||||
continue
|
||||
elif isinstance(field, SortedListField) and field._ordering:
|
||||
|
@ -1,11 +1,15 @@
|
||||
import operator
|
||||
import weakref
|
||||
|
||||
from bson import DBRef, ObjectId, SON
|
||||
import pymongo
|
||||
from bson import SON, DBRef, ObjectId
|
||||
|
||||
from mongoengine.base.common import UPDATE_OPERATORS
|
||||
from mongoengine.base.datastructures import BaseDict, BaseList, EmbeddedDocumentList
|
||||
from mongoengine.base.datastructures import (
|
||||
BaseDict,
|
||||
BaseList,
|
||||
EmbeddedDocumentList,
|
||||
)
|
||||
from mongoengine.common import _import_class
|
||||
from mongoengine.errors import DeprecatedError, ValidationError
|
||||
|
||||
@ -267,6 +271,17 @@ class ComplexBaseField(BaseField):
|
||||
self.field = field
|
||||
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):
|
||||
"""Descriptor to automatically dereference references."""
|
||||
if instance is None:
|
||||
@ -284,19 +299,15 @@ class ComplexBaseField(BaseField):
|
||||
or isinstance(self.field, (GenericReferenceField, ReferenceField))
|
||||
)
|
||||
|
||||
_dereference = _import_class("DeReference")()
|
||||
|
||||
if (
|
||||
instance._initialised
|
||||
and dereference
|
||||
and instance._data.get(self.name)
|
||||
and not getattr(instance._data[self.name], "_dereferenced", False)
|
||||
):
|
||||
instance._data[self.name] = _dereference(
|
||||
instance._data.get(self.name),
|
||||
max_depth=1,
|
||||
instance=instance,
|
||||
name=self.name,
|
||||
ref_values = instance._data.get(self.name)
|
||||
instance._data[self.name] = self._lazy_load_refs(
|
||||
ref_values=ref_values, instance=instance, name=self.name, max_depth=1
|
||||
)
|
||||
if hasattr(instance._data[self.name], "_dereferenced"):
|
||||
instance._data[self.name]._dereferenced = True
|
||||
@ -322,7 +333,9 @@ class ComplexBaseField(BaseField):
|
||||
and isinstance(value, (BaseList, BaseDict))
|
||||
and not value._dereferenced
|
||||
):
|
||||
value = _dereference(value, max_depth=1, instance=instance, name=self.name)
|
||||
value = self._lazy_load_refs(
|
||||
ref_values=value, instance=instance, name=self.name, max_depth=1
|
||||
)
|
||||
value._dereferenced = True
|
||||
instance._data[self.name] = value
|
||||
|
||||
|
@ -2,7 +2,11 @@ import itertools
|
||||
import warnings
|
||||
|
||||
from mongoengine.base.common import _document_registry
|
||||
from mongoengine.base.fields import BaseField, ComplexBaseField, ObjectIdField
|
||||
from mongoengine.base.fields import (
|
||||
BaseField,
|
||||
ComplexBaseField,
|
||||
ObjectIdField,
|
||||
)
|
||||
from mongoengine.common import _import_class
|
||||
from mongoengine.errors import InvalidDocumentError
|
||||
from mongoengine.queryset import (
|
||||
@ -12,7 +16,6 @@ from mongoengine.queryset import (
|
||||
QuerySetManager,
|
||||
)
|
||||
|
||||
|
||||
__all__ = ("DocumentMetaclass", "TopLevelDocumentMetaclass")
|
||||
|
||||
|
||||
|
@ -58,20 +58,20 @@ def _get_connection_settings(
|
||||
):
|
||||
"""Get the connection settings as a dict
|
||||
|
||||
: param db: the name of the database to use, for compatibility with connect
|
||||
: param name: the name of the specific database to use
|
||||
: param host: the host name of the: program: `mongod` instance to connect to
|
||||
: param port: the port that the: program: `mongod` instance is running on
|
||||
: param read_preference: The read preference for the collection
|
||||
: param username: username to authenticate with
|
||||
: param password: password to authenticate with
|
||||
: param authentication_source: database to authenticate against
|
||||
: param authentication_mechanism: database authentication mechanisms.
|
||||
:param db: the name of the database to use, for compatibility with connect
|
||||
:param name: the name of the specific database to use
|
||||
:param host: the host name of the: program: `mongod` instance to connect to
|
||||
:param port: the port that the: program: `mongod` instance is running on
|
||||
:param read_preference: The read preference for the collection
|
||||
:param username: username to authenticate with
|
||||
:param password: password to authenticate with
|
||||
: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,
|
||||
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)
|
||||
: 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 pymongo's `MongoClient` for a full list.
|
||||
"""
|
||||
@ -181,22 +181,21 @@ def register_connection(
|
||||
):
|
||||
"""Register the connection settings.
|
||||
|
||||
: param alias: the name that will be used to refer to this connection
|
||||
throughout MongoEngine
|
||||
: param db: the name of the database to use, for compatibility with connect
|
||||
: param name: the name of the specific database to use
|
||||
: param host: the host name of the: program: `mongod` instance to connect to
|
||||
: param port: the port that the: program: `mongod` instance is running on
|
||||
: param read_preference: The read preference for the collection
|
||||
: param username: username to authenticate with
|
||||
: param password: password to authenticate with
|
||||
: param authentication_source: database to authenticate against
|
||||
: param authentication_mechanism: database authentication mechanisms.
|
||||
:param alias: the name that will be used to refer to this connection throughout MongoEngine
|
||||
:param db: the name of the database to use, for compatibility with connect
|
||||
:param name: the name of the specific database to use
|
||||
:param host: the host name of the: program: `mongod` instance to connect to
|
||||
:param port: the port that the: program: `mongod` instance is running on
|
||||
:param read_preference: The read preference for the collection
|
||||
:param username: username to authenticate with
|
||||
:param password: password to authenticate with
|
||||
: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,
|
||||
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)
|
||||
: 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 pymongo's `MongoClient` for a full list.
|
||||
"""
|
||||
@ -217,8 +216,8 @@ def register_connection(
|
||||
|
||||
def disconnect(alias=DEFAULT_CONNECTION_NAME):
|
||||
"""Close the connection with a given alias."""
|
||||
from mongoengine.base.common import _get_documents_by_db
|
||||
from mongoengine import Document
|
||||
from mongoengine.base.common import _get_documents_by_db
|
||||
|
||||
if alias in _connections:
|
||||
get_connection(alias=alias).close()
|
||||
|
@ -177,14 +177,28 @@ class query_counter:
|
||||
This was designed for debugging purpose. In fact it is a global counter so queries issued by other threads/processes
|
||||
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:
|
||||
- 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)
|
||||
|
||||
- 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)
|
||||
- Some queries are ignored by default by the counter (killcursors, db.system.indexes)
|
||||
"""
|
||||
|
||||
def __init__(self, alias=DEFAULT_CONNECTION_NAME):
|
||||
"""Construct the query_counter"""
|
||||
self.db = get_db(alias=alias)
|
||||
self.initial_profiling_level = None
|
||||
self._ctx_query_counter = 0 # number of queries issued by the context
|
||||
|
@ -1,4 +1,4 @@
|
||||
from bson import DBRef, SON
|
||||
from bson import SON, DBRef
|
||||
|
||||
from mongoengine.base import (
|
||||
BaseDict,
|
||||
@ -10,7 +10,12 @@ from mongoengine.base import (
|
||||
from mongoengine.base.datastructures import LazyReference
|
||||
from mongoengine.connection import get_db
|
||||
from mongoengine.document import Document, EmbeddedDocument
|
||||
from mongoengine.fields import DictField, ListField, MapField, ReferenceField
|
||||
from mongoengine.fields import (
|
||||
DictField,
|
||||
ListField,
|
||||
MapField,
|
||||
ReferenceField,
|
||||
)
|
||||
from mongoengine.queryset import QuerySet
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import re
|
||||
|
||||
from bson.dbref import DBRef
|
||||
import pymongo
|
||||
from bson.dbref import DBRef
|
||||
from pymongo.read_preferences import ReadPreference
|
||||
|
||||
from mongoengine import signals
|
||||
@ -16,14 +16,23 @@ from mongoengine.base import (
|
||||
)
|
||||
from mongoengine.common import _import_class
|
||||
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
|
||||
from mongoengine.context_managers import set_write_concern, switch_collection, switch_db
|
||||
from mongoengine.context_managers import (
|
||||
set_write_concern,
|
||||
switch_collection,
|
||||
switch_db,
|
||||
)
|
||||
from mongoengine.errors import (
|
||||
InvalidDocumentError,
|
||||
InvalidQueryError,
|
||||
SaveConditionError,
|
||||
)
|
||||
from mongoengine.pymongo_support import list_collection_names
|
||||
from mongoengine.queryset import NotUniqueError, OperationError, QuerySet, transform
|
||||
from mongoengine.queryset import (
|
||||
NotUniqueError,
|
||||
OperationError,
|
||||
QuerySet,
|
||||
transform,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
"Document",
|
||||
@ -90,6 +99,15 @@ class EmbeddedDocument(BaseDocument, metaclass=DocumentMetaclass):
|
||||
def __ne__(self, 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):
|
||||
data = super().to_mongo(*args, **kwargs)
|
||||
|
||||
@ -109,7 +127,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
|
||||
|
||||
By default, the MongoDB collection used to store documents created using a
|
||||
:class:`~mongoengine.Document` subclass will be the name of the subclass
|
||||
converted to lowercase. A different collection may be specified by
|
||||
converted to snake_case. A different collection may be specified by
|
||||
providing :attr:`collection` to the :attr:`meta` dictionary in the class
|
||||
definition.
|
||||
|
||||
@ -117,7 +135,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
|
||||
create a specialised version of the document that will be stored in the
|
||||
same collection. To facilitate this behaviour a `_cls`
|
||||
field is added to documents (hidden though the MongoEngine interface).
|
||||
To enable this behaviourset :attr:`allow_inheritance` to ``True`` in the
|
||||
To enable this behaviour set :attr:`allow_inheritance` to ``True`` in the
|
||||
:attr:`meta` dictionary.
|
||||
|
||||
A :class:`~mongoengine.Document` may use a **Capped Collection** by
|
||||
@ -323,7 +341,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
|
||||
_refs=None,
|
||||
save_condition=None,
|
||||
signal_kwargs=None,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
):
|
||||
"""Save the :class:`~mongoengine.Document` to the database. If the
|
||||
document already exists, it will be updated, otherwise it will be
|
||||
@ -554,7 +572,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
|
||||
if not getattr(ref, "_changed_fields", True):
|
||||
continue
|
||||
|
||||
ref_id = "{},{}".format(ref.__class__.__name__, str(ref._data))
|
||||
ref_id = f"{ref.__class__.__name__},{str(ref._data)}"
|
||||
if ref and ref_id not in _refs:
|
||||
_refs.append(ref_id)
|
||||
kwargs["_refs"] = _refs
|
||||
@ -565,7 +583,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
|
||||
def _qs(self):
|
||||
"""Return the default queryset corresponding to this document."""
|
||||
if not hasattr(self, "__objects"):
|
||||
self.__objects = QuerySet(self, self._get_collection())
|
||||
self.__objects = QuerySet(self.__class__, self._get_collection())
|
||||
return self.__objects
|
||||
|
||||
@property
|
||||
@ -857,6 +875,10 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
|
||||
|
||||
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
|
||||
`auto_create_index` to False in the documents meta data
|
||||
"""
|
||||
@ -906,8 +928,10 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
|
||||
|
||||
@classmethod
|
||||
def list_indexes(cls):
|
||||
"""Lists all of the indexes that should be created for given
|
||||
collection. It includes all the indexes from super- and sub-classes.
|
||||
"""Lists all indexes that should be created for the Document 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"):
|
||||
return []
|
||||
|
@ -1,6 +1,5 @@
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
__all__ = (
|
||||
"NotRegistered",
|
||||
"InvalidDocumentError",
|
||||
@ -18,11 +17,15 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class NotRegistered(Exception):
|
||||
class MongoEngineException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidDocumentError(Exception):
|
||||
class NotRegistered(MongoEngineException):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidDocumentError(MongoEngineException):
|
||||
pass
|
||||
|
||||
|
||||
@ -30,19 +33,19 @@ class LookUpError(AttributeError):
|
||||
pass
|
||||
|
||||
|
||||
class DoesNotExist(Exception):
|
||||
class DoesNotExist(MongoEngineException):
|
||||
pass
|
||||
|
||||
|
||||
class MultipleObjectsReturned(Exception):
|
||||
class MultipleObjectsReturned(MongoEngineException):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidQueryError(Exception):
|
||||
class InvalidQueryError(MongoEngineException):
|
||||
pass
|
||||
|
||||
|
||||
class OperationError(Exception):
|
||||
class OperationError(MongoEngineException):
|
||||
pass
|
||||
|
||||
|
||||
@ -58,7 +61,7 @@ class SaveConditionError(OperationError):
|
||||
pass
|
||||
|
||||
|
||||
class FieldDoesNotExist(Exception):
|
||||
class FieldDoesNotExist(MongoEngineException):
|
||||
"""Raised when trying to set a field
|
||||
not declared in a :class:`~mongoengine.Document`
|
||||
or an :class:`~mongoengine.EmbeddedDocument`.
|
||||
@ -156,7 +159,7 @@ class ValidationError(AssertionError):
|
||||
return " ".join([f"{k}: {v}" for k, v in error_dict.items()])
|
||||
|
||||
|
||||
class DeprecatedError(Exception):
|
||||
class DeprecatedError(MongoEngineException):
|
||||
"""Raise when a user uses a feature that has been Deprecated"""
|
||||
|
||||
pass
|
||||
|
@ -1,5 +1,6 @@
|
||||
import datetime
|
||||
import decimal
|
||||
import inspect
|
||||
import itertools
|
||||
import re
|
||||
import socket
|
||||
@ -8,10 +9,10 @@ import uuid
|
||||
from io import BytesIO
|
||||
from operator import itemgetter
|
||||
|
||||
from bson import Binary, DBRef, ObjectId, SON
|
||||
from bson.int64 import Int64
|
||||
import gridfs
|
||||
import pymongo
|
||||
from bson import SON, Binary, DBRef, ObjectId
|
||||
from bson.int64 import Int64
|
||||
from pymongo import ReturnDocument
|
||||
|
||||
try:
|
||||
@ -21,7 +22,6 @@ except ImportError:
|
||||
else:
|
||||
import dateutil.parser
|
||||
|
||||
|
||||
from mongoengine.base import (
|
||||
BaseDocument,
|
||||
BaseField,
|
||||
@ -35,7 +35,11 @@ from mongoengine.base.utils import LazyRegexCompiler
|
||||
from mongoengine.common import _import_class
|
||||
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
|
||||
from mongoengine.document import Document, EmbeddedDocument
|
||||
from mongoengine.errors import DoesNotExist, InvalidQueryError, ValidationError
|
||||
from mongoengine.errors import (
|
||||
DoesNotExist,
|
||||
InvalidQueryError,
|
||||
ValidationError,
|
||||
)
|
||||
from mongoengine.queryset import DO_NOTHING
|
||||
from mongoengine.queryset.base import BaseQuerySet
|
||||
from mongoengine.queryset.transform import STRING_OPERATORS
|
||||
@ -153,7 +157,14 @@ class StringField(BaseField):
|
||||
regex = r"%s$"
|
||||
elif op == "exact":
|
||||
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
|
||||
value = re.escape(value)
|
||||
value = re.compile(regex % value, flags)
|
||||
@ -464,6 +475,10 @@ class DecimalField(BaseField):
|
||||
self.min_value = min_value
|
||||
self.max_value = max_value
|
||||
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.rounding = rounding
|
||||
|
||||
@ -478,9 +493,12 @@ class DecimalField(BaseField):
|
||||
value = decimal.Decimal("%s" % value)
|
||||
except (TypeError, ValueError, decimal.InvalidOperation):
|
||||
return value
|
||||
if self.precision > 0:
|
||||
return value.quantize(
|
||||
decimal.Decimal(".%s" % ("0" * self.precision)), rounding=self.rounding
|
||||
)
|
||||
else:
|
||||
return value.quantize(decimal.Decimal(), rounding=self.rounding)
|
||||
|
||||
def to_mongo(self, value):
|
||||
if value is None:
|
||||
@ -514,7 +532,7 @@ class BooleanField(BaseField):
|
||||
def to_python(self, value):
|
||||
try:
|
||||
value = bool(value)
|
||||
except ValueError:
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
return value
|
||||
|
||||
@ -914,7 +932,7 @@ class ListField(ComplexBaseField):
|
||||
"""A list field that wraps a standard field, allowing multiple instances
|
||||
of the field to be used as a list in the database.
|
||||
|
||||
If using with ReferenceFields see: :ref:`one-to-many-with-listfields`
|
||||
If using with ReferenceFields see: :ref:`many-to-many-with-listfields`
|
||||
|
||||
.. note::
|
||||
Required means it cannot be empty - as the default for ListFields is []
|
||||
@ -1028,17 +1046,6 @@ def key_not_string(d):
|
||||
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):
|
||||
"""Helper function to recursively determine if any key in a
|
||||
dictionary starts with a dollar
|
||||
@ -1086,16 +1093,7 @@ class DictField(ComplexBaseField):
|
||||
return DictField(db_field=member_name)
|
||||
|
||||
def prepare_query_value(self, op, value):
|
||||
match_operators = [
|
||||
"contains",
|
||||
"icontains",
|
||||
"startswith",
|
||||
"istartswith",
|
||||
"endswith",
|
||||
"iendswith",
|
||||
"exact",
|
||||
"iexact",
|
||||
]
|
||||
match_operators = [*STRING_OPERATORS]
|
||||
|
||||
if op in match_operators and isinstance(value, str):
|
||||
return StringField().prepare_query_value(op, value)
|
||||
@ -1172,7 +1170,7 @@ class ReferenceField(BaseField):
|
||||
|
||||
:param document_type: The type of Document that will be referenced
|
||||
:param dbref: Store the reference as :class:`~pymongo.dbref.DBRef`
|
||||
or as the :class:`~pymongo.objectid.ObjectId`.id .
|
||||
or as the :class:`~pymongo.objectid.ObjectId`.
|
||||
:param reverse_delete_rule: Determines what to do when the referring
|
||||
object is deleted
|
||||
:param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.BaseField`
|
||||
@ -1204,6 +1202,14 @@ class ReferenceField(BaseField):
|
||||
self.document_type_obj = get_document(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):
|
||||
"""Descriptor to allow lazy dereferencing."""
|
||||
if instance is None:
|
||||
@ -1211,20 +1217,17 @@ class ReferenceField(BaseField):
|
||||
return self
|
||||
|
||||
# Get value from document instance if available
|
||||
value = instance._data.get(self.name)
|
||||
ref_value = instance._data.get(self.name)
|
||||
auto_dereference = instance._fields[self.name]._auto_dereference
|
||||
# Dereference DBRefs
|
||||
if auto_dereference and isinstance(value, DBRef):
|
||||
if hasattr(value, "cls"):
|
||||
if auto_dereference and isinstance(ref_value, DBRef):
|
||||
if hasattr(ref_value, "cls"):
|
||||
# Dereference using the class type specified in the reference
|
||||
cls = get_document(value.cls)
|
||||
cls = get_document(ref_value.cls)
|
||||
else:
|
||||
cls = self.document_type
|
||||
dereferenced = cls._get_db().dereference(value)
|
||||
if dereferenced is None:
|
||||
raise DoesNotExist("Trying to dereference unknown document %s" % value)
|
||||
else:
|
||||
instance._data[self.name] = cls._from_son(dereferenced)
|
||||
|
||||
instance._data[self.name] = self._lazy_load_ref(cls, ref_value)
|
||||
|
||||
return super().__get__(instance, owner)
|
||||
|
||||
@ -1311,8 +1314,8 @@ class CachedReferenceField(BaseField):
|
||||
fields = []
|
||||
|
||||
# XXX ValidationError raised outside of the "validate" method.
|
||||
if not isinstance(document_type, str) and not issubclass(
|
||||
document_type, Document
|
||||
if not isinstance(document_type, str) and not (
|
||||
inspect.isclass(document_type) and issubclass(document_type, Document)
|
||||
):
|
||||
self.error(
|
||||
"Argument to CachedReferenceField constructor must be a"
|
||||
@ -1363,6 +1366,14 @@ class CachedReferenceField(BaseField):
|
||||
self.document_type_obj = get_document(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):
|
||||
if instance is None:
|
||||
# Document class being used rather than a document object
|
||||
@ -1374,11 +1385,7 @@ class CachedReferenceField(BaseField):
|
||||
|
||||
# Dereference DBRefs
|
||||
if auto_dereference and isinstance(value, DBRef):
|
||||
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)
|
||||
instance._data[self.name] = self._lazy_load_ref(self.document_type, value)
|
||||
|
||||
return super().__get__(instance, owner)
|
||||
|
||||
@ -1503,6 +1510,14 @@ class GenericReferenceField(BaseField):
|
||||
value = value._class_name
|
||||
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):
|
||||
if instance is None:
|
||||
return self
|
||||
@ -1510,12 +1525,9 @@ class GenericReferenceField(BaseField):
|
||||
value = instance._data.get(self.name)
|
||||
|
||||
auto_dereference = instance._fields[self.name]._auto_dereference
|
||||
if auto_dereference and isinstance(value, (dict, SON)):
|
||||
dereferenced = self.dereference(value)
|
||||
if dereferenced is None:
|
||||
raise DoesNotExist("Trying to dereference unknown document %s" % value)
|
||||
else:
|
||||
instance._data[self.name] = dereferenced
|
||||
if auto_dereference and isinstance(value, dict):
|
||||
doc_cls = get_document(value["_cls"])
|
||||
instance._data[self.name] = self._lazy_load_ref(doc_cls, value["_ref"])
|
||||
|
||||
return super().__get__(instance, owner)
|
||||
|
||||
@ -1534,14 +1546,6 @@ class GenericReferenceField(BaseField):
|
||||
" 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):
|
||||
if document is None:
|
||||
return None
|
||||
@ -1612,11 +1616,14 @@ class EnumField(BaseField):
|
||||
"""Enumeration Field. Values are stored underneath as is,
|
||||
so it will only work with simple types (str, int, etc) that
|
||||
are bson encodable
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Status(Enum):
|
||||
NEW = 'new'
|
||||
ONGOING = 'ongoing'
|
||||
DONE = 'done'
|
||||
|
||||
class ModelWithEnum(Document):
|
||||
@ -1626,23 +1633,31 @@ class EnumField(BaseField):
|
||||
ModelWithEnum(status=Status.DONE)
|
||||
|
||||
Enum fields can be searched using enum or its value:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ModelWithEnum.objects(status='new').count()
|
||||
ModelWithEnum.objects(status=Status.NEW).count()
|
||||
|
||||
Note that choices cannot be set explicitly, they are derived
|
||||
from the provided enum class.
|
||||
The values can be restricted to a subset of the enum by using the ``choices`` parameter:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ModelWithEnum(Document):
|
||||
status = EnumField(Status, choices=[Status.NEW, Status.DONE])
|
||||
"""
|
||||
|
||||
def __init__(self, enum, **kwargs):
|
||||
self._enum_cls = enum
|
||||
if "choices" in kwargs:
|
||||
raise ValueError(
|
||||
"'choices' can't be set on EnumField, "
|
||||
"it is implicitly set as the enum class"
|
||||
)
|
||||
kwargs["choices"] = list(self._enum_cls)
|
||||
if kwargs.get("choices"):
|
||||
invalid_choices = []
|
||||
for choice in kwargs["choices"]:
|
||||
if not isinstance(choice, enum):
|
||||
invalid_choices.append(choice)
|
||||
if invalid_choices:
|
||||
raise ValueError("Invalid choices: %r" % invalid_choices)
|
||||
else:
|
||||
kwargs["choices"] = list(self._enum_cls) # Implicit validator
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
@ -1659,13 +1674,6 @@ class EnumField(BaseField):
|
||||
return value.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):
|
||||
if value is None:
|
||||
return value
|
||||
@ -2431,7 +2439,7 @@ class LazyReferenceField(BaseField):
|
||||
object is deleted
|
||||
:param passthrough: When trying to access unknown fields, the
|
||||
:class:`~mongoengine.base.datastructure.LazyReference` instance will
|
||||
automatically call `fetch()` and try to retrive the field on the fetched
|
||||
automatically call `fetch()` and try to retrieve the field on the fetched
|
||||
document. Note this only work getting field (not setting or deleting).
|
||||
"""
|
||||
# XXX ValidationError raised outside of the "validate" method.
|
||||
|
@ -3,11 +3,12 @@ Helper functions, constants, and types to aid with MongoDB version support
|
||||
"""
|
||||
from mongoengine.connection import get_connection
|
||||
|
||||
|
||||
# Constant that can be used to compare the version retrieved with
|
||||
# get_mongodb_version()
|
||||
MONGODB_34 = (3, 4)
|
||||
MONGODB_36 = (3, 6)
|
||||
MONGODB_42 = (4, 2)
|
||||
MONGODB_44 = (4, 4)
|
||||
|
||||
|
||||
def get_mongodb_version():
|
||||
|
@ -2,13 +2,12 @@ import copy
|
||||
import itertools
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from collections.abc import Mapping
|
||||
|
||||
from bson import SON, json_util
|
||||
from bson.code import Code
|
||||
import pymongo
|
||||
import pymongo.errors
|
||||
from bson import SON, json_util
|
||||
from bson.code import Code
|
||||
from pymongo.collection import ReturnDocument
|
||||
from pymongo.common import validate_read_preference
|
||||
from pymongo.read_concern import ReadConcern
|
||||
@ -34,7 +33,6 @@ from mongoengine.queryset import transform
|
||||
from mongoengine.queryset.field_list import QueryFieldList
|
||||
from mongoengine.queryset.visitor import Q, QNode
|
||||
|
||||
|
||||
__all__ = ("BaseQuerySet", "DO_NOTHING", "NULLIFY", "CASCADE", "DENY", "PULL")
|
||||
|
||||
# Delete rules
|
||||
@ -64,6 +62,7 @@ class BaseQuerySet:
|
||||
self._ordering = None
|
||||
self._snapshot = False
|
||||
self._timeout = True
|
||||
self._allow_disk_use = False
|
||||
self._read_preference = None
|
||||
self._read_concern = None
|
||||
self._iter = False
|
||||
@ -721,7 +720,7 @@ class BaseQuerySet:
|
||||
return queryset.filter(pk=object_id).first()
|
||||
|
||||
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
|
||||
:rtype: dict of ObjectId's as keys and collection-specific
|
||||
@ -799,6 +798,7 @@ class BaseQuerySet:
|
||||
"_ordering",
|
||||
"_snapshot",
|
||||
"_timeout",
|
||||
"_allow_disk_use",
|
||||
"_read_preference",
|
||||
"_read_concern",
|
||||
"_iter",
|
||||
@ -1165,6 +1165,16 @@ class BaseQuerySet:
|
||||
queryset._snapshot = enabled
|
||||
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):
|
||||
"""Enable or disable the default mongod timeout when querying. (no_cursor_timeout option)
|
||||
|
||||
@ -1343,21 +1353,18 @@ class BaseQuerySet:
|
||||
|
||||
MapReduceDocument = _import_class("MapReduceDocument")
|
||||
|
||||
if not hasattr(self._collection, "map_reduce"):
|
||||
raise NotImplementedError("Requires MongoDB >= 1.7.1")
|
||||
|
||||
map_f_scope = {}
|
||||
if isinstance(map_f, Code):
|
||||
map_f_scope = map_f.scope
|
||||
map_f = str(map_f)
|
||||
map_f = Code(queryset._sub_js_fields(map_f), map_f_scope)
|
||||
map_f = Code(queryset._sub_js_fields(map_f), map_f_scope or None)
|
||||
|
||||
reduce_f_scope = {}
|
||||
if isinstance(reduce_f, Code):
|
||||
reduce_f_scope = reduce_f.scope
|
||||
reduce_f = str(reduce_f)
|
||||
reduce_f_code = queryset._sub_js_fields(reduce_f)
|
||||
reduce_f = Code(reduce_f_code, reduce_f_scope)
|
||||
reduce_f = Code(reduce_f_code, reduce_f_scope or None)
|
||||
|
||||
mr_args = {"query": queryset._query}
|
||||
|
||||
@ -1367,7 +1374,7 @@ class BaseQuerySet:
|
||||
finalize_f_scope = finalize_f.scope
|
||||
finalize_f = str(finalize_f)
|
||||
finalize_f_code = queryset._sub_js_fields(finalize_f)
|
||||
finalize_f = Code(finalize_f_code, finalize_f_scope)
|
||||
finalize_f = Code(finalize_f_code, finalize_f_scope or None)
|
||||
mr_args["finalize"] = finalize_f
|
||||
|
||||
if scope:
|
||||
@ -1604,6 +1611,9 @@ class BaseQuerySet:
|
||||
if not self._timeout:
|
||||
cursor_args["no_cursor_timeout"] = True
|
||||
|
||||
if self._allow_disk_use:
|
||||
cursor_args["allow_disk_use"] = True
|
||||
|
||||
if self._loaded_fields:
|
||||
cursor_args[fields_name] = self._loaded_fields.as_dict()
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
from functools import partial
|
||||
|
||||
from mongoengine.queryset.queryset import QuerySet
|
||||
|
||||
__all__ = ("queryset_manager", "QuerySetManager")
|
||||
|
@ -1,11 +1,11 @@
|
||||
from mongoengine.errors import OperationError
|
||||
from mongoengine.queryset.base import (
|
||||
BaseQuerySet,
|
||||
CASCADE,
|
||||
DENY,
|
||||
DO_NOTHING,
|
||||
NULLIFY,
|
||||
PULL,
|
||||
BaseQuerySet,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
|
@ -1,8 +1,8 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from bson import ObjectId, SON
|
||||
from bson.dbref import DBRef
|
||||
import pymongo
|
||||
from bson import SON, ObjectId
|
||||
from bson.dbref import DBRef
|
||||
|
||||
from mongoengine.base import UPDATE_OPERATORS
|
||||
from mongoengine.common import _import_class
|
||||
@ -51,6 +51,10 @@ STRING_OPERATORS = (
|
||||
"iendswith",
|
||||
"exact",
|
||||
"iexact",
|
||||
"regex",
|
||||
"iregex",
|
||||
"wholeword",
|
||||
"iwholeword",
|
||||
)
|
||||
CUSTOM_OPERATORS = ("match",)
|
||||
MATCH_OPERATORS = (
|
||||
|
@ -1,6 +1,5 @@
|
||||
black
|
||||
flake8
|
||||
flake8-import-order
|
||||
pre-commit
|
||||
pytest
|
||||
ipdb
|
||||
|
10
setup.cfg
10
setup.cfg
@ -2,9 +2,17 @@
|
||||
ignore=E501,F403,F405,I201,I202,W504,W605,W503,B007
|
||||
exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests
|
||||
max-complexity=47
|
||||
application-import-names=mongoengine,tests
|
||||
|
||||
[tool:pytest]
|
||||
# Limits the discovery to tests directory
|
||||
# avoids that it runs for instance the benchmark
|
||||
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):
|
||||
# import here, cause outside the eggs aren't loaded
|
||||
from pkg_resources import _namespace_packages
|
||||
import pytest
|
||||
from pkg_resources import _namespace_packages
|
||||
|
||||
# Purge modules under test from sys.modules. The test loader will
|
||||
# re-import them from the build location. Required when 2to3 is used
|
||||
@ -98,10 +98,11 @@ CLASSIFIERS = [
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
"Topic :: Database",
|
||||
@ -111,9 +112,9 @@ CLASSIFIERS = [
|
||||
extra_opts = {
|
||||
"packages": find_packages(exclude=["tests", "tests.*"]),
|
||||
"tests_require": [
|
||||
"pytest<5.0",
|
||||
"pytest",
|
||||
"pytest-cov",
|
||||
"coverage<5.0", # recent coverage switched to sqlite format for the .coverage file which isn't handled properly by coveralls
|
||||
"coverage",
|
||||
"blinker",
|
||||
"Pillow>=7.0.0",
|
||||
],
|
||||
@ -140,7 +141,7 @@ setup(
|
||||
long_description=LONG_DESCRIPTION,
|
||||
platforms=["any"],
|
||||
classifiers=CLASSIFIERS,
|
||||
python_requires=">=3.5",
|
||||
python_requires=">=3.6",
|
||||
install_requires=["pymongo>=3.4, <4.0"],
|
||||
cmdclass={"test": PyTest},
|
||||
**extra_opts
|
||||
|
@ -177,7 +177,7 @@ class TestClassMethods(unittest.TestCase):
|
||||
assert BlogPostWithCustomField.compare_indexes() == {"missing": [], "extra": []}
|
||||
|
||||
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):
|
||||
a = StringField()
|
||||
|
@ -1,6 +1,7 @@
|
||||
import unittest
|
||||
|
||||
from bson import SON
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine.pymongo_support import list_collection_names
|
||||
from tests.utils import MongoDBTestCase
|
||||
|
@ -1,12 +1,16 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from pymongo.collation import Collation
|
||||
from pymongo.errors import OperationFailure
|
||||
import pytest
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine.connection import get_db
|
||||
from mongoengine.mongodb_support import (
|
||||
MONGODB_42,
|
||||
get_mongodb_version,
|
||||
)
|
||||
|
||||
|
||||
class TestIndexes(unittest.TestCase):
|
||||
@ -452,9 +456,11 @@ class TestIndexes(unittest.TestCase):
|
||||
.get("stage")
|
||||
== "IXSCAN"
|
||||
)
|
||||
mongo_db = get_mongodb_version()
|
||||
PROJECTION_STR = "PROJECTION" if mongo_db < MONGODB_42 else "PROJECTION_COVERED"
|
||||
assert (
|
||||
query_plan.get("queryPlanner").get("winningPlan").get("stage")
|
||||
== "PROJECTION"
|
||||
== PROJECTION_STR
|
||||
)
|
||||
|
||||
query_plan = Test.objects(a=1).explain()
|
||||
|
@ -6,9 +6,9 @@ import weakref
|
||||
from datetime import datetime
|
||||
|
||||
import bson
|
||||
import pytest
|
||||
from bson import DBRef, ObjectId
|
||||
from pymongo.errors import DuplicateKeyError
|
||||
import pytest
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine import signals
|
||||
@ -23,7 +23,11 @@ from mongoengine.errors import (
|
||||
NotUniqueError,
|
||||
SaveConditionError,
|
||||
)
|
||||
from mongoengine.mongodb_support import MONGODB_34, MONGODB_36, get_mongodb_version
|
||||
from mongoengine.mongodb_support import (
|
||||
MONGODB_34,
|
||||
MONGODB_36,
|
||||
get_mongodb_version,
|
||||
)
|
||||
from mongoengine.pymongo_support import list_collection_names
|
||||
from mongoengine.queryset import NULLIFY, Q
|
||||
from tests import fixtures
|
||||
@ -61,12 +65,12 @@ class TestDocumentInstance(MongoDBTestCase):
|
||||
for collection in list_collection_names(self.db):
|
||||
self.db.drop_collection(collection)
|
||||
|
||||
def assertDbEqual(self, docs):
|
||||
def _assert_db_equal(self, docs):
|
||||
assert list(self.Person._get_collection().find().sort("id")) == sorted(
|
||||
docs, key=lambda doc: doc["_id"]
|
||||
)
|
||||
|
||||
def assertHasInstance(self, field, instance):
|
||||
def _assert_has_instance(self, field, instance):
|
||||
assert hasattr(field, "_instance")
|
||||
assert field._instance is not None
|
||||
if isinstance(field._instance, weakref.ProxyType):
|
||||
@ -403,6 +407,16 @@ class TestDocumentInstance(MongoDBTestCase):
|
||||
assert person.name == "Test User"
|
||||
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):
|
||||
"""Ensure that you can get a dbref of a document."""
|
||||
person = self.Person(name="Test User", age=30)
|
||||
@ -726,11 +740,11 @@ class TestDocumentInstance(MongoDBTestCase):
|
||||
Doc.drop_collection()
|
||||
|
||||
doc = Doc(embedded_field=Embedded(string="Hi"))
|
||||
self.assertHasInstance(doc.embedded_field, doc)
|
||||
self._assert_has_instance(doc.embedded_field, doc)
|
||||
|
||||
doc.save()
|
||||
doc = Doc.objects.get()
|
||||
self.assertHasInstance(doc.embedded_field, doc)
|
||||
self._assert_has_instance(doc.embedded_field, doc)
|
||||
|
||||
def test_embedded_document_complex_instance(self):
|
||||
"""Ensure that embedded documents in complex fields can reference
|
||||
@ -745,11 +759,11 @@ class TestDocumentInstance(MongoDBTestCase):
|
||||
|
||||
Doc.drop_collection()
|
||||
doc = Doc(embedded_field=[Embedded(string="Hi")])
|
||||
self.assertHasInstance(doc.embedded_field[0], doc)
|
||||
self._assert_has_instance(doc.embedded_field[0], doc)
|
||||
|
||||
doc.save()
|
||||
doc = Doc.objects.get()
|
||||
self.assertHasInstance(doc.embedded_field[0], doc)
|
||||
self._assert_has_instance(doc.embedded_field[0], doc)
|
||||
|
||||
def test_embedded_document_complex_instance_no_use_db_field(self):
|
||||
"""Ensure that use_db_field is propagated to list of Emb Docs."""
|
||||
@ -778,11 +792,11 @@ class TestDocumentInstance(MongoDBTestCase):
|
||||
|
||||
acc = Account()
|
||||
acc.email = Email(email="test@example.com")
|
||||
self.assertHasInstance(acc._data["email"], acc)
|
||||
self._assert_has_instance(acc._data["email"], acc)
|
||||
acc.save()
|
||||
|
||||
acc1 = Account.objects.first()
|
||||
self.assertHasInstance(acc1._data["email"], acc1)
|
||||
self._assert_has_instance(acc1._data["email"], acc1)
|
||||
|
||||
def test_instance_is_set_on_setattr_on_embedded_document_list(self):
|
||||
class Email(EmbeddedDocument):
|
||||
@ -794,11 +808,11 @@ class TestDocumentInstance(MongoDBTestCase):
|
||||
Account.drop_collection()
|
||||
acc = Account()
|
||||
acc.emails = [Email(email="test@example.com")]
|
||||
self.assertHasInstance(acc._data["emails"][0], acc)
|
||||
self._assert_has_instance(acc._data["emails"][0], acc)
|
||||
acc.save()
|
||||
|
||||
acc1 = Account.objects.first()
|
||||
self.assertHasInstance(acc1._data["emails"][0], acc1)
|
||||
self._assert_has_instance(acc1._data["emails"][0], acc1)
|
||||
|
||||
def test_save_checks_that_clean_is_called(self):
|
||||
class CustomError(Exception):
|
||||
@ -907,7 +921,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
||||
with pytest.raises(InvalidDocumentError):
|
||||
self.Person().modify(set__age=10)
|
||||
|
||||
self.assertDbEqual([dict(doc.to_mongo())])
|
||||
self._assert_db_equal([dict(doc.to_mongo())])
|
||||
|
||||
def test_modify_invalid_query(self):
|
||||
doc1 = self.Person(name="bob", age=10).save()
|
||||
@ -917,7 +931,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
||||
with pytest.raises(InvalidQueryError):
|
||||
doc1.modify({"id": doc2.id}, set__value=20)
|
||||
|
||||
self.assertDbEqual(docs)
|
||||
self._assert_db_equal(docs)
|
||||
|
||||
def test_modify_match_another_document(self):
|
||||
doc1 = self.Person(name="bob", age=10).save()
|
||||
@ -927,7 +941,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
||||
n_modified = doc1.modify({"name": doc2.name}, set__age=100)
|
||||
assert n_modified == 0
|
||||
|
||||
self.assertDbEqual(docs)
|
||||
self._assert_db_equal(docs)
|
||||
|
||||
def test_modify_not_exists(self):
|
||||
doc1 = self.Person(name="bob", age=10).save()
|
||||
@ -937,7 +951,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
||||
n_modified = doc2.modify({"name": doc2.name}, set__age=100)
|
||||
assert n_modified == 0
|
||||
|
||||
self.assertDbEqual(docs)
|
||||
self._assert_db_equal(docs)
|
||||
|
||||
def test_modify_update(self):
|
||||
other_doc = self.Person(name="bob", age=10).save()
|
||||
@ -963,7 +977,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
||||
assert doc.to_json() == doc_copy.to_json()
|
||||
assert doc._get_changed_fields() == []
|
||||
|
||||
self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
|
||||
self._assert_db_equal([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
|
||||
|
||||
def test_modify_with_positional_push(self):
|
||||
class Content(EmbeddedDocument):
|
||||
|
@ -1,7 +1,7 @@
|
||||
import unittest
|
||||
import uuid
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from bson import ObjectId
|
||||
|
||||
from mongoengine import *
|
||||
|
@ -1,7 +1,7 @@
|
||||
import uuid
|
||||
|
||||
from bson import Binary
|
||||
import pytest
|
||||
from bson import Binary
|
||||
|
||||
from mongoengine import *
|
||||
from tests.utils import MongoDBTestCase
|
||||
|
@ -13,6 +13,17 @@ class TestBooleanField(MongoDBTestCase):
|
||||
person.save()
|
||||
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):
|
||||
"""Ensure that invalid values cannot be assigned to boolean
|
||||
fields.
|
||||
|
@ -2,11 +2,28 @@ from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine import (
|
||||
CachedReferenceField,
|
||||
DecimalField,
|
||||
Document,
|
||||
EmbeddedDocument,
|
||||
EmbeddedDocumentField,
|
||||
InvalidDocumentError,
|
||||
ListField,
|
||||
ReferenceField,
|
||||
StringField,
|
||||
ValidationError,
|
||||
)
|
||||
from tests.utils import 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):
|
||||
"""
|
||||
Tests #1047: CachedReferenceField creates DBRefs on to_python,
|
||||
|
@ -6,7 +6,6 @@ import re
|
||||
import pytest
|
||||
|
||||
from mongoengine import *
|
||||
|
||||
from tests.utils import MongoDBTestCase
|
||||
|
||||
|
||||
|
@ -9,7 +9,6 @@ except ImportError:
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine import connection
|
||||
|
||||
from tests.utils import MongoDBTestCase
|
||||
|
||||
|
||||
|
@ -2,59 +2,11 @@ from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine import DecimalField, Document, ValidationError
|
||||
from tests.utils import MongoDBTestCase
|
||||
|
||||
|
||||
class TestDecimalField(MongoDBTestCase):
|
||||
def test_validation(self):
|
||||
"""Ensure that invalid values cannot be assigned to decimal fields."""
|
||||
|
||||
class Person(Document):
|
||||
height = DecimalField(min_value=Decimal("0.1"), max_value=Decimal("3.5"))
|
||||
|
||||
Person.drop_collection()
|
||||
|
||||
Person(height=Decimal("1.89")).save()
|
||||
person = Person.objects.first()
|
||||
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):
|
||||
class Person(Document):
|
||||
float_value = DecimalField(precision=4)
|
||||
@ -106,3 +58,83 @@ class TestDecimalField(MongoDBTestCase):
|
||||
for field_name in ["float_value", "string_value"]:
|
||||
actual = list(Person.objects().scalar(field_name))
|
||||
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,10 +1,12 @@
|
||||
from bson import InvalidDocument
|
||||
import pytest
|
||||
from bson import InvalidDocument
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine.base import BaseDict
|
||||
from mongoengine.mongodb_support import MONGODB_36, get_mongodb_version
|
||||
|
||||
from mongoengine.mongodb_support import (
|
||||
MONGODB_36,
|
||||
get_mongodb_version,
|
||||
)
|
||||
from tests.utils import MongoDBTestCase, get_as_pymongo
|
||||
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
from copy import deepcopy
|
||||
|
||||
import pytest
|
||||
from bson import ObjectId
|
||||
|
||||
from mongoengine import (
|
||||
Document,
|
||||
@ -9,10 +12,10 @@ from mongoengine import (
|
||||
InvalidQueryError,
|
||||
ListField,
|
||||
LookUpError,
|
||||
MapField,
|
||||
StringField,
|
||||
ValidationError,
|
||||
)
|
||||
|
||||
from tests.utils import MongoDBTestCase
|
||||
|
||||
|
||||
@ -351,3 +354,30 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
|
||||
# Test existing attribute
|
||||
assert Person.objects(settings__base_foo="basefoo").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 bson import InvalidDocument
|
||||
import pytest
|
||||
from bson import InvalidDocument
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine import Document, EnumField, ValidationError
|
||||
from tests.utils import MongoDBTestCase, get_as_pymongo
|
||||
|
||||
|
||||
@ -12,6 +12,11 @@ class Status(Enum):
|
||||
DONE = "done"
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
BLUE = 2
|
||||
|
||||
|
||||
class ModelWithEnum(Document):
|
||||
status = EnumField(Status)
|
||||
|
||||
@ -45,6 +50,11 @@ class TestStringEnumField(MongoDBTestCase):
|
||||
m.save()
|
||||
assert m.status == Status.DONE
|
||||
|
||||
m.status = "wrong"
|
||||
assert m.status == "wrong"
|
||||
with pytest.raises(ValidationError):
|
||||
m.validate()
|
||||
|
||||
def test_set_default(self):
|
||||
class ModelWithDefault(Document):
|
||||
status = EnumField(Status, default=Status.DONE)
|
||||
@ -69,14 +79,27 @@ class TestStringEnumField(MongoDBTestCase):
|
||||
with pytest.raises(ValidationError):
|
||||
m.validate()
|
||||
|
||||
def test_user_is_informed_when_tries_to_set_choices(self):
|
||||
with pytest.raises(ValueError, match="'choices' can't be set on EnumField"):
|
||||
def test_partial_choices(self):
|
||||
partial = [Status.DONE]
|
||||
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"])
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
BLUE = 2
|
||||
with pytest.raises(ValueError, match="Invalid choices"):
|
||||
EnumField(Status, choices=[Color.RED])
|
||||
with pytest.raises(ValueError, match="Invalid choices"):
|
||||
EnumField(Status, choices=[Status.DONE, Color.RED])
|
||||
|
||||
|
||||
class ModelWithColor(Document):
|
||||
|
@ -1,8 +1,8 @@
|
||||
import datetime
|
||||
import unittest
|
||||
|
||||
from bson import DBRef, ObjectId, SON
|
||||
import pytest
|
||||
from bson import SON, DBRef, ObjectId
|
||||
|
||||
from mongoengine import (
|
||||
BooleanField,
|
||||
@ -34,9 +34,12 @@ from mongoengine import (
|
||||
StringField,
|
||||
ValidationError,
|
||||
)
|
||||
from mongoengine.base import BaseField, EmbeddedDocumentList, _document_registry
|
||||
from mongoengine.base import (
|
||||
BaseField,
|
||||
EmbeddedDocumentList,
|
||||
_document_registry,
|
||||
)
|
||||
from mongoengine.errors import DeprecatedError
|
||||
|
||||
from tests.utils import MongoDBTestCase
|
||||
|
||||
|
||||
@ -306,7 +309,7 @@ class TestField(MongoDBTestCase):
|
||||
)
|
||||
assert res == 1
|
||||
|
||||
# Retrive data from db and verify it.
|
||||
# Retrieve data from db and verify it.
|
||||
ret = HandleNoneFields.objects.all()[0]
|
||||
assert ret.str_fld is None
|
||||
assert ret.int_fld is None
|
||||
@ -340,7 +343,7 @@ class TestField(MongoDBTestCase):
|
||||
{"$unset": {"str_fld": 1, "int_fld": 1, "flt_fld": 1, "comp_dt_fld": 1}},
|
||||
)
|
||||
|
||||
# Retrive data from db and verify it.
|
||||
# Retrieve data from db and verify it.
|
||||
ret = HandleNoneFields.objects.first()
|
||||
assert ret.str_fld is None
|
||||
assert ret.int_fld is None
|
||||
@ -374,34 +377,6 @@ class TestField(MongoDBTestCase):
|
||||
person.id = str(ObjectId())
|
||||
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):
|
||||
"""Ensure that db_field doesn't accept invalid values."""
|
||||
|
||||
@ -2102,7 +2077,7 @@ class TestField(MongoDBTestCase):
|
||||
a ComplexBaseField.
|
||||
"""
|
||||
|
||||
class EnumField(BaseField):
|
||||
class SomeField(BaseField):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@ -2113,7 +2088,7 @@ class TestField(MongoDBTestCase):
|
||||
return tuple(value)
|
||||
|
||||
class TestDoc(Document):
|
||||
items = ListField(EnumField())
|
||||
items = ListField(SomeField())
|
||||
|
||||
TestDoc.drop_collection()
|
||||
|
||||
|
@ -308,7 +308,7 @@ class TestFileField(MongoDBTestCase):
|
||||
assert test_file.the_file not in [{"test": 1}]
|
||||
|
||||
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):
|
||||
the_file = FileField()
|
||||
|
@ -1,7 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from mongoengine import *
|
||||
|
||||
from tests.utils import MongoDBTestCase
|
||||
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from mongoengine import *
|
||||
|
||||
from tests.utils import MongoDBTestCase
|
||||
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
from bson import DBRef, ObjectId
|
||||
import pytest
|
||||
from bson import DBRef, ObjectId
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine.base import LazyReference
|
||||
from mongoengine.context_managers import query_counter
|
||||
|
||||
from tests.utils import MongoDBTestCase
|
||||
|
||||
|
||||
@ -375,6 +374,26 @@ class TestLazyReferenceField(MongoDBTestCase):
|
||||
assert isinstance(ref.author, LazyReference)
|
||||
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):
|
||||
def test_generic_lazy_reference_simple(self):
|
||||
|
@ -1,13 +1,28 @@
|
||||
from bson.int64 import Int64
|
||||
import pytest
|
||||
from bson.int64 import Int64
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine.connection import get_db
|
||||
|
||||
from tests.utils import MongoDBTestCase
|
||||
from tests.utils import MongoDBTestCase, get_as_pymongo
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
Tests that long fields are stored as long in mongo, even if long
|
||||
@ -30,19 +45,16 @@ class TestLongField(MongoDBTestCase):
|
||||
class TestDocument(Document):
|
||||
value = LongField(min_value=0, max_value=110)
|
||||
|
||||
doc = TestDocument()
|
||||
doc.value = 50
|
||||
doc.validate()
|
||||
TestDocument(value=50).validate()
|
||||
|
||||
doc.value = -1
|
||||
with pytest.raises(ValidationError):
|
||||
doc.validate()
|
||||
doc.value = 120
|
||||
TestDocument(value=-1).validate()
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
doc.validate()
|
||||
doc.value = "ten"
|
||||
TestDocument(value=120).validate()
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
doc.validate()
|
||||
TestDocument(value="ten").validate()
|
||||
|
||||
def test_long_ne_operator(self):
|
||||
class TestDocument(Document):
|
||||
@ -53,4 +65,5 @@ class TestLongField(MongoDBTestCase):
|
||||
TestDocument(long_fld=None).save()
|
||||
TestDocument(long_fld=1).save()
|
||||
|
||||
assert 1 == TestDocument.objects(long_fld__ne=None).count()
|
||||
assert TestDocument.objects(long_fld__ne=None).count() == 1
|
||||
assert TestDocument.objects(long_fld__ne=1).count() == 1
|
||||
|
@ -1,5 +1,5 @@
|
||||
from bson import DBRef, SON
|
||||
import pytest
|
||||
from bson import SON, DBRef
|
||||
|
||||
from mongoengine import *
|
||||
from tests.utils import MongoDBTestCase
|
||||
|
@ -1,5 +1,4 @@
|
||||
from mongoengine import *
|
||||
|
||||
from tests.utils import MongoDBTestCase
|
||||
|
||||
|
||||
@ -274,3 +273,25 @@ class TestSequenceField(MongoDBTestCase):
|
||||
assert foo.counter == bar.counter
|
||||
assert foo._fields["counter"].owner_document == Foo
|
||||
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
|
||||
|
43
tests/fields/test_string_field.py
Normal file
43
tests/fields/test_string_field.py
Normal file
@ -0,0 +1,43 @@
|
||||
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,7 +2,6 @@ import datetime
|
||||
import unittest
|
||||
|
||||
from mongoengine import *
|
||||
|
||||
from tests.utils import MongoDBTestCase
|
||||
|
||||
|
||||
|
@ -1,6 +1,12 @@
|
||||
import unittest
|
||||
|
||||
from mongoengine import Document, IntField, ListField, StringField, connect
|
||||
from mongoengine import (
|
||||
Document,
|
||||
IntField,
|
||||
ListField,
|
||||
StringField,
|
||||
connect,
|
||||
)
|
||||
|
||||
|
||||
class Doc(Document):
|
||||
@ -13,7 +19,7 @@ class TestFindAndModify(unittest.TestCase):
|
||||
connect(db="mongoenginetest")
|
||||
Doc.drop_collection()
|
||||
|
||||
def assertDbEqual(self, docs):
|
||||
def _assert_db_equal(self, docs):
|
||||
assert list(Doc._collection.find().sort("id")) == docs
|
||||
|
||||
def test_modify(self):
|
||||
@ -22,7 +28,7 @@ class TestFindAndModify(unittest.TestCase):
|
||||
|
||||
old_doc = Doc.objects(id=1).modify(set__value=-1)
|
||||
assert old_doc.to_json() == doc.to_json()
|
||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||
|
||||
def test_modify_with_new(self):
|
||||
Doc(id=0, value=0).save()
|
||||
@ -31,18 +37,18 @@ class TestFindAndModify(unittest.TestCase):
|
||||
new_doc = Doc.objects(id=1).modify(set__value=-1, new=True)
|
||||
doc.value = -1
|
||||
assert new_doc.to_json() == doc.to_json()
|
||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||
|
||||
def test_modify_not_existing(self):
|
||||
Doc(id=0, value=0).save()
|
||||
assert Doc.objects(id=1).modify(set__value=-1) is None
|
||||
self.assertDbEqual([{"_id": 0, "value": 0}])
|
||||
self._assert_db_equal([{"_id": 0, "value": 0}])
|
||||
|
||||
def test_modify_with_upsert(self):
|
||||
Doc(id=0, value=0).save()
|
||||
old_doc = Doc.objects(id=1).modify(set__value=1, upsert=True)
|
||||
assert old_doc is None
|
||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
|
||||
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
|
||||
|
||||
def test_modify_with_upsert_existing(self):
|
||||
Doc(id=0, value=0).save()
|
||||
@ -50,13 +56,13 @@ class TestFindAndModify(unittest.TestCase):
|
||||
|
||||
old_doc = Doc.objects(id=1).modify(set__value=-1, upsert=True)
|
||||
assert old_doc.to_json() == doc.to_json()
|
||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||
|
||||
def test_modify_with_upsert_with_new(self):
|
||||
Doc(id=0, value=0).save()
|
||||
new_doc = Doc.objects(id=1).modify(upsert=True, new=True, set__value=1)
|
||||
assert new_doc.to_mongo() == {"_id": 1, "value": 1}
|
||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
|
||||
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
|
||||
|
||||
def test_modify_with_remove(self):
|
||||
Doc(id=0, value=0).save()
|
||||
@ -64,12 +70,12 @@ class TestFindAndModify(unittest.TestCase):
|
||||
|
||||
old_doc = Doc.objects(id=1).modify(remove=True)
|
||||
assert old_doc.to_json() == doc.to_json()
|
||||
self.assertDbEqual([{"_id": 0, "value": 0}])
|
||||
self._assert_db_equal([{"_id": 0, "value": 0}])
|
||||
|
||||
def test_find_and_modify_with_remove_not_existing(self):
|
||||
Doc(id=0, value=0).save()
|
||||
assert Doc.objects(id=1).modify(remove=True) is None
|
||||
self.assertDbEqual([{"_id": 0, "value": 0}])
|
||||
self._assert_db_equal([{"_id": 0, "value": 0}])
|
||||
|
||||
def test_modify_with_order_by(self):
|
||||
Doc(id=0, value=3).save()
|
||||
@ -79,7 +85,7 @@ class TestFindAndModify(unittest.TestCase):
|
||||
|
||||
old_doc = Doc.objects().order_by("-id").modify(set__value=-1)
|
||||
assert old_doc.to_json() == doc.to_json()
|
||||
self.assertDbEqual(
|
||||
self._assert_db_equal(
|
||||
[
|
||||
{"_id": 0, "value": 3},
|
||||
{"_id": 1, "value": 2},
|
||||
@ -94,7 +100,7 @@ class TestFindAndModify(unittest.TestCase):
|
||||
|
||||
old_doc = Doc.objects(id=1).only("id").modify(set__value=-1)
|
||||
assert old_doc.to_mongo() == {"_id": 1}
|
||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||
|
||||
def test_modify_with_push(self):
|
||||
class BlogPost(Document):
|
||||
|
@ -3,17 +3,20 @@ import unittest
|
||||
import uuid
|
||||
from decimal import Decimal
|
||||
|
||||
from bson import DBRef, ObjectId
|
||||
import pymongo
|
||||
import pytest
|
||||
from bson import DBRef, ObjectId
|
||||
from pymongo.read_preferences import ReadPreference
|
||||
from pymongo.results import UpdateResult
|
||||
import pytest
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine.connection import get_db
|
||||
from mongoengine.context_managers import query_counter, switch_db
|
||||
from mongoengine.errors import InvalidQueryError
|
||||
from mongoengine.mongodb_support import MONGODB_36, get_mongodb_version
|
||||
from mongoengine.mongodb_support import (
|
||||
MONGODB_36,
|
||||
get_mongodb_version,
|
||||
)
|
||||
from mongoengine.queryset import (
|
||||
DoesNotExist,
|
||||
MultipleObjectsReturned,
|
||||
@ -21,6 +24,10 @@ from mongoengine.queryset import (
|
||||
QuerySetManager,
|
||||
queryset_manager,
|
||||
)
|
||||
from tests.utils import (
|
||||
requires_mongodb_gte_44,
|
||||
requires_mongodb_lt_42,
|
||||
)
|
||||
|
||||
|
||||
class db_ops_tracker(query_counter):
|
||||
@ -860,6 +867,21 @@ class TestQueryset(unittest.TestCase):
|
||||
assert "Bob" == bob.name
|
||||
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):
|
||||
class Embed(EmbeddedDocument):
|
||||
field = IntField()
|
||||
@ -1233,6 +1255,34 @@ class TestQueryset(unittest.TestCase):
|
||||
obj = self.Person.objects(name__iexact="gUIDO VAN rOSSU").first()
|
||||
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
|
||||
person = self.Person(name="Guido van Rossum [.'Geek']")
|
||||
person.save()
|
||||
@ -1317,7 +1367,14 @@ class TestQueryset(unittest.TestCase):
|
||||
person.save()
|
||||
|
||||
people = self.Person.objects
|
||||
people = people.filter(name__startswith="Gui").filter(name__not__endswith="tum")
|
||||
people = (
|
||||
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
|
||||
|
||||
def assertSequence(self, qs, expected):
|
||||
@ -1489,6 +1546,7 @@ class TestQueryset(unittest.TestCase):
|
||||
|
||||
BlogPost.drop_collection()
|
||||
|
||||
@requires_mongodb_lt_42
|
||||
def test_exec_js_query(self):
|
||||
"""Ensure that queries are properly formed for use in exec_js."""
|
||||
|
||||
@ -1526,6 +1584,7 @@ class TestQueryset(unittest.TestCase):
|
||||
|
||||
BlogPost.drop_collection()
|
||||
|
||||
@requires_mongodb_lt_42
|
||||
def test_exec_js_field_sub(self):
|
||||
"""Ensure that field substitutions occur properly in exec_js functions."""
|
||||
|
||||
@ -2659,6 +2718,8 @@ class TestQueryset(unittest.TestCase):
|
||||
title = StringField(primary_key=True)
|
||||
tags = ListField(StringField())
|
||||
|
||||
BlogPost.drop_collection()
|
||||
|
||||
post1 = BlogPost(title="Post #1", tags=["mongodb", "mongoengine"])
|
||||
post2 = BlogPost(title="Post #2", tags=["django", "mongodb"])
|
||||
post3 = BlogPost(title="Post #3", tags=["hitchcock films"])
|
||||
@ -2687,12 +2748,15 @@ class TestQueryset(unittest.TestCase):
|
||||
}
|
||||
"""
|
||||
|
||||
results = BlogPost.objects.map_reduce(map_f, reduce_f, "myresults")
|
||||
results = BlogPost.objects.order_by("_id").map_reduce(
|
||||
map_f, reduce_f, "myresults2"
|
||||
)
|
||||
results = list(results)
|
||||
|
||||
assert results[0].object == post1
|
||||
assert results[1].object == post2
|
||||
assert results[2].object == post3
|
||||
assert len(results) == 3
|
||||
assert results[0].object.id == post1.id
|
||||
assert results[1].object.id == post2.id
|
||||
assert results[2].object.id == post3.id
|
||||
|
||||
BlogPost.drop_collection()
|
||||
|
||||
@ -2700,7 +2764,6 @@ class TestQueryset(unittest.TestCase):
|
||||
"""
|
||||
Test map/reduce custom output
|
||||
"""
|
||||
register_connection("test2", "mongoenginetest2")
|
||||
|
||||
class Family(Document):
|
||||
id = IntField(primary_key=True)
|
||||
@ -2773,6 +2836,7 @@ class TestQueryset(unittest.TestCase):
|
||||
family.persons.push(person);
|
||||
family.totalAge += person.age;
|
||||
});
|
||||
family.persons.sort((a, b) => (a.age > b.age))
|
||||
}
|
||||
});
|
||||
|
||||
@ -2801,10 +2865,10 @@ class TestQueryset(unittest.TestCase):
|
||||
"_id": 1,
|
||||
"value": {
|
||||
"persons": [
|
||||
{"age": 21, "name": "Wilson Jr"},
|
||||
{"age": 45, "name": "Wilson Father"},
|
||||
{"age": 40, "name": "Eliana Costa"},
|
||||
{"age": 17, "name": "Tayza Mariana"},
|
||||
{"age": 21, "name": "Wilson Jr"},
|
||||
{"age": 40, "name": "Eliana Costa"},
|
||||
{"age": 45, "name": "Wilson Father"},
|
||||
],
|
||||
"totalAge": 123,
|
||||
},
|
||||
@ -2814,9 +2878,9 @@ class TestQueryset(unittest.TestCase):
|
||||
"_id": 2,
|
||||
"value": {
|
||||
"persons": [
|
||||
{"age": 10, "name": "Igor Gabriel"},
|
||||
{"age": 16, "name": "Isabella Luanna"},
|
||||
{"age": 36, "name": "Sandra Mara"},
|
||||
{"age": 10, "name": "Igor Gabriel"},
|
||||
],
|
||||
"totalAge": 62,
|
||||
},
|
||||
@ -2826,8 +2890,8 @@ class TestQueryset(unittest.TestCase):
|
||||
"_id": 3,
|
||||
"value": {
|
||||
"persons": [
|
||||
{"age": 30, "name": "Arthur WA"},
|
||||
{"age": 25, "name": "Paula Leonel"},
|
||||
{"age": 30, "name": "Arthur WA"},
|
||||
],
|
||||
"totalAge": 55,
|
||||
},
|
||||
@ -3108,6 +3172,7 @@ class TestQueryset(unittest.TestCase):
|
||||
freq = Person.objects.item_frequencies("city", normalize=True, map_reduce=True)
|
||||
assert freq == {"CRB": 0.5, None: 0.5}
|
||||
|
||||
@requires_mongodb_lt_42
|
||||
def test_item_frequencies_with_null_embedded(self):
|
||||
class Data(EmbeddedDocument):
|
||||
name = StringField()
|
||||
@ -3136,6 +3201,7 @@ class TestQueryset(unittest.TestCase):
|
||||
ot = Person.objects.item_frequencies("extra.tag", map_reduce=True)
|
||||
assert ot == {None: 1.0, "friend": 1.0}
|
||||
|
||||
@requires_mongodb_lt_42
|
||||
def test_item_frequencies_with_0_values(self):
|
||||
class Test(Document):
|
||||
val = IntField()
|
||||
@ -3150,6 +3216,7 @@ class TestQueryset(unittest.TestCase):
|
||||
ot = Test.objects.item_frequencies("val", map_reduce=False)
|
||||
assert ot == {0: 1}
|
||||
|
||||
@requires_mongodb_lt_42
|
||||
def test_item_frequencies_with_False_values(self):
|
||||
class Test(Document):
|
||||
val = BooleanField()
|
||||
@ -3164,6 +3231,7 @@ class TestQueryset(unittest.TestCase):
|
||||
ot = Test.objects.item_frequencies("val", map_reduce=False)
|
||||
assert ot == {False: 1}
|
||||
|
||||
@requires_mongodb_lt_42
|
||||
def test_item_frequencies_normalize(self):
|
||||
class Test(Document):
|
||||
val = IntField()
|
||||
@ -3550,7 +3618,8 @@ class TestQueryset(unittest.TestCase):
|
||||
Book.objects.create(title="The Stories", authors=[mark_twain, john_tolkien])
|
||||
|
||||
authors = Book.objects.distinct("authors")
|
||||
assert authors == [mark_twain, john_tolkien]
|
||||
authors_names = {author.name for author in authors}
|
||||
assert authors_names == {mark_twain.name, john_tolkien.name}
|
||||
|
||||
def test_distinct_ListField_EmbeddedDocumentField_EmbeddedDocumentField(self):
|
||||
class Continent(EmbeddedDocument):
|
||||
@ -3587,7 +3656,8 @@ class TestQueryset(unittest.TestCase):
|
||||
assert country_list == [scotland, tibet]
|
||||
|
||||
continent_list = Book.objects.distinct("authors.country.continent")
|
||||
assert continent_list == [europe, asia]
|
||||
continent_list_names = {c.continent_name for c in continent_list}
|
||||
assert continent_list_names == {europe.continent_name, asia.continent_name}
|
||||
|
||||
def test_distinct_ListField_ReferenceField(self):
|
||||
class Bar(Document):
|
||||
@ -5656,6 +5726,31 @@ class TestQueryset(unittest.TestCase):
|
||||
qs = self.Person.objects().timeout(False)
|
||||
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__":
|
||||
unittest.main()
|
||||
|
@ -248,6 +248,34 @@ class TestQuerysetAggregate(MongoDBTestCase):
|
||||
|
||||
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__":
|
||||
unittest.main()
|
||||
|
@ -1,7 +1,7 @@
|
||||
import unittest
|
||||
|
||||
from bson.son import SON
|
||||
import pytest
|
||||
from bson.son import SON
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine.queryset import Q, transform
|
||||
|
@ -2,8 +2,8 @@ import datetime
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from bson import ObjectId
|
||||
import pytest
|
||||
from bson import ObjectId
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine.errors import InvalidQueryError
|
||||
|
@ -1,11 +1,11 @@
|
||||
import datetime
|
||||
import unittest
|
||||
|
||||
from bson.tz_util import utc
|
||||
import pymongo
|
||||
import pytest
|
||||
from bson.tz_util import utc
|
||||
from pymongo import MongoClient, ReadPreference
|
||||
from pymongo.errors import InvalidName, OperationFailure
|
||||
import pytest
|
||||
|
||||
import mongoengine.connection
|
||||
from mongoengine import (
|
||||
@ -17,8 +17,8 @@ from mongoengine import (
|
||||
register_connection,
|
||||
)
|
||||
from mongoengine.connection import (
|
||||
ConnectionFailure,
|
||||
DEFAULT_DATABASE_NAME,
|
||||
ConnectionFailure,
|
||||
disconnect,
|
||||
get_connection,
|
||||
get_db,
|
||||
|
@ -3,15 +3,9 @@ import unittest
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
try:
|
||||
import mongomock
|
||||
|
||||
|
@ -3,7 +3,11 @@ import unittest
|
||||
import pytest
|
||||
|
||||
from mongoengine import Document
|
||||
from mongoengine.base.datastructures import BaseDict, BaseList, StrictDict
|
||||
from mongoengine.base.datastructures import (
|
||||
BaseDict,
|
||||
BaseList,
|
||||
StrictDict,
|
||||
)
|
||||
|
||||
|
||||
class DocumentStub:
|
||||
|
@ -5,7 +5,6 @@ from pymongo import MongoClient, ReadPreference
|
||||
import mongoengine
|
||||
from mongoengine.connection import ConnectionFailure
|
||||
|
||||
|
||||
CONN_CLASS = MongoClient
|
||||
READ_PREF = ReadPreference.SECONDARY
|
||||
|
||||
|
@ -249,7 +249,7 @@ class TestSignal(unittest.TestCase):
|
||||
assert self.pre_signals == post_signals
|
||||
|
||||
def test_model_signals(self):
|
||||
""" Model saves should throw some signals. """
|
||||
"""Model saves should throw some signals."""
|
||||
|
||||
def create_author():
|
||||
self.Author(name="Bill Shakespeare")
|
||||
@ -340,7 +340,7 @@ class TestSignal(unittest.TestCase):
|
||||
]
|
||||
|
||||
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():
|
||||
a = self.Author(name="Bill Shakespeare")
|
||||
@ -385,7 +385,7 @@ class TestSignal(unittest.TestCase):
|
||||
]
|
||||
|
||||
def test_queryset_delete_signals(self):
|
||||
""" Queryset delete should throw some signals. """
|
||||
"""Queryset delete should throw some signals."""
|
||||
|
||||
self.Another(name="Bill Shakespeare").save()
|
||||
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):
|
||||
""" 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)
|
||||
# post save must received the created flag, even if there's already
|
||||
# an object id present
|
||||
|
@ -1,3 +1,4 @@
|
||||
import operator
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
@ -6,7 +7,6 @@ from mongoengine import connect
|
||||
from mongoengine.connection import disconnect_all, get_db
|
||||
from mongoengine.mongodb_support import get_mongodb_version
|
||||
|
||||
|
||||
MONGO_TEST_DB = "mongoenginetest" # standard name for the test database
|
||||
|
||||
|
||||
@ -33,6 +33,14 @@ def get_as_pymongo(doc):
|
||||
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):
|
||||
"""Return a MongoDB version requirement decorator.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user