Compare commits
50 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
86ad8d119d | ||
|
34d273015c | ||
|
7147043d63 | ||
|
b9b536133d | ||
|
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, 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
|
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
|
108
.travis_.yml
Normal file
108
.travis_.yml
Normal file
@@ -0,0 +1,108 @@
|
||||
## 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 run builds on the master branch and GitHub releases (tagged as vX.Y.Z)
|
||||
# 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
|
@@ -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
|
||||
|
@@ -1,4 +1,5 @@
|
||||
|
||||
|
||||
=========
|
||||
Changelog
|
||||
=========
|
||||
@@ -7,6 +8,17 @@ Development
|
||||
===========
|
||||
- (Fill this out as you fix issues and develop your features).
|
||||
|
||||
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
|
||||
=================
|
||||
- Fix LazyReferenceField dereferencing in embedded documents #2426
|
||||
|
@@ -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"]
|
||||
|
@@ -44,7 +44,7 @@ the :attr:`host` to
|
||||
)
|
||||
|
||||
will establish connection to ``production`` database using
|
||||
``admin`` username and ``qwerty`` password.
|
||||
``admin`` username and ``12345`` password.
|
||||
|
||||
.. note:: Calling :func:`~mongoengine.connect` without argument will establish
|
||||
a connection to the "test" database by default
|
||||
|
@@ -432,10 +432,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::
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -28,7 +28,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
VERSION = (0, 22, 0)
|
||||
VERSION = (0, 23, 0)
|
||||
|
||||
|
||||
def get_version():
|
||||
|
@@ -156,7 +156,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
|
||||
|
@@ -109,7 +109,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.
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import datetime
|
||||
import decimal
|
||||
import inspect
|
||||
import itertools
|
||||
import re
|
||||
import socket
|
||||
@@ -514,7 +515,7 @@ class BooleanField(BaseField):
|
||||
def to_python(self, value):
|
||||
try:
|
||||
value = bool(value)
|
||||
except ValueError:
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
return value
|
||||
|
||||
@@ -1028,17 +1029,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
|
||||
@@ -1172,7 +1162,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`
|
||||
@@ -1311,8 +1301,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"
|
||||
@@ -1642,7 +1632,7 @@ class EnumField(BaseField):
|
||||
"'choices' can't be set on EnumField, "
|
||||
"it is implicitly set as the enum class"
|
||||
)
|
||||
kwargs["choices"] = list(self._enum_cls)
|
||||
kwargs["choices"] = list(self._enum_cls) # Implicit validator
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
@@ -1659,13 +1649,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 +2414,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.
|
||||
|
@@ -8,6 +8,8 @@ from mongoengine.connection import get_connection
|
||||
# get_mongodb_version()
|
||||
MONGODB_34 = (3, 4)
|
||||
MONGODB_36 = (3, 6)
|
||||
MONGODB_42 = (4, 2)
|
||||
MONGODB_44 = (4, 4)
|
||||
|
||||
|
||||
def get_mongodb_version():
|
||||
|
@@ -64,6 +64,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
|
||||
@@ -799,6 +800,7 @@ class BaseQuerySet:
|
||||
"_ordering",
|
||||
"_snapshot",
|
||||
"_timeout",
|
||||
"_allow_disk_use",
|
||||
"_read_preference",
|
||||
"_read_concern",
|
||||
"_iter",
|
||||
@@ -1165,6 +1167,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 +1355,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 +1376,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 +1613,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()
|
||||
|
||||
|
5
setup.py
5
setup.py
@@ -98,7 +98,6 @@ 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",
|
||||
@@ -113,7 +112,7 @@ extra_opts = {
|
||||
"tests_require": [
|
||||
"pytest<5.0",
|
||||
"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 +139,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
|
||||
|
@@ -7,6 +7,7 @@ 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 +453,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()
|
||||
|
@@ -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,
|
||||
|
@@ -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,63 @@ 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()
|
||||
|
@@ -3,7 +3,7 @@ from enum import Enum
|
||||
from bson import InvalidDocument
|
||||
import pytest
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine import Document, EnumField, ValidationError
|
||||
from tests.utils import MongoDBTestCase, get_as_pymongo
|
||||
|
||||
|
||||
@@ -45,6 +45,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)
|
||||
|
@@ -306,7 +306,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 +340,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 +374,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."""
|
||||
|
||||
|
@@ -4,10 +4,26 @@ import pytest
|
||||
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 +46,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 +66,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
|
||||
|
@@ -274,3 +274,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()
|
@@ -21,6 +21,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):
|
||||
@@ -1489,6 +1493,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 +1531,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 +2665,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 +2695,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 +2711,6 @@ class TestQueryset(unittest.TestCase):
|
||||
"""
|
||||
Test map/reduce custom output
|
||||
"""
|
||||
register_connection("test2", "mongoenginetest2")
|
||||
|
||||
class Family(Document):
|
||||
id = IntField(primary_key=True)
|
||||
@@ -2773,6 +2783,7 @@ class TestQueryset(unittest.TestCase):
|
||||
family.persons.push(person);
|
||||
family.totalAge += person.age;
|
||||
});
|
||||
family.persons.sort((a, b) => (a.age > b.age))
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2801,10 +2812,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 +2825,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 +2837,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 +3119,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 +3148,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 +3163,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 +3178,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 +3565,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 +3603,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 +5673,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,3 +1,4 @@
|
||||
import operator
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
@@ -33,6 +34,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.
|
||||
|
||||
|
Reference in New Issue
Block a user