Compare commits

...

50 Commits

Author SHA1 Message Date
Bastien Gérard
86ad8d119d Merge pull request #2487 from bagerard/prepare_0_23_0
update changelog for 0.23.0 release
2021-03-04 23:03:39 +01:00
Bastien Gerard
34d273015c update changelog for 0.23.0 release 2021-03-04 22:46:29 +01:00
Bastien Gérard
7147043d63 Merge pull request #2483 from bagerard/mongodb44_ci
Add Mongodb 44 ci and fix incompat
2021-03-04 22:42:13 +01:00
Bastien Gerard
b9b536133d fix test incompat for 4.2 2021-03-04 00:10:08 +01:00
Bastien Gerard
f244207168 fix mongo download link for 4.2 4.4 explicitly 2021-03-03 12:26:48 +01:00
Bastien Gerard
0620ac5641 Fix mongo download link as convention changed in official repo with > 4.0 2021-03-03 11:33:36 +01:00
Bastien Gerard
3b9a167022 Add Mongo 4.4 to ci 2021-03-03 10:57:18 +01:00
Bastien Gerard
b479bb7c6b Fix tests for supporting Mongo4.4 for some reason results comes sorted differently in map reduce when no sort is specified 2021-03-03 10:52:46 +01:00
Bastien Gerard
8ef771912d fixing incompatibility with mongoengine aggregation to support mongo 4.4 2021-03-03 10:51:03 +01:00
Bastien Gérard
2d1c9afbb7 Merge pull request #2477 from lukasloetkolben/feature_allow_disk_use
Feature: "allow_disk_use"
2021-02-27 23:05:50 +01:00
Bastien Gerard
9ff5d8426c restrict test on allowDiskUse to mongoDB >= 4.4 in CI + ran black 2021-02-27 21:30:07 +01:00
lukasloetkolben
467e9c3ddf typo wether --> whether 2021-02-26 18:12:58 +01:00
lukasloetkolben
0d5e028c55 - tests for feature allow_disk_use 2021-02-26 18:11:32 +01:00
Bastien Gérard
5858ea1bf0 Merge pull request #2478 from bagerard/add_geonear_aggregation_test
Add $geonear aggregation test
2021-02-24 11:55:46 +01:00
Bastien Gerard
1f220b4eaf update changelog and fix minor typo in docstring 2021-02-23 21:51:41 +01:00
Bastien Gerard
97c99ca40d Add test case for #2473 2021-02-23 21:49:45 +01:00
lukasloetkolben
80a3b1c88c - feature: allow_disk_use 2021-02-22 22:06:10 +01:00
lukasloetkolben
68447af127 - feature: allow_disk_use 2021-02-22 21:42:02 +01:00
Bastien Gérard
d033e3b133 Merge pull request #2475 from bagerard/fix_imprecise_doc_collection_name
Fix for imprecise doc on collection name (CamelCase -> snake_case)
2021-02-22 19:52:28 +01:00
Bastien Gérard
4428842e77 Merge pull request #2476 from bagerard/fix_typo
fix_minor_typo
2021-02-22 19:52:08 +01:00
Bastien Gérard
f38cc6edd3 Merge pull request #2472 from vainu-jussi/master
Check that manually setting Sequence Field in DynamicDocument doesn't increment the counter
2021-02-21 22:39:56 +01:00
Bastien Gerard
aeb4f8f4da fix_minor_typo 2021-02-21 22:04:33 +01:00
Bastien Gerard
1b7c2085c9 Fix for imprecise doc on collection name (CamelCase -> snake_case) 2021-02-21 21:17:49 +01:00
Jussi Kauppila
48b979599f Check that manually setting Sequence Field in DynamicDocument doesn't increment the counter
Fixes #2471
2021-02-16 12:23:29 +02:00
Bastien Gérard
af3d3b7ee6 Merge pull request #2463 from PeterNerlich/patch-2
Fix inconsistency with example
2021-01-12 23:10:53 +01:00
Bastien Gérard
56fe126f3a Merge pull request #2464 from bagerard/fix_coveralls_3_0_0
Fix coveralls in CI
2021-01-12 23:10:17 +01:00
Bastien Gerard
04905d4b37 Add env var to please python-coveralls 3.0.0 2021-01-12 23:07:11 +01:00
Peter Nerlich
460df112f4 Fix inconsistency with example
connection example shows a password of 12345, but text spoke of qwerty
2021-01-10 23:43:46 +01:00
Bastien Gérard
772096ec55 Merge pull request #2454 from bagerard/improve_test_field_coverage
improvements of test coverage in fields
2020-12-24 00:22:39 +01:00
Bastien Gerard
98d64f41c6 improvements of test coverage in fields 2020-12-18 00:05:40 +01:00
Bastien Gérard
9a3bca8ab6 Merge pull request #2453 from bagerard/prepare_0_22_1
Update changelog for 0.22.1
2020-12-16 23:07:18 +01:00
Bastien Gerard
5781753cc8 Update changelog for 0.22.1 2020-12-16 23:03:39 +01:00
Bastien Gérard
fd3699a519 Merge pull request #2452 from bagerard/github_actions_improvements
improve github actions
2020-12-16 22:51:36 +01:00
Bastien Gerard
4f6a24411d remove IRC notif from CI as it doesn't seem to be still valuable these days 2020-12-16 22:47:43 +01:00
Bastien Gerard
de3888a48b improve github actions 2020-12-16 22:45:45 +01:00
Bastien Gérard
700fe80a00 Merge pull request #2421 from bagerard/test_github_actions2
Migrate to GitHub actions
2020-12-16 22:36:19 +01:00
Bastien Gerard
49e33b978d move condition of build-n-publish to job level + disable test-pypi
releasing as there is some limitation on the size
2020-12-16 22:22:03 +01:00
Bastien Gerard
81197d6061 comment out travis file, until next release is fully operational from github actions 2020-12-16 22:07:34 +01:00
Bastien Gerard
aa368be4d3 minor comment improvement 2020-12-16 21:54:55 +01:00
Bastien Gerard
0f1fce4a7b Add separate jobs for test-pypi and pypi in github actions 2020-12-16 21:52:49 +01:00
Bastien Gérard
cc591a634a add coveralls to actions 2020-12-16 21:36:43 +01:00
Bastien Gérard
6e332e782b minor improvement to github actions 2020-12-16 21:21:22 +01:00
Bastien Gérard
0e9920b190 Build sphinx doc as part of ci 2020-12-16 21:21:22 +01:00
Bastien Gérard
fd35df07c4 add regular pypi deploy to github actions 2020-12-16 21:21:22 +01:00
Bastien Gérard
8f3d21c312 improve tag format match 2020-12-16 21:21:22 +01:00
Bastien Gérard
7b772e3a4a publish to test-pypi 2020-12-16 21:21:22 +01:00
Bastien Gérard
59438a4768 add IRC notif to github actions 2020-12-16 21:21:22 +01:00
Bastien Gérard
fe9f7f1f80 test github actions 2020-12-16 21:21:21 +01:00
Bastien Gérard
6b5231265c Merge pull request #2449 from Kyle-Verhoog/drop-35
Drop Python 3.5 support
2020-12-15 09:18:49 +01:00
kyle-verhoog
0014346de1 Drop Python 3.5 support 2020-12-15 02:31:23 -05:00
32 changed files with 625 additions and 257 deletions

143
.github/workflows/github-actions.yml vendored Normal file
View 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 }}

View 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
View 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
View 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

View File

@@ -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
View 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

View File

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

View File

@@ -1,4 +1,5 @@
========= =========
Changelog Changelog
========= =========
@@ -7,6 +8,17 @@ Development
=========== ===========
- (Fill this out as you fix issues and develop your features). - (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 Changes in 0.22.0
================= =================
- Fix LazyReferenceField dereferencing in embedded documents #2426 - Fix LazyReferenceField dereferencing in embedded documents #2426

View File

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

View File

@@ -44,7 +44,7 @@ the :attr:`host` to
) )
will establish connection to ``production`` database using will establish connection to ``production`` database using
``admin`` username and ``qwerty`` password. ``admin`` username and ``12345`` password.
.. note:: Calling :func:`~mongoengine.connect` without argument will establish .. note:: Calling :func:`~mongoengine.connect` without argument will establish
a connection to the "test" database by default a connection to the "test" database by default

View File

@@ -432,10 +432,10 @@ Document collections
==================== ====================
Document classes that inherit **directly** from :class:`~mongoengine.Document` Document classes that inherit **directly** from :class:`~mongoengine.Document`
will have their own **collection** in the database. The name of the collection will have their own **collection** in the database. The name of the collection
is by default the name of the class, converted to lowercase (so in the example is by default the name of the class converted to snake_case (e.g if your Document class
above, the collection would be called `page`). If you need to change the name is named `CompanyUser`, the corresponding collection would be `company_user`). If you need
of the collection (e.g. to use MongoEngine with an existing database), then to change the name of the collection (e.g. to use MongoEngine with an existing database),
create a class dictionary attribute called :attr:`meta` on your document, and 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 set :attr:`collection` to the name of the collection that you want your
document class to use:: document class to use::

View File

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

View File

@@ -28,7 +28,7 @@ __all__ = (
) )
VERSION = (0, 22, 0) VERSION = (0, 23, 0)
def get_version(): def get_version():

View File

@@ -156,7 +156,7 @@ class BaseDocument:
# Handle dynamic data only if an initialised dynamic document # Handle dynamic data only if an initialised dynamic document
if self._dynamic and not self._dynamic_lock: if self._dynamic and not self._dynamic_lock:
if not hasattr(self, name) and not name.startswith("_"): if name not in self._fields_ordered and not name.startswith("_"):
DynamicField = _import_class("DynamicField") DynamicField = _import_class("DynamicField")
field = DynamicField(db_field=name, null=True) field = DynamicField(db_field=name, null=True)
field.name = name field.name = name

View File

@@ -109,7 +109,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
By default, the MongoDB collection used to store documents created using a By default, the MongoDB collection used to store documents created using a
:class:`~mongoengine.Document` subclass will be the name of the subclass :class:`~mongoengine.Document` subclass will be the name of the subclass
converted to 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 providing :attr:`collection` to the :attr:`meta` dictionary in the class
definition. definition.

View File

@@ -1,5 +1,6 @@
import datetime import datetime
import decimal import decimal
import inspect
import itertools import itertools
import re import re
import socket import socket
@@ -514,7 +515,7 @@ class BooleanField(BaseField):
def to_python(self, value): def to_python(self, value):
try: try:
value = bool(value) value = bool(value)
except ValueError: except (ValueError, TypeError):
pass pass
return value return value
@@ -1028,17 +1029,6 @@ def key_not_string(d):
return True return True
def key_has_dot_or_dollar(d):
"""Helper function to recursively determine if any key in a
dictionary contains a dot or a dollar sign.
"""
for k, v in d.items():
if ("." in k or k.startswith("$")) or (
isinstance(v, dict) and key_has_dot_or_dollar(v)
):
return True
def key_starts_with_dollar(d): def key_starts_with_dollar(d):
"""Helper function to recursively determine if any key in a """Helper function to recursively determine if any key in a
dictionary starts with a dollar dictionary starts with a dollar
@@ -1172,7 +1162,7 @@ class ReferenceField(BaseField):
:param document_type: The type of Document that will be referenced :param document_type: The type of Document that will be referenced
:param dbref: Store the reference as :class:`~pymongo.dbref.DBRef` :param dbref: Store the reference as :class:`~pymongo.dbref.DBRef`
or as the :class:`~pymongo.objectid.ObjectId`.id . or as the :class:`~pymongo.objectid.ObjectId`.
:param reverse_delete_rule: Determines what to do when the referring :param reverse_delete_rule: Determines what to do when the referring
object is deleted object is deleted
:param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.BaseField` :param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.BaseField`
@@ -1311,8 +1301,8 @@ class CachedReferenceField(BaseField):
fields = [] fields = []
# XXX ValidationError raised outside of the "validate" method. # XXX ValidationError raised outside of the "validate" method.
if not isinstance(document_type, str) and not issubclass( if not isinstance(document_type, str) and not (
document_type, Document inspect.isclass(document_type) and issubclass(document_type, Document)
): ):
self.error( self.error(
"Argument to CachedReferenceField constructor must be a" "Argument to CachedReferenceField constructor must be a"
@@ -1642,7 +1632,7 @@ class EnumField(BaseField):
"'choices' can't be set on EnumField, " "'choices' can't be set on EnumField, "
"it is implicitly set as the enum class" "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) super().__init__(**kwargs)
def __set__(self, instance, value): def __set__(self, instance, value):
@@ -1659,13 +1649,6 @@ class EnumField(BaseField):
return value.value return value.value
return value return value
def validate(self, value):
if value and not isinstance(value, self._enum_cls):
try:
self._enum_cls(value)
except Exception as e:
self.error(str(e))
def prepare_query_value(self, op, value): def prepare_query_value(self, op, value):
if value is None: if value is None:
return value return value
@@ -2431,7 +2414,7 @@ class LazyReferenceField(BaseField):
object is deleted object is deleted
:param passthrough: When trying to access unknown fields, the :param passthrough: When trying to access unknown fields, the
:class:`~mongoengine.base.datastructure.LazyReference` instance will :class:`~mongoengine.base.datastructure.LazyReference` instance will
automatically call `fetch()` and try to 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). document. Note this only work getting field (not setting or deleting).
""" """
# XXX ValidationError raised outside of the "validate" method. # XXX ValidationError raised outside of the "validate" method.

View File

@@ -8,6 +8,8 @@ from mongoengine.connection import get_connection
# get_mongodb_version() # get_mongodb_version()
MONGODB_34 = (3, 4) MONGODB_34 = (3, 4)
MONGODB_36 = (3, 6) MONGODB_36 = (3, 6)
MONGODB_42 = (4, 2)
MONGODB_44 = (4, 4)
def get_mongodb_version(): def get_mongodb_version():

View File

@@ -64,6 +64,7 @@ class BaseQuerySet:
self._ordering = None self._ordering = None
self._snapshot = False self._snapshot = False
self._timeout = True self._timeout = True
self._allow_disk_use = False
self._read_preference = None self._read_preference = None
self._read_concern = None self._read_concern = None
self._iter = False self._iter = False
@@ -799,6 +800,7 @@ class BaseQuerySet:
"_ordering", "_ordering",
"_snapshot", "_snapshot",
"_timeout", "_timeout",
"_allow_disk_use",
"_read_preference", "_read_preference",
"_read_concern", "_read_concern",
"_iter", "_iter",
@@ -1165,6 +1167,16 @@ class BaseQuerySet:
queryset._snapshot = enabled queryset._snapshot = enabled
return queryset return queryset
def allow_disk_use(self, enabled):
"""Enable or disable the use of temporary files on disk while processing a blocking sort operation.
(To store data exceeding the 100 megabyte system memory limit)
:param enabled: whether or not temporary files on disk are used
"""
queryset = self.clone()
queryset._allow_disk_use = enabled
return queryset
def timeout(self, enabled): def timeout(self, enabled):
"""Enable or disable the default mongod timeout when querying. (no_cursor_timeout option) """Enable or disable the default mongod timeout when querying. (no_cursor_timeout option)
@@ -1343,21 +1355,18 @@ class BaseQuerySet:
MapReduceDocument = _import_class("MapReduceDocument") MapReduceDocument = _import_class("MapReduceDocument")
if not hasattr(self._collection, "map_reduce"):
raise NotImplementedError("Requires MongoDB >= 1.7.1")
map_f_scope = {} map_f_scope = {}
if isinstance(map_f, Code): if isinstance(map_f, Code):
map_f_scope = map_f.scope map_f_scope = map_f.scope
map_f = str(map_f) map_f = str(map_f)
map_f = Code(queryset._sub_js_fields(map_f), map_f_scope) map_f = Code(queryset._sub_js_fields(map_f), map_f_scope or None)
reduce_f_scope = {} reduce_f_scope = {}
if isinstance(reduce_f, Code): if isinstance(reduce_f, Code):
reduce_f_scope = reduce_f.scope reduce_f_scope = reduce_f.scope
reduce_f = str(reduce_f) reduce_f = str(reduce_f)
reduce_f_code = queryset._sub_js_fields(reduce_f) reduce_f_code = queryset._sub_js_fields(reduce_f)
reduce_f = Code(reduce_f_code, reduce_f_scope) reduce_f = Code(reduce_f_code, reduce_f_scope or None)
mr_args = {"query": queryset._query} mr_args = {"query": queryset._query}
@@ -1367,7 +1376,7 @@ class BaseQuerySet:
finalize_f_scope = finalize_f.scope finalize_f_scope = finalize_f.scope
finalize_f = str(finalize_f) finalize_f = str(finalize_f)
finalize_f_code = queryset._sub_js_fields(finalize_f) finalize_f_code = queryset._sub_js_fields(finalize_f)
finalize_f = Code(finalize_f_code, finalize_f_scope) finalize_f = Code(finalize_f_code, finalize_f_scope or None)
mr_args["finalize"] = finalize_f mr_args["finalize"] = finalize_f
if scope: if scope:
@@ -1604,6 +1613,9 @@ class BaseQuerySet:
if not self._timeout: if not self._timeout:
cursor_args["no_cursor_timeout"] = True cursor_args["no_cursor_timeout"] = True
if self._allow_disk_use:
cursor_args["allow_disk_use"] = True
if self._loaded_fields: if self._loaded_fields:
cursor_args[fields_name] = self._loaded_fields.as_dict() cursor_args[fields_name] = self._loaded_fields.as_dict()

View File

@@ -98,7 +98,6 @@ CLASSIFIERS = [
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
@@ -113,7 +112,7 @@ extra_opts = {
"tests_require": [ "tests_require": [
"pytest<5.0", "pytest<5.0",
"pytest-cov", "pytest-cov",
"coverage<5.0", # recent coverage switched to sqlite format for the .coverage file which isn't handled properly by coveralls "coverage",
"blinker", "blinker",
"Pillow>=7.0.0", "Pillow>=7.0.0",
], ],
@@ -140,7 +139,7 @@ setup(
long_description=LONG_DESCRIPTION, long_description=LONG_DESCRIPTION,
platforms=["any"], platforms=["any"],
classifiers=CLASSIFIERS, classifiers=CLASSIFIERS,
python_requires=">=3.5", python_requires=">=3.6",
install_requires=["pymongo>=3.4, <4.0"], install_requires=["pymongo>=3.4, <4.0"],
cmdclass={"test": PyTest}, cmdclass={"test": PyTest},
**extra_opts **extra_opts

View File

@@ -7,6 +7,7 @@ import pytest
from mongoengine import * from mongoengine import *
from mongoengine.connection import get_db from mongoengine.connection import get_db
from mongoengine.mongodb_support import MONGODB_42, get_mongodb_version
class TestIndexes(unittest.TestCase): class TestIndexes(unittest.TestCase):
@@ -452,9 +453,11 @@ class TestIndexes(unittest.TestCase):
.get("stage") .get("stage")
== "IXSCAN" == "IXSCAN"
) )
mongo_db = get_mongodb_version()
PROJECTION_STR = "PROJECTION" if mongo_db < MONGODB_42 else "PROJECTION_COVERED"
assert ( assert (
query_plan.get("queryPlanner").get("winningPlan").get("stage") query_plan.get("queryPlanner").get("winningPlan").get("stage")
== "PROJECTION" == PROJECTION_STR
) )
query_plan = Test.objects(a=1).explain() query_plan = Test.objects(a=1).explain()

View File

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

View File

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

View File

@@ -2,59 +2,11 @@ from decimal import Decimal
import pytest import pytest
from mongoengine import * from mongoengine import DecimalField, Document, ValidationError
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
class TestDecimalField(MongoDBTestCase): class TestDecimalField(MongoDBTestCase):
def test_validation(self):
"""Ensure that invalid values cannot be assigned to decimal fields."""
class Person(Document):
height = DecimalField(min_value=Decimal("0.1"), max_value=Decimal("3.5"))
Person.drop_collection()
Person(height=Decimal("1.89")).save()
person = Person.objects.first()
assert person.height == Decimal("1.89")
person.height = "2.0"
person.save()
person.height = 0.01
with pytest.raises(ValidationError):
person.validate()
person.height = Decimal("0.01")
with pytest.raises(ValidationError):
person.validate()
person.height = Decimal("4.0")
with pytest.raises(ValidationError):
person.validate()
person.height = "something invalid"
with pytest.raises(ValidationError):
person.validate()
person_2 = Person(height="something invalid")
with pytest.raises(ValidationError):
person_2.validate()
def test_comparison(self):
class Person(Document):
money = DecimalField()
Person.drop_collection()
Person(money=6).save()
Person(money=7).save()
Person(money=8).save()
Person(money=10).save()
assert 2 == Person.objects(money__gt=Decimal("7")).count()
assert 2 == Person.objects(money__gt=7).count()
assert 2 == Person.objects(money__gt="7").count()
assert 3 == Person.objects(money__gte="7").count()
def test_storage(self): def test_storage(self):
class Person(Document): class Person(Document):
float_value = DecimalField(precision=4) float_value = DecimalField(precision=4)
@@ -106,3 +58,63 @@ class TestDecimalField(MongoDBTestCase):
for field_name in ["float_value", "string_value"]: for field_name in ["float_value", "string_value"]:
actual = list(Person.objects().scalar(field_name)) actual = list(Person.objects().scalar(field_name))
assert expected == actual assert expected == actual
def test_save_none(self):
class Person(Document):
value = DecimalField()
Person.drop_collection()
person = Person(value=None)
assert person.value is None
person.save()
fetched_person = Person.objects.first()
fetched_person.value is None
def test_validation(self):
"""Ensure that invalid values cannot be assigned to decimal fields."""
class Person(Document):
height = DecimalField(min_value=Decimal("0.1"), max_value=Decimal("3.5"))
Person.drop_collection()
Person(height=Decimal("1.89")).save()
person = Person.objects.first()
assert person.height == Decimal("1.89")
person.height = "2.0"
person.save()
person.height = 0.01
with pytest.raises(ValidationError):
person.validate()
person.height = Decimal("0.01")
with pytest.raises(ValidationError):
person.validate()
person.height = Decimal("4.0")
with pytest.raises(ValidationError):
person.validate()
person.height = "something invalid"
with pytest.raises(ValidationError):
person.validate()
person_2 = Person(height="something invalid")
with pytest.raises(ValidationError):
person_2.validate()
def test_comparison(self):
class Person(Document):
money = DecimalField()
Person.drop_collection()
Person(money=6).save()
Person(money=7).save()
Person(money=8).save()
Person(money=10).save()
assert 2 == Person.objects(money__gt=Decimal("7")).count()
assert 2 == Person.objects(money__gt=7).count()
assert 2 == Person.objects(money__gt="7").count()
assert 3 == Person.objects(money__gte="7").count()

View File

@@ -3,7 +3,7 @@ from enum import Enum
from bson import InvalidDocument from bson import InvalidDocument
import pytest import pytest
from mongoengine import * from mongoengine import Document, EnumField, ValidationError
from tests.utils import MongoDBTestCase, get_as_pymongo from tests.utils import MongoDBTestCase, get_as_pymongo
@@ -45,6 +45,11 @@ class TestStringEnumField(MongoDBTestCase):
m.save() m.save()
assert m.status == Status.DONE assert m.status == Status.DONE
m.status = "wrong"
assert m.status == "wrong"
with pytest.raises(ValidationError):
m.validate()
def test_set_default(self): def test_set_default(self):
class ModelWithDefault(Document): class ModelWithDefault(Document):
status = EnumField(Status, default=Status.DONE) status = EnumField(Status, default=Status.DONE)

View File

@@ -306,7 +306,7 @@ class TestField(MongoDBTestCase):
) )
assert res == 1 assert res == 1
# Retrive data from db and verify it. # Retrieve data from db and verify it.
ret = HandleNoneFields.objects.all()[0] ret = HandleNoneFields.objects.all()[0]
assert ret.str_fld is None assert ret.str_fld is None
assert ret.int_fld is None assert ret.int_fld is None
@@ -340,7 +340,7 @@ class TestField(MongoDBTestCase):
{"$unset": {"str_fld": 1, "int_fld": 1, "flt_fld": 1, "comp_dt_fld": 1}}, {"$unset": {"str_fld": 1, "int_fld": 1, "flt_fld": 1, "comp_dt_fld": 1}},
) )
# Retrive data from db and verify it. # Retrieve data from db and verify it.
ret = HandleNoneFields.objects.first() ret = HandleNoneFields.objects.first()
assert ret.str_fld is None assert ret.str_fld is None
assert ret.int_fld is None assert ret.int_fld is None
@@ -374,34 +374,6 @@ class TestField(MongoDBTestCase):
person.id = str(ObjectId()) person.id = str(ObjectId())
person.validate() person.validate()
def test_string_validation(self):
"""Ensure that invalid values cannot be assigned to string fields."""
class Person(Document):
name = StringField(max_length=20)
userid = StringField(r"[0-9a-z_]+$")
person = Person(name=34)
with pytest.raises(ValidationError):
person.validate()
# Test regex validation on userid
person = Person(userid="test.User")
with pytest.raises(ValidationError):
person.validate()
person.userid = "test_user"
assert person.userid == "test_user"
person.validate()
# Test max length validation on name
person = Person(name="Name that is more than twenty characters")
with pytest.raises(ValidationError):
person.validate()
person.name = "Shorter name"
person.validate()
def test_db_field_validation(self): def test_db_field_validation(self):
"""Ensure that db_field doesn't accept invalid values.""" """Ensure that db_field doesn't accept invalid values."""

View File

@@ -4,10 +4,26 @@ import pytest
from mongoengine import * from mongoengine import *
from mongoengine.connection import get_db from mongoengine.connection import get_db
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase, get_as_pymongo
class TestLongField(MongoDBTestCase): class TestLongField(MongoDBTestCase):
def test_storage(self):
class Person(Document):
value = LongField()
Person.drop_collection()
person = Person(value=5000)
person.save()
assert get_as_pymongo(person) == {"_id": person.id, "value": 5000}
def test_construction_does_not_fail_with_invalid_value(self):
class Person(Document):
value = LongField()
person = Person(value="not_an_int")
assert person.value == "not_an_int"
def test_long_field_is_considered_as_int64(self): def test_long_field_is_considered_as_int64(self):
""" """
Tests that long fields are stored as long in mongo, even if long Tests that long fields are stored as long in mongo, even if long
@@ -30,19 +46,16 @@ class TestLongField(MongoDBTestCase):
class TestDocument(Document): class TestDocument(Document):
value = LongField(min_value=0, max_value=110) value = LongField(min_value=0, max_value=110)
doc = TestDocument() TestDocument(value=50).validate()
doc.value = 50
doc.validate()
doc.value = -1
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
doc.validate() TestDocument(value=-1).validate()
doc.value = 120
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
doc.validate() TestDocument(value=120).validate()
doc.value = "ten"
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
doc.validate() TestDocument(value="ten").validate()
def test_long_ne_operator(self): def test_long_ne_operator(self):
class TestDocument(Document): class TestDocument(Document):
@@ -53,4 +66,5 @@ class TestLongField(MongoDBTestCase):
TestDocument(long_fld=None).save() TestDocument(long_fld=None).save()
TestDocument(long_fld=1).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

View File

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

View File

@@ -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()

View File

@@ -21,6 +21,10 @@ from mongoengine.queryset import (
QuerySetManager, QuerySetManager,
queryset_manager, queryset_manager,
) )
from tests.utils import (
requires_mongodb_gte_44,
requires_mongodb_lt_42,
)
class db_ops_tracker(query_counter): class db_ops_tracker(query_counter):
@@ -1489,6 +1493,7 @@ class TestQueryset(unittest.TestCase):
BlogPost.drop_collection() BlogPost.drop_collection()
@requires_mongodb_lt_42
def test_exec_js_query(self): def test_exec_js_query(self):
"""Ensure that queries are properly formed for use in exec_js.""" """Ensure that queries are properly formed for use in exec_js."""
@@ -1526,6 +1531,7 @@ class TestQueryset(unittest.TestCase):
BlogPost.drop_collection() BlogPost.drop_collection()
@requires_mongodb_lt_42
def test_exec_js_field_sub(self): def test_exec_js_field_sub(self):
"""Ensure that field substitutions occur properly in exec_js functions.""" """Ensure that field substitutions occur properly in exec_js functions."""
@@ -2659,6 +2665,8 @@ class TestQueryset(unittest.TestCase):
title = StringField(primary_key=True) title = StringField(primary_key=True)
tags = ListField(StringField()) tags = ListField(StringField())
BlogPost.drop_collection()
post1 = BlogPost(title="Post #1", tags=["mongodb", "mongoengine"]) post1 = BlogPost(title="Post #1", tags=["mongodb", "mongoengine"])
post2 = BlogPost(title="Post #2", tags=["django", "mongodb"]) post2 = BlogPost(title="Post #2", tags=["django", "mongodb"])
post3 = BlogPost(title="Post #3", tags=["hitchcock films"]) post3 = BlogPost(title="Post #3", tags=["hitchcock films"])
@@ -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) results = list(results)
assert results[0].object == post1 assert len(results) == 3
assert results[1].object == post2 assert results[0].object.id == post1.id
assert results[2].object == post3 assert results[1].object.id == post2.id
assert results[2].object.id == post3.id
BlogPost.drop_collection() BlogPost.drop_collection()
@@ -2700,7 +2711,6 @@ class TestQueryset(unittest.TestCase):
""" """
Test map/reduce custom output Test map/reduce custom output
""" """
register_connection("test2", "mongoenginetest2")
class Family(Document): class Family(Document):
id = IntField(primary_key=True) id = IntField(primary_key=True)
@@ -2773,6 +2783,7 @@ class TestQueryset(unittest.TestCase):
family.persons.push(person); family.persons.push(person);
family.totalAge += person.age; family.totalAge += person.age;
}); });
family.persons.sort((a, b) => (a.age > b.age))
} }
}); });
@@ -2801,10 +2812,10 @@ class TestQueryset(unittest.TestCase):
"_id": 1, "_id": 1,
"value": { "value": {
"persons": [ "persons": [
{"age": 21, "name": "Wilson Jr"},
{"age": 45, "name": "Wilson Father"},
{"age": 40, "name": "Eliana Costa"},
{"age": 17, "name": "Tayza Mariana"}, {"age": 17, "name": "Tayza Mariana"},
{"age": 21, "name": "Wilson Jr"},
{"age": 40, "name": "Eliana Costa"},
{"age": 45, "name": "Wilson Father"},
], ],
"totalAge": 123, "totalAge": 123,
}, },
@@ -2814,9 +2825,9 @@ class TestQueryset(unittest.TestCase):
"_id": 2, "_id": 2,
"value": { "value": {
"persons": [ "persons": [
{"age": 10, "name": "Igor Gabriel"},
{"age": 16, "name": "Isabella Luanna"}, {"age": 16, "name": "Isabella Luanna"},
{"age": 36, "name": "Sandra Mara"}, {"age": 36, "name": "Sandra Mara"},
{"age": 10, "name": "Igor Gabriel"},
], ],
"totalAge": 62, "totalAge": 62,
}, },
@@ -2826,8 +2837,8 @@ class TestQueryset(unittest.TestCase):
"_id": 3, "_id": 3,
"value": { "value": {
"persons": [ "persons": [
{"age": 30, "name": "Arthur WA"},
{"age": 25, "name": "Paula Leonel"}, {"age": 25, "name": "Paula Leonel"},
{"age": 30, "name": "Arthur WA"},
], ],
"totalAge": 55, "totalAge": 55,
}, },
@@ -3108,6 +3119,7 @@ class TestQueryset(unittest.TestCase):
freq = Person.objects.item_frequencies("city", normalize=True, map_reduce=True) freq = Person.objects.item_frequencies("city", normalize=True, map_reduce=True)
assert freq == {"CRB": 0.5, None: 0.5} assert freq == {"CRB": 0.5, None: 0.5}
@requires_mongodb_lt_42
def test_item_frequencies_with_null_embedded(self): def test_item_frequencies_with_null_embedded(self):
class Data(EmbeddedDocument): class Data(EmbeddedDocument):
name = StringField() name = StringField()
@@ -3136,6 +3148,7 @@ class TestQueryset(unittest.TestCase):
ot = Person.objects.item_frequencies("extra.tag", map_reduce=True) ot = Person.objects.item_frequencies("extra.tag", map_reduce=True)
assert ot == {None: 1.0, "friend": 1.0} assert ot == {None: 1.0, "friend": 1.0}
@requires_mongodb_lt_42
def test_item_frequencies_with_0_values(self): def test_item_frequencies_with_0_values(self):
class Test(Document): class Test(Document):
val = IntField() val = IntField()
@@ -3150,6 +3163,7 @@ class TestQueryset(unittest.TestCase):
ot = Test.objects.item_frequencies("val", map_reduce=False) ot = Test.objects.item_frequencies("val", map_reduce=False)
assert ot == {0: 1} assert ot == {0: 1}
@requires_mongodb_lt_42
def test_item_frequencies_with_False_values(self): def test_item_frequencies_with_False_values(self):
class Test(Document): class Test(Document):
val = BooleanField() val = BooleanField()
@@ -3164,6 +3178,7 @@ class TestQueryset(unittest.TestCase):
ot = Test.objects.item_frequencies("val", map_reduce=False) ot = Test.objects.item_frequencies("val", map_reduce=False)
assert ot == {False: 1} assert ot == {False: 1}
@requires_mongodb_lt_42
def test_item_frequencies_normalize(self): def test_item_frequencies_normalize(self):
class Test(Document): class Test(Document):
val = IntField() val = IntField()
@@ -3550,7 +3565,8 @@ class TestQueryset(unittest.TestCase):
Book.objects.create(title="The Stories", authors=[mark_twain, john_tolkien]) Book.objects.create(title="The Stories", authors=[mark_twain, john_tolkien])
authors = Book.objects.distinct("authors") authors = Book.objects.distinct("authors")
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): def test_distinct_ListField_EmbeddedDocumentField_EmbeddedDocumentField(self):
class Continent(EmbeddedDocument): class Continent(EmbeddedDocument):
@@ -3587,7 +3603,8 @@ class TestQueryset(unittest.TestCase):
assert country_list == [scotland, tibet] assert country_list == [scotland, tibet]
continent_list = Book.objects.distinct("authors.country.continent") continent_list = Book.objects.distinct("authors.country.continent")
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): def test_distinct_ListField_ReferenceField(self):
class Bar(Document): class Bar(Document):
@@ -5656,6 +5673,31 @@ class TestQueryset(unittest.TestCase):
qs = self.Person.objects().timeout(False) qs = self.Person.objects().timeout(False)
assert qs._cursor_args == {"no_cursor_timeout": True} assert qs._cursor_args == {"no_cursor_timeout": True}
@requires_mongodb_gte_44
def test_allow_disk_use(self):
qs = self.Person.objects()
assert qs._cursor_args == {}
qs = self.Person.objects().allow_disk_use(False)
assert qs._cursor_args == {}
qs = self.Person.objects().allow_disk_use(True)
assert qs._cursor_args == {"allow_disk_use": True}
# Test if allow_disk_use changes the results
self.Person.drop_collection()
self.Person.objects.create(name="Foo", age=12)
self.Person.objects.create(name="Baz", age=17)
self.Person.objects.create(name="Bar", age=13)
qs_disk = self.Person.objects().order_by("age").allow_disk_use(True)
qs = self.Person.objects().order_by("age")
assert qs_disk.count() == qs.count()
for index in range(qs_disk.count()):
assert qs_disk[index] == qs[index]
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

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

View File

@@ -1,3 +1,4 @@
import operator
import unittest import unittest
import pytest import pytest
@@ -33,6 +34,14 @@ def get_as_pymongo(doc):
return doc.__class__.objects.as_pymongo().get(id=doc.id) return doc.__class__.objects.as_pymongo().get(id=doc.id)
def requires_mongodb_lt_42(func):
return _decorated_with_ver_requirement(func, (4, 2), oper=operator.lt)
def requires_mongodb_gte_44(func):
return _decorated_with_ver_requirement(func, (4, 4), oper=operator.ge)
def _decorated_with_ver_requirement(func, mongo_version_req, oper): def _decorated_with_ver_requirement(func, mongo_version_req, oper):
"""Return a MongoDB version requirement decorator. """Return a MongoDB version requirement decorator.

View File

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