Compare commits

...

72 Commits

Author SHA1 Message Date
Bastien Gerard
2f4b2aadb4 fix yaml float - string conversion 2021-10-25 14:45:07 +02:00
Bastien Gerard
f4a87bff1d remove travis deprecated config 2021-10-25 10:23:33 +02:00
Bastien Gerard
3a32c195d5 remove useless quotes in yaml 2021-10-25 10:22:27 +02:00
Bastien Gérard
880a8efc63
Merge pull request #2580 from rooterkyberian/py310
test against python 3.10
2021-10-25 10:03:23 +02:00
Maciej Urbanski
9a414edb25 unfreeze pytest (pytest>=6.2.5 is required for py3.10) 2021-10-24 18:26:56 +02:00
Maciej Urbanski
4b643ad01f test against python 3.10 2021-10-24 18:11:29 +02:00
Bastien Gérard
811dc12db5
Merge pull request #2573 from bagerard/improve_compound_indexes
Improve compound indexes in rst
2021-10-13 09:28:15 +02:00
Bastien Gérard
dbd72282bc
Merge pull request #2574 from bagerard/update_changelog_recent_merge
update changelog with recent changes that were merged
2021-10-13 09:27:52 +02:00
Bastien Gerard
686b42c29b update changelog with recent changes that were merged 2021-10-12 21:06:11 +02:00
Bastien Gerard
4210ea06c3 Improve compound indexes in rst 2021-10-12 21:02:12 +02:00
Bastien Gérard
8c3e2b340b
Merge pull request #2568 from idoshr/regex_query
Regex and whole word text search query
2021-10-12 20:47:36 +02:00
Ido Shraga
7a0a58c163 black reformat 2021-10-01 07:48:01 +03:00
Bastien Gérard
0af1a1151f
Merge pull request #2571 from bagerard/idoshr-master-fixed
Idoshr master fixed
2021-09-30 20:49:33 +02:00
Bastien Gerard
28226f81a8 black it 2021-09-30 20:39:44 +02:00
Bastien Gerard
558e3299fe Merge branch 'master' of https://github.com/idoshr/mongoengine into idoshr-master 2021-09-30 20:38:32 +02:00
Bastien Gerard
cb0035f87a minor typo fix in docstring 2021-09-30 20:25:26 +02:00
idoshr
85b0c1c945
Update AUTHORS 2021-09-20 22:36:13 +03:00
Ido Shraga
3d6b650592 add doc
change has_word ==> wholeword
2021-09-12 18:21:01 +03:00
Ido Shraga
6bc1b83695 add option to filter by free regex
and option to search by whole word
2021-09-10 16:28:06 +03:00
Ido Shraga
7e10bb2894 rename tester 2021-09-01 10:24:26 +03:00
Ido Shraga
8428da368d add update operator to documentation 2021-08-31 16:49:33 +03:00
erdenezul
5fe9436ea7
Merge pull request #2561 from bagerard/improve_index_doc
improve index doc
2021-08-23 09:22:17 +02:00
Bastien Gerard
1b249e336e improve list_indexes, ensure_indexes docstring and add index migration example 2021-08-22 22:01:05 +02:00
Bastien Gérard
533700583d
Merge pull request #2557 from yugyesh/patch-1
Minor typo error
2021-08-16 08:00:04 +02:00
Yugesh kc
639124c311
Minor typo error
Remove unwanted and
2021-08-16 11:52:33 +09:00
Bastien Gérard
8b5cf9e2be
Merge pull request #2549 from bagerard/fix_decimal_field_precision0
Fix decimal field precision0
2021-08-07 22:50:46 +02:00
Bastien Gerard
0af96b1323 Add test for DecimalField(precision=0) and raise exception if negative/invalid values are provided 2021-08-07 22:38:27 +02:00
Bastien Gerard
f6d864b6d1 Merge branch 'patch-1' of https://github.com/oleksandr-l5/mongoengine 2021-08-07 22:09:04 +02:00
Bastien Gérard
bb9ba73a7b
Merge pull request #2545 from bagerard/fix_embedded_instance_deepcopy
Fix embedded instance deepcopy
2021-08-07 21:56:55 +02:00
Bastien Gerard
66978aea67 fix test and changelog 2021-08-02 22:09:21 +02:00
Bastien Gerard
5a41998eda Updated recent bugfix with embedded DOc deepcopy so that it set _instance to None 2021-08-02 21:51:15 +02:00
Bastien Gerard
c32b308730 minor renaming in test classes 2021-08-01 14:12:27 +02:00
Timothé Perez
88642eb021 Fix deepcopy on EmbeddedDocument 2021-08-01 10:09:24 +02:00
Bastien Gérard
3b10236b5e
Merge pull request #2536 from ottj3/patch-1
Mention DynamicEmbeddedDocument in usage docs.
2021-07-02 09:51:31 +02:00
Jan-Lucas Ott
bffe058726
Mention DynamicEmbeddedDocument in embedded section. 2021-06-30 17:16:07 -04:00
Oleksandr
09c415a416
fix DecimalField precision
fix decimal.InvalidOperation: [<class 'decimal.ConversionSyntax'>] error if DecimalField param precision is zero or less
2021-06-29 17:32:51 +03:00
Bastien Gérard
4670508a1c
Merge pull request #2530 from bagerard/fix_doc_formatting
fix doc formatting (enumfield, etc)
2021-06-13 23:06:13 +02:00
Bastien Gerard
e7f2d77a75 fix doc formatting (enumfield, etc) 2021-06-13 23:01:20 +02:00
Bastien Gérard
7bf7153e4f
Merge pull request #2515 from par-tec/master
define a base exception for engine
2021-06-06 15:58:26 +02:00
Bastien Gérard
0df0585d07
Merge pull request #2528 from bagerard/precommit_update
precommit update
2021-06-05 21:29:59 +02:00
Bastien Gerard
6ae722d04b precommit update 2021-06-05 21:17:31 +02:00
Bastien Gérard
cc85165b0c
Merge pull request #2519 from bagerard/bugfix_qs_property
Fix instance._qs that was failing due to bad parametrization that went unnoticed
2021-05-18 09:48:33 +02:00
Bastien Gerard
2fd4169656 Fix instance._qs that was failing due to bad parametrization that went unnoticed 2021-05-17 22:37:35 +02:00
Daniele Marcocci
5897b1d042 blacked 2021-05-05 18:31:15 +02:00
Daniele Marcocci
756bffe868 define a base exception for engine 2021-05-05 14:59:38 +02:00
Bastien Gérard
5209547a89
Merge pull request #2514 from bagerard/enum_field_update
improvement to recent updates
2021-05-05 09:21:45 +02:00
Bastien Gerard
ecb5dda281 improvement to recent updates 2021-05-04 20:43:33 +02:00
Bastien Gérard
34245fe258
Merge pull request #2509 from jaesivsm/fix/EnumField-choices
Fixing EnumField choices validation
2021-05-04 20:33:25 +02:00
Bastien Gérard
04c26acdd6
Merge pull request #2513 from bagerard/prepare_release
Prepare release
2021-05-04 12:15:34 +02:00
Bastien Gerard
d0a15a8924 prepare changelog 2021-05-04 10:50:56 +02:00
Bastien Gerard
2215e2746b run latest autoformatter 2021-05-04 10:50:56 +02:00
Bastien Gerard
0e6bcbc030 update precommit 2021-05-04 10:50:56 +02:00
François Schmidts
ecf84cbd5d Fixing EnumField choices validation 2021-04-27 16:32:55 +02:00
Bastien Gérard
232071f8f4
Merge pull request #2505 from deveil/patch-1
Typo correction
2021-04-23 20:45:33 +02:00
Bastien Gérard
fc0fb31d43
Merge pull request #2503 from bagerard/precommit_update
housekeeping - pre-commit autoupdate
2021-04-23 20:44:34 +02:00
deveil
1bd8cd803e
Typo correction 2021-04-23 16:08:57 +02:00
Bastien Gerard
ef57a58155 housekeeping - pre-commit autoupdate 2021-04-22 10:18:15 +02:00
Bastien Gérard
9680259904
Merge pull request #2497 from bagerard/switch_to_isort
Switch to isort + add few pre-commit hooks
2021-03-31 14:11:23 +02:00
Bastien Gerard
49a4d23371 update hooks version 2021-03-29 22:04:07 +02:00
Bastien Gerard
b9d370c885 add standard hooks 2021-03-29 22:02:57 +02:00
Bastien Gerard
e5a2714baf update doc for isort 2021-03-29 21:55:34 +02:00
Bastien Gerard
ff596fcb7e replace flake8-import-order by isort 2021-03-29 21:50:22 +02:00
Bastien Gérard
f0fad6df19
Merge pull request #2485 from janste63/fix_2484
Test case and proposed solution for #2484
2021-03-29 20:52:53 +02:00
Jan Stein
da173cf0e2 Merge branch 'master' into fix_2484 2021-03-25 15:27:48 +01:00
Bastien Gérard
1669f0c5a4
Merge pull request #2489 from bagerard/fix_one_to_many_doc
Fix one-to-many example that is actually a many to many
2021-03-09 13:41:03 +01:00
Bastien Gérard
b045925efe
Merge pull request #2481 from bagerard/improve_connect_doc
improve connect() doc and put more emphasis on URI string
2021-03-09 13:40:45 +01:00
Bastien Gerard
b3ce65453a Fix one-to-many example that is actually a many to many 2021-03-08 22:21:48 +01:00
Bastien Gerard
50d891cb7b more improvement to connect doc 2021-03-08 21:52:14 +01:00
Bastien Gerard
e31f9150d2 improve connect() doc and put more emphasis on URI string 2021-03-08 21:51:43 +01:00
Bastien Gérard
74ceb9703b
Merge pull request #2488 from bagerard/add_lazy_loading_method_ease_profiling
Add _lazy_load_ref methods to make profiling easier
2021-03-08 19:18:54 +01:00
Bastien Gerard
58a3c6de03 Add _lazy_load_ref methods so that the impact of lazy loading surfaces when profiling 2021-03-08 00:14:53 +01:00
Jan Stein
8fd969aba9 Test case and proposed solution for #2484 2021-03-03 14:58:03 +01:00
62 changed files with 660 additions and 396 deletions

View File

@ -44,7 +44,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8, 3.9, pypy3]
python-version: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3]
MONGODB: [$MONGODB_4_0]
PYMONGO: [$PYMONGO_3_11]
include:

View File

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

View File

@ -1,108 +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 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,10 +9,10 @@ import uuid
from io import BytesIO
from operator import itemgetter
from bson import Binary, DBRef, ObjectId, SON
from bson.int64 import Int64
import gridfs
import pymongo
from bson import SON, Binary, DBRef, ObjectId
from bson.int64 import Int64
from pymongo import ReturnDocument
try:
@ -22,7 +22,6 @@ except ImportError:
else:
import dateutil.parser
from mongoengine.base import (
BaseDocument,
BaseField,
@ -36,7 +35,11 @@ from mongoengine.base.utils import LazyRegexCompiler
from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.document import Document, EmbeddedDocument
from mongoengine.errors import DoesNotExist, InvalidQueryError, ValidationError
from mongoengine.errors import (
DoesNotExist,
InvalidQueryError,
ValidationError,
)
from mongoengine.queryset import DO_NOTHING
from mongoengine.queryset.base import BaseQuerySet
from mongoengine.queryset.transform import STRING_OPERATORS
@ -154,10 +157,17 @@ class StringField(BaseField):
regex = r"%s$"
elif op == "exact":
regex = r"^%s$"
elif op == "wholeword":
regex = r"\b%s\b"
elif op == "regex":
regex = value
# escape unsafe characters which could lead to a re.error
value = re.escape(value)
value = re.compile(regex % value, flags)
if op == "regex":
value = re.compile(regex, flags)
else:
# escape unsafe characters which could lead to a re.error
value = re.escape(value)
value = re.compile(regex % value, flags)
return super().prepare_query_value(op, value)
@ -465,6 +475,10 @@ class DecimalField(BaseField):
self.min_value = min_value
self.max_value = max_value
self.force_string = force_string
if precision < 0 or not isinstance(precision, int):
self.error("precision must be a positive integer")
self.precision = precision
self.rounding = rounding
@ -479,9 +493,12 @@ class DecimalField(BaseField):
value = decimal.Decimal("%s" % value)
except (TypeError, ValueError, decimal.InvalidOperation):
return value
return value.quantize(
decimal.Decimal(".%s" % ("0" * self.precision)), rounding=self.rounding
)
if self.precision > 0:
return value.quantize(
decimal.Decimal(".%s" % ("0" * self.precision)), rounding=self.rounding
)
else:
return value.quantize(decimal.Decimal(), rounding=self.rounding)
def to_mongo(self, value):
if value is None:
@ -915,7 +932,7 @@ class ListField(ComplexBaseField):
"""A list field that wraps a standard field, allowing multiple instances
of the field to be used as a list in the database.
If using with ReferenceFields see: :ref:`one-to-many-with-listfields`
If using with ReferenceFields see: :ref:`many-to-many-with-listfields`
.. note::
Required means it cannot be empty - as the default for ListFields is []
@ -1076,16 +1093,7 @@ class DictField(ComplexBaseField):
return DictField(db_field=member_name)
def prepare_query_value(self, op, value):
match_operators = [
"contains",
"icontains",
"startswith",
"istartswith",
"endswith",
"iendswith",
"exact",
"iexact",
]
match_operators = [*STRING_OPERATORS]
if op in match_operators and isinstance(value, str):
return StringField().prepare_query_value(op, value)
@ -1194,6 +1202,14 @@ class ReferenceField(BaseField):
self.document_type_obj = get_document(self.document_type_obj)
return self.document_type_obj
@staticmethod
def _lazy_load_ref(ref_cls, dbref):
dereferenced_son = ref_cls._get_db().dereference(dbref)
if dereferenced_son is None:
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
return ref_cls._from_son(dereferenced_son)
def __get__(self, instance, owner):
"""Descriptor to allow lazy dereferencing."""
if instance is None:
@ -1201,20 +1217,17 @@ class ReferenceField(BaseField):
return self
# Get value from document instance if available
value = instance._data.get(self.name)
ref_value = instance._data.get(self.name)
auto_dereference = instance._fields[self.name]._auto_dereference
# Dereference DBRefs
if auto_dereference and isinstance(value, DBRef):
if hasattr(value, "cls"):
if auto_dereference and isinstance(ref_value, DBRef):
if hasattr(ref_value, "cls"):
# Dereference using the class type specified in the reference
cls = get_document(value.cls)
cls = get_document(ref_value.cls)
else:
cls = self.document_type
dereferenced = cls._get_db().dereference(value)
if dereferenced is None:
raise DoesNotExist("Trying to dereference unknown document %s" % value)
else:
instance._data[self.name] = cls._from_son(dereferenced)
instance._data[self.name] = self._lazy_load_ref(cls, ref_value)
return super().__get__(instance, owner)
@ -1353,6 +1366,14 @@ class CachedReferenceField(BaseField):
self.document_type_obj = get_document(self.document_type_obj)
return self.document_type_obj
@staticmethod
def _lazy_load_ref(ref_cls, dbref):
dereferenced_son = ref_cls._get_db().dereference(dbref)
if dereferenced_son is None:
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
return ref_cls._from_son(dereferenced_son)
def __get__(self, instance, owner):
if instance is None:
# Document class being used rather than a document object
@ -1364,11 +1385,7 @@ class CachedReferenceField(BaseField):
# Dereference DBRefs
if auto_dereference and isinstance(value, DBRef):
dereferenced = self.document_type._get_db().dereference(value)
if dereferenced is None:
raise DoesNotExist("Trying to dereference unknown document %s" % value)
else:
instance._data[self.name] = self.document_type._from_son(dereferenced)
instance._data[self.name] = self._lazy_load_ref(self.document_type, value)
return super().__get__(instance, owner)
@ -1493,6 +1510,14 @@ class GenericReferenceField(BaseField):
value = value._class_name
super()._validate_choices(value)
@staticmethod
def _lazy_load_ref(ref_cls, dbref):
dereferenced_son = ref_cls._get_db().dereference(dbref)
if dereferenced_son is None:
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
return ref_cls._from_son(dereferenced_son)
def __get__(self, instance, owner):
if instance is None:
return self
@ -1500,12 +1525,9 @@ class GenericReferenceField(BaseField):
value = instance._data.get(self.name)
auto_dereference = instance._fields[self.name]._auto_dereference
if auto_dereference and isinstance(value, (dict, SON)):
dereferenced = self.dereference(value)
if dereferenced is None:
raise DoesNotExist("Trying to dereference unknown document %s" % value)
else:
instance._data[self.name] = dereferenced
if auto_dereference and isinstance(value, dict):
doc_cls = get_document(value["_cls"])
instance._data[self.name] = self._lazy_load_ref(doc_cls, value["_ref"])
return super().__get__(instance, owner)
@ -1524,14 +1546,6 @@ class GenericReferenceField(BaseField):
" saved to the database"
)
def dereference(self, value):
doc_cls = get_document(value["_cls"])
reference = value["_ref"]
doc = doc_cls._get_db().dereference(reference)
if doc is not None:
doc = doc_cls._from_son(doc)
return doc
def to_mongo(self, document):
if document is None:
return None
@ -1602,11 +1616,14 @@ class EnumField(BaseField):
"""Enumeration Field. Values are stored underneath as is,
so it will only work with simple types (str, int, etc) that
are bson encodable
Example usage:
Example usage:
.. code-block:: python
class Status(Enum):
NEW = 'new'
ONGOING = 'ongoing'
DONE = 'done'
class ModelWithEnum(Document):
@ -1616,23 +1633,31 @@ class EnumField(BaseField):
ModelWithEnum(status=Status.DONE)
Enum fields can be searched using enum or its value:
.. code-block:: python
ModelWithEnum.objects(status='new').count()
ModelWithEnum.objects(status=Status.NEW).count()
Note that choices cannot be set explicitly, they are derived
from the provided enum class.
The values can be restricted to a subset of the enum by using the ``choices`` parameter:
.. code-block:: python
class ModelWithEnum(Document):
status = EnumField(Status, choices=[Status.NEW, Status.DONE])
"""
def __init__(self, enum, **kwargs):
self._enum_cls = enum
if "choices" in kwargs:
raise ValueError(
"'choices' can't be set on EnumField, "
"it is implicitly set as the enum class"
)
kwargs["choices"] = list(self._enum_cls) # Implicit validator
if kwargs.get("choices"):
invalid_choices = []
for choice in kwargs["choices"]:
if not isinstance(choice, enum):
invalid_choices.append(choice)
if invalid_choices:
raise ValueError("Invalid choices: %r" % invalid_choices)
else:
kwargs["choices"] = list(self._enum_cls) # Implicit validator
super().__init__(**kwargs)
def __set__(self, instance, value):

View File

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

View File

@ -2,13 +2,12 @@ import copy
import itertools
import re
import warnings
from collections.abc import Mapping
from bson import SON, json_util
from bson.code import Code
import pymongo
import pymongo.errors
from bson import SON, json_util
from bson.code import Code
from pymongo.collection import ReturnDocument
from pymongo.common import validate_read_preference
from pymongo.read_concern import ReadConcern
@ -34,7 +33,6 @@ from mongoengine.queryset import transform
from mongoengine.queryset.field_list import QueryFieldList
from mongoengine.queryset.visitor import Q, QNode
__all__ = ("BaseQuerySet", "DO_NOTHING", "NULLIFY", "CASCADE", "DENY", "PULL")
# Delete rules
@ -722,7 +720,7 @@ class BaseQuerySet:
return queryset.filter(pk=object_id).first()
def in_bulk(self, object_ids):
""" "Retrieve a set of documents by their ids.
"""Retrieve a set of documents by their ids.
:param object_ids: a list or tuple of ObjectId's
:rtype: dict of ObjectId's as keys and collection-specific

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -53,8 +53,8 @@ class PyTest(TestCommand):
def run_tests(self):
# import here, cause outside the eggs aren't loaded
from pkg_resources import _namespace_packages
import pytest
from pkg_resources import _namespace_packages
# Purge modules under test from sys.modules. The test loader will
# re-import them from the build location. Required when 2to3 is used
@ -101,6 +101,8 @@ CLASSIFIERS = [
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Database",
@ -110,7 +112,7 @@ CLASSIFIERS = [
extra_opts = {
"packages": find_packages(exclude=["tests", "tests.*"]),
"tests_require": [
"pytest<5.0",
"pytest",
"pytest-cov",
"coverage",
"blinker",

View File

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

View File

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

View File

@ -1,13 +1,16 @@
import unittest
from datetime import datetime
import pytest
from pymongo.collation import Collation
from pymongo.errors import OperationFailure
import pytest
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.mongodb_support import MONGODB_42, get_mongodb_version
from mongoengine.mongodb_support import (
MONGODB_42,
get_mongodb_version,
)
class TestIndexes(unittest.TestCase):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -118,3 +118,23 @@ class TestDecimalField(MongoDBTestCase):
assert 2 == Person.objects(money__gt="7").count()
assert 3 == Person.objects(money__gte="7").count()
def test_precision_0(self):
"""prevent regression of a bug that was raising an exception when using precision=0"""
class TestDoc(Document):
d = DecimalField(precision=0)
TestDoc.drop_collection()
td = TestDoc(d=Decimal("12.00032678131263"))
assert td.d == Decimal("12")
def test_precision_negative_raise(self):
"""prevent regression of a bug that was raising an exception when using precision=0"""
with pytest.raises(
ValidationError, match="precision must be a positive integer"
):
class TestDoc(Document):
dneg = DecimalField(precision=-1)

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import datetime
import unittest
from bson import DBRef, ObjectId, SON
import pytest
from bson import SON, DBRef, ObjectId
from mongoengine import (
BooleanField,
@ -34,9 +34,12 @@ from mongoengine import (
StringField,
ValidationError,
)
from mongoengine.base import BaseField, EmbeddedDocumentList, _document_registry
from mongoengine.base import (
BaseField,
EmbeddedDocumentList,
_document_registry,
)
from mongoengine.errors import DeprecatedError
from tests.utils import MongoDBTestCase
@ -2074,7 +2077,7 @@ class TestField(MongoDBTestCase):
a ComplexBaseField.
"""
class EnumField(BaseField):
class SomeField(BaseField):
def __init__(self, **kwargs):
super().__init__(**kwargs)
@ -2085,7 +2088,7 @@ class TestField(MongoDBTestCase):
return tuple(value)
class TestDoc(Document):
items = ListField(EnumField())
items = ListField(SomeField())
TestDoc.drop_collection()

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,8 @@
from bson.int64 import Int64
import pytest
from bson.int64 import Int64
from mongoengine import *
from mongoengine.connection import get_db
from tests.utils import MongoDBTestCase, get_as_pymongo

View File

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

View File

@ -1,5 +1,4 @@
from mongoengine import *
from tests.utils import MongoDBTestCase

View File

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

View File

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

View File

@ -3,17 +3,20 @@ import unittest
import uuid
from decimal import Decimal
from bson import DBRef, ObjectId
import pymongo
import pytest
from bson import DBRef, ObjectId
from pymongo.read_preferences import ReadPreference
from pymongo.results import UpdateResult
import pytest
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.context_managers import query_counter, switch_db
from mongoengine.errors import InvalidQueryError
from mongoengine.mongodb_support import MONGODB_36, get_mongodb_version
from mongoengine.mongodb_support import (
MONGODB_36,
get_mongodb_version,
)
from mongoengine.queryset import (
DoesNotExist,
MultipleObjectsReturned,
@ -864,6 +867,21 @@ class TestQueryset(unittest.TestCase):
assert "Bob" == bob.name
assert 30 == bob.age
def test_rename(self):
self.Person.drop_collection()
self.Person.objects.create(name="Foo", age=11)
bob = self.Person.objects.as_pymongo().first()
assert "age" in bob
assert bob["age"] == 11
self.Person.objects(name="Foo").update(rename__age="person_age")
bob = self.Person.objects.as_pymongo().first()
assert "age" not in bob
assert "person_age" in bob
assert bob["person_age"] == 11
def test_save_and_only_on_fields_with_default(self):
class Embed(EmbeddedDocument):
field = IntField()
@ -1237,6 +1255,34 @@ class TestQueryset(unittest.TestCase):
obj = self.Person.objects(name__iexact="gUIDO VAN rOSSU").first()
assert obj is None
# Test wholeword
obj = self.Person.objects(name__wholeword="Guido").first()
assert obj == person
obj = self.Person.objects(name__wholeword="rossum").first()
assert obj is None
obj = self.Person.objects(name__wholeword="Rossu").first()
assert obj is None
# Test iwholeword
obj = self.Person.objects(name__iwholeword="rOSSUM").first()
assert obj == person
obj = self.Person.objects(name__iwholeword="rOSSU").first()
assert obj is None
# Test regex
obj = self.Person.objects(name__regex="^[Guido].*[Rossum]$").first()
assert obj == person
obj = self.Person.objects(name__regex="^[guido].*[rossum]$").first()
assert obj is None
obj = self.Person.objects(name__regex="^[uido].*[Rossum]$").first()
assert obj is None
# Test iregex
obj = self.Person.objects(name__iregex="^[guido].*[rossum]$").first()
assert obj == person
obj = self.Person.objects(name__iregex="^[Uido].*[Rossum]$").first()
assert obj is None
# Test unsafe expressions
person = self.Person(name="Guido van Rossum [.'Geek']")
person.save()
@ -1321,7 +1367,14 @@ class TestQueryset(unittest.TestCase):
person.save()
people = self.Person.objects
people = people.filter(name__startswith="Gui").filter(name__not__endswith="tum")
people = (
people.filter(name__startswith="Gui")
.filter(name__not__endswith="tum")
.filter(name__icontains="VAN")
.filter(name__regex="^Guido")
.filter(name__wholeword="Guido")
.filter(name__wholeword="van")
)
assert people.count() == 1
def assertSequence(self, qs, expected):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,6 @@ from mongoengine import connect
from mongoengine.connection import disconnect_all, get_db
from mongoengine.mongodb_support import get_mongodb_version
MONGO_TEST_DB = "mongoenginetest" # standard name for the test database