Compare commits

...

151 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
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
Jan Stein
8fd969aba9 Test case and proposed solution for #2484 2021-03-03 14:58:03 +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
Bastien Gérard
bc0e04d833
Merge pull request #2447 from bagerard/_changelog_0_22_0
prepare 0.22.0
2020-12-14 22:49:09 +01:00
Bastien Gerard
dd11911ed7 update changelog and bump version to 0.22.0 2020-12-14 22:39:48 +01:00
Bastien Gérard
0b3feedf94
Merge pull request #2446 from bagerard/improve_mongomock_tests
Improve mongomock tests to avoid regression
2020-12-14 21:24:33 +01:00
Bastien Gerard
65b8cfc96a Improve mongomock tests to avoid regression 2020-12-14 00:03:39 +01:00
Bastien Gérard
2e284b93b8
Merge pull request #2445 from bagerard/fix_count_mongomock
Use Queryset._query instead of Cursor.__spec for count()
2020-12-13 23:26:39 +01:00
Bastien Gerard
c28bb517cb Use Queryset._query instead of Cursor.__spec for count() 2020-12-13 10:43:26 +01:00
Bastien Gérard
aa3ff39ef8
Merge pull request #2444 from bagerard/improve_code_health
Improve code health
2020-12-13 10:27:37 +01:00
Bastien Gerard
149fb953e7 fix recent update not aligned with latest black 2020-12-13 00:16:10 +01:00
Bastien Gerard
bf12621ce9 update flake8 version in pre-commit 2020-12-13 00:14:53 +01:00
Bastien Gerard
8a1a68ea7c fix flake8 warnings 2020-12-13 00:14:53 +01:00
Bastien Gerard
eabb8f60f5 autoformat with pyupgrade 2020-12-13 00:14:53 +01:00
Bastien Gerard
c00a378776 autoformat with updated black 2020-12-13 00:14:52 +01:00
Bastien Gerard
ee6ef1ff4b autoformat w prettier 2020-12-13 00:14:52 +01:00
Bastien Gérard
fb8f02d0c0
Merge pull request #2441 from bagerard/remove_useless_cls_var
minor improvement in code
2020-12-08 23:22:57 +01:00
Bastien Gerard
a025199294 Merge branch 'master' of github.com:MongoEngine/mongoengine into remove_useless_cls_var 2020-12-08 23:02:17 +01:00
Bastien Gérard
87babaaa30
Merge pull request #2439 from bagerard/improve_doc
Improve Fields documentation
2020-12-08 22:58:00 +01:00
Bastien Gerard
a4fff15491 minor improvement in code 2020-12-08 22:41:27 +01:00
Bastien Gerard
a190dfe2c4 additional improvements to fields constructor 2020-12-08 21:48:54 +01:00
Bastien Gerard
3926473917 Improve Fields documentation + remove versionadded/changed as it's not maintained 2020-12-06 22:25:12 +01:00
Bastien Gérard
9ffe0bcdee
Merge pull request #2432 from bagerard/remove_deprecated_landscape
remove landscape integration
2020-12-01 21:40:56 +01:00
Bastien Gérard
4fa3134294 remove landscape integration as it is dead 2020-11-29 21:15:02 +01:00
Bastien Gérard
92f6fce77d
Merge pull request #2431 from bagerard/remove_pillow_test_restriction
Remove restriction on Pillow version in tests
2020-11-29 20:48:13 +01:00
Bastien Gérard
b1a2cf061d update test requirement for pillow > 7.0 2020-11-29 10:22:59 +01:00
Bastien Gérard
0a05c1f590 Fix image size that needs to be forced in test 2020-11-29 10:08:58 +01:00
Bastien Gérard
7dbc217768 Remove restriction on Pillow version as it was there due to Py2 support 2020-11-28 22:27:24 +01:00
Bastien Gérard
bf411ab2ca Update changelog with recent changes that were merged 2020-11-28 22:21:51 +01:00
Bastien Gérard
277b827d4d
Merge pull request #2426 from volfpeter/master
Fix LazyReferenceField dereferencing in embedded documents
2020-11-26 21:45:12 +01:00
Peter Volf
e0bec881bc removed unused variable to fix a warning 2020-11-25 15:17:52 +01:00
Peter Volf
cc5e2ba054 fix LazyReferenceField dereferencing bug in embedded documents, refs #2375 2020-11-25 11:05:37 +01:00
95 changed files with 1970 additions and 1621 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, "3.10", pypy3]
MONGODB: [$MONGODB_4_0]
PYMONGO: [$PYMONGO_3_11]
include:
- python-version: 3.7
MONGODB: $MONGODB_3_6
PYMONGO: $PYMONGO_3_9
- python-version: 3.7
MONGODB: $MONGODB_4_2
PYMONGO: $PYMONGO_3_6
- python-version: 3.7
MONGODB: $MONGODB_4_4
PYMONGO: $PYMONGO_3_11
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: install mongo and ci dependencies
run: |
bash .github/workflows/install_mongo.sh ${{ matrix.MONGODB }}
bash .github/workflows/install_ci_python_dep.sh
bash .github/workflows/start_mongo.sh ${{ matrix.MONGODB }}
- name: tox dry-run (to pre-install venv)
run: tox -e $(echo py${{ matrix.python-version }}-mg${{ matrix.PYMONGO }} | tr -d . | sed -e 's/pypypy/pypy/') -- -a "-k=test_ci_placeholder"
- name: Run test suite
run: tox -e $(echo py${{ matrix.python-version }}-mg${{ matrix.PYMONGO }} | tr -d . | sed -e 's/pypypy/pypy/') -- -a "--cov=mongoengine"
- name: Send coverage to Coveralls
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_SERVICE_NAME: github
if: ${{ matrix.python-version == env.MAIN_PYTHON_VERSION }}
run: coveralls
build_doc_dryrun:
# ensures that readthedocs can be built continuously
# to avoid that it breaks when new releases are being created
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: install python dep
run: |
pip install -e .
pip install -r docs/requirements.txt
- name: build doc
run: |
cd docs
make html-readthedocs
build-n-publish-dummy:
runs-on: ubuntu-latest
needs: [linting, test, build_doc_dryrun]
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@master
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: build dummy wheel for test-pypi
run: |
pip install wheel
python setup.py egg_info -b ".dev`date '+%Y%m%d%H%M%S'`" build sdist bdist_wheel
# - name: publish test-pypi
# # Although working and recommended, test-pypi has a limit
# # in the size of projects so it's better to avoid publishing
# # until there is a way to garbage collect these dummy releases
# uses: pypa/gh-action-pypi-publish@master
# with:
# password: ${{ secrets.test_pypi_token }}
# repository_url: https://test.pypi.org/legacy/
build-n-publish:
runs-on: ubuntu-latest
needs: [linting, test, build_doc_dryrun, build-n-publish-dummy]
if: github.event_name == 'create' && startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@master
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7
# todo separate build from publish
# https://stackoverflow.com/questions/59349905/which-properties-does-github-event-in-a-github-workflow-have
- name: build dummy wheel for test-pypi
run: |
pip install wheel
python setup.py sdist bdist_wheel
- name: publish pypi
uses: pypa/gh-action-pypi-publish@master
with:
password: ${{ secrets.pypi_token }}

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,17 +0,0 @@
pylint:
disable:
# We use this a lot (e.g. via document._meta)
- protected-access
options:
additional-builtins:
# add long as valid built-ins.
- long
pyflakes:
disable:
# undefined variables are already covered by pylint (and exclude long)
- F821
ignore-paths:
- benchmark.py

View File

@ -1,12 +1,26 @@
fail_fast: false fail_fast: false
repos: repos:
- repo: https://github.com/ambv/black - repo: https://github.com/pre-commit/pre-commit-hooks
rev: 19.10b0 rev: v4.0.1
hooks: hooks:
- id: black - id: check-merge-conflict
- repo: https://gitlab.com/pycqa/flake8 - id: debug-statements
rev: 3.8.0a2 - id: trailing-whitespace
hooks: - id: end-of-file-fixer
- id: flake8 - repo: https://github.com/ambv/black
additional_dependencies: rev: 21.5b2
- flake8-import-order hooks:
- id: black
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
hooks:
- id: flake8
- repo: https://github.com/asottile/pyupgrade
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,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

View File

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

View File

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

View File

@ -12,10 +12,6 @@ MongoEngine
.. image:: https://coveralls.io/repos/github/MongoEngine/mongoengine/badge.svg?branch=master .. image:: https://coveralls.io/repos/github/MongoEngine/mongoengine/badge.svg?branch=master
:target: https://coveralls.io/github/MongoEngine/mongoengine?branch=master :target: https://coveralls.io/github/MongoEngine/mongoengine?branch=master
.. image:: https://landscape.io/github/MongoEngine/mongoengine/master/landscape.svg?style=flat
:target: https://landscape.io/github/MongoEngine/mongoengine/master
:alt: Code Health
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/ambv/black :target: https://github.com/ambv/black

View File

@ -45,7 +45,7 @@ def test_basic():
print( print(
"Doc setattr: %.3fus" "Doc setattr: %.3fus"
% (timeit(lambda: setattr(b, "name", "New name"), 10000) * 10 ** 6) % (timeit(lambda: setattr(b, "name", "New name"), 10000) * 10 ** 6) # noqa B010
) )
print("Doc to mongo: %.3fus" % (timeit(b.to_mongo, 1000) * 10 ** 6)) print("Doc to mongo: %.3fus" % (timeit(b.to_mongo, 1000) * 10 ** 6))

View File

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

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

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

View File

@ -1,4 +1,5 @@
========= =========
Changelog Changelog
========= =========
@ -6,6 +7,32 @@ Changelog
Development Development
=========== ===========
- (Fill this out as you fix issues and develop your features). - (Fill this out as you fix issues and develop your features).
- EnumField improvements: now `choices` limits the values of an enum to allow
- Fix deepcopy of EmbeddedDocument #2202
- Fix error when using precision=0 with DecimalField #2535
- Add support for regex and whole word text search query #2568
Changes in 0.23.1
===========
- Bug fix: ignore LazyReferenceFields when clearing _changed_fields #2484
- Improve connection doc #2481
Changes in 0.23.0
=================
- Bugfix: manually setting SequenceField in DynamicDocument doesn't increment the counter #2471
- Add MongoDB 4.2 and 4.4 to CI
- Add support for allowDiskUse on querysets #2468
Changes in 0.22.1
=================
- Declare that Py3.5 is not supported in package metadata #2449
- Moved CI from Travis to Github-Actions
Changes in 0.22.0
=================
- Fix LazyReferenceField dereferencing in embedded documents #2426
- Fix regarding the recent use of Cursor.__spec in .count() that was interfering with mongomock #2425
- Drop support for Python 3.5 by introducing f-strings in the codebase
Changes in 0.21.0 Changes in 0.21.0
================= =================

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"]
@ -41,8 +41,8 @@ source_suffix = ".rst"
master_doc = "index" master_doc = "index"
# General information about the project. # General information about the project.
project = u"MongoEngine" project = "MongoEngine"
copyright = u"2009, MongoEngine Authors" copyright = "2009, MongoEngine Authors" # noqa: A001
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the

View File

@ -5,7 +5,7 @@ Connecting to MongoDB
===================== =====================
Connections in MongoEngine are registered globally and are identified with aliases. Connections in MongoEngine are registered globally and are identified with aliases.
If no `alias` is provided during the connection, it will use "default" as alias. If no ``alias`` is provided during the connection, it will use "default" as alias.
To connect to a running instance of :program:`mongod`, use the :func:`~mongoengine.connect` To connect to a running instance of :program:`mongod`, use the :func:`~mongoengine.connect`
function. The first argument is the name of the database to connect to:: function. The first argument is the name of the database to connect to::
@ -14,27 +14,66 @@ function. The first argument is the name of the database to connect to::
connect('project1') connect('project1')
By default, MongoEngine assumes that the :program:`mongod` instance is running By default, MongoEngine assumes that the :program:`mongod` instance is running
on **localhost** on port **27017**. If MongoDB is running elsewhere, you should on **localhost** on port **27017**.
provide the :attr:`host` and :attr:`port` arguments to
:func:`~mongoengine.connect`::
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` If the database requires authentication, :attr:`username`, :attr:`password`
and :attr:`authentication_source` arguments should be provided:: and :attr:`authentication_source` arguments should be provided::
connect('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 set of attributes that :func:`~mongoengine.connect` recognizes includes but is not limited to:
the :attr:`host` to :attr:`host`, :attr:`port`, :attr:`read_preference`, :attr:`username`, :attr:`password`, :attr:`authentication_source`, :attr:`authentication_mechanism`,
:func:`~mongoengine.connect`:: :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`.
connect('project1', host='mongodb://localhost/database_name')
.. note:: URI containing SRV records (e.g mongodb+srv://server.example.com/) can be used as well as the :attr:`host`
.. note:: Database, username and password from URI string overrides .. note:: Database, username and password from URI string overrides
corresponding parameters in :func:`~mongoengine.connect`: :: corresponding parameters in :func:`~mongoengine.connect`, this should
obviously be avoided: ::
connect( connect(
db='test', db='test',
@ -43,28 +82,19 @@ the :attr:`host` to
host='mongodb://admin:qwerty@localhost/production' host='mongodb://admin:qwerty@localhost/production'
) )
will establish connection to ``production`` database using will establish connection to ``production`` database using ``admin`` username and ``qwerty`` password.
``admin`` username and ``qwerty`` password.
.. note:: Calling :func:`~mongoengine.connect` without argument will establish .. note:: Calling :func:`~mongoengine.connect` without argument will establish
a connection to the "test" database by default a connection to the "test" database by default
Replica Sets Read Preferences
============ ================
MongoEngine supports connecting to replica sets:: As stated above, Read preferences are supported through the connection but also via individual
from mongoengine import connect
# Regular connect
connect('dbname', replicaset='rs-name')
# MongoDB URI-style connect
connect(host='mongodb://localhost/dbname?replicaSet=rs-name')
Read preferences are supported through the connection or via individual
queries by passing the read_preference :: queries by passing the read_preference ::
from pymongo import ReadPreference
Bar.objects().read_preference(ReadPreference.PRIMARY) Bar.objects().read_preference(ReadPreference.PRIMARY)
Bar.objects(read_preference=ReadPreference.PRIMARY) Bar.objects(read_preference=ReadPreference.PRIMARY)

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

View File

@ -2,8 +2,6 @@
GridFS GridFS
====== ======
.. versionadded:: 0.4
Writing Writing
------- -------

View File

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

View File

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

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(age=1000).save(validate=False)
person = Person.objects.first() person = Person.objects.first()
assert person.age == 1000 assert person.age == 1000

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

@ -1,22 +1,23 @@
# Import submodules so that we can expose their __all__ # Import submodules so that we can expose their __all__
from mongoengine import connection from mongoengine import (
from mongoengine import document connection,
from mongoengine import errors document,
from mongoengine import fields errors,
from mongoengine import queryset fields,
from mongoengine import signals queryset,
signals,
)
# Import everything from each submodule so that it can be accessed via # Import everything from each submodule so that it can be accessed via
# mongoengine, e.g. instead of `from mongoengine.connection import connect`, # mongoengine, e.g. instead of `from mongoengine.connection import connect`,
# users can simply use `from mongoengine import connect`, or even # users can simply use `from mongoengine import connect`, or even
# `from mongoengine import *` and then `connect('testdb')`. # `from mongoengine import *` and then `connect('testdb')`.
from mongoengine.connection import * from mongoengine.connection import * # noqa: F401
from mongoengine.document import * from mongoengine.document import * # noqa: F401
from mongoengine.errors import * from mongoengine.errors import * # noqa: F401
from mongoengine.fields import * from mongoengine.fields import * # noqa: F401
from mongoengine.queryset import * from mongoengine.queryset import * # noqa: F401
from mongoengine.signals import * from mongoengine.signals import * # noqa: F401
__all__ = ( __all__ = (
list(document.__all__) list(document.__all__)
@ -28,7 +29,7 @@ __all__ = (
) )
VERSION = (0, 21, 0) VERSION = (0, 23, 1)
def get_version(): def get_version():

View File

@ -67,11 +67,11 @@ class BaseDict(dict):
if isinstance(value, EmbeddedDocument) and value._instance is None: if isinstance(value, EmbeddedDocument) and value._instance is None:
value._instance = self._instance value._instance = self._instance
elif isinstance(value, dict) and not isinstance(value, BaseDict): elif isinstance(value, dict) and not isinstance(value, BaseDict):
value = BaseDict(value, None, "{}.{}".format(self._name, key)) value = BaseDict(value, None, f"{self._name}.{key}")
super().__setitem__(key, value) super().__setitem__(key, value)
value._instance = self._instance value._instance = self._instance
elif isinstance(value, list) and not isinstance(value, BaseList): elif isinstance(value, list) and not isinstance(value, BaseList):
value = BaseList(value, None, "{}.{}".format(self._name, key)) value = BaseList(value, None, f"{self._name}.{key}")
super().__setitem__(key, value) super().__setitem__(key, value)
value._instance = self._instance value._instance = self._instance
return value return value
@ -97,7 +97,7 @@ class BaseDict(dict):
def _mark_as_changed(self, key=None): def _mark_as_changed(self, key=None):
if hasattr(self._instance, "_mark_as_changed"): if hasattr(self._instance, "_mark_as_changed"):
if key: if key:
self._instance._mark_as_changed("{}.{}".format(self._name, key)) self._instance._mark_as_changed(f"{self._name}.{key}")
else: else:
self._instance._mark_as_changed(self._name) self._instance._mark_as_changed(self._name)
@ -133,12 +133,12 @@ class BaseList(list):
value._instance = self._instance value._instance = self._instance
elif isinstance(value, dict) and not isinstance(value, BaseDict): elif isinstance(value, dict) and not isinstance(value, BaseDict):
# Replace dict by BaseDict # Replace dict by BaseDict
value = BaseDict(value, None, "{}.{}".format(self._name, key)) value = BaseDict(value, None, f"{self._name}.{key}")
super().__setitem__(key, value) super().__setitem__(key, value)
value._instance = self._instance value._instance = self._instance
elif isinstance(value, list) and not isinstance(value, BaseList): elif isinstance(value, list) and not isinstance(value, BaseList):
# Replace list by BaseList # Replace list by BaseList
value = BaseList(value, None, "{}.{}".format(self._name, key)) value = BaseList(value, None, f"{self._name}.{key}")
super().__setitem__(key, value) super().__setitem__(key, value)
value._instance = self._instance value._instance = self._instance
return value return value
@ -180,9 +180,7 @@ class BaseList(list):
def _mark_as_changed(self, key=None): def _mark_as_changed(self, key=None):
if hasattr(self._instance, "_mark_as_changed"): if hasattr(self._instance, "_mark_as_changed"):
if key is not None: if key is not None:
self._instance._mark_as_changed( self._instance._mark_as_changed(f"{self._name}.{key % len(self)}")
"{}.{}".format(self._name, key % len(self))
)
else: else:
self._instance._mark_as_changed(self._name) self._instance._mark_as_changed(self._name)
@ -429,7 +427,7 @@ class StrictDict:
def __repr__(self): def __repr__(self):
return "{%s}" % ", ".join( return "{%s}" % ", ".join(
'"{!s}": {!r}'.format(k, v) for k, v in self.items() f'"{k!s}": {v!r}' for k, v in self.items()
) )
cls._classes[allowed_keys] = SpecificStrictDict cls._classes[allowed_keys] = SpecificStrictDict
@ -472,4 +470,4 @@ class LazyReference(DBRef):
raise AttributeError() raise AttributeError()
def __repr__(self): def __repr__(self):
return "<LazyReference({}, {!r})>".format(self.document_type, self.pk) return f"<LazyReference({self.document_type}, {self.pk!r})>"

View File

@ -1,10 +1,9 @@
import copy import copy
import numbers import numbers
from functools import partial from functools import partial
from bson import DBRef, ObjectId, SON, json_util
import pymongo import pymongo
from bson import SON, DBRef, ObjectId, json_util
from mongoengine import signals from mongoengine import signals
from mongoengine.base.common import get_document from mongoengine.base.common import get_document
@ -89,9 +88,7 @@ class BaseDocument:
list(self._fields.keys()) + ["id", "pk", "_cls", "_text_score"] list(self._fields.keys()) + ["id", "pk", "_cls", "_text_score"]
) )
if _undefined_fields: if _undefined_fields:
msg = ('The fields "{}" do not exist on the document "{}"').format( msg = f'The fields "{_undefined_fields}" do not exist on the document "{self._class_name}"'
_undefined_fields, self._class_name
)
raise FieldDoesNotExist(msg) raise FieldDoesNotExist(msg)
if self.STRICT and not self._dynamic: if self.STRICT and not self._dynamic:
@ -158,7 +155,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
@ -231,10 +228,10 @@ class BaseDocument:
setattr(self, k, data[k]) setattr(self, k, data[k])
if "_fields_ordered" in data: if "_fields_ordered" in data:
if self._dynamic: if self._dynamic:
setattr(self, "_fields_ordered", data["_fields_ordered"]) self._fields_ordered = data["_fields_ordered"]
else: else:
_super_fields_ordered = type(self)._fields_ordered _super_fields_ordered = type(self)._fields_ordered
setattr(self, "_fields_ordered", _super_fields_ordered) self._fields_ordered = _super_fields_ordered
dynamic_fields = data.get("_dynamic_fields") or SON() dynamic_fields = data.get("_dynamic_fields") or SON()
for k in dynamic_fields.keys(): for k in dynamic_fields.keys():
@ -244,8 +241,7 @@ class BaseDocument:
return iter(self._fields_ordered) return iter(self._fields_ordered)
def __getitem__(self, name): def __getitem__(self, name):
"""Dictionary-style field access, return a field's value if present. """Dictionary-style field access, return a field's value if present."""
"""
try: try:
if name in self._fields_ordered: if name in self._fields_ordered:
return getattr(self, name) return getattr(self, name)
@ -254,8 +250,7 @@ class BaseDocument:
raise KeyError(name) raise KeyError(name)
def __setitem__(self, name, value): def __setitem__(self, name, value):
"""Dictionary-style field access, set a field's value. """Dictionary-style field access, set a field's value."""
"""
# Ensure that the field exists before settings its value # Ensure that the field exists before settings its value
if not self._dynamic and name not in self._fields: if not self._dynamic and name not in self._fields:
raise KeyError(name) raise KeyError(name)
@ -277,7 +272,7 @@ class BaseDocument:
except (UnicodeEncodeError, UnicodeDecodeError): except (UnicodeEncodeError, UnicodeDecodeError):
u = "[Bad Unicode data]" u = "[Bad Unicode data]"
repr_type = str if u is None else type(u) repr_type = str if u is None else type(u)
return repr_type("<{}: {}>".format(self.__class__.__name__, u)) return repr_type(f"<{self.__class__.__name__}: {u}>")
def __str__(self): def __str__(self):
# TODO this could be simpler? # TODO this could be simpler?
@ -433,7 +428,7 @@ class BaseDocument:
pk = self.pk pk = self.pk
elif self._instance and hasattr(self._instance, "pk"): elif self._instance and hasattr(self._instance, "pk"):
pk = self._instance.pk pk = self._instance.pk
message = "ValidationError ({}:{}) ".format(self._class_name, pk) message = f"ValidationError ({self._class_name}:{pk}) "
raise ValidationError(message, errors=errors) raise ValidationError(message, errors=errors)
def to_json(self, *args, **kwargs): def to_json(self, *args, **kwargs):
@ -506,7 +501,7 @@ class BaseDocument:
if "." in key: if "." in key:
key, rest = key.split(".", 1) key, rest = key.split(".", 1)
key = self._db_field_map.get(key, key) key = self._db_field_map.get(key, key)
key = "{}.{}".format(key, rest) key = f"{key}.{rest}"
else: else:
key = self._db_field_map.get(key, key) key = self._db_field_map.get(key, key)
@ -578,7 +573,7 @@ class BaseDocument:
else: else:
iterator = data.items() iterator = data.items()
for index_or_key, value in iterator: for _index_or_key, value in iterator:
if hasattr(value, "_get_changed_fields") and not isinstance( if hasattr(value, "_get_changed_fields") and not isinstance(
value, Document value, Document
): # don't follow references ): # don't follow references
@ -602,7 +597,7 @@ class BaseDocument:
iterator = data.items() iterator = data.items()
for index_or_key, value in iterator: for index_or_key, value in iterator:
item_key = "{}{}.".format(base_key, index_or_key) item_key = f"{base_key}{index_or_key}."
# don't check anything lower if this key is already marked # don't check anything lower if this key is already marked
# as changed. # as changed.
if item_key[:-1] in changed_fields: if item_key[:-1] in changed_fields:
@ -610,17 +605,18 @@ class BaseDocument:
if hasattr(value, "_get_changed_fields"): if hasattr(value, "_get_changed_fields"):
changed = value._get_changed_fields() changed = value._get_changed_fields()
changed_fields += ["{}{}".format(item_key, k) for k in changed if k] changed_fields += [f"{item_key}{k}" for k in changed if k]
elif isinstance(value, (list, tuple, dict)): elif isinstance(value, (list, tuple, dict)):
BaseDocument._nestable_types_changed_fields( BaseDocument._nestable_types_changed_fields(
changed_fields, item_key, value changed_fields, item_key, value
) )
def _get_changed_fields(self): def _get_changed_fields(self):
"""Return a list of all fields that have explicitly been changed. """Return a list of all fields that have explicitly been changed."""
"""
EmbeddedDocument = _import_class("EmbeddedDocument") EmbeddedDocument = _import_class("EmbeddedDocument")
LazyReferenceField = _import_class("LazyReferenceField")
ReferenceField = _import_class("ReferenceField") ReferenceField = _import_class("ReferenceField")
GenericLazyReferenceField = _import_class("GenericLazyReferenceField")
GenericReferenceField = _import_class("GenericReferenceField") GenericReferenceField = _import_class("GenericReferenceField")
SortedListField = _import_class("SortedListField") SortedListField = _import_class("SortedListField")
@ -643,10 +639,16 @@ class BaseDocument:
if isinstance(data, EmbeddedDocument): if isinstance(data, EmbeddedDocument):
# Find all embedded fields that have been changed # Find all embedded fields that have been changed
changed = data._get_changed_fields() changed = data._get_changed_fields()
changed_fields += ["{}{}".format(key, k) for k in changed if k] changed_fields += [f"{key}{k}" for k in changed if k]
elif isinstance(data, (list, tuple, dict)): elif isinstance(data, (list, tuple, dict)):
if hasattr(field, "field") and isinstance( if hasattr(field, "field") and isinstance(
field.field, (ReferenceField, GenericReferenceField) field.field,
(
LazyReferenceField,
ReferenceField,
GenericLazyReferenceField,
GenericReferenceField,
),
): ):
continue continue
elif isinstance(field, SortedListField) and field._ordering: elif isinstance(field, SortedListField) and field._ordering:
@ -750,8 +752,7 @@ class BaseDocument:
@classmethod @classmethod
def _from_son(cls, son, _auto_dereference=True, created=False): def _from_son(cls, son, _auto_dereference=True, created=False):
"""Create an instance of a Document (subclass) from a PyMongo SON (dict) """Create an instance of a Document (subclass) from a PyMongo SON (dict)"""
"""
if son and not isinstance(son, dict): if son and not isinstance(son, dict):
raise ValueError( raise ValueError(
"The source SON object needs to be of type 'dict' but a '%s' was found" "The source SON object needs to be of type 'dict' but a '%s' was found"
@ -796,11 +797,10 @@ class BaseDocument:
errors_dict[field_name] = e errors_dict[field_name] = e
if errors_dict: if errors_dict:
errors = "\n".join( errors = "\n".join([f"Field '{k}' - {v}" for k, v in errors_dict.items()])
["Field '{}' - {}".format(k, v) for k, v in errors_dict.items()]
)
msg = "Invalid data to create a `{}` instance.\n{}".format( msg = "Invalid data to create a `{}` instance.\n{}".format(
cls._class_name, errors, cls._class_name,
errors,
) )
raise InvalidDocumentError(msg) raise InvalidDocumentError(msg)
@ -968,10 +968,7 @@ class BaseDocument:
unique_fields += unique_with unique_fields += unique_with
# Add the new index to the list # Add the new index to the list
fields = [ fields = [(f"{namespace}{f}", pymongo.ASCENDING) for f in unique_fields]
("{}{}".format(namespace, f), pymongo.ASCENDING)
for f in unique_fields
]
index = {"fields": fields, "unique": True, "sparse": sparse} index = {"fields": fields, "unique": True, "sparse": sparse}
unique_indexes.append(index) unique_indexes.append(index)
@ -1007,9 +1004,7 @@ class BaseDocument:
"PolygonField", "PolygonField",
) )
geo_field_types = tuple( geo_field_types = tuple(_import_class(field) for field in geo_field_type_names)
[_import_class(field) for field in geo_field_type_names]
)
for field in cls._fields.values(): for field in cls._fields.values():
if not isinstance(field, geo_field_types): if not isinstance(field, geo_field_types):
@ -1027,7 +1022,7 @@ class BaseDocument:
elif field._geo_index: elif field._geo_index:
field_name = field.db_field field_name = field.db_field
if parent_field: if parent_field:
field_name = "{}.{}".format(parent_field, field_name) field_name = f"{parent_field}.{field_name}"
geo_indices.append({"fields": [(field_name, field._geo_index)]}) geo_indices.append({"fields": [(field_name, field._geo_index)]})
return geo_indices return geo_indices
@ -1165,8 +1160,7 @@ class BaseDocument:
@classmethod @classmethod
def _translate_field_name(cls, field, sep="."): def _translate_field_name(cls, field, sep="."):
"""Translate a field attribute name to a database field name. """Translate a field attribute name to a database field name."""
"""
parts = field.split(sep) parts = field.split(sep)
parts = [f.db_field for f in cls._lookup_field(parts)] parts = [f.db_field for f in cls._lookup_field(parts)]
return ".".join(parts) return ".".join(parts)

View File

@ -1,12 +1,15 @@
import operator import operator
import warnings
import weakref import weakref
from bson import DBRef, ObjectId, SON
import pymongo import pymongo
from bson import SON, DBRef, ObjectId
from mongoengine.base.common import UPDATE_OPERATORS from mongoengine.base.common import UPDATE_OPERATORS
from mongoengine.base.datastructures import BaseDict, BaseList, EmbeddedDocumentList from mongoengine.base.datastructures import (
BaseDict,
BaseList,
EmbeddedDocumentList,
)
from mongoengine.common import _import_class from mongoengine.common import _import_class
from mongoengine.errors import DeprecatedError, ValidationError from mongoengine.errors import DeprecatedError, ValidationError
@ -16,11 +19,9 @@ __all__ = ("BaseField", "ComplexBaseField", "ObjectIdField", "GeoJsonBaseField")
class BaseField: class BaseField:
"""A base class for fields in a MongoDB document. Instances of this class """A base class for fields in a MongoDB document. Instances of this class
may be added to subclasses of `Document` to define a document's schema. may be added to subclasses of `Document` to define a document's schema.
.. versionchanged:: 0.5 - added verbose and help text
""" """
name = None name = None # set in TopLevelDocumentMetaclass
_geo_index = False _geo_index = False
_auto_gen = False # Call `generate` to generate a value _auto_gen = False # Call `generate` to generate a value
_auto_dereference = True _auto_dereference = True
@ -43,7 +44,7 @@ class BaseField:
choices=None, choices=None,
null=False, null=False,
sparse=False, sparse=False,
**kwargs **kwargs,
): ):
""" """
:param db_field: The database field to store this field in :param db_field: The database field to store this field in
@ -120,8 +121,7 @@ class BaseField:
BaseField.creation_counter += 1 BaseField.creation_counter += 1
def __get__(self, instance, owner): def __get__(self, instance, owner):
"""Descriptor for retrieving a value from a field in a document. """Descriptor for retrieving a value from a field in a document."""
"""
if instance is None: if instance is None:
# Document class being used rather than a document object # Document class being used rather than a document object
return self return self
@ -265,11 +265,22 @@ class ComplexBaseField(BaseField):
Allows for nesting of embedded documents inside complex types. Allows for nesting of embedded documents inside complex types.
Handles the lazy dereferencing of a queryset by lazily dereferencing all Handles the lazy dereferencing of a queryset by lazily dereferencing all
items in a list / dict rather than one at a time. items in a list / dict rather than one at a time.
.. versionadded:: 0.5
""" """
field = None def __init__(self, field=None, **kwargs):
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): def __get__(self, instance, owner):
"""Descriptor to automatically dereference references.""" """Descriptor to automatically dereference references."""
@ -288,19 +299,15 @@ class ComplexBaseField(BaseField):
or isinstance(self.field, (GenericReferenceField, ReferenceField)) or isinstance(self.field, (GenericReferenceField, ReferenceField))
) )
_dereference = _import_class("DeReference")()
if ( if (
instance._initialised instance._initialised
and dereference and dereference
and instance._data.get(self.name) and instance._data.get(self.name)
and not getattr(instance._data[self.name], "_dereferenced", False) and not getattr(instance._data[self.name], "_dereferenced", False)
): ):
instance._data[self.name] = _dereference( ref_values = instance._data.get(self.name)
instance._data.get(self.name), instance._data[self.name] = self._lazy_load_refs(
max_depth=1, ref_values=ref_values, instance=instance, name=self.name, max_depth=1
instance=instance,
name=self.name,
) )
if hasattr(instance._data[self.name], "_dereferenced"): if hasattr(instance._data[self.name], "_dereferenced"):
instance._data[self.name]._dereferenced = True instance._data[self.name]._dereferenced = True
@ -326,7 +333,9 @@ class ComplexBaseField(BaseField):
and isinstance(value, (BaseList, BaseDict)) and isinstance(value, (BaseList, BaseDict))
and not value._dereferenced 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 value._dereferenced = True
instance._data[self.name] = value instance._data[self.name] = value
@ -469,9 +478,7 @@ class ComplexBaseField(BaseField):
if errors: if errors:
field_class = self.field.__class__.__name__ field_class = self.field.__class__.__name__
self.error( self.error(f"Invalid {field_class} item ({value})", errors=errors)
"Invalid {} item ({})".format(field_class, value), errors=errors
)
# Don't allow empty values if required # Don't allow empty values if required
if self.required and not value: if self.required and not value:
self.error("Field is required and cannot be empty") self.error("Field is required and cannot be empty")
@ -520,10 +527,7 @@ class ObjectIdField(BaseField):
class GeoJsonBaseField(BaseField): class GeoJsonBaseField(BaseField):
"""A geo json field storing a geojson style object. """A geo json field storing a geojson style object."""
.. versionadded:: 0.8
"""
_geo_index = pymongo.GEOSPHERE _geo_index = pymongo.GEOSPHERE
_type = "GeoBase" _type = "GeoBase"
@ -543,7 +547,7 @@ class GeoJsonBaseField(BaseField):
if isinstance(value, dict): if isinstance(value, dict):
if set(value.keys()) == {"type", "coordinates"}: if set(value.keys()) == {"type", "coordinates"}:
if value["type"] != self._type: if value["type"] != self._type:
self.error('{} type must be "{}"'.format(self._name, self._type)) self.error(f'{self._name} type must be "{self._type}"')
return self.validate(value["coordinates"]) return self.validate(value["coordinates"])
else: else:
self.error( self.error(

View File

@ -2,7 +2,11 @@ import itertools
import warnings import warnings
from mongoengine.base.common import _document_registry from mongoengine.base.common import _document_registry
from mongoengine.base.fields import BaseField, ComplexBaseField, ObjectIdField from mongoengine.base.fields import (
BaseField,
ComplexBaseField,
ObjectIdField,
)
from mongoengine.common import _import_class from mongoengine.common import _import_class
from mongoengine.errors import InvalidDocumentError from mongoengine.errors import InvalidDocumentError
from mongoengine.queryset import ( from mongoengine.queryset import (
@ -12,7 +16,6 @@ from mongoengine.queryset import (
QuerySetManager, QuerySetManager,
) )
__all__ = ("DocumentMetaclass", "TopLevelDocumentMetaclass") __all__ = ("DocumentMetaclass", "TopLevelDocumentMetaclass")
@ -337,7 +340,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
# allow_inheritance to False. If the base Document allows inheritance, # allow_inheritance to False. If the base Document allows inheritance,
# none of its subclasses can override allow_inheritance to False. # none of its subclasses can override allow_inheritance to False.
simple_class = all( simple_class = all(
[b._meta.get("abstract") for b in flattened_bases if hasattr(b, "_meta")] b._meta.get("abstract") for b in flattened_bases if hasattr(b, "_meta")
) )
if ( if (
not simple_class not simple_class
@ -439,8 +442,8 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
id_basename, id_db_basename, i = ("auto_id", "_auto_id", 0) id_basename, id_db_basename, i = ("auto_id", "_auto_id", 0)
for i in itertools.count(): for i in itertools.count():
id_name = "{}_{}".format(id_basename, i) id_name = f"{id_basename}_{i}"
id_db_name = "{}_{}".format(id_db_basename, i) id_db_name = f"{id_db_basename}_{i}"
if id_name not in existing_fields and id_db_name not in existing_db_fields: if id_name not in existing_fields and id_db_name not in existing_db_fields:
return id_name, id_db_name return id_name, id_db_name

View File

@ -54,28 +54,26 @@ def _get_connection_settings(
password=None, password=None,
authentication_source=None, authentication_source=None,
authentication_mechanism=None, authentication_mechanism=None,
**kwargs **kwargs,
): ):
"""Get the connection settings as a dict """Get the connection settings as a dict
: param db: the name of the database to use, for compatibility with connect :param db: the name of the database to use, for compatibility with connect
: param name: the name of the specific database to use :param name: the name of the specific database to use
: param host: the host name of the: program: `mongod` instance to connect to :param host: the host name of the: program: `mongod` instance to connect to
: param port: the port that the: program: `mongod` instance is running on :param port: the port that the: program: `mongod` instance is running on
: param read_preference: The read preference for the collection :param read_preference: The read preference for the collection
: param username: username to authenticate with :param username: username to authenticate with
: param password: password to authenticate with :param password: password to authenticate with
: param authentication_source: database to authenticate against :param authentication_source: database to authenticate against
: param authentication_mechanism: database authentication mechanisms. :param authentication_mechanism: database authentication mechanisms.
By default, use SCRAM-SHA-1 with MongoDB 3.0 and later, By default, use SCRAM-SHA-1 with MongoDB 3.0 and later,
MONGODB-CR (MongoDB Challenge Response protocol) for older servers. MONGODB-CR (MongoDB Challenge Response protocol) for older servers.
: param is_mock: explicitly use mongomock for this connection :param is_mock: explicitly use mongomock for this connection
(can also be done by using `mongomock: // ` as db host prefix) (can also be done by using `mongomock: // ` as db host prefix)
: param kwargs: ad-hoc parameters to be passed into the pymongo driver, :param kwargs: ad-hoc parameters to be passed into the pymongo driver,
for example maxpoolsize, tz_aware, etc. See the documentation for example maxpoolsize, tz_aware, etc. See the documentation
for pymongo's `MongoClient` for a full list. for pymongo's `MongoClient` for a full list.
.. versionchanged:: 0.10.6 - added mongomock support
""" """
conn_settings = { conn_settings = {
"name": name or db or DEFAULT_DATABASE_NAME, "name": name or db or DEFAULT_DATABASE_NAME,
@ -179,30 +177,27 @@ def register_connection(
password=None, password=None,
authentication_source=None, authentication_source=None,
authentication_mechanism=None, authentication_mechanism=None,
**kwargs **kwargs,
): ):
"""Register the connection settings. """Register the connection settings.
: param alias: the name that will be used to refer to this connection :param alias: the name that will be used to refer to this connection throughout MongoEngine
throughout MongoEngine :param db: the name of the database to use, for compatibility with connect
: param db: the name of the database to use, for compatibility with connect :param name: the name of the specific database to use
: param name: the name of the specific database to use :param host: the host name of the: program: `mongod` instance to connect to
: param host: the host name of the: program: `mongod` instance to connect to :param port: the port that the: program: `mongod` instance is running on
: param port: the port that the: program: `mongod` instance is running on :param read_preference: The read preference for the collection
: param read_preference: The read preference for the collection :param username: username to authenticate with
: param username: username to authenticate with :param password: password to authenticate with
: param password: password to authenticate with :param authentication_source: database to authenticate against
: param authentication_source: database to authenticate against :param authentication_mechanism: database authentication mechanisms.
: param authentication_mechanism: database authentication mechanisms.
By default, use SCRAM-SHA-1 with MongoDB 3.0 and later, By default, use SCRAM-SHA-1 with MongoDB 3.0 and later,
MONGODB-CR (MongoDB Challenge Response protocol) for older servers. MONGODB-CR (MongoDB Challenge Response protocol) for older servers.
: param is_mock: explicitly use mongomock for this connection :param is_mock: explicitly use mongomock for this connection
(can also be done by using `mongomock: // ` as db host prefix) (can also be done by using `mongomock: // ` as db host prefix)
: param kwargs: ad-hoc parameters to be passed into the pymongo driver, :param kwargs: ad-hoc parameters to be passed into the pymongo driver,
for example maxpoolsize, tz_aware, etc. See the documentation for example maxpoolsize, tz_aware, etc. See the documentation
for pymongo's `MongoClient` for a full list. for pymongo's `MongoClient` for a full list.
.. versionchanged:: 0.10.6 - added mongomock support
""" """
conn_settings = _get_connection_settings( conn_settings = _get_connection_settings(
db=db, db=db,
@ -214,15 +209,15 @@ def register_connection(
password=password, password=password,
authentication_source=authentication_source, authentication_source=authentication_source,
authentication_mechanism=authentication_mechanism, authentication_mechanism=authentication_mechanism,
**kwargs **kwargs,
) )
_connection_settings[alias] = conn_settings _connection_settings[alias] = conn_settings
def disconnect(alias=DEFAULT_CONNECTION_NAME): def disconnect(alias=DEFAULT_CONNECTION_NAME):
"""Close the connection with a given alias.""" """Close the connection with a given alias."""
from mongoengine.base.common import _get_documents_by_db
from mongoengine import Document from mongoengine import Document
from mongoengine.base.common import _get_documents_by_db
if alias in _connections: if alias in _connections:
get_connection(alias=alias).close() get_connection(alias=alias).close()
@ -317,7 +312,7 @@ def _create_connection(alias, connection_class, **connection_settings):
try: try:
return connection_class(**connection_settings) return connection_class(**connection_settings)
except Exception as e: except Exception as e:
raise ConnectionFailure("Cannot connect to database {} :\n{}".format(alias, e)) raise ConnectionFailure(f"Cannot connect to database {alias} :\n{e}")
def _find_existing_connection(connection_settings): def _find_existing_connection(connection_settings):
@ -386,8 +381,6 @@ def connect(db=None, alias=DEFAULT_CONNECTION_NAME, **kwargs):
See the docstring for `register_connection` for more details about all See the docstring for `register_connection` for more details about all
supported kwargs. supported kwargs.
.. versionchanged:: 0.6 - added multiple database support.
""" """
if alias in _connections: if alias in _connections:
prev_conn_setting = _connection_settings[alias] prev_conn_setting = _connection_settings[alias]

View File

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

View File

@ -1,4 +1,4 @@
from bson import DBRef, SON from bson import SON, DBRef
from mongoengine.base import ( from mongoengine.base import (
BaseDict, BaseDict,
@ -10,7 +10,12 @@ from mongoengine.base import (
from mongoengine.base.datastructures import LazyReference from mongoengine.base.datastructures import LazyReference
from mongoengine.connection import get_db from mongoengine.connection import get_db
from mongoengine.document import Document, EmbeddedDocument from mongoengine.document import Document, EmbeddedDocument
from mongoengine.fields import DictField, ListField, MapField, ReferenceField from mongoengine.fields import (
DictField,
ListField,
MapField,
ReferenceField,
)
from mongoengine.queryset import QuerySet from mongoengine.queryset import QuerySet
@ -51,10 +56,10 @@ class DeReference:
doc_type = doc_type.document_type doc_type = doc_type.document_type
is_list = not hasattr(items, "items") is_list = not hasattr(items, "items")
if is_list and all([i.__class__ == doc_type for i in items]): if is_list and all(i.__class__ == doc_type for i in items):
return items return items
elif not is_list and all( elif not is_list and all(
[i.__class__ == doc_type for i in items.values()] i.__class__ == doc_type for i in items.values()
): ):
return items return items
elif not field.dbref: elif not field.dbref:
@ -157,8 +162,7 @@ class DeReference:
return reference_map return reference_map
def _fetch_objects(self, doc_type=None): def _fetch_objects(self, doc_type=None):
"""Fetch all references and convert to their document objects """Fetch all references and convert to their document objects"""
"""
object_map = {} object_map = {}
for collection, dbrefs in self.reference_map.items(): for collection, dbrefs in self.reference_map.items():
@ -272,12 +276,12 @@ class DeReference:
(v["_ref"].collection, v["_ref"].id), v (v["_ref"].collection, v["_ref"].id), v
) )
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth: elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
item_name = "{}.{}.{}".format(name, k, field_name) item_name = f"{name}.{k}.{field_name}"
data[k]._data[field_name] = self._attach_objects( data[k]._data[field_name] = self._attach_objects(
v, depth, instance=instance, name=item_name v, depth, instance=instance, name=item_name
) )
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth: elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
item_name = "{}.{}".format(name, k) if name else name item_name = f"{name}.{k}" if name else name
data[k] = self._attach_objects( data[k] = self._attach_objects(
v, depth - 1, instance=instance, name=item_name v, depth - 1, instance=instance, name=item_name
) )

View File

@ -1,8 +1,7 @@
import re import re
import warnings
from bson.dbref import DBRef
import pymongo import pymongo
from bson.dbref import DBRef
from pymongo.read_preferences import ReadPreference from pymongo.read_preferences import ReadPreference
from mongoengine import signals from mongoengine import signals
@ -17,14 +16,23 @@ from mongoengine.base import (
) )
from mongoengine.common import _import_class from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.context_managers import set_write_concern, switch_collection, switch_db from mongoengine.context_managers import (
set_write_concern,
switch_collection,
switch_db,
)
from mongoengine.errors import ( from mongoengine.errors import (
InvalidDocumentError, InvalidDocumentError,
InvalidQueryError, InvalidQueryError,
SaveConditionError, SaveConditionError,
) )
from mongoengine.pymongo_support import list_collection_names from mongoengine.pymongo_support import list_collection_names
from mongoengine.queryset import NotUniqueError, OperationError, QuerySet, transform from mongoengine.queryset import (
NotUniqueError,
OperationError,
QuerySet,
transform,
)
__all__ = ( __all__ = (
"Document", "Document",
@ -91,6 +99,15 @@ class EmbeddedDocument(BaseDocument, metaclass=DocumentMetaclass):
def __ne__(self, other): def __ne__(self, other):
return not self.__eq__(other) return not self.__eq__(other)
def __getstate__(self):
data = super().__getstate__()
data["_instance"] = None
return data
def __setstate__(self, state):
super().__setstate__(state)
self._instance = state["_instance"]
def to_mongo(self, *args, **kwargs): def to_mongo(self, *args, **kwargs):
data = super().to_mongo(*args, **kwargs) data = super().to_mongo(*args, **kwargs)
@ -110,7 +127,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.
@ -118,7 +135,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
create a specialised version of the document that will be stored in the create a specialised version of the document that will be stored in the
same collection. To facilitate this behaviour a `_cls` same collection. To facilitate this behaviour a `_cls`
field is added to documents (hidden though the MongoEngine interface). field is added to documents (hidden though the MongoEngine interface).
To enable this behaviourset :attr:`allow_inheritance` to ``True`` in the To enable this behaviour set :attr:`allow_inheritance` to ``True`` in the
:attr:`meta` dictionary. :attr:`meta` dictionary.
A :class:`~mongoengine.Document` may use a **Capped Collection** by A :class:`~mongoengine.Document` may use a **Capped Collection** by
@ -324,7 +341,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
_refs=None, _refs=None,
save_condition=None, save_condition=None,
signal_kwargs=None, signal_kwargs=None,
**kwargs **kwargs,
): ):
"""Save the :class:`~mongoengine.Document` to the database. If the """Save the :class:`~mongoengine.Document` to the database. If the
document already exists, it will be updated, otherwise it will be document already exists, it will be updated, otherwise it will be
@ -367,15 +384,6 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
meta['cascade'] = True. Also you can pass different kwargs to meta['cascade'] = True. Also you can pass different kwargs to
the cascade save using cascade_kwargs which overwrites the the cascade save using cascade_kwargs which overwrites the
existing kwargs with custom values. existing kwargs with custom values.
.. versionchanged:: 0.8.5
Optional save_condition that only overwrites existing documents
if the condition is satisfied in the current db record.
.. versionchanged:: 0.10
:class:`OperationError` exception raised if save_condition fails.
.. versionchanged:: 0.10.1
:class: save_condition failure now raises a `SaveConditionError`
.. versionchanged:: 0.10.7
Add signal_kwargs argument
""" """
signal_kwargs = signal_kwargs or {} signal_kwargs = signal_kwargs or {}
@ -564,7 +572,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
if not getattr(ref, "_changed_fields", True): if not getattr(ref, "_changed_fields", True):
continue 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: if ref and ref_id not in _refs:
_refs.append(ref_id) _refs.append(ref_id)
kwargs["_refs"] = _refs kwargs["_refs"] = _refs
@ -575,7 +583,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
def _qs(self): def _qs(self):
"""Return the default queryset corresponding to this document.""" """Return the default queryset corresponding to this document."""
if not hasattr(self, "__objects"): if not hasattr(self, "__objects"):
self.__objects = QuerySet(self, self._get_collection()) self.__objects = QuerySet(self.__class__, self._get_collection())
return self.__objects return self.__objects
@property @property
@ -630,9 +638,6 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
For example, ``save(..., w: 2, fsync: True)`` will For example, ``save(..., w: 2, fsync: True)`` will
wait until at least two servers have recorded the write and wait until at least two servers have recorded the write and
will force an fsync on the primary server. will force an fsync on the primary server.
.. versionchanged:: 0.10.7
Add signal_kwargs argument
""" """
signal_kwargs = signal_kwargs or {} signal_kwargs = signal_kwargs or {}
signals.pre_delete.send(self.__class__, document=self, **signal_kwargs) signals.pre_delete.send(self.__class__, document=self, **signal_kwargs)
@ -714,8 +719,6 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
def select_related(self, max_depth=1): def select_related(self, max_depth=1):
"""Handles dereferencing of :class:`~bson.dbref.DBRef` objects to """Handles dereferencing of :class:`~bson.dbref.DBRef` objects to
a maximum depth in order to cut down the number queries to mongodb. a maximum depth in order to cut down the number queries to mongodb.
.. versionadded:: 0.5
""" """
DeReference = _import_class("DeReference") DeReference = _import_class("DeReference")
DeReference()([self], max_depth + 1) DeReference()([self], max_depth + 1)
@ -726,10 +729,6 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
:param fields: (optional) args list of fields to reload :param fields: (optional) args list of fields to reload
:param max_depth: (optional) depth of dereferencing to follow :param max_depth: (optional) depth of dereferencing to follow
.. versionadded:: 0.1.2
.. versionchanged:: 0.6 Now chainable
.. versionchanged:: 0.9 Can provide specific fields to reload
""" """
max_depth = 1 max_depth = 1
if fields and isinstance(fields[0], int): if fields and isinstance(fields[0], int):
@ -831,9 +830,6 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
Raises :class:`OperationError` if the document has no collection set Raises :class:`OperationError` if the document has no collection set
(i.g. if it is `abstract`) (i.g. if it is `abstract`)
.. versionchanged:: 0.10.7
:class:`OperationError` exception raised if no collection available
""" """
coll_name = cls._get_collection_name() coll_name = cls._get_collection_name()
if not coll_name: if not coll_name:
@ -879,6 +875,10 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
Global defaults can be set in the meta - see :doc:`guide/defining-documents` Global defaults can be set in the meta - see :doc:`guide/defining-documents`
By default, this will get called automatically upon first interaction with the
Document collection (query, save, etc) so unless you disabled `auto_create_index`, you
shouldn't have to call this manually.
.. note:: You can disable automatic index creation by setting .. note:: You can disable automatic index creation by setting
`auto_create_index` to False in the documents meta data `auto_create_index` to False in the documents meta data
""" """
@ -928,8 +928,10 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
@classmethod @classmethod
def list_indexes(cls): def list_indexes(cls):
"""Lists all of the indexes that should be created for given """Lists all indexes that should be created for the Document collection.
collection. It includes all the indexes from super- and sub-classes. It includes all the indexes from super- and sub-classes.
Note that it will only return the indexes' fields, not the indexes' options
""" """
if cls._meta.get("abstract"): if cls._meta.get("abstract"):
return [] return []
@ -1088,8 +1090,6 @@ class MapReduceDocument:
an ``ObjectId`` found in the given ``collection``, an ``ObjectId`` found in the given ``collection``,
the object can be accessed via the ``object`` property. the object can be accessed via the ``object`` property.
:param value: The result(s) for this key. :param value: The result(s) for this key.
.. versionadded:: 0.3
""" """
def __init__(self, document, collection, key, value): def __init__(self, document, collection, key, value):

View File

@ -1,6 +1,5 @@
from collections import defaultdict from collections import defaultdict
__all__ = ( __all__ = (
"NotRegistered", "NotRegistered",
"InvalidDocumentError", "InvalidDocumentError",
@ -18,11 +17,15 @@ __all__ = (
) )
class NotRegistered(Exception): class MongoEngineException(Exception):
pass pass
class InvalidDocumentError(Exception): class NotRegistered(MongoEngineException):
pass
class InvalidDocumentError(MongoEngineException):
pass pass
@ -30,19 +33,19 @@ class LookUpError(AttributeError):
pass pass
class DoesNotExist(Exception): class DoesNotExist(MongoEngineException):
pass pass
class MultipleObjectsReturned(Exception): class MultipleObjectsReturned(MongoEngineException):
pass pass
class InvalidQueryError(Exception): class InvalidQueryError(MongoEngineException):
pass pass
class OperationError(Exception): class OperationError(MongoEngineException):
pass pass
@ -58,7 +61,7 @@ class SaveConditionError(OperationError):
pass pass
class FieldDoesNotExist(Exception): class FieldDoesNotExist(MongoEngineException):
"""Raised when trying to set a field """Raised when trying to set a field
not declared in a :class:`~mongoengine.Document` not declared in a :class:`~mongoengine.Document`
or an :class:`~mongoengine.EmbeddedDocument`. or an :class:`~mongoengine.EmbeddedDocument`.
@ -94,7 +97,7 @@ class ValidationError(AssertionError):
return str(self.message) return str(self.message)
def __repr__(self): def __repr__(self):
return "{}({},)".format(self.__class__.__name__, self.message) return f"{self.__class__.__name__}({self.message},)"
def __getattribute__(self, name): def __getattribute__(self, name):
message = super().__getattribute__(name) message = super().__getattribute__(name)
@ -102,7 +105,7 @@ class ValidationError(AssertionError):
if self.field_name: if self.field_name:
message = "%s" % message message = "%s" % message
if self.errors: if self.errors:
message = "{}({})".format(message, self._format_errors()) message = f"{message}({self._format_errors()})"
return message return message
def _get_message(self): def _get_message(self):
@ -147,16 +150,16 @@ class ValidationError(AssertionError):
elif isinstance(value, dict): elif isinstance(value, dict):
value = " ".join([generate_key(v, k) for k, v in value.items()]) value = " ".join([generate_key(v, k) for k, v in value.items()])
results = "{}.{}".format(prefix, value) if prefix else value results = f"{prefix}.{value}" if prefix else value
return results return results
error_dict = defaultdict(list) error_dict = defaultdict(list)
for k, v in self.to_dict().items(): for k, v in self.to_dict().items():
error_dict[generate_key(v)].append(k) error_dict[generate_key(v)].append(k)
return " ".join(["{}: {}".format(k, v) for k, v in error_dict.items()]) 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""" """Raise when a user uses a feature that has been Deprecated"""
pass pass

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
@ -8,10 +9,10 @@ import uuid
from io import BytesIO from io import BytesIO
from operator import itemgetter from operator import itemgetter
from bson import Binary, DBRef, ObjectId, SON
from bson.int64 import Int64
import gridfs import gridfs
import pymongo import pymongo
from bson import SON, Binary, DBRef, ObjectId
from bson.int64 import Int64
from pymongo import ReturnDocument from pymongo import ReturnDocument
try: try:
@ -21,7 +22,6 @@ except ImportError:
else: else:
import dateutil.parser import dateutil.parser
from mongoengine.base import ( from mongoengine.base import (
BaseDocument, BaseDocument,
BaseField, BaseField,
@ -35,8 +35,11 @@ from mongoengine.base.utils import LazyRegexCompiler
from mongoengine.common import _import_class from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.document import Document, EmbeddedDocument from mongoengine.document import Document, EmbeddedDocument
from mongoengine.errors import DoesNotExist, InvalidQueryError, ValidationError from mongoengine.errors import (
from mongoengine.mongodb_support import MONGODB_36, get_mongodb_version DoesNotExist,
InvalidQueryError,
ValidationError,
)
from mongoengine.queryset import DO_NOTHING from mongoengine.queryset import DO_NOTHING
from mongoengine.queryset.base import BaseQuerySet from mongoengine.queryset.base import BaseQuerySet
from mongoengine.queryset.transform import STRING_OPERATORS from mongoengine.queryset.transform import STRING_OPERATORS
@ -101,6 +104,12 @@ class StringField(BaseField):
"""A unicode string field.""" """A unicode string field."""
def __init__(self, regex=None, max_length=None, min_length=None, **kwargs): def __init__(self, regex=None, max_length=None, min_length=None, **kwargs):
"""
:param regex: (optional) A string pattern that will be applied during validation
:param max_length: (optional) A max length that will be applied during validation
:param min_length: (optional) A min length that will be applied during validation
:param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.BaseField`
"""
self.regex = re.compile(regex) if regex else None self.regex = re.compile(regex) if regex else None
self.max_length = max_length self.max_length = max_length
self.min_length = min_length self.min_length = min_length
@ -148,18 +157,22 @@ class StringField(BaseField):
regex = r"%s$" regex = r"%s$"
elif op == "exact": elif op == "exact":
regex = r"^%s$" regex = r"^%s$"
elif op == "wholeword":
regex = r"\b%s\b"
elif op == "regex":
regex = value
# escape unsafe characters which could lead to a re.error if op == "regex":
value = re.escape(value) value = re.compile(regex, flags)
value = re.compile(regex % value, 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) return super().prepare_query_value(op, value)
class URLField(StringField): class URLField(StringField):
"""A field that validates input as an URL. """A field that validates input as an URL."""
.. versionadded:: 0.3
"""
_URL_REGEX = LazyRegexCompiler( _URL_REGEX = LazyRegexCompiler(
r"^(?:[a-z0-9\.\-]*)://" # scheme is validated separately r"^(?:[a-z0-9\.\-]*)://" # scheme is validated separately
@ -174,6 +187,11 @@ class URLField(StringField):
_URL_SCHEMES = ["http", "https", "ftp", "ftps"] _URL_SCHEMES = ["http", "https", "ftp", "ftps"]
def __init__(self, url_regex=None, schemes=None, **kwargs): def __init__(self, url_regex=None, schemes=None, **kwargs):
"""
:param url_regex: (optional) Overwrite the default regex used for validation
:param schemes: (optional) Overwrite the default URL schemes that are allowed
:param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.StringField`
"""
self.url_regex = url_regex or self._URL_REGEX self.url_regex = url_regex or self._URL_REGEX
self.schemes = schemes or self._URL_SCHEMES self.schemes = schemes or self._URL_SCHEMES
super().__init__(**kwargs) super().__init__(**kwargs)
@ -182,18 +200,15 @@ class URLField(StringField):
# Check first if the scheme is valid # Check first if the scheme is valid
scheme = value.split("://")[0].lower() scheme = value.split("://")[0].lower()
if scheme not in self.schemes: if scheme not in self.schemes:
self.error("Invalid scheme {} in URL: {}".format(scheme, value)) self.error(f"Invalid scheme {scheme} in URL: {value}")
# Then check full URL # Then check full URL
if not self.url_regex.match(value): if not self.url_regex.match(value):
self.error("Invalid URL: {}".format(value)) self.error(f"Invalid URL: {value}")
class EmailField(StringField): class EmailField(StringField):
"""A field that validates input as an email address. """A field that validates input as an email address."""
.. versionadded:: 0.4
"""
USER_REGEX = LazyRegexCompiler( USER_REGEX = LazyRegexCompiler(
# `dot-atom` defined in RFC 5322 Section 3.2.3. # `dot-atom` defined in RFC 5322 Section 3.2.3.
@ -227,18 +242,13 @@ class EmailField(StringField):
allow_utf8_user=False, allow_utf8_user=False,
allow_ip_domain=False, allow_ip_domain=False,
*args, *args,
**kwargs **kwargs,
): ):
"""Initialize the EmailField. """
:param domain_whitelist: (optional) list of valid domain names applied during validation
Args: :param allow_utf8_user: Allow user part of the email to contain utf8 char
domain_whitelist (list) - list of otherwise invalid domain :param allow_ip_domain: Allow domain part of the email to be an IPv4 or IPv6 address
names which you'd like to support. :param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.StringField`
allow_utf8_user (bool) - if True, the user part of the email
address can contain UTF8 characters.
False by default.
allow_ip_domain (bool) - if True, the domain part of the email
can be a valid IPv4 or IPv6 address.
""" """
self.domain_whitelist = domain_whitelist or [] self.domain_whitelist = domain_whitelist or []
self.allow_utf8_user = allow_utf8_user self.allow_utf8_user = allow_utf8_user
@ -310,6 +320,11 @@ class IntField(BaseField):
"""32-bit integer field.""" """32-bit integer field."""
def __init__(self, min_value=None, max_value=None, **kwargs): def __init__(self, min_value=None, max_value=None, **kwargs):
"""
:param min_value: (optional) A min value that will be applied during validation
:param max_value: (optional) A max value that will be applied during validation
:param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.BaseField`
"""
self.min_value, self.max_value = min_value, max_value self.min_value, self.max_value = min_value, max_value
super().__init__(**kwargs) super().__init__(**kwargs)
@ -343,6 +358,11 @@ class LongField(BaseField):
"""64-bit integer field. (Equivalent to IntField since the support to Python2 was dropped)""" """64-bit integer field. (Equivalent to IntField since the support to Python2 was dropped)"""
def __init__(self, min_value=None, max_value=None, **kwargs): def __init__(self, min_value=None, max_value=None, **kwargs):
"""
:param min_value: (optional) A min value that will be applied during validation
:param max_value: (optional) A max value that will be applied during validation
:param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.BaseField`
"""
self.min_value, self.max_value = min_value, max_value self.min_value, self.max_value = min_value, max_value
super().__init__(**kwargs) super().__init__(**kwargs)
@ -379,6 +399,11 @@ class FloatField(BaseField):
"""Floating point number field.""" """Floating point number field."""
def __init__(self, min_value=None, max_value=None, **kwargs): def __init__(self, min_value=None, max_value=None, **kwargs):
"""
:param min_value: (optional) A min value that will be applied during validation
:param max_value: (optional) A max value that will be applied during validation
:param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.BaseField`
"""
self.min_value, self.max_value = min_value, max_value self.min_value, self.max_value = min_value, max_value
super().__init__(**kwargs) super().__init__(**kwargs)
@ -415,9 +440,6 @@ class FloatField(BaseField):
class DecimalField(BaseField): class DecimalField(BaseField):
"""Fixed-point decimal number field. Stores the value as a float by default unless `force_string` is used. """Fixed-point decimal number field. Stores the value as a float by default unless `force_string` is used.
If using floats, beware of Decimal to float conversion (potential precision loss) If using floats, beware of Decimal to float conversion (potential precision loss)
.. versionchanged:: 0.8
.. versionadded:: 0.3
""" """
def __init__( def __init__(
@ -427,11 +449,11 @@ class DecimalField(BaseField):
force_string=False, force_string=False,
precision=2, precision=2,
rounding=decimal.ROUND_HALF_UP, rounding=decimal.ROUND_HALF_UP,
**kwargs **kwargs,
): ):
""" """
:param min_value: Validation rule for the minimum acceptable value. :param min_value: (optional) A min value that will be applied during validation
:param max_value: Validation rule for the maximum acceptable value. :param max_value: (optional) A max value that will be applied during validation
:param force_string: Store the value as a string (instead of a float). :param force_string: Store the value as a string (instead of a float).
Be aware that this affects query sorting and operation like lte, gte (as string comparison is applied) Be aware that this affects query sorting and operation like lte, gte (as string comparison is applied)
and some query operator won't work (e.g. inc, dec) and some query operator won't work (e.g. inc, dec)
@ -448,11 +470,15 @@ class DecimalField(BaseField):
- decimal.ROUND_05UP (away from zero if last digit after rounding towards zero would have been 0 or 5; otherwise towards zero) - decimal.ROUND_05UP (away from zero if last digit after rounding towards zero would have been 0 or 5; otherwise towards zero)
Defaults to: ``decimal.ROUND_HALF_UP`` Defaults to: ``decimal.ROUND_HALF_UP``
:param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.BaseField`
""" """
self.min_value = min_value self.min_value = min_value
self.max_value = max_value self.max_value = max_value
self.force_string = force_string self.force_string = force_string
if precision < 0 or not isinstance(precision, int):
self.error("precision must be a positive integer")
self.precision = precision self.precision = precision
self.rounding = rounding self.rounding = rounding
@ -467,9 +493,12 @@ class DecimalField(BaseField):
value = decimal.Decimal("%s" % value) value = decimal.Decimal("%s" % value)
except (TypeError, ValueError, decimal.InvalidOperation): except (TypeError, ValueError, decimal.InvalidOperation):
return value return value
return value.quantize( if self.precision > 0:
decimal.Decimal(".%s" % ("0" * self.precision)), rounding=self.rounding 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): def to_mongo(self, value):
if value is None: if value is None:
@ -498,15 +527,12 @@ class DecimalField(BaseField):
class BooleanField(BaseField): class BooleanField(BaseField):
"""Boolean field type. """Boolean field type."""
.. versionadded:: 0.1.2
"""
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
@ -546,12 +572,13 @@ class DateTimeField(BaseField):
if callable(value): if callable(value):
return value() return value()
if not isinstance(value, str): if isinstance(value, str):
return self._parse_datetime(value)
else:
return None return None
return self._parse_datetime(value) @staticmethod
def _parse_datetime(value):
def _parse_datetime(self, value):
# Attempt to parse a datetime from a string # Attempt to parse a datetime from a string
value = value.strip() value = value.strip()
if not value: if not value:
@ -627,13 +654,12 @@ class ComplexDateTimeField(StringField):
keyword when initializing the field. keyword when initializing the field.
Note: To default the field to the current datetime, use: DateTimeField(default=datetime.utcnow) Note: To default the field to the current datetime, use: DateTimeField(default=datetime.utcnow)
.. versionadded:: 0.5
""" """
def __init__(self, separator=",", **kwargs): def __init__(self, separator=",", **kwargs):
""" """
:param separator: Allows to customize the separator used for storage (default ``,``) :param separator: Allows to customize the separator used for storage (default ``,``)
:param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.StringField`
""" """
self.separator = separator self.separator = separator
self.format = separator.join(["%Y", "%m", "%d", "%H", "%M", "%S", "%f"]) self.format = separator.join(["%Y", "%m", "%d", "%H", "%M", "%S", "%f"])
@ -906,17 +932,16 @@ class ListField(ComplexBaseField):
"""A list field that wraps a standard field, allowing multiple instances """A list field that wraps a standard field, allowing multiple instances
of the field to be used as a list in the database. of the field to be used as a list in the database.
If using with ReferenceFields see: :ref:`one-to-many-with-listfields` If using with ReferenceFields see: :ref:`many-to-many-with-listfields`
.. note:: .. note::
Required means it cannot be empty - as the default for ListFields is [] Required means it cannot be empty - as the default for ListFields is []
""" """
def __init__(self, field=None, max_length=None, **kwargs): def __init__(self, field=None, max_length=None, **kwargs):
self.field = field
self.max_length = max_length self.max_length = max_length
kwargs.setdefault("default", lambda: []) kwargs.setdefault("default", lambda: [])
super().__init__(**kwargs) super().__init__(field=field, **kwargs)
def __get__(self, instance, owner): def __get__(self, instance, owner):
if instance is None: if instance is None:
@ -975,16 +1000,13 @@ class EmbeddedDocumentListField(ListField):
.. note:: .. note::
The only valid list values are subclasses of The only valid list values are subclasses of
:class:`~mongoengine.EmbeddedDocument`. :class:`~mongoengine.EmbeddedDocument`.
.. versionadded:: 0.9
""" """
def __init__(self, document_type, **kwargs): def __init__(self, document_type, **kwargs):
""" """
:param document_type: The type of :param document_type: The type of
:class:`~mongoengine.EmbeddedDocument` the list will hold. :class:`~mongoengine.EmbeddedDocument` the list will hold.
:param kwargs: Keyword arguments passed directly into the parent :param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.ListField`
:class:`~mongoengine.ListField`.
""" """
super().__init__(field=EmbeddedDocumentField(document_type), **kwargs) super().__init__(field=EmbeddedDocumentField(document_type), **kwargs)
@ -999,19 +1021,11 @@ class SortedListField(ListField):
save the whole list then other processes trying to save the whole list save the whole list then other processes trying to save the whole list
as well could overwrite changes. The safest way to append to a list is as well could overwrite changes. The safest way to append to a list is
to perform a push operation. to perform a push operation.
.. versionadded:: 0.4
.. versionchanged:: 0.6 - added reverse keyword
""" """
_ordering = None
_order_reverse = False
def __init__(self, field, **kwargs): def __init__(self, field, **kwargs):
if "ordering" in kwargs.keys(): self._ordering = kwargs.pop("ordering", None)
self._ordering = kwargs.pop("ordering") self._order_reverse = kwargs.pop("reverse", False)
if "reverse" in kwargs.keys():
self._order_reverse = kwargs.pop("reverse")
super().__init__(field, **kwargs) super().__init__(field, **kwargs)
def to_mongo(self, value, use_db_field=True, fields=None): def to_mongo(self, value, use_db_field=True, fields=None):
@ -1032,17 +1046,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
@ -1058,17 +1061,13 @@ class DictField(ComplexBaseField):
.. note:: .. note::
Required means it cannot be empty - as the default for DictFields is {} Required means it cannot be empty - as the default for DictFields is {}
.. versionadded:: 0.3
.. versionchanged:: 0.5 - Can now handle complex / varying types of data
""" """
def __init__(self, field=None, *args, **kwargs): def __init__(self, field=None, *args, **kwargs):
self.field = field
self._auto_dereference = False self._auto_dereference = False
kwargs.setdefault("default", lambda: {}) kwargs.setdefault("default", lambda: {})
super().__init__(*args, **kwargs) super().__init__(*args, field=field, **kwargs)
def validate(self, value): def validate(self, value):
"""Make sure that a list of valid fields is being used.""" """Make sure that a list of valid fields is being used."""
@ -1094,16 +1093,7 @@ class DictField(ComplexBaseField):
return DictField(db_field=member_name) return DictField(db_field=member_name)
def prepare_query_value(self, op, value): def prepare_query_value(self, op, value):
match_operators = [ match_operators = [*STRING_OPERATORS]
"contains",
"icontains",
"startswith",
"istartswith",
"endswith",
"iendswith",
"exact",
"iexact",
]
if op in match_operators and isinstance(value, str): if op in match_operators and isinstance(value, str):
return StringField().prepare_query_value(op, value) return StringField().prepare_query_value(op, value)
@ -1124,8 +1114,6 @@ class MapField(DictField):
"""A field that maps a name to a specified field type. Similar to """A field that maps a name to a specified field type. Similar to
a DictField, except the 'value' of each item must match the specified a DictField, except the 'value' of each item must match the specified
field type. field type.
.. versionadded:: 0.5
""" """
def __init__(self, field=None, *args, **kwargs): def __init__(self, field=None, *args, **kwargs):
@ -1173,8 +1161,6 @@ class ReferenceField(BaseField):
org = ReferenceField('Org', reverse_delete_rule=CASCADE) org = ReferenceField('Org', reverse_delete_rule=CASCADE)
User.register_delete_rule(Org, 'owner', DENY) User.register_delete_rule(Org, 'owner', DENY)
.. versionchanged:: 0.5 added `reverse_delete_rule`
""" """
def __init__( def __init__(
@ -1182,10 +1168,12 @@ class ReferenceField(BaseField):
): ):
"""Initialises the Reference Field. """Initialises the Reference Field.
: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`
.. note :: .. note ::
A reference to an abstract document type is always stored as a A reference to an abstract document type is always stored as a
@ -1214,6 +1202,14 @@ class ReferenceField(BaseField):
self.document_type_obj = get_document(self.document_type_obj) self.document_type_obj = get_document(self.document_type_obj)
return self.document_type_obj return self.document_type_obj
@staticmethod
def _lazy_load_ref(ref_cls, dbref):
dereferenced_son = ref_cls._get_db().dereference(dbref)
if dereferenced_son is None:
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
return ref_cls._from_son(dereferenced_son)
def __get__(self, instance, owner): def __get__(self, instance, owner):
"""Descriptor to allow lazy dereferencing.""" """Descriptor to allow lazy dereferencing."""
if instance is None: if instance is None:
@ -1221,20 +1217,17 @@ class ReferenceField(BaseField):
return self return self
# Get value from document instance if available # 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 auto_dereference = instance._fields[self.name]._auto_dereference
# Dereference DBRefs # Dereference DBRefs
if auto_dereference and isinstance(value, DBRef): if auto_dereference and isinstance(ref_value, DBRef):
if hasattr(value, "cls"): if hasattr(ref_value, "cls"):
# Dereference using the class type specified in the reference # Dereference using the class type specified in the reference
cls = get_document(value.cls) cls = get_document(ref_value.cls)
else: else:
cls = self.document_type cls = self.document_type
dereferenced = cls._get_db().dereference(value)
if dereferenced is None: instance._data[self.name] = self._lazy_load_ref(cls, ref_value)
raise DoesNotExist("Trying to dereference unknown document %s" % value)
else:
instance._data[self.name] = cls._from_son(dereferenced)
return super().__get__(instance, owner) return super().__get__(instance, owner)
@ -1307,24 +1300,22 @@ class ReferenceField(BaseField):
class CachedReferenceField(BaseField): class CachedReferenceField(BaseField):
""" """A referencefield with cache fields to purpose pseudo-joins"""
A referencefield with cache fields to purpose pseudo-joins
.. versionadded:: 0.9
"""
def __init__(self, document_type, fields=None, auto_sync=True, **kwargs): def __init__(self, document_type, fields=None, auto_sync=True, **kwargs):
"""Initialises the Cached Reference Field. """Initialises the Cached Reference Field.
:param document_type: The type of Document that will be referenced
:param fields: A list of fields to be cached in document :param fields: A list of fields to be cached in document
:param auto_sync: if True documents are auto updated. :param auto_sync: if True documents are auto updated
:param kwargs: Keyword arguments passed into the parent :class:`~mongoengine.BaseField`
""" """
if fields is None: if fields is None:
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"
@ -1346,7 +1337,7 @@ class CachedReferenceField(BaseField):
return None return None
update_kwargs = { update_kwargs = {
"set__{}__{}".format(self.name, key): val f"set__{self.name}__{key}": val
for key, val in document._delta()[0].items() for key, val in document._delta()[0].items()
if key in self.fields if key in self.fields
} }
@ -1375,6 +1366,14 @@ class CachedReferenceField(BaseField):
self.document_type_obj = get_document(self.document_type_obj) self.document_type_obj = get_document(self.document_type_obj)
return self.document_type_obj return self.document_type_obj
@staticmethod
def _lazy_load_ref(ref_cls, dbref):
dereferenced_son = ref_cls._get_db().dereference(dbref)
if dereferenced_son is None:
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
return ref_cls._from_son(dereferenced_son)
def __get__(self, instance, owner): def __get__(self, instance, owner):
if instance is None: if instance is None:
# Document class being used rather than a document object # Document class being used rather than a document object
@ -1386,11 +1385,7 @@ class CachedReferenceField(BaseField):
# Dereference DBRefs # Dereference DBRefs
if auto_dereference and isinstance(value, DBRef): if auto_dereference and isinstance(value, DBRef):
dereferenced = self.document_type._get_db().dereference(value) instance._data[self.name] = self._lazy_load_ref(self.document_type, value)
if dereferenced is None:
raise DoesNotExist("Trying to dereference unknown document %s" % value)
else:
instance._data[self.name] = self.document_type._from_son(dereferenced)
return super().__get__(instance, owner) return super().__get__(instance, owner)
@ -1485,8 +1480,6 @@ class GenericReferenceField(BaseField):
it. it.
* You can use the choices param to limit the acceptable Document types * You can use the choices param to limit the acceptable Document types
.. versionadded:: 0.3
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -1517,6 +1510,14 @@ class GenericReferenceField(BaseField):
value = value._class_name value = value._class_name
super()._validate_choices(value) super()._validate_choices(value)
@staticmethod
def _lazy_load_ref(ref_cls, dbref):
dereferenced_son = ref_cls._get_db().dereference(dbref)
if dereferenced_son is None:
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
return ref_cls._from_son(dereferenced_son)
def __get__(self, instance, owner): def __get__(self, instance, owner):
if instance is None: if instance is None:
return self return self
@ -1524,12 +1525,9 @@ class GenericReferenceField(BaseField):
value = instance._data.get(self.name) value = instance._data.get(self.name)
auto_dereference = instance._fields[self.name]._auto_dereference auto_dereference = instance._fields[self.name]._auto_dereference
if auto_dereference and isinstance(value, (dict, SON)): if auto_dereference and isinstance(value, dict):
dereferenced = self.dereference(value) doc_cls = get_document(value["_cls"])
if dereferenced is None: instance._data[self.name] = self._lazy_load_ref(doc_cls, value["_ref"])
raise DoesNotExist("Trying to dereference unknown document %s" % value)
else:
instance._data[self.name] = dereferenced
return super().__get__(instance, owner) return super().__get__(instance, owner)
@ -1548,14 +1546,6 @@ class GenericReferenceField(BaseField):
" saved to the database" " saved to the database"
) )
def dereference(self, value):
doc_cls = get_document(value["_cls"])
reference = value["_ref"]
doc = doc_cls._get_db().dereference(reference)
if doc is not None:
doc = doc_cls._from_son(doc)
return doc
def to_mongo(self, document): def to_mongo(self, document):
if document is None: if document is None:
return None return None
@ -1626,11 +1616,14 @@ class EnumField(BaseField):
"""Enumeration Field. Values are stored underneath as is, """Enumeration Field. Values are stored underneath as is,
so it will only work with simple types (str, int, etc) that so it will only work with simple types (str, int, etc) that
are bson encodable are bson encodable
Example usage:
Example usage:
.. code-block:: python .. code-block:: python
class Status(Enum): class Status(Enum):
NEW = 'new' NEW = 'new'
ONGOING = 'ongoing'
DONE = 'done' DONE = 'done'
class ModelWithEnum(Document): class ModelWithEnum(Document):
@ -1640,23 +1633,31 @@ class EnumField(BaseField):
ModelWithEnum(status=Status.DONE) ModelWithEnum(status=Status.DONE)
Enum fields can be searched using enum or its value: Enum fields can be searched using enum or its value:
.. code-block:: python .. code-block:: python
ModelWithEnum.objects(status='new').count() ModelWithEnum.objects(status='new').count()
ModelWithEnum.objects(status=Status.NEW).count() ModelWithEnum.objects(status=Status.NEW).count()
Note that choices cannot be set explicitly, they are derived The values can be restricted to a subset of the enum by using the ``choices`` parameter:
from the provided enum class.
.. code-block:: python
class ModelWithEnum(Document):
status = EnumField(Status, choices=[Status.NEW, Status.DONE])
""" """
def __init__(self, enum, **kwargs): def __init__(self, enum, **kwargs):
self._enum_cls = enum self._enum_cls = enum
if "choices" in kwargs: if kwargs.get("choices"):
raise ValueError( invalid_choices = []
"'choices' can't be set on EnumField, " for choice in kwargs["choices"]:
"it is implicitly set as the enum class" if not isinstance(choice, enum):
) invalid_choices.append(choice)
kwargs["choices"] = list(self._enum_cls) if invalid_choices:
raise ValueError("Invalid choices: %r" % invalid_choices)
else:
kwargs["choices"] = list(self._enum_cls) # Implicit validator
super().__init__(**kwargs) super().__init__(**kwargs)
def __set__(self, instance, value): def __set__(self, instance, value):
@ -1673,13 +1674,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
@ -1691,12 +1685,7 @@ class GridFSError(Exception):
class GridFSProxy: class GridFSProxy:
"""Proxy object to handle writing and reading of files to and from GridFS """Proxy object to handle writing and reading of files to and from GridFS"""
.. versionadded:: 0.4
.. versionchanged:: 0.5 - added optional size param to read
.. versionchanged:: 0.6 - added collection name param
"""
_fs = None _fs = None
@ -1754,12 +1743,12 @@ class GridFSProxy:
return self.__copy__() return self.__copy__()
def __repr__(self): def __repr__(self):
return "<{}: {}>".format(self.__class__.__name__, self.grid_id) return f"<{self.__class__.__name__}: {self.grid_id}>"
def __str__(self): def __str__(self):
gridout = self.get() gridout = self.get()
filename = getattr(gridout, "filename") if gridout else "<no file>" filename = gridout.filename if gridout else "<no file>"
return "<{}: {} ({})>".format(self.__class__.__name__, filename, self.grid_id) return f"<{self.__class__.__name__}: {filename} ({self.grid_id})>"
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, GridFSProxy): if isinstance(other, GridFSProxy):
@ -1858,12 +1847,7 @@ class GridFSProxy:
class FileField(BaseField): class FileField(BaseField):
"""A GridFS storage field. """A GridFS storage field."""
.. versionadded:: 0.4
.. versionchanged:: 0.5 added optional size param for read
.. versionchanged:: 0.6 added db_alias for multidb support
"""
proxy_class = GridFSProxy proxy_class = GridFSProxy
@ -1945,11 +1929,7 @@ class FileField(BaseField):
class ImageGridFsProxy(GridFSProxy): class ImageGridFsProxy(GridFSProxy):
""" """Proxy for ImageField"""
Proxy for ImageField
versionadded: 0.6
"""
def put(self, file_obj, **kwargs): def put(self, file_obj, **kwargs):
""" """
@ -2083,8 +2063,6 @@ class ImageField(FileField):
:param size: max size to store images, provided as (width, height, force) :param size: max size to store images, provided as (width, height, force)
if larger, it will be automatically resized (ex: size=(800, 600, True)) if larger, it will be automatically resized (ex: size=(800, 600, True))
:param thumbnail_size: size to generate a thumbnail, provided as (width, height, force) :param thumbnail_size: size to generate a thumbnail, provided as (width, height, force)
.. versionadded:: 0.6
""" """
proxy_class = ImageGridFsProxy proxy_class = ImageGridFsProxy
@ -2132,9 +2110,6 @@ class SequenceField(BaseField):
In case the counter is defined in the abstract document, it will be In case the counter is defined in the abstract document, it will be
common to all inherited documents and the default sequence name will common to all inherited documents and the default sequence name will
be the class name of the abstract document. be the class name of the abstract document.
.. versionadded:: 0.5
.. versionchanged:: 0.8 added `value_decorator`
""" """
_auto_gen = True _auto_gen = True
@ -2148,7 +2123,7 @@ class SequenceField(BaseField):
sequence_name=None, sequence_name=None,
value_decorator=None, value_decorator=None,
*args, *args,
**kwargs **kwargs,
): ):
self.collection_name = collection_name or self.COLLECTION_NAME self.collection_name = collection_name or self.COLLECTION_NAME
self.db_alias = db_alias or DEFAULT_CONNECTION_NAME self.db_alias = db_alias or DEFAULT_CONNECTION_NAME
@ -2163,7 +2138,7 @@ class SequenceField(BaseField):
Generate and Increment the counter Generate and Increment the counter
""" """
sequence_name = self.get_sequence_name() sequence_name = self.get_sequence_name()
sequence_id = "{}.{}".format(sequence_name, self.name) sequence_id = f"{sequence_name}.{self.name}"
collection = get_db(alias=self.db_alias)[self.collection_name] collection = get_db(alias=self.db_alias)[self.collection_name]
counter = collection.find_one_and_update( counter = collection.find_one_and_update(
@ -2177,7 +2152,7 @@ class SequenceField(BaseField):
def set_next_value(self, value): def set_next_value(self, value):
"""Helper method to set the next sequence value""" """Helper method to set the next sequence value"""
sequence_name = self.get_sequence_name() sequence_name = self.get_sequence_name()
sequence_id = "{}.{}".format(sequence_name, self.name) sequence_id = f"{sequence_name}.{self.name}"
collection = get_db(alias=self.db_alias)[self.collection_name] collection = get_db(alias=self.db_alias)[self.collection_name]
counter = collection.find_one_and_update( counter = collection.find_one_and_update(
filter={"_id": sequence_id}, filter={"_id": sequence_id},
@ -2194,7 +2169,7 @@ class SequenceField(BaseField):
as it is only fixed on set. as it is only fixed on set.
""" """
sequence_name = self.get_sequence_name() sequence_name = self.get_sequence_name()
sequence_id = "{}.{}".format(sequence_name, self.name) sequence_id = f"{sequence_name}.{self.name}"
collection = get_db(alias=self.db_alias)[self.collection_name] collection = get_db(alias=self.db_alias)[self.collection_name]
data = collection.find_one({"_id": sequence_id}) data = collection.find_one({"_id": sequence_id})
@ -2247,10 +2222,7 @@ class SequenceField(BaseField):
class UUIDField(BaseField): class UUIDField(BaseField):
"""A UUID field. """A UUID field."""
.. versionadded:: 0.6
"""
_binary = None _binary = None
@ -2259,9 +2231,6 @@ class UUIDField(BaseField):
Store UUID data in the database Store UUID data in the database
:param binary: if False store as a string. :param binary: if False store as a string.
.. versionchanged:: 0.8.0
.. versionchanged:: 0.6.19
""" """
self._binary = binary self._binary = binary
super().__init__(**kwargs) super().__init__(**kwargs)
@ -2306,8 +2275,6 @@ class GeoPointField(BaseField):
representing a geo point. It admits 2d indexes but not "2dsphere" indexes representing a geo point. It admits 2d indexes but not "2dsphere" indexes
in MongoDB > 2.4 which are more natural for modeling geospatial points. in MongoDB > 2.4 which are more natural for modeling geospatial points.
See :ref:`geospatial-indexes` See :ref:`geospatial-indexes`
.. versionadded:: 0.4
""" """
_geo_index = pymongo.GEO2D _geo_index = pymongo.GEO2D
@ -2339,8 +2306,6 @@ class PointField(GeoJsonBaseField):
to set the value. to set the value.
Requires mongodb >= 2.4 Requires mongodb >= 2.4
.. versionadded:: 0.8
""" """
_type = "Point" _type = "Point"
@ -2359,8 +2324,6 @@ class LineStringField(GeoJsonBaseField):
You can either pass a dict with the full information or a list of points. You can either pass a dict with the full information or a list of points.
Requires mongodb >= 2.4 Requires mongodb >= 2.4
.. versionadded:: 0.8
""" """
_type = "LineString" _type = "LineString"
@ -2382,8 +2345,6 @@ class PolygonField(GeoJsonBaseField):
holes. holes.
Requires mongodb >= 2.4 Requires mongodb >= 2.4
.. versionadded:: 0.8
""" """
_type = "Polygon" _type = "Polygon"
@ -2403,8 +2364,6 @@ class MultiPointField(GeoJsonBaseField):
to set the value. to set the value.
Requires mongodb >= 2.6 Requires mongodb >= 2.6
.. versionadded:: 0.9
""" """
_type = "MultiPoint" _type = "MultiPoint"
@ -2424,8 +2383,6 @@ class MultiLineStringField(GeoJsonBaseField):
You can either pass a dict with the full information or a list of points. You can either pass a dict with the full information or a list of points.
Requires mongodb >= 2.6 Requires mongodb >= 2.6
.. versionadded:: 0.9
""" """
_type = "MultiLineString" _type = "MultiLineString"
@ -2452,8 +2409,6 @@ class MultiPolygonField(GeoJsonBaseField):
of Polygons. of Polygons.
Requires mongodb >= 2.6 Requires mongodb >= 2.6
.. versionadded:: 0.9
""" """
_type = "MultiPolygon" _type = "MultiPolygon"
@ -2466,8 +2421,6 @@ class LazyReferenceField(BaseField):
Instead, access will return a :class:`~mongoengine.base.LazyReference` class Instead, access will return a :class:`~mongoengine.base.LazyReference` class
instance, allowing access to `pk` or manual dereference by using instance, allowing access to `pk` or manual dereference by using
``fetch()`` method. ``fetch()`` method.
.. versionadded:: 0.15
""" """
def __init__( def __init__(
@ -2476,7 +2429,7 @@ class LazyReferenceField(BaseField):
passthrough=False, passthrough=False,
dbref=False, dbref=False,
reverse_delete_rule=DO_NOTHING, reverse_delete_rule=DO_NOTHING,
**kwargs **kwargs,
): ):
"""Initialises the Reference Field. """Initialises the Reference Field.
@ -2486,7 +2439,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.
@ -2570,6 +2523,7 @@ class LazyReferenceField(BaseField):
if not isinstance(value, (DBRef, Document, EmbeddedDocument)): if not isinstance(value, (DBRef, Document, EmbeddedDocument)):
collection = self.document_type._get_collection_name() collection = self.document_type._get_collection_name()
value = DBRef(collection, self.document_type.id.to_python(value)) value = DBRef(collection, self.document_type.id.to_python(value))
value = self.build_lazyref(value)
return value return value
def validate(self, value): def validate(self, value):
@ -2630,8 +2584,6 @@ class GenericLazyReferenceField(GenericReferenceField):
it. it.
* You can use the choices param to limit the acceptable Document types * You can use the choices param to limit the acceptable Document types
.. versionadded:: 0.15
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

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

View File

@ -14,8 +14,7 @@ IS_PYMONGO_GTE_37 = PYMONGO_VERSION >= _PYMONGO_37
def count_documents( def count_documents(
collection, filter, skip=None, limit=None, hint=None, collation=None collection, filter, skip=None, limit=None, hint=None, collation=None
): ):
"""Pymongo>3.7 deprecates count in favour of count_documents """Pymongo>3.7 deprecates count in favour of count_documents"""
"""
if limit == 0: if limit == 0:
return 0 # Pymongo raises an OperationFailure if called with limit=0 return 0 # Pymongo raises an OperationFailure if called with limit=0

View File

@ -2,13 +2,12 @@ import copy
import itertools import itertools
import re import re
import warnings import warnings
from collections.abc import Mapping from collections.abc import Mapping
from bson import SON, json_util
from bson.code import Code
import pymongo import pymongo
import pymongo.errors import pymongo.errors
from bson import SON, json_util
from bson.code import Code
from pymongo.collection import ReturnDocument from pymongo.collection import ReturnDocument
from pymongo.common import validate_read_preference from pymongo.common import validate_read_preference
from pymongo.read_concern import ReadConcern from pymongo.read_concern import ReadConcern
@ -34,7 +33,6 @@ from mongoengine.queryset import transform
from mongoengine.queryset.field_list import QueryFieldList from mongoengine.queryset.field_list import QueryFieldList
from mongoengine.queryset.visitor import Q, QNode from mongoengine.queryset.visitor import Q, QNode
__all__ = ("BaseQuerySet", "DO_NOTHING", "NULLIFY", "CASCADE", "DENY", "PULL") __all__ = ("BaseQuerySet", "DO_NOTHING", "NULLIFY", "CASCADE", "DENY", "PULL")
# Delete rules # Delete rules
@ -64,6 +62,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
@ -189,7 +188,8 @@ class BaseQuerySet:
if queryset._scalar: if queryset._scalar:
return queryset._get_scalar( return queryset._get_scalar(
queryset._document._from_son( queryset._document._from_son(
queryset._cursor[key], _auto_dereference=self._auto_dereference, queryset._cursor[key],
_auto_dereference=self._auto_dereference,
) )
) )
@ -197,7 +197,8 @@ class BaseQuerySet:
return queryset._cursor[key] return queryset._cursor[key]
return queryset._document._from_son( return queryset._document._from_son(
queryset._cursor[key], _auto_dereference=self._auto_dereference, queryset._cursor[key],
_auto_dereference=self._auto_dereference,
) )
raise TypeError("Provide a slice or an integer index") raise TypeError("Provide a slice or an integer index")
@ -256,8 +257,6 @@ class BaseQuerySet:
`DocumentName.MultipleObjectsReturned` exception if multiple results `DocumentName.MultipleObjectsReturned` exception if multiple results
and :class:`~mongoengine.queryset.DoesNotExist` or and :class:`~mongoengine.queryset.DoesNotExist` or
`DocumentName.DoesNotExist` if no results are found. `DocumentName.DoesNotExist` if no results are found.
.. versionadded:: 0.3
""" """
queryset = self.clone() queryset = self.clone()
queryset = queryset.order_by().limit(2) queryset = queryset.order_by().limit(2)
@ -281,10 +280,7 @@ class BaseQuerySet:
) )
def create(self, **kwargs): def create(self, **kwargs):
"""Create new object. Returns the saved object instance. """Create new object. Returns the saved object instance."""
.. versionadded:: 0.4
"""
return self._document(**kwargs).save(force_insert=True) return self._document(**kwargs).save(force_insert=True)
def first(self): def first(self):
@ -316,10 +312,6 @@ class BaseQuerySet:
By default returns document instances, set ``load_bulk`` to False to By default returns document instances, set ``load_bulk`` to False to
return just ``ObjectIds`` return just ``ObjectIds``
.. versionadded:: 0.5
.. versionchanged:: 0.10.7
Add signal_kwargs argument
""" """
Document = _import_class("Document") Document = _import_class("Document")
@ -427,8 +419,8 @@ class BaseQuerySet:
count = count_documents( count = count_documents(
collection=self._cursor.collection, collection=self._cursor.collection,
filter=self._cursor._Cursor__spec, filter=self._query,
**kwargs **kwargs,
) )
self._cursor_obj = None self._cursor_obj = None
@ -532,7 +524,7 @@ class BaseQuerySet:
write_concern=None, write_concern=None,
read_concern=None, read_concern=None,
full_result=False, full_result=False,
**update **update,
): ):
"""Perform an atomic update on the fields matched by the query. """Perform an atomic update on the fields matched by the query.
@ -550,8 +542,6 @@ class BaseQuerySet:
:param update: Django-style update keyword arguments :param update: Django-style update keyword arguments
:returns the number of updated documents (unless ``full_result`` is True) :returns the number of updated documents (unless ``full_result`` is True)
.. versionadded:: 0.2
""" """
if not update and not upsert: if not update and not upsert:
raise OperationError("No update parameters, would remove data") raise OperationError("No update parameters, would remove data")
@ -603,8 +593,6 @@ class BaseQuerySet:
:param update: Django-style update keyword arguments :param update: Django-style update keyword arguments
:returns the new or overwritten document :returns the new or overwritten document
.. versionadded:: 0.10.2
""" """
atomic_update = self.update( atomic_update = self.update(
@ -613,7 +601,7 @@ class BaseQuerySet:
write_concern=write_concern, write_concern=write_concern,
read_concern=read_concern, read_concern=read_concern,
full_result=True, full_result=True,
**update **update,
) )
if atomic_update.raw_result["updatedExisting"]: if atomic_update.raw_result["updatedExisting"]:
@ -638,14 +626,13 @@ class BaseQuerySet:
:param update: Django-style update keyword arguments :param update: Django-style update keyword arguments
full_result full_result
:returns the number of updated documents (unless ``full_result`` is True) :returns the number of updated documents (unless ``full_result`` is True)
.. versionadded:: 0.2
""" """
return self.update( return self.update(
upsert=upsert, upsert=upsert,
multi=False, multi=False,
write_concern=write_concern, write_concern=write_concern,
full_result=full_result, full_result=full_result,
**update **update,
) )
def modify( def modify(
@ -670,8 +657,6 @@ class BaseQuerySet:
:param new: return updated rather than original document :param new: return updated rather than original document
(default ``False``) (default ``False``)
:param update: Django-style update keyword arguments :param update: Django-style update keyword arguments
.. versionadded:: 0.9
""" """
if remove and new: if remove and new:
@ -705,7 +690,7 @@ class BaseQuerySet:
upsert=upsert, upsert=upsert,
sort=sort, sort=sort,
return_document=return_doc, return_document=return_doc,
**self._cursor_args **self._cursor_args,
) )
except pymongo.errors.DuplicateKeyError as err: except pymongo.errors.DuplicateKeyError as err:
raise NotUniqueError("Update failed (%s)" % err) raise NotUniqueError("Update failed (%s)" % err)
@ -727,8 +712,6 @@ class BaseQuerySet:
`None` if no document exists with that id. `None` if no document exists with that id.
:param object_id: the value for the id of the document to look up :param object_id: the value for the id of the document to look up
.. versionchanged:: 0.6 Raises InvalidQueryError if filter has been set
""" """
queryset = self.clone() queryset = self.clone()
if not queryset._query_obj.empty: if not queryset._query_obj.empty:
@ -737,13 +720,11 @@ class BaseQuerySet:
return queryset.filter(pk=object_id).first() return queryset.filter(pk=object_id).first()
def in_bulk(self, object_ids): def in_bulk(self, object_ids):
""""Retrieve a set of documents by their ids. """Retrieve a set of documents by their ids.
:param object_ids: a list or tuple of ObjectId's :param object_ids: a list or tuple of ObjectId's
:rtype: dict of ObjectId's as keys and collection-specific :rtype: dict of ObjectId's as keys and collection-specific
Document subclasses as values. Document subclasses as values.
.. versionadded:: 0.3
""" """
doc_map = {} doc_map = {}
@ -757,7 +738,8 @@ class BaseQuerySet:
else: else:
for doc in docs: for doc in docs:
doc_map[doc["_id"]] = self._document._from_son( doc_map[doc["_id"]] = self._document._from_son(
doc, _auto_dereference=self._auto_dereference, doc,
_auto_dereference=self._auto_dereference,
) )
return doc_map return doc_map
@ -785,8 +767,6 @@ class BaseQuerySet:
evaluated against if you are using more than one database. evaluated against if you are using more than one database.
:param alias: The database alias :param alias: The database alias
.. versionadded:: 0.9
""" """
with switch_db(self._document, alias) as cls: with switch_db(self._document, alias) as cls:
@ -818,6 +798,7 @@ class BaseQuerySet:
"_ordering", "_ordering",
"_snapshot", "_snapshot",
"_timeout", "_timeout",
"_allow_disk_use",
"_read_preference", "_read_preference",
"_read_concern", "_read_concern",
"_iter", "_iter",
@ -848,8 +829,6 @@ class BaseQuerySet:
"""Handles dereferencing of :class:`~bson.dbref.DBRef` objects or """Handles dereferencing of :class:`~bson.dbref.DBRef` objects or
:class:`~bson.object_id.ObjectId` a maximum depth in order to cut down :class:`~bson.object_id.ObjectId` a maximum depth in order to cut down
the number queries to mongodb. the number queries to mongodb.
.. versionadded:: 0.5
""" """
# Make select related work the same for querysets # Make select related work the same for querysets
max_depth += 1 max_depth += 1
@ -898,8 +877,6 @@ class BaseQuerySet:
Hinting will not do anything if the corresponding index does not exist. Hinting will not do anything if the corresponding index does not exist.
The last hint applied to this cursor takes precedence over all others. The last hint applied to this cursor takes precedence over all others.
.. versionadded:: 0.5
""" """
queryset = self.clone() queryset = self.clone()
queryset._hint = index queryset._hint = index
@ -961,10 +938,6 @@ class BaseQuerySet:
.. note:: This is a command and won't take ordering or limit into .. note:: This is a command and won't take ordering or limit into
account. account.
.. versionadded:: 0.4
.. versionchanged:: 0.5 - Fixed handling references
.. versionchanged:: 0.6 - Improved db_field refrence handling
""" """
queryset = self.clone() queryset = self.clone()
@ -1028,9 +1001,6 @@ class BaseQuerySet:
field filters. field filters.
:param fields: fields to include :param fields: fields to include
.. versionadded:: 0.3
.. versionchanged:: 0.5 - Added subfield support
""" """
fields = {f: QueryFieldList.ONLY for f in fields} fields = {f: QueryFieldList.ONLY for f in fields}
return self.fields(True, **fields) return self.fields(True, **fields)
@ -1049,8 +1019,6 @@ class BaseQuerySet:
field filters. field filters.
:param fields: fields to exclude :param fields: fields to exclude
.. versionadded:: 0.5
""" """
fields = {f: QueryFieldList.EXCLUDE for f in fields} fields = {f: QueryFieldList.EXCLUDE for f in fields}
return self.fields(**fields) return self.fields(**fields)
@ -1077,8 +1045,6 @@ class BaseQuerySet:
:param kwargs: A set of keyword arguments identifying what to :param kwargs: A set of keyword arguments identifying what to
include, exclude, or slice. include, exclude, or slice.
.. versionadded:: 0.5
""" """
# Check for an operator and transform to mongo-style if there is # Check for an operator and transform to mongo-style if there is
@ -1120,8 +1086,6 @@ class BaseQuerySet:
.exclude(). :: .exclude(). ::
post = BlogPost.objects.exclude('comments').all_fields() post = BlogPost.objects.exclude('comments').all_fields()
.. versionadded:: 0.5
""" """
queryset = self.clone() queryset = self.clone()
queryset._loaded_fields = QueryFieldList( queryset._loaded_fields = QueryFieldList(
@ -1194,9 +1158,6 @@ class BaseQuerySet:
"""Enable or disable snapshot mode when querying. """Enable or disable snapshot mode when querying.
:param enabled: whether or not snapshot mode is enabled :param enabled: whether or not snapshot mode is enabled
..versionchanged:: 0.5 - made chainable
.. deprecated:: Ignored with PyMongo 3+
""" """
msg = "snapshot is deprecated as it has no impact when using PyMongo 3+." msg = "snapshot is deprecated as it has no impact when using PyMongo 3+."
warnings.warn(msg, DeprecationWarning) warnings.warn(msg, DeprecationWarning)
@ -1204,12 +1165,20 @@ 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)
:param enabled: whether or not the timeout is used :param enabled: whether or not the timeout is used
..versionchanged:: 0.5 - made chainable
""" """
queryset = self.clone() queryset = self.clone()
queryset._timeout = enabled queryset._timeout = enabled
@ -1234,7 +1203,7 @@ class BaseQuerySet:
preference. preference.
""" """
if read_concern is not None and not isinstance(read_concern, Mapping): if read_concern is not None and not isinstance(read_concern, Mapping):
raise TypeError("%r is not a valid read concern." % (read_concern,)) raise TypeError(f"{read_concern!r} is not a valid read concern.")
queryset = self.clone() queryset = self.clone()
queryset._read_concern = ( queryset._read_concern = (
@ -1308,7 +1277,6 @@ class BaseQuerySet:
parameter will be removed shortly parameter will be removed shortly
:param kwargs: (optional) kwargs dictionary to be passed to pymongo's aggregate call :param kwargs: (optional) kwargs dictionary to be passed to pymongo's aggregate call
See https://api.mongodb.com/python/current/api/pymongo/collection.html#pymongo.collection.Collection.aggregate See https://api.mongodb.com/python/current/api/pymongo/collection.html#pymongo.collection.Collection.aggregate
.. versionadded:: 0.9
""" """
using_deprecated_interface = isinstance(pipeline, dict) or bool(suppl_pipeline) using_deprecated_interface = isinstance(pipeline, dict) or bool(suppl_pipeline)
user_pipeline = [pipeline] if isinstance(pipeline, dict) else list(pipeline) user_pipeline = [pipeline] if isinstance(pipeline, dict) else list(pipeline)
@ -1380,32 +1348,23 @@ class BaseQuerySet:
Map/Reduce changed in server version **>= 1.7.4**. The PyMongo Map/Reduce changed in server version **>= 1.7.4**. The PyMongo
:meth:`~pymongo.collection.Collection.map_reduce` helper requires :meth:`~pymongo.collection.Collection.map_reduce` helper requires
PyMongo version **>= 1.11**. PyMongo version **>= 1.11**.
.. versionchanged:: 0.5
- removed ``keep_temp`` keyword argument, which was only relevant
for MongoDB server versions older than 1.7.4
.. versionadded:: 0.3
""" """
queryset = self.clone() queryset = self.clone()
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}
@ -1415,7 +1374,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:
@ -1522,8 +1481,6 @@ class BaseQuerySet:
.. note:: When using this mode of query, the database will call your .. note:: When using this mode of query, the database will call your
function, or evaluate your predicate clause, for each object function, or evaluate your predicate clause, for each object
in the collection. in the collection.
.. versionadded:: 0.5
""" """
queryset = self.clone() queryset = self.clone()
where_clause = queryset._sub_js_fields(where_clause) where_clause = queryset._sub_js_fields(where_clause)
@ -1600,9 +1557,6 @@ class BaseQuerySet:
:param field: the field to use :param field: the field to use
:param normalize: normalize the results so they add to 1.0 :param normalize: normalize the results so they add to 1.0
:param map_reduce: Use map_reduce over exec_js :param map_reduce: Use map_reduce over exec_js
.. versionchanged:: 0.5 defaults to map_reduce and can handle embedded
document lookups
""" """
if map_reduce: if map_reduce:
return self._item_frequencies_map_reduce(field, normalize=normalize) return self._item_frequencies_map_reduce(field, normalize=normalize)
@ -1611,8 +1565,7 @@ class BaseQuerySet:
# Iterator helpers # Iterator helpers
def __next__(self): def __next__(self):
"""Wrap the result in a :class:`~mongoengine.Document` object. """Wrap the result in a :class:`~mongoengine.Document` object."""
"""
if self._none or self._empty: if self._none or self._empty:
raise StopIteration raise StopIteration
@ -1622,7 +1575,8 @@ class BaseQuerySet:
return raw_doc return raw_doc
doc = self._document._from_son( doc = self._document._from_son(
raw_doc, _auto_dereference=self._auto_dereference, raw_doc,
_auto_dereference=self._auto_dereference,
) )
if self._scalar: if self._scalar:
@ -1631,10 +1585,7 @@ class BaseQuerySet:
return doc return doc
def rewind(self): def rewind(self):
"""Rewind the cursor to its unevaluated state. """Rewind the cursor to its unevaluated state."""
.. versionadded:: 0.3
"""
self._iter = False self._iter = False
self._cursor.rewind() self._cursor.rewind()
@ -1660,6 +1611,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

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

View File

@ -1,11 +1,11 @@
from mongoengine.errors import OperationError from mongoengine.errors import OperationError
from mongoengine.queryset.base import ( from mongoengine.queryset.base import (
BaseQuerySet,
CASCADE, CASCADE,
DENY, DENY,
DO_NOTHING, DO_NOTHING,
NULLIFY, NULLIFY,
PULL, PULL,
BaseQuerySet,
) )
__all__ = ( __all__ = (
@ -150,10 +150,7 @@ class QuerySet(BaseQuerySet):
return self._len return self._len
def no_cache(self): def no_cache(self):
"""Convert to a non-caching queryset """Convert to a non-caching queryset"""
.. versionadded:: 0.8.3 Convert to non caching queryset
"""
if self._result_cache is not None: if self._result_cache is not None:
raise OperationError("QuerySet already cached") raise OperationError("QuerySet already cached")
@ -164,17 +161,11 @@ class QuerySetNoCache(BaseQuerySet):
"""A non caching QuerySet""" """A non caching QuerySet"""
def cache(self): def cache(self):
"""Convert to a caching queryset """Convert to a caching queryset"""
.. versionadded:: 0.8.3 Convert to caching queryset
"""
return self._clone_into(QuerySet(self._document, self._collection)) return self._clone_into(QuerySet(self._document, self._collection))
def __repr__(self): def __repr__(self):
"""Provides the string representation of the QuerySet """Provides the string representation of the QuerySet"""
.. versionchanged:: 0.6.13 Now doesnt modify the cursor
"""
if self._iter: if self._iter:
return ".. queryset mid-iteration .." return ".. queryset mid-iteration .."

View File

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

View File

@ -13,17 +13,14 @@ def warn_empty_is_deprecated():
class QNodeVisitor: class QNodeVisitor:
"""Base visitor class for visiting Q-object nodes in a query tree. """Base visitor class for visiting Q-object nodes in a query tree."""
"""
def visit_combination(self, combination): def visit_combination(self, combination):
"""Called by QCombination objects. """Called by QCombination objects."""
"""
return combination return combination
def visit_query(self, query): def visit_query(self, query):
"""Called by (New)Q objects. """Called by (New)Q objects."""
"""
return query return query
@ -49,8 +46,7 @@ class SimplificationVisitor(QNodeVisitor):
return combination return combination
def _query_conjunction(self, queries): def _query_conjunction(self, queries):
"""Merges query dicts - effectively &ing them together. """Merges query dicts - effectively &ing them together."""
"""
query_ops = set() query_ops = set()
combined_query = {} combined_query = {}
for query in queries: for query in queries:

View File

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

View File

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

View File

@ -7,7 +7,7 @@ from setuptools.command.test import test as TestCommand
# Hack to silence atexit traceback in newer python versions # Hack to silence atexit traceback in newer python versions
try: try:
import multiprocessing import multiprocessing # noqa: F401
except ImportError: except ImportError:
pass pass
@ -53,8 +53,8 @@ class PyTest(TestCommand):
def run_tests(self): def run_tests(self):
# import here, cause outside the eggs aren't loaded # import here, cause outside the eggs aren't loaded
from pkg_resources import _namespace_packages
import pytest import pytest
from pkg_resources import _namespace_packages
# Purge modules under test from sys.modules. The test loader will # Purge modules under test from sys.modules. The test loader will
# re-import them from the build location. Required when 2to3 is used # re-import them from the build location. Required when 2to3 is used
@ -98,10 +98,11 @@ CLASSIFIERS = [
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Database", "Topic :: Database",
@ -111,11 +112,11 @@ CLASSIFIERS = [
extra_opts = { extra_opts = {
"packages": find_packages(exclude=["tests", "tests.*"]), "packages": find_packages(exclude=["tests", "tests.*"]),
"tests_require": [ "tests_require": [
"pytest<5.0", "pytest",
"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>=2.0.0, <7.0.0", # 7.0.0 dropped Python2 support "Pillow>=7.0.0",
], ],
} }
@ -140,7 +141,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

@ -26,16 +26,14 @@ class TestClassMethods(unittest.TestCase):
self.db.drop_collection(collection) self.db.drop_collection(collection)
def test_definition(self): def test_definition(self):
"""Ensure that document may be defined using fields. """Ensure that document may be defined using fields."""
"""
assert ["_cls", "age", "id", "name"] == sorted(self.Person._fields.keys()) assert ["_cls", "age", "id", "name"] == sorted(self.Person._fields.keys())
assert ["IntField", "ObjectIdField", "StringField", "StringField"] == sorted( assert ["IntField", "ObjectIdField", "StringField", "StringField"] == sorted(
[x.__class__.__name__ for x in self.Person._fields.values()] x.__class__.__name__ for x in self.Person._fields.values()
) )
def test_get_db(self): def test_get_db(self):
"""Ensure that get_db returns the expected db. """Ensure that get_db returns the expected db."""
"""
db = self.Person._get_db() db = self.Person._get_db()
assert self.db == db assert self.db == db
@ -47,15 +45,13 @@ class TestClassMethods(unittest.TestCase):
assert collection_name == self.Person._get_collection_name() assert collection_name == self.Person._get_collection_name()
def test_get_collection(self): def test_get_collection(self):
"""Ensure that get_collection returns the expected collection. """Ensure that get_collection returns the expected collection."""
"""
collection_name = "person" collection_name = "person"
collection = self.Person._get_collection() collection = self.Person._get_collection()
assert self.db[collection_name] == collection assert self.db[collection_name] == collection
def test_drop_collection(self): def test_drop_collection(self):
"""Ensure that the collection may be dropped from the database. """Ensure that the collection may be dropped from the database."""
"""
collection_name = "person" collection_name = "person"
self.Person(name="Test").save() self.Person(name="Test").save()
assert collection_name in list_collection_names(self.db) assert collection_name in list_collection_names(self.db)
@ -77,7 +73,7 @@ class TestClassMethods(unittest.TestCase):
assert self.Person._meta["delete_rules"] == {(Job, "employee"): NULLIFY} assert self.Person._meta["delete_rules"] == {(Job, "employee"): NULLIFY}
def test_compare_indexes(self): def test_compare_indexes(self):
""" Ensure that the indexes are properly created and that """Ensure that the indexes are properly created and that
compare_indexes identifies the missing/extra indexes compare_indexes identifies the missing/extra indexes
""" """
@ -110,7 +106,7 @@ class TestClassMethods(unittest.TestCase):
} }
def test_compare_indexes_inheritance(self): def test_compare_indexes_inheritance(self):
""" Ensure that the indexes are properly created and that """Ensure that the indexes are properly created and that
compare_indexes identifies the missing/extra indexes for subclassed compare_indexes identifies the missing/extra indexes for subclassed
documents (_cls included) documents (_cls included)
""" """
@ -150,7 +146,7 @@ class TestClassMethods(unittest.TestCase):
} }
def test_compare_indexes_multiple_subclasses(self): def test_compare_indexes_multiple_subclasses(self):
""" Ensure that compare_indexes behaves correctly if called from a """Ensure that compare_indexes behaves correctly if called from a
class, which base class has multiple subclasses class, which base class has multiple subclasses
""" """
@ -181,7 +177,7 @@ class TestClassMethods(unittest.TestCase):
assert BlogPostWithCustomField.compare_indexes() == {"missing": [], "extra": []} assert BlogPostWithCustomField.compare_indexes() == {"missing": [], "extra": []}
def test_compare_indexes_for_text_indexes(self): def test_compare_indexes_for_text_indexes(self):
""" Ensure that compare_indexes behaves correctly for text indexes """ """Ensure that compare_indexes behaves correctly for text indexes"""
class Doc(Document): class Doc(Document):
a = StringField() a = StringField()
@ -203,7 +199,7 @@ class TestClassMethods(unittest.TestCase):
assert actual == expected assert actual == expected
def test_list_indexes_inheritance(self): def test_list_indexes_inheritance(self):
""" ensure that all of the indexes are listed regardless of the super- """ensure that all of the indexes are listed regardless of the super-
or sub-class that we call it from or sub-class that we call it from
""" """
@ -235,7 +231,7 @@ class TestClassMethods(unittest.TestCase):
assert BlogPost.list_indexes() == [ assert BlogPost.list_indexes() == [
[("_cls", 1), ("author", 1), ("tags", 1)], [("_cls", 1), ("author", 1), ("tags", 1)],
[("_cls", 1), ("author", 1), ("tags", 1), ("extra_text", 1)], [("_cls", 1), ("author", 1), ("tags", 1), ("extra_text", 1)],
[(u"_id", 1)], [("_id", 1)],
[("_cls", 1)], [("_cls", 1)],
] ]
@ -260,8 +256,7 @@ class TestClassMethods(unittest.TestCase):
assert Vaccine._meta["delete_rules"][(Cat, "vaccine_made")] == PULL assert Vaccine._meta["delete_rules"][(Cat, "vaccine_made")] == PULL
def test_collection_naming(self): def test_collection_naming(self):
"""Ensure that a collection with a specified name may be used. """Ensure that a collection with a specified name may be used."""
"""
class DefaultNamingTest(Document): class DefaultNamingTest(Document):
pass pass
@ -293,7 +288,7 @@ class TestClassMethods(unittest.TestCase):
assert "wibble" == InheritedAbstractNamingTest._get_collection_name() assert "wibble" == InheritedAbstractNamingTest._get_collection_name()
# Mixin tests # Mixin tests
class BaseMixin(object): class BaseMixin:
meta = {"collection": lambda c: c.__name__.lower()} meta = {"collection": lambda c: c.__name__.lower()}
class OldMixinNamingConvention(Document, BaseMixin): class OldMixinNamingConvention(Document, BaseMixin):
@ -304,7 +299,7 @@ class TestClassMethods(unittest.TestCase):
== OldMixinNamingConvention._get_collection_name() == OldMixinNamingConvention._get_collection_name()
) )
class BaseMixin(object): class BaseMixin:
meta = {"collection": lambda c: c.__name__.lower()} meta = {"collection": lambda c: c.__name__.lower()}
class BaseDocument(Document, BaseMixin): class BaseDocument(Document, BaseMixin):
@ -316,8 +311,7 @@ class TestClassMethods(unittest.TestCase):
assert "basedocument" == MyDocument._get_collection_name() assert "basedocument" == MyDocument._get_collection_name()
def test_custom_collection_name_operations(self): def test_custom_collection_name_operations(self):
"""Ensure that a collection with a specified name is used as expected. """Ensure that a collection with a specified name is used as expected."""
"""
collection_name = "personCollTest" collection_name = "personCollTest"
class Person(Document): class Person(Document):
@ -337,8 +331,7 @@ class TestClassMethods(unittest.TestCase):
assert collection_name not in list_collection_names(self.db) assert collection_name not in list_collection_names(self.db)
def test_collection_name_and_primary(self): def test_collection_name_and_primary(self):
"""Ensure that a collection with a specified name may be used. """Ensure that a collection with a specified name may be used."""
"""
class Person(Document): class Person(Document):
name = StringField(primary_key=True) name = StringField(primary_key=True)

View File

@ -1,6 +1,7 @@
import unittest import unittest
from bson import SON from bson import SON
from mongoengine import * from mongoengine import *
from mongoengine.pymongo_support import list_collection_names from mongoengine.pymongo_support import list_collection_names
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -8,7 +9,7 @@ from tests.utils import MongoDBTestCase
class TestDelta(MongoDBTestCase): class TestDelta(MongoDBTestCase):
def setUp(self): def setUp(self):
super(TestDelta, self).setUp() super().setUp()
class Person(Document): class Person(Document):
name = StringField() name = StringField()
@ -643,7 +644,10 @@ class TestDelta(MongoDBTestCase):
doc.save() doc.save()
doc = doc.reload(10) doc = doc.reload(10)
assert doc._delta() == ({}, {},) assert doc._delta() == (
{},
{},
)
del doc.embedded_field.list_field[2].list_field del doc.embedded_field.list_field[2].list_field
assert doc._delta() == ( assert doc._delta() == (
{}, {},

View File

@ -10,7 +10,7 @@ __all__ = ("TestDynamicDocument",)
class TestDynamicDocument(MongoDBTestCase): class TestDynamicDocument(MongoDBTestCase):
def setUp(self): def setUp(self):
super(TestDynamicDocument, self).setUp() super().setUp()
class Person(DynamicDocument): class Person(DynamicDocument):
name = StringField() name = StringField()
@ -118,17 +118,17 @@ class TestDynamicDocument(MongoDBTestCase):
p.save() p.save()
raw_p = Person.objects.as_pymongo().get(id=p.id) raw_p = Person.objects.as_pymongo().get(id=p.id)
assert raw_p == {"_cls": u"Person", "_id": p.id, "name": u"Dean"} assert raw_p == {"_cls": "Person", "_id": p.id, "name": "Dean"}
p.name = "OldDean" p.name = "OldDean"
p.newattr = "garbage" p.newattr = "garbage"
p.save() p.save()
raw_p = Person.objects.as_pymongo().get(id=p.id) raw_p = Person.objects.as_pymongo().get(id=p.id)
assert raw_p == { assert raw_p == {
"_cls": u"Person", "_cls": "Person",
"_id": p.id, "_id": p.id,
"name": "OldDean", "name": "OldDean",
"newattr": u"garbage", "newattr": "garbage",
} }
def test_fields_containing_underscore(self): def test_fields_containing_underscore(self):
@ -144,14 +144,14 @@ class TestDynamicDocument(MongoDBTestCase):
p.save() p.save()
raw_p = WeirdPerson.objects.as_pymongo().get(id=p.id) raw_p = WeirdPerson.objects.as_pymongo().get(id=p.id)
assert raw_p == {"_id": p.id, "_name": u"Dean", "name": u"Dean"} assert raw_p == {"_id": p.id, "_name": "Dean", "name": "Dean"}
p.name = "OldDean" p.name = "OldDean"
p._name = "NewDean" p._name = "NewDean"
p._newattr1 = "garbage" # Unknown fields won't be added p._newattr1 = "garbage" # Unknown fields won't be added
p.save() p.save()
raw_p = WeirdPerson.objects.as_pymongo().get(id=p.id) raw_p = WeirdPerson.objects.as_pymongo().get(id=p.id)
assert raw_p == {"_id": p.id, "_name": u"NewDean", "name": u"OldDean"} assert raw_p == {"_id": p.id, "_name": "NewDean", "name": "OldDean"}
def test_dynamic_document_queries(self): def test_dynamic_document_queries(self):
"""Ensure we can query dynamic fields""" """Ensure we can query dynamic fields"""

View File

@ -1,12 +1,16 @@
import unittest import unittest
from datetime import datetime from datetime import datetime
import pytest
from pymongo.collation import Collation from pymongo.collation import Collation
from pymongo.errors import OperationFailure from pymongo.errors import OperationFailure
import pytest
from mongoengine import * from mongoengine import *
from mongoengine.connection import get_db from mongoengine.connection import get_db
from mongoengine.mongodb_support import (
MONGODB_42,
get_mongodb_version,
)
class TestIndexes(unittest.TestCase): class TestIndexes(unittest.TestCase):
@ -171,8 +175,7 @@ class TestIndexes(unittest.TestCase):
assert MyDoc._meta["index_specs"] == [{"fields": [("keywords", 1)]}] assert MyDoc._meta["index_specs"] == [{"fields": [("keywords", 1)]}]
def test_embedded_document_index_meta(self): def test_embedded_document_index_meta(self):
"""Ensure that embedded document indexes are created explicitly """Ensure that embedded document indexes are created explicitly"""
"""
class Rank(EmbeddedDocument): class Rank(EmbeddedDocument):
title = StringField(required=True) title = StringField(required=True)
@ -194,8 +197,7 @@ class TestIndexes(unittest.TestCase):
assert [("rank.title", 1)] in info assert [("rank.title", 1)] in info
def test_explicit_geo2d_index(self): def test_explicit_geo2d_index(self):
"""Ensure that geo2d indexes work when created via meta[indexes] """Ensure that geo2d indexes work when created via meta[indexes]"""
"""
class Place(Document): class Place(Document):
location = DictField() location = DictField()
@ -209,8 +211,7 @@ class TestIndexes(unittest.TestCase):
assert [("location.point", "2d")] in info assert [("location.point", "2d")] in info
def test_explicit_geo2d_index_embedded(self): def test_explicit_geo2d_index_embedded(self):
"""Ensure that geo2d indexes work when created via meta[indexes] """Ensure that geo2d indexes work when created via meta[indexes]"""
"""
class EmbeddedLocation(EmbeddedDocument): class EmbeddedLocation(EmbeddedDocument):
location = DictField() location = DictField()
@ -229,8 +230,7 @@ class TestIndexes(unittest.TestCase):
assert [("current.location.point", "2d")] in info assert [("current.location.point", "2d")] in info
def test_explicit_geosphere_index(self): def test_explicit_geosphere_index(self):
"""Ensure that geosphere indexes work when created via meta[indexes] """Ensure that geosphere indexes work when created via meta[indexes]"""
"""
class Place(Document): class Place(Document):
location = DictField() location = DictField()
@ -246,8 +246,7 @@ class TestIndexes(unittest.TestCase):
assert [("location.point", "2dsphere")] in info assert [("location.point", "2dsphere")] in info
def test_explicit_geohaystack_index(self): def test_explicit_geohaystack_index(self):
"""Ensure that geohaystack indexes work when created via meta[indexes] """Ensure that geohaystack indexes work when created via meta[indexes]"""
"""
pytest.skip( pytest.skip(
"GeoHaystack index creation is not supported for now" "GeoHaystack index creation is not supported for now"
"from meta, as it requires a bucketSize parameter." "from meta, as it requires a bucketSize parameter."
@ -268,8 +267,7 @@ class TestIndexes(unittest.TestCase):
assert [("location.point", "geoHaystack")] in info assert [("location.point", "geoHaystack")] in info
def test_create_geohaystack_index(self): def test_create_geohaystack_index(self):
"""Ensure that geohaystack indexes can be created """Ensure that geohaystack indexes can be created"""
"""
class Place(Document): class Place(Document):
location = DictField() location = DictField()
@ -364,8 +362,7 @@ class TestIndexes(unittest.TestCase):
assert sorted(info.keys()) == ["_cls_1_user_guid_1", "_id_"] assert sorted(info.keys()) == ["_cls_1_user_guid_1", "_id_"]
def test_embedded_document_index(self): def test_embedded_document_index(self):
"""Tests settings an index on an embedded document """Tests settings an index on an embedded document"""
"""
class Date(EmbeddedDocument): class Date(EmbeddedDocument):
year = IntField(db_field="yr") year = IntField(db_field="yr")
@ -382,8 +379,7 @@ class TestIndexes(unittest.TestCase):
assert sorted(info.keys()) == ["_id_", "date.yr_-1"] assert sorted(info.keys()) == ["_id_", "date.yr_-1"]
def test_list_embedded_document_index(self): def test_list_embedded_document_index(self):
"""Ensure list embedded documents can be indexed """Ensure list embedded documents can be indexed"""
"""
class Tag(EmbeddedDocument): class Tag(EmbeddedDocument):
name = StringField(db_field="tag") name = StringField(db_field="tag")
@ -419,8 +415,7 @@ class TestIndexes(unittest.TestCase):
assert sorted(info.keys()) == ["_cls_1", "_id_"] assert sorted(info.keys()) == ["_cls_1", "_id_"]
def test_covered_index(self): def test_covered_index(self):
"""Ensure that covered indexes can be used """Ensure that covered indexes can be used"""
"""
class Test(Document): class Test(Document):
a = IntField() a = IntField()
@ -461,9 +456,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()
@ -558,8 +555,7 @@ class TestIndexes(unittest.TestCase):
assert [x.name for x in query_result] == sorted(names) assert [x.name for x in query_result] == sorted(names)
def test_unique(self): def test_unique(self):
"""Ensure that uniqueness constraints are applied to fields. """Ensure that uniqueness constraints are applied to fields."""
"""
class BlogPost(Document): class BlogPost(Document):
title = StringField() title = StringField()
@ -607,8 +603,7 @@ class TestIndexes(unittest.TestCase):
) )
def test_unique_with(self): def test_unique_with(self):
"""Ensure that unique_with constraints are applied to fields. """Ensure that unique_with constraints are applied to fields."""
"""
class Date(EmbeddedDocument): class Date(EmbeddedDocument):
year = IntField(db_field="yr") year = IntField(db_field="yr")
@ -633,8 +628,7 @@ class TestIndexes(unittest.TestCase):
post3.save() post3.save()
def test_unique_embedded_document(self): def test_unique_embedded_document(self):
"""Ensure that uniqueness constraints are applied to fields on embedded documents. """Ensure that uniqueness constraints are applied to fields on embedded documents."""
"""
class SubDocument(EmbeddedDocument): class SubDocument(EmbeddedDocument):
year = IntField(db_field="yr") year = IntField(db_field="yr")

View File

@ -45,8 +45,7 @@ class TestInheritance(MongoDBTestCase):
test_doc.delete() test_doc.delete()
def test_superclasses(self): def test_superclasses(self):
"""Ensure that the correct list of superclasses is assembled. """Ensure that the correct list of superclasses is assembled."""
"""
class Animal(Document): class Animal(Document):
meta = {"allow_inheritance": True} meta = {"allow_inheritance": True}
@ -216,8 +215,7 @@ class TestInheritance(MongoDBTestCase):
assert Pike._subclasses == ("Animal.Fish.Pike",) assert Pike._subclasses == ("Animal.Fish.Pike",)
def test_inheritance_meta_data(self): def test_inheritance_meta_data(self):
"""Ensure that document may inherit fields from a superclass document. """Ensure that document may inherit fields from a superclass document."""
"""
class Person(Document): class Person(Document):
name = StringField() name = StringField()
@ -234,8 +232,7 @@ class TestInheritance(MongoDBTestCase):
assert Employee._get_collection_name() == Person._get_collection_name() assert Employee._get_collection_name() == Person._get_collection_name()
def test_inheritance_to_mongo_keys(self): def test_inheritance_to_mongo_keys(self):
"""Ensure that document may inherit fields from a superclass document. """Ensure that document may inherit fields from a superclass document."""
"""
class Person(Document): class Person(Document):
name = StringField() name = StringField()
@ -259,7 +256,7 @@ class TestInheritance(MongoDBTestCase):
assert Employee._get_collection_name() == Person._get_collection_name() assert Employee._get_collection_name() == Person._get_collection_name()
def test_indexes_and_multiple_inheritance(self): def test_indexes_and_multiple_inheritance(self):
""" Ensure that all of the indexes are created for a document with """Ensure that all of the indexes are created for a document with
multiple inheritance. multiple inheritance.
""" """
@ -283,14 +280,11 @@ class TestInheritance(MongoDBTestCase):
C.ensure_indexes() C.ensure_indexes()
assert sorted( assert sorted(
[idx["key"] for idx in C._get_collection().index_information().values()] idx["key"] for idx in C._get_collection().index_information().values()
) == sorted( ) == sorted([[("_cls", 1), ("b", 1)], [("_id", 1)], [("_cls", 1), ("a", 1)]])
[[(u"_cls", 1), (u"b", 1)], [(u"_id", 1)], [(u"_cls", 1), (u"a", 1)]]
)
def test_polymorphic_queries(self): def test_polymorphic_queries(self):
"""Ensure that the correct subclasses are returned from a query """Ensure that the correct subclasses are returned from a query"""
"""
class Animal(Document): class Animal(Document):
meta = {"allow_inheritance": True} meta = {"allow_inheritance": True}
@ -347,8 +341,7 @@ class TestInheritance(MongoDBTestCase):
assert "_cls" not in obj assert "_cls" not in obj
def test_cant_turn_off_inheritance_on_subclass(self): def test_cant_turn_off_inheritance_on_subclass(self):
"""Ensure if inheritance is on in a subclass you cant turn it off. """Ensure if inheritance is on in a subclass you cant turn it off."""
"""
class Animal(Document): class Animal(Document):
name = StringField() name = StringField()
@ -474,7 +467,7 @@ class TestInheritance(MongoDBTestCase):
assert city.pk is None assert city.pk is None
# TODO: expected error? Shouldn't we create a new error type? # TODO: expected error? Shouldn't we create a new error type?
with pytest.raises(KeyError): with pytest.raises(KeyError):
setattr(city, "pk", 1) city.pk = 1
def test_allow_inheritance_embedded_document(self): def test_allow_inheritance_embedded_document(self):
"""Ensure embedded documents respect inheritance.""" """Ensure embedded documents respect inheritance."""
@ -498,8 +491,7 @@ class TestInheritance(MongoDBTestCase):
assert "_cls" in doc.to_mongo() assert "_cls" in doc.to_mongo()
def test_document_inheritance(self): def test_document_inheritance(self):
"""Ensure mutliple inheritance of abstract documents """Ensure mutliple inheritance of abstract documents"""
"""
class DateCreatedDocument(Document): class DateCreatedDocument(Document):
meta = {"allow_inheritance": True, "abstract": True} meta = {"allow_inheritance": True, "abstract": True}
@ -507,13 +499,8 @@ class TestInheritance(MongoDBTestCase):
class DateUpdatedDocument(Document): class DateUpdatedDocument(Document):
meta = {"allow_inheritance": True, "abstract": True} meta = {"allow_inheritance": True, "abstract": True}
try: class MyDocument(DateCreatedDocument, DateUpdatedDocument):
pass
class MyDocument(DateCreatedDocument, DateUpdatedDocument):
pass
except Exception:
assert False, "Couldn't create MyDocument class"
def test_abstract_documents(self): def test_abstract_documents(self):
"""Ensure that a document superclass can be marked as abstract """Ensure that a document superclass can be marked as abstract

View File

@ -6,9 +6,9 @@ import weakref
from datetime import datetime from datetime import datetime
import bson import bson
import pytest
from bson import DBRef, ObjectId from bson import DBRef, ObjectId
from pymongo.errors import DuplicateKeyError from pymongo.errors import DuplicateKeyError
import pytest
from mongoengine import * from mongoengine import *
from mongoengine import signals from mongoengine import signals
@ -23,7 +23,11 @@ from mongoengine.errors import (
NotUniqueError, NotUniqueError,
SaveConditionError, 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.pymongo_support import list_collection_names
from mongoengine.queryset import NULLIFY, Q from mongoengine.queryset import NULLIFY, Q
from tests import fixtures from tests import fixtures
@ -61,12 +65,12 @@ class TestDocumentInstance(MongoDBTestCase):
for collection in list_collection_names(self.db): for collection in list_collection_names(self.db):
self.db.drop_collection(collection) self.db.drop_collection(collection)
def assertDbEqual(self, docs): def _assert_db_equal(self, docs):
assert list(self.Person._get_collection().find().sort("id")) == sorted( assert list(self.Person._get_collection().find().sort("id")) == sorted(
docs, key=lambda doc: doc["_id"] docs, key=lambda doc: doc["_id"]
) )
def assertHasInstance(self, field, instance): def _assert_has_instance(self, field, instance):
assert hasattr(field, "_instance") assert hasattr(field, "_instance")
assert field._instance is not None assert field._instance is not None
if isinstance(field._instance, weakref.ProxyType): if isinstance(field._instance, weakref.ProxyType):
@ -160,8 +164,7 @@ class TestDocumentInstance(MongoDBTestCase):
Log.objects Log.objects
def test_repr(self): def test_repr(self):
"""Ensure that unicode representation works """Ensure that unicode representation works"""
"""
class Article(Document): class Article(Document):
title = StringField() title = StringField()
@ -169,7 +172,7 @@ class TestDocumentInstance(MongoDBTestCase):
def __unicode__(self): def __unicode__(self):
return self.title return self.title
doc = Article(title=u"привет мир") doc = Article(title="привет мир")
assert "<Article: привет мир>" == repr(doc) assert "<Article: привет мир>" == repr(doc)
@ -182,7 +185,7 @@ class TestDocumentInstance(MongoDBTestCase):
def __str__(self): def __str__(self):
return None return None
doc = Article(title=u"привет мир") doc = Article(title="привет мир")
assert "<Article: None>" == repr(doc) assert "<Article: None>" == repr(doc)
def test_queryset_resurrects_dropped_collection(self): def test_queryset_resurrects_dropped_collection(self):
@ -404,6 +407,16 @@ class TestDocumentInstance(MongoDBTestCase):
assert person.name == "Test User" assert person.name == "Test User"
assert person.age == 30 assert person.age == 30
def test__qs_property_does_not_raise(self):
# ensures no regression of #2500
class MyDocument(Document):
pass
MyDocument.drop_collection()
object = MyDocument()
object._qs().insert([MyDocument()])
assert MyDocument.objects.count() == 1
def test_to_dbref(self): def test_to_dbref(self):
"""Ensure that you can get a dbref of a document.""" """Ensure that you can get a dbref of a document."""
person = self.Person(name="Test User", age=30) person = self.Person(name="Test User", age=30)
@ -522,9 +535,9 @@ class TestDocumentInstance(MongoDBTestCase):
query_op = q.db.system.profile.find({"ns": "mongoenginetest.animal"})[0] query_op = q.db.system.profile.find({"ns": "mongoenginetest.animal"})[0]
assert query_op["op"] == "update" assert query_op["op"] == "update"
if mongo_db <= MONGODB_34: if mongo_db <= MONGODB_34:
assert set(query_op["query"].keys()) == set(["_id", "is_mammal"]) assert set(query_op["query"].keys()) == {"_id", "is_mammal"}
else: else:
assert set(query_op["command"]["q"].keys()) == set(["_id", "is_mammal"]) assert set(query_op["command"]["q"].keys()) == {"_id", "is_mammal"}
Animal.drop_collection() Animal.drop_collection()
@ -547,7 +560,7 @@ class TestDocumentInstance(MongoDBTestCase):
query_op = q.db.system.profile.find({"ns": "mongoenginetest.animal"})[0] query_op = q.db.system.profile.find({"ns": "mongoenginetest.animal"})[0]
assert query_op["op"] == "command" assert query_op["op"] == "command"
assert query_op["command"]["findAndModify"] == "animal" assert query_op["command"]["findAndModify"] == "animal"
assert set(query_op["command"]["query"].keys()) == set(["_id", "is_mammal"]) assert set(query_op["command"]["query"].keys()) == {"_id", "is_mammal"}
Animal.drop_collection() Animal.drop_collection()
@ -727,11 +740,11 @@ class TestDocumentInstance(MongoDBTestCase):
Doc.drop_collection() Doc.drop_collection()
doc = Doc(embedded_field=Embedded(string="Hi")) doc = Doc(embedded_field=Embedded(string="Hi"))
self.assertHasInstance(doc.embedded_field, doc) self._assert_has_instance(doc.embedded_field, doc)
doc.save() doc.save()
doc = Doc.objects.get() 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): def test_embedded_document_complex_instance(self):
"""Ensure that embedded documents in complex fields can reference """Ensure that embedded documents in complex fields can reference
@ -746,11 +759,11 @@ class TestDocumentInstance(MongoDBTestCase):
Doc.drop_collection() Doc.drop_collection()
doc = Doc(embedded_field=[Embedded(string="Hi")]) doc = Doc(embedded_field=[Embedded(string="Hi")])
self.assertHasInstance(doc.embedded_field[0], doc) self._assert_has_instance(doc.embedded_field[0], doc)
doc.save() doc.save()
doc = Doc.objects.get() 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): def test_embedded_document_complex_instance_no_use_db_field(self):
"""Ensure that use_db_field is propagated to list of Emb Docs.""" """Ensure that use_db_field is propagated to list of Emb Docs."""
@ -779,11 +792,11 @@ class TestDocumentInstance(MongoDBTestCase):
acc = Account() acc = Account()
acc.email = Email(email="test@example.com") acc.email = Email(email="test@example.com")
self.assertHasInstance(acc._data["email"], acc) self._assert_has_instance(acc._data["email"], acc)
acc.save() acc.save()
acc1 = Account.objects.first() 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): def test_instance_is_set_on_setattr_on_embedded_document_list(self):
class Email(EmbeddedDocument): class Email(EmbeddedDocument):
@ -795,11 +808,11 @@ class TestDocumentInstance(MongoDBTestCase):
Account.drop_collection() Account.drop_collection()
acc = Account() acc = Account()
acc.emails = [Email(email="test@example.com")] acc.emails = [Email(email="test@example.com")]
self.assertHasInstance(acc._data["emails"][0], acc) self._assert_has_instance(acc._data["emails"][0], acc)
acc.save() acc.save()
acc1 = Account.objects.first() 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): def test_save_checks_that_clean_is_called(self):
class CustomError(Exception): class CustomError(Exception):
@ -908,7 +921,7 @@ class TestDocumentInstance(MongoDBTestCase):
with pytest.raises(InvalidDocumentError): with pytest.raises(InvalidDocumentError):
self.Person().modify(set__age=10) self.Person().modify(set__age=10)
self.assertDbEqual([dict(doc.to_mongo())]) self._assert_db_equal([dict(doc.to_mongo())])
def test_modify_invalid_query(self): def test_modify_invalid_query(self):
doc1 = self.Person(name="bob", age=10).save() doc1 = self.Person(name="bob", age=10).save()
@ -918,7 +931,7 @@ class TestDocumentInstance(MongoDBTestCase):
with pytest.raises(InvalidQueryError): with pytest.raises(InvalidQueryError):
doc1.modify({"id": doc2.id}, set__value=20) doc1.modify({"id": doc2.id}, set__value=20)
self.assertDbEqual(docs) self._assert_db_equal(docs)
def test_modify_match_another_document(self): def test_modify_match_another_document(self):
doc1 = self.Person(name="bob", age=10).save() doc1 = self.Person(name="bob", age=10).save()
@ -928,7 +941,7 @@ class TestDocumentInstance(MongoDBTestCase):
n_modified = doc1.modify({"name": doc2.name}, set__age=100) n_modified = doc1.modify({"name": doc2.name}, set__age=100)
assert n_modified == 0 assert n_modified == 0
self.assertDbEqual(docs) self._assert_db_equal(docs)
def test_modify_not_exists(self): def test_modify_not_exists(self):
doc1 = self.Person(name="bob", age=10).save() doc1 = self.Person(name="bob", age=10).save()
@ -938,7 +951,7 @@ class TestDocumentInstance(MongoDBTestCase):
n_modified = doc2.modify({"name": doc2.name}, set__age=100) n_modified = doc2.modify({"name": doc2.name}, set__age=100)
assert n_modified == 0 assert n_modified == 0
self.assertDbEqual(docs) self._assert_db_equal(docs)
def test_modify_update(self): def test_modify_update(self):
other_doc = self.Person(name="bob", age=10).save() other_doc = self.Person(name="bob", age=10).save()
@ -964,7 +977,7 @@ class TestDocumentInstance(MongoDBTestCase):
assert doc.to_json() == doc_copy.to_json() assert doc.to_json() == doc_copy.to_json()
assert doc._get_changed_fields() == [] assert doc._get_changed_fields() == []
self.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): def test_modify_with_positional_push(self):
class Content(EmbeddedDocument): class Content(EmbeddedDocument):
@ -1429,11 +1442,11 @@ class TestDocumentInstance(MongoDBTestCase):
coll = self.Person._get_collection() coll = self.Person._get_collection()
doc = self.Person(name="John").save() doc = self.Person(name="John").save()
raw_doc = coll.find_one({"_id": doc.pk}) raw_doc = coll.find_one({"_id": doc.pk})
assert set(raw_doc.keys()) == set(["_id", "_cls", "name"]) assert set(raw_doc.keys()) == {"_id", "_cls", "name"}
doc.update(rename__name="first_name") doc.update(rename__name="first_name")
raw_doc = coll.find_one({"_id": doc.pk}) raw_doc = coll.find_one({"_id": doc.pk})
assert set(raw_doc.keys()) == set(["_id", "_cls", "first_name"]) assert set(raw_doc.keys()) == {"_id", "_cls", "first_name"}
assert raw_doc["first_name"] == "John" assert raw_doc["first_name"] == "John"
def test_inserts_if_you_set_the_pk(self): def test_inserts_if_you_set_the_pk(self):
@ -1553,8 +1566,7 @@ class TestDocumentInstance(MongoDBTestCase):
assert site.page.log_message == "Error: Dummy message" assert site.page.log_message == "Error: Dummy message"
def test_update_list_field(self): def test_update_list_field(self):
"""Test update on `ListField` with $pull + $in. """Test update on `ListField` with $pull + $in."""
"""
class Doc(Document): class Doc(Document):
foo = ListField(StringField()) foo = ListField(StringField())
@ -2043,7 +2055,7 @@ class TestDocumentInstance(MongoDBTestCase):
assert promoted_employee.details is None assert promoted_employee.details is None
def test_object_mixins(self): def test_object_mixins(self):
class NameMixin(object): class NameMixin:
name = StringField() name = StringField()
class Foo(EmbeddedDocument, NameMixin): class Foo(EmbeddedDocument, NameMixin):
@ -2057,7 +2069,7 @@ class TestDocumentInstance(MongoDBTestCase):
assert ["id", "name", "widgets"] == sorted(Bar._fields.keys()) assert ["id", "name", "widgets"] == sorted(Bar._fields.keys())
def test_mixin_inheritance(self): def test_mixin_inheritance(self):
class BaseMixIn(object): class BaseMixIn:
count = IntField() count = IntField()
data = StringField() data = StringField()
@ -2816,15 +2828,13 @@ class TestDocumentInstance(MongoDBTestCase):
register_connection("testdb-2", "mongoenginetest2") register_connection("testdb-2", "mongoenginetest2")
class A(Document): class A(Document):
"""Uses default db_alias """Uses default db_alias"""
"""
name = StringField() name = StringField()
meta = {"allow_inheritance": True} meta = {"allow_inheritance": True}
class B(A): class B(A):
"""Uses testdb-2 db_alias """Uses testdb-2 db_alias"""
"""
meta = {"db_alias": "testdb-2"} meta = {"db_alias": "testdb-2"}
@ -2904,50 +2914,32 @@ class TestDocumentInstance(MongoDBTestCase):
# Checks # Checks
assert ",".join([str(b) for b in Book.objects.all()]) == "1,2,3,4,5,6,7,8,9" assert ",".join([str(b) for b in Book.objects.all()]) == "1,2,3,4,5,6,7,8,9"
# bob related books # bob related books
assert ( bob_books_qs = Book.objects.filter(
",".join( Q(extra__a=bob) | Q(author=bob) | Q(extra__b=bob)
[
str(b)
for b in Book.objects.filter(
Q(extra__a=bob) | Q(author=bob) | Q(extra__b=bob)
)
]
)
== "1,2,3,4"
) )
assert [str(b) for b in bob_books_qs] == ["1", "2", "3", "4"]
assert bob_books_qs.count() == 4
# Susan & Karl related books # Susan & Karl related books
assert ( susan_karl_books_qs = Book.objects.filter(
",".join( Q(extra__a__all=[karl, susan])
[ | Q(author__all=[karl, susan])
str(b) | Q(extra__b__all=[karl.to_dbref(), susan.to_dbref()])
for b in Book.objects.filter(
Q(extra__a__all=[karl, susan])
| Q(author__all=[karl, susan])
| Q(extra__b__all=[karl.to_dbref(), susan.to_dbref()])
)
]
)
== "1"
) )
assert [str(b) for b in susan_karl_books_qs] == ["1"]
assert susan_karl_books_qs.count() == 1
# $Where # $Where
assert ( custom_qs = Book.objects.filter(
u",".join( __raw__={
[ "$where": """
str(b)
for b in Book.objects.filter(
__raw__={
"$where": """
function(){ function(){
return this.name == '1' || return this.name == '1' ||
this.name == '2';}""" this.name == '2';}"""
} }
)
]
)
== "1,2"
) )
assert [str(b) for b in custom_qs] == ["1", "2"]
assert custom_qs.count() == 2
def test_switch_db_instance(self): def test_switch_db_instance(self):
register_connection("testdb-1", "mongoenginetest2") register_connection("testdb-1", "mongoenginetest2")
@ -3307,7 +3299,7 @@ class TestDocumentInstance(MongoDBTestCase):
for node_name, node in self.nodes.items(): for node_name, node in self.nodes.items():
node.expand() node.expand()
node.save(*args, **kwargs) node.save(*args, **kwargs)
super(NodesSystem, self).save(*args, **kwargs) super().save(*args, **kwargs)
NodesSystem.drop_collection() NodesSystem.drop_collection()
Node.drop_collection() Node.drop_collection()
@ -3612,8 +3604,7 @@ class TestDocumentInstance(MongoDBTestCase):
assert u_from_db.height is None assert u_from_db.height is None
def test_not_saved_eq(self): def test_not_saved_eq(self):
"""Ensure we can compare documents not saved. """Ensure we can compare documents not saved."""
"""
class Person(Document): class Person(Document):
pass pass
@ -3757,7 +3748,7 @@ class TestDocumentInstance(MongoDBTestCase):
_ = list(Jedi.objects) # Ensure a proper document loads without errors _ = list(Jedi.objects) # Ensure a proper document loads without errors
# Forces a document with a wrong shape (may occur in case of migration) # Forces a document with a wrong shape (may occur in case of migration)
value = u"I_should_be_a_dict" value = "I_should_be_a_dict"
coll.insert_one({"light_saber": value}) coll.insert_one({"light_saber": value})
with pytest.raises(InvalidDocumentError) as exc_info: with pytest.raises(InvalidDocumentError) as exc_info:
@ -3824,7 +3815,7 @@ class ObjectKeyTestCase(MongoDBTestCase):
class DBFieldMappingTest(MongoDBTestCase): class DBFieldMappingTest(MongoDBTestCase):
def setUp(self): def setUp(self):
class Fields(object): class Fields:
w1 = BooleanField(db_field="w2") w1 = BooleanField(db_field="w2")
x1 = BooleanField(db_field="x2") x1 = BooleanField(db_field="x2")

View File

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

View File

@ -9,8 +9,7 @@ from tests.utils import MongoDBTestCase
class TestValidatorError(MongoDBTestCase): class TestValidatorError(MongoDBTestCase):
def test_to_dict(self): def test_to_dict(self):
"""Ensure a ValidationError handles error to_dict correctly. """Ensure a ValidationError handles error to_dict correctly."""
"""
error = ValidationError("root") error = ValidationError("root")
assert error.to_dict() == {} assert error.to_dict() == {}
@ -90,8 +89,7 @@ class TestValidatorError(MongoDBTestCase):
p.validate() p.validate()
def test_embedded_document_validation(self): def test_embedded_document_validation(self):
"""Ensure that embedded documents may be validated. """Ensure that embedded documents may be validated."""
"""
class Comment(EmbeddedDocument): class Comment(EmbeddedDocument):
date = DateTimeField() date = DateTimeField()
@ -212,10 +210,7 @@ class TestValidatorError(MongoDBTestCase):
child.reference = parent child.reference = parent
# Saving the child should not raise a ValidationError # Saving the child should not raise a ValidationError
try: child.save()
child.save()
except ValidationError as e:
self.fail("ValidationError raised: %s" % e.message)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,7 +1,7 @@
import uuid import uuid
from bson import Binary
import pytest import pytest
from bson import Binary
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -13,14 +13,13 @@ BIN_VALUE = "\xa9\xf3\x8d(\xd7\x03\x84\xb4k[\x0f\xe3\xa2\x19\x85p[J\xa3\xd2>\xde
class TestBinaryField(MongoDBTestCase): class TestBinaryField(MongoDBTestCase):
def test_binary_fields(self): def test_binary_fields(self):
"""Ensure that binary fields can be stored and retrieved. """Ensure that binary fields can be stored and retrieved."""
"""
class Attachment(Document): class Attachment(Document):
content_type = StringField() content_type = StringField()
blob = BinaryField() blob = BinaryField()
BLOB = "\xe6\x00\xc4\xff\x07".encode("latin-1") BLOB = b"\xe6\x00\xc4\xff\x07"
MIME_TYPE = "application/octet-stream" MIME_TYPE = "application/octet-stream"
Attachment.drop_collection() Attachment.drop_collection()
@ -33,8 +32,7 @@ class TestBinaryField(MongoDBTestCase):
assert BLOB == bytes(attachment_1.blob) assert BLOB == bytes(attachment_1.blob)
def test_validation_succeeds(self): def test_validation_succeeds(self):
"""Ensure that valid values can be assigned to binary fields. """Ensure that valid values can be assigned to binary fields."""
"""
class AttachmentRequired(Document): class AttachmentRequired(Document):
blob = BinaryField(required=True) blob = BinaryField(required=True)
@ -45,11 +43,11 @@ class TestBinaryField(MongoDBTestCase):
attachment_required = AttachmentRequired() attachment_required = AttachmentRequired()
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
attachment_required.validate() attachment_required.validate()
attachment_required.blob = Binary("\xe6\x00\xc4\xff\x07".encode("latin-1")) attachment_required.blob = Binary(b"\xe6\x00\xc4\xff\x07")
attachment_required.validate() attachment_required.validate()
_5_BYTES = "\xe6\x00\xc4\xff\x07".encode("latin-1") _5_BYTES = b"\xe6\x00\xc4\xff\x07"
_4_BYTES = "\xe6\x00\xc4\xff".encode("latin-1") _4_BYTES = b"\xe6\x00\xc4\xff"
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
AttachmentSizeLimit(blob=_5_BYTES).validate() AttachmentSizeLimit(blob=_5_BYTES).validate()
AttachmentSizeLimit(blob=_4_BYTES).validate() AttachmentSizeLimit(blob=_4_BYTES).validate()
@ -60,7 +58,7 @@ class TestBinaryField(MongoDBTestCase):
class Attachment(Document): class Attachment(Document):
blob = BinaryField() blob = BinaryField()
for invalid_data in (2, u"Im_a_unicode", ["some_str"]): for invalid_data in (2, "Im_a_unicode", ["some_str"]):
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
Attachment(blob=invalid_data).validate() Attachment(blob=invalid_data).validate()
@ -131,7 +129,7 @@ class TestBinaryField(MongoDBTestCase):
MyDocument.drop_collection() MyDocument.drop_collection()
bin_data = "\xe6\x00\xc4\xff\x07".encode("latin-1") bin_data = b"\xe6\x00\xc4\xff\x07"
doc = MyDocument(bin_field=bin_data).save() doc = MyDocument(bin_field=bin_data).save()
n_updated = MyDocument.objects(bin_field=bin_data).update_one( n_updated = MyDocument.objects(bin_field=bin_data).update_one(

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,
@ -190,9 +207,9 @@ class TestCachedReferenceField(MongoDBTestCase):
assert dict(a2.to_mongo()) == { assert dict(a2.to_mongo()) == {
"_id": a2.pk, "_id": a2.pk,
"name": u"Wilson Junior", "name": "Wilson Junior",
"tp": u"pf", "tp": "pf",
"father": {"_id": a1.pk, "tp": u"pj"}, "father": {"_id": a1.pk, "tp": "pj"},
} }
assert Person.objects(father=a1)._query == {"father._id": a1.pk} assert Person.objects(father=a1)._query == {"father._id": a1.pk}
@ -204,9 +221,9 @@ class TestCachedReferenceField(MongoDBTestCase):
a2.reload() a2.reload()
assert dict(a2.to_mongo()) == { assert dict(a2.to_mongo()) == {
"_id": a2.pk, "_id": a2.pk,
"name": u"Wilson Junior", "name": "Wilson Junior",
"tp": u"pf", "tp": "pf",
"father": {"_id": a1.pk, "tp": u"pf"}, "father": {"_id": a1.pk, "tp": "pf"},
} }
def test_cached_reference_fields_on_embedded_documents(self): def test_cached_reference_fields_on_embedded_documents(self):

View File

@ -6,7 +6,6 @@ import re
import pytest import pytest
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -60,7 +59,7 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
assert log == log1 assert log == log1
# Test string padding # Test string padding
microsecond = map(int, [math.pow(10, x) for x in range(6)]) microsecond = map(int, (math.pow(10, x) for x in range(6)))
mm = dd = hh = ii = ss = [1, 10] mm = dd = hh = ii = ss = [1, 10]
for values in itertools.product([2014], mm, dd, hh, ii, ss, microsecond): for values in itertools.product([2014], mm, dd, hh, ii, ss, microsecond):

View File

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

View File

@ -2,60 +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)
@ -107,3 +58,83 @@ class TestDecimalField(MongoDBTestCase):
for field_name in ["float_value", "string_value"]: for field_name in ["float_value", "string_value"]:
actual = list(Person.objects().scalar(field_name)) actual = list(Person.objects().scalar(field_name))
assert expected == actual assert expected == actual
def test_save_none(self):
class Person(Document):
value = DecimalField()
Person.drop_collection()
person = Person(value=None)
assert person.value is None
person.save()
fetched_person = Person.objects.first()
fetched_person.value is None
def test_validation(self):
"""Ensure that invalid values cannot be assigned to decimal fields."""
class Person(Document):
height = DecimalField(min_value=Decimal("0.1"), max_value=Decimal("3.5"))
Person.drop_collection()
Person(height=Decimal("1.89")).save()
person = Person.objects.first()
assert person.height == Decimal("1.89")
person.height = "2.0"
person.save()
person.height = 0.01
with pytest.raises(ValidationError):
person.validate()
person.height = Decimal("0.01")
with pytest.raises(ValidationError):
person.validate()
person.height = Decimal("4.0")
with pytest.raises(ValidationError):
person.validate()
person.height = "something invalid"
with pytest.raises(ValidationError):
person.validate()
person_2 = Person(height="something invalid")
with pytest.raises(ValidationError):
person_2.validate()
def test_comparison(self):
class Person(Document):
money = DecimalField()
Person.drop_collection()
Person(money=6).save()
Person(money=7).save()
Person(money=8).save()
Person(money=10).save()
assert 2 == Person.objects(money__gt=Decimal("7")).count()
assert 2 == Person.objects(money__gt=7).count()
assert 2 == Person.objects(money__gt="7").count()
assert 3 == Person.objects(money__gte="7").count()
def test_precision_0(self):
"""prevent regression of a bug that was raising an exception when using precision=0"""
class TestDoc(Document):
d = DecimalField(precision=0)
TestDoc.drop_collection()
td = TestDoc(d=Decimal("12.00032678131263"))
assert td.d == Decimal("12")
def test_precision_negative_raise(self):
"""prevent regression of a bug that was raising an exception when using precision=0"""
with pytest.raises(
ValidationError, match="precision must be a positive integer"
):
class TestDoc(Document):
dneg = DecimalField(precision=-1)

View File

@ -1,10 +1,12 @@
from bson import InvalidDocument
import pytest import pytest
from bson import InvalidDocument
from mongoengine import * from mongoengine import *
from mongoengine.base import BaseDict 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 from tests.utils import MongoDBTestCase, get_as_pymongo

View File

@ -1,8 +1,6 @@
import sys
import pytest import pytest
from mongoengine import * from mongoengine import Document, EmailField, ValidationError
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -30,11 +28,11 @@ class TestEmailField(MongoDBTestCase):
user.validate() user.validate()
# unicode domain # unicode domain
user = User(email=u"user@пример.рф") user = User(email="user@пример.рф")
user.validate() user.validate()
# invalid unicode domain # invalid unicode domain
user = User(email=u"user@пример") user = User(email="user@пример")
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
user.validate() user.validate()
@ -48,7 +46,7 @@ class TestEmailField(MongoDBTestCase):
email = EmailField() email = EmailField()
# unicode user shouldn't validate by default... # unicode user shouldn't validate by default...
user = User(email=u"Dörte@Sörensen.example.com") user = User(email="Dörte@Sörensen.example.com")
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
user.validate() user.validate()
@ -56,7 +54,7 @@ class TestEmailField(MongoDBTestCase):
class User(Document): class User(Document):
email = EmailField(allow_utf8_user=True) email = EmailField(allow_utf8_user=True)
user = User(email=u"Dörte@Sörensen.example.com") user = User(email="Dörte@Sörensen.example.com")
user.validate() user.validate()
def test_email_field_domain_whitelist(self): def test_email_field_domain_whitelist(self):

View File

@ -1,4 +1,7 @@
from copy import deepcopy
import pytest import pytest
from bson import ObjectId
from mongoengine import ( from mongoengine import (
Document, Document,
@ -9,10 +12,10 @@ from mongoengine import (
InvalidQueryError, InvalidQueryError,
ListField, ListField,
LookUpError, LookUpError,
MapField,
StringField, StringField,
ValidationError, ValidationError,
) )
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -74,7 +77,7 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
# Test non exiting attribute # Test non exiting attribute
with pytest.raises(InvalidQueryError) as exc_info: with pytest.raises(InvalidQueryError) as exc_info:
Person.objects(settings__notexist="bar").first() Person.objects(settings__notexist="bar").first()
assert str(exc_info.value) == u'Cannot resolve field "notexist"' assert str(exc_info.value) == 'Cannot resolve field "notexist"'
with pytest.raises(LookUpError): with pytest.raises(LookUpError):
Person.objects.only("settings.notexist") Person.objects.only("settings.notexist")
@ -110,7 +113,7 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
# Test non exiting attribute # Test non exiting attribute
with pytest.raises(InvalidQueryError) as exc_info: with pytest.raises(InvalidQueryError) as exc_info:
assert Person.objects(settings__notexist="bar").first().id == p.id assert Person.objects(settings__notexist="bar").first().id == p.id
assert str(exc_info.value) == u'Cannot resolve field "notexist"' assert str(exc_info.value) == 'Cannot resolve field "notexist"'
# Test existing attribute # Test existing attribute
assert Person.objects(settings__base_foo="basefoo").first().id == p.id assert Person.objects(settings__base_foo="basefoo").first().id == p.id
@ -318,7 +321,7 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
# Test non exiting attribute # Test non exiting attribute
with pytest.raises(InvalidQueryError) as exc_info: with pytest.raises(InvalidQueryError) as exc_info:
Person.objects(settings__notexist="bar").first() Person.objects(settings__notexist="bar").first()
assert str(exc_info.value) == u'Cannot resolve field "notexist"' assert str(exc_info.value) == 'Cannot resolve field "notexist"'
with pytest.raises(LookUpError): with pytest.raises(LookUpError):
Person.objects.only("settings.notexist") Person.objects.only("settings.notexist")
@ -346,8 +349,35 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
# Test non exiting attribute # Test non exiting attribute
with pytest.raises(InvalidQueryError) as exc_info: with pytest.raises(InvalidQueryError) as exc_info:
assert Person.objects(settings__notexist="bar").first().id == p.id assert Person.objects(settings__notexist="bar").first().id == p.id
assert str(exc_info.value) == u'Cannot resolve field "notexist"' assert str(exc_info.value) == 'Cannot resolve field "notexist"'
# Test existing attribute # Test existing attribute
assert Person.objects(settings__base_foo="basefoo").first().id == p.id assert Person.objects(settings__base_foo="basefoo").first().id == p.id
assert Person.objects(settings__sub_foo="subfoo").first().id == p.id assert Person.objects(settings__sub_foo="subfoo").first().id == p.id
def test_deepcopy_set__instance(self):
"""Ensure that the _instance attribute on EmbeddedDocument exists after a deepcopy"""
class Wallet(EmbeddedDocument):
money = IntField()
class Person(Document):
wallet = EmbeddedDocumentField(Wallet)
wallet_map = MapField(EmbeddedDocumentField(Wallet))
# Test on fresh EmbeddedDoc
emb_doc = Wallet(money=1)
assert emb_doc._instance is None
copied_emb_doc = deepcopy(emb_doc)
assert copied_emb_doc._instance is None
# Test on attached EmbeddedDoc
doc = Person(
id=ObjectId(), wallet=Wallet(money=2), wallet_map={"test": Wallet(money=2)}
)
assert doc.wallet._instance == doc
copied_emb_doc = deepcopy(doc.wallet)
assert copied_emb_doc._instance is None
copied_map_emb_doc = deepcopy(doc.wallet_map)
assert copied_map_emb_doc["test"]._instance is None

View File

@ -1,9 +1,9 @@
from enum import Enum from enum import Enum
from bson import InvalidDocument
import pytest import pytest
from bson import InvalidDocument
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
@ -12,6 +12,11 @@ class Status(Enum):
DONE = "done" DONE = "done"
class Color(Enum):
RED = 1
BLUE = 2
class ModelWithEnum(Document): class ModelWithEnum(Document):
status = EnumField(Status) status = EnumField(Status)
@ -45,6 +50,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)
@ -69,14 +79,27 @@ class TestStringEnumField(MongoDBTestCase):
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
m.validate() m.validate()
def test_user_is_informed_when_tries_to_set_choices(self): def test_partial_choices(self):
with pytest.raises(ValueError, match="'choices' can't be set on EnumField"): 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"]) EnumField(Status, choices=["my", "custom", "options"])
with pytest.raises(ValueError, match="Invalid choices"):
EnumField(Status, choices=[Color.RED])
class Color(Enum): with pytest.raises(ValueError, match="Invalid choices"):
RED = 1 EnumField(Status, choices=[Status.DONE, Color.RED])
BLUE = 2
class ModelWithColor(Document): class ModelWithColor(Document):

View File

@ -1,8 +1,8 @@
import datetime import datetime
import unittest import unittest
from bson import DBRef, ObjectId, SON
import pytest import pytest
from bson import SON, DBRef, ObjectId
from mongoengine import ( from mongoengine import (
BooleanField, BooleanField,
@ -34,9 +34,12 @@ from mongoengine import (
StringField, StringField,
ValidationError, ValidationError,
) )
from mongoengine.base import BaseField, EmbeddedDocumentList, _document_registry from mongoengine.base import (
BaseField,
EmbeddedDocumentList,
_document_registry,
)
from mongoengine.errors import DeprecatedError from mongoengine.errors import DeprecatedError
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -292,7 +295,7 @@ class TestField(MongoDBTestCase):
HandleNoneFields.drop_collection() HandleNoneFields.drop_collection()
doc = HandleNoneFields() doc = HandleNoneFields()
doc.str_fld = u"spam ham egg" doc.str_fld = "spam ham egg"
doc.int_fld = 42 doc.int_fld = 42
doc.flt_fld = 4.2 doc.flt_fld = 4.2
doc.com_dt_fld = datetime.datetime.utcnow() doc.com_dt_fld = datetime.datetime.utcnow()
@ -306,7 +309,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
@ -328,7 +331,7 @@ class TestField(MongoDBTestCase):
HandleNoneFields.drop_collection() HandleNoneFields.drop_collection()
doc = HandleNoneFields() doc = HandleNoneFields()
doc.str_fld = u"spam ham egg" doc.str_fld = "spam ham egg"
doc.int_fld = 42 doc.int_fld = 42
doc.flt_fld = 4.2 doc.flt_fld = 4.2
doc.comp_dt_fld = datetime.datetime.utcnow() doc.comp_dt_fld = datetime.datetime.utcnow()
@ -340,7 +343,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 +377,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."""
@ -426,9 +401,9 @@ class TestField(MongoDBTestCase):
def test_list_validation(self): def test_list_validation(self):
"""Ensure that a list field only accepts lists with valid elements.""" """Ensure that a list field only accepts lists with valid elements."""
access_level_choices = ( access_level_choices = (
("a", u"Administration"), ("a", "Administration"),
("b", u"Manager"), ("b", "Manager"),
("c", u"Staff"), ("c", "Staff"),
) )
class User(Document): class User(Document):
@ -476,7 +451,7 @@ class TestField(MongoDBTestCase):
post.access_list = ["a", "b"] post.access_list = ["a", "b"]
post.validate() post.validate()
assert post.get_access_list_display() == u"Administration, Manager" assert post.get_access_list_display() == "Administration, Manager"
post.comments = ["a"] post.comments = ["a"]
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
@ -544,8 +519,7 @@ class TestField(MongoDBTestCase):
post.validate() post.validate()
def test_sorted_list_sorting(self): def test_sorted_list_sorting(self):
"""Ensure that a sorted list field properly sorts values. """Ensure that a sorted list field properly sorts values."""
"""
class Comment(EmbeddedDocument): class Comment(EmbeddedDocument):
order = IntField() order = IntField()
@ -661,8 +635,7 @@ class TestField(MongoDBTestCase):
) )
def test_list_field_manipulative_operators(self): def test_list_field_manipulative_operators(self):
"""Ensure that ListField works with standard list operators that manipulate the list. """Ensure that ListField works with standard list operators that manipulate the list."""
"""
class BlogPost(Document): class BlogPost(Document):
ref = StringField() ref = StringField()
@ -1359,9 +1332,9 @@ class TestField(MongoDBTestCase):
foo.delete() foo.delete()
bar = Bar.objects.get() bar = Bar.objects.get()
with pytest.raises(DoesNotExist): with pytest.raises(DoesNotExist):
getattr(bar, "ref") bar.ref
with pytest.raises(DoesNotExist): with pytest.raises(DoesNotExist):
getattr(bar, "generic_ref") bar.generic_ref
# When auto_dereference is disabled, there is no trouble returning DBRef # When auto_dereference is disabled, there is no trouble returning DBRef
bar = Bar.objects.get() bar = Bar.objects.get()
@ -1372,8 +1345,7 @@ class TestField(MongoDBTestCase):
assert bar.generic_ref == {"_ref": expected, "_cls": "Foo"} assert bar.generic_ref == {"_ref": expected, "_cls": "Foo"}
def test_list_item_dereference(self): def test_list_item_dereference(self):
"""Ensure that DBRef items in ListFields are dereferenced. """Ensure that DBRef items in ListFields are dereferenced."""
"""
class User(Document): class User(Document):
name = StringField() name = StringField()
@ -1398,8 +1370,7 @@ class TestField(MongoDBTestCase):
assert group_obj.members[1].name == user2.name assert group_obj.members[1].name == user2.name
def test_recursive_reference(self): def test_recursive_reference(self):
"""Ensure that ReferenceFields can reference their own documents. """Ensure that ReferenceFields can reference their own documents."""
"""
class Employee(Document): class Employee(Document):
name = StringField() name = StringField()
@ -1426,8 +1397,7 @@ class TestField(MongoDBTestCase):
assert peter.friends == friends assert peter.friends == friends
def test_recursive_embedding(self): def test_recursive_embedding(self):
"""Ensure that EmbeddedDocumentFields can contain their own documents. """Ensure that EmbeddedDocumentFields can contain their own documents."""
"""
class TreeNode(EmbeddedDocument): class TreeNode(EmbeddedDocument):
name = StringField() name = StringField()
@ -1503,8 +1473,7 @@ class TestField(MongoDBTestCase):
AbstractDoc.drop_collection() AbstractDoc.drop_collection()
def test_reference_class_with_abstract_parent(self): def test_reference_class_with_abstract_parent(self):
"""Ensure that a class with an abstract parent can be referenced. """Ensure that a class with an abstract parent can be referenced."""
"""
class Sibling(Document): class Sibling(Document):
name = StringField() name = StringField()
@ -1574,8 +1543,7 @@ class TestField(MongoDBTestCase):
brother.save() brother.save()
def test_generic_reference(self): def test_generic_reference(self):
"""Ensure that a GenericReferenceField properly dereferences items. """Ensure that a GenericReferenceField properly dereferences items."""
"""
class Link(Document): class Link(Document):
title = StringField() title = StringField()
@ -1614,8 +1582,7 @@ class TestField(MongoDBTestCase):
assert isinstance(bm.bookmark_object, Link) assert isinstance(bm.bookmark_object, Link)
def test_generic_reference_list(self): def test_generic_reference_list(self):
"""Ensure that a ListField properly dereferences generic references. """Ensure that a ListField properly dereferences generic references."""
"""
class Link(Document): class Link(Document):
title = StringField() title = StringField()
@ -1718,8 +1685,7 @@ class TestField(MongoDBTestCase):
assert bm.bookmark_object == post_1 assert bm.bookmark_object == post_1
def test_generic_reference_string_choices(self): def test_generic_reference_string_choices(self):
"""Ensure that a GenericReferenceField can handle choices as strings """Ensure that a GenericReferenceField can handle choices as strings"""
"""
class Link(Document): class Link(Document):
title = StringField() title = StringField()
@ -1811,8 +1777,7 @@ class TestField(MongoDBTestCase):
assert user.bookmarks == [post_1] assert user.bookmarks == [post_1]
def test_generic_reference_list_item_modification(self): def test_generic_reference_list_item_modification(self):
"""Ensure that modifications of related documents (through generic reference) don't influence on querying """Ensure that modifications of related documents (through generic reference) don't influence on querying"""
"""
class Post(Document): class Post(Document):
title = StringField() title = StringField()
@ -1900,8 +1865,7 @@ class TestField(MongoDBTestCase):
assert doc == doc2 assert doc == doc2
def test_choices_allow_using_sets_as_choices(self): def test_choices_allow_using_sets_as_choices(self):
"""Ensure that sets can be used when setting choices """Ensure that sets can be used when setting choices"""
"""
class Shirt(Document): class Shirt(Document):
size = StringField(choices={"M", "L"}) size = StringField(choices={"M", "L"})
@ -1920,8 +1884,7 @@ class TestField(MongoDBTestCase):
shirt.validate() shirt.validate()
def test_choices_validation_accept_possible_value(self): def test_choices_validation_accept_possible_value(self):
"""Ensure that value is in a container of allowed values. """Ensure that value is in a container of allowed values."""
"""
class Shirt(Document): class Shirt(Document):
size = StringField(choices=("S", "M")) size = StringField(choices=("S", "M"))
@ -1930,8 +1893,7 @@ class TestField(MongoDBTestCase):
shirt.validate() shirt.validate()
def test_choices_validation_reject_unknown_value(self): def test_choices_validation_reject_unknown_value(self):
"""Ensure that unallowed value are rejected upon validation """Ensure that unallowed value are rejected upon validation"""
"""
class Shirt(Document): class Shirt(Document):
size = StringField(choices=("S", "M")) size = StringField(choices=("S", "M"))
@ -1989,8 +1951,7 @@ class TestField(MongoDBTestCase):
shirt1.validate() shirt1.validate()
def test_simple_choices_validation(self): def test_simple_choices_validation(self):
"""Ensure that value is in a container of allowed values. """Ensure that value is in a container of allowed values."""
"""
class Shirt(Document): class Shirt(Document):
size = StringField(max_length=3, choices=("S", "M", "L", "XL", "XXL")) size = StringField(max_length=3, choices=("S", "M", "L", "XL", "XXL"))
@ -2039,12 +2000,11 @@ class TestField(MongoDBTestCase):
shirt.validate() shirt.validate()
def test_simple_choices_validation_invalid_value(self): def test_simple_choices_validation_invalid_value(self):
"""Ensure that error messages are correct. """Ensure that error messages are correct."""
"""
SIZES = ("S", "M", "L", "XL", "XXL") SIZES = ("S", "M", "L", "XL", "XXL")
COLORS = (("R", "Red"), ("B", "Blue")) COLORS = (("R", "Red"), ("B", "Blue"))
SIZE_MESSAGE = u"Value must be one of ('S', 'M', 'L', 'XL', 'XXL')" SIZE_MESSAGE = "Value must be one of ('S', 'M', 'L', 'XL', 'XXL')"
COLOR_MESSAGE = u"Value must be one of ['R', 'B']" COLOR_MESSAGE = "Value must be one of ['R', 'B']"
class Shirt(Document): class Shirt(Document):
size = StringField(max_length=3, choices=SIZES) size = StringField(max_length=3, choices=SIZES)
@ -2107,7 +2067,7 @@ class TestField(MongoDBTestCase):
assert "comments" in error_dict assert "comments" in error_dict
assert 1 in error_dict["comments"] assert 1 in error_dict["comments"]
assert "content" in error_dict["comments"][1] assert "content" in error_dict["comments"][1]
assert error_dict["comments"][1]["content"] == u"Field is required" assert error_dict["comments"][1]["content"] == "Field is required"
post.comments[1].content = "here we go" post.comments[1].content = "here we go"
post.validate() post.validate()
@ -2117,9 +2077,9 @@ class TestField(MongoDBTestCase):
a ComplexBaseField. a ComplexBaseField.
""" """
class EnumField(BaseField): class SomeField(BaseField):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(EnumField, self).__init__(**kwargs) super().__init__(**kwargs)
def to_mongo(self, value): def to_mongo(self, value):
return value return value
@ -2128,7 +2088,7 @@ class TestField(MongoDBTestCase):
return tuple(value) return tuple(value)
class TestDoc(Document): class TestDoc(Document):
items = ListField(EnumField()) items = ListField(SomeField())
TestDoc.drop_collection() TestDoc.drop_collection()
@ -2621,11 +2581,11 @@ class TestEmbeddedDocumentListField(MongoDBTestCase):
""" """
post = self.BlogPost( post = self.BlogPost(
comments=[ comments=[
self.Comments(author="user1", message=u"сообщение"), self.Comments(author="user1", message="сообщение"),
self.Comments(author="user2", message=u"хабарлама"), self.Comments(author="user2", message="хабарлама"),
] ]
).save() ).save()
assert post.comments.get(message=u"сообщение").author == "user1" assert post.comments.get(message="сообщение").author == "user1"
def test_save(self): def test_save(self):
""" """

View File

@ -11,7 +11,7 @@ from mongoengine import *
from mongoengine.connection import get_db from mongoengine.connection import get_db
try: try:
from PIL import Image from PIL import Image # noqa: F401
HAS_PIL = True HAS_PIL = True
except ImportError: except ImportError:
@ -48,15 +48,14 @@ class TestFileField(MongoDBTestCase):
DemoFile.objects.create() DemoFile.objects.create()
def test_file_fields(self): def test_file_fields(self):
"""Ensure that file fields can be written to and their data retrieved """Ensure that file fields can be written to and their data retrieved"""
"""
class PutFile(Document): class PutFile(Document):
the_file = FileField() the_file = FileField()
PutFile.drop_collection() PutFile.drop_collection()
text = "Hello, World!".encode("latin-1") text = b"Hello, World!"
content_type = "text/plain" content_type = "text/plain"
putfile = PutFile() putfile = PutFile()
@ -91,16 +90,15 @@ class TestFileField(MongoDBTestCase):
result.the_file.delete() result.the_file.delete()
def test_file_fields_stream(self): def test_file_fields_stream(self):
"""Ensure that file fields can be written to and their data retrieved """Ensure that file fields can be written to and their data retrieved"""
"""
class StreamFile(Document): class StreamFile(Document):
the_file = FileField() the_file = FileField()
StreamFile.drop_collection() StreamFile.drop_collection()
text = "Hello, World!".encode("latin-1") text = b"Hello, World!"
more_text = "Foo Bar".encode("latin-1") more_text = b"Foo Bar"
content_type = "text/plain" content_type = "text/plain"
streamfile = StreamFile() streamfile = StreamFile()
@ -135,8 +133,8 @@ class TestFileField(MongoDBTestCase):
StreamFile.drop_collection() StreamFile.drop_collection()
text = "Hello, World!".encode("latin-1") text = b"Hello, World!"
more_text = "Foo Bar".encode("latin-1") more_text = b"Foo Bar"
streamfile = StreamFile() streamfile = StreamFile()
streamfile.save() streamfile.save()
@ -165,8 +163,8 @@ class TestFileField(MongoDBTestCase):
class SetFile(Document): class SetFile(Document):
the_file = FileField() the_file = FileField()
text = "Hello, World!".encode("latin-1") text = b"Hello, World!"
more_text = "Foo Bar".encode("latin-1") more_text = b"Foo Bar"
SetFile.drop_collection() SetFile.drop_collection()
@ -194,7 +192,7 @@ class TestFileField(MongoDBTestCase):
GridDocument.drop_collection() GridDocument.drop_collection()
with tempfile.TemporaryFile() as f: with tempfile.TemporaryFile() as f:
f.write("Hello World!".encode("latin-1")) f.write(b"Hello World!")
f.flush() f.flush()
# Test without default # Test without default
@ -211,7 +209,7 @@ class TestFileField(MongoDBTestCase):
assert doc_b.the_file.grid_id == doc_c.the_file.grid_id assert doc_b.the_file.grid_id == doc_c.the_file.grid_id
# Test with default # Test with default
doc_d = GridDocument(the_file="".encode("latin-1")) doc_d = GridDocument(the_file=b"")
doc_d.save() doc_d.save()
doc_e = GridDocument.objects.with_id(doc_d.id) doc_e = GridDocument.objects.with_id(doc_d.id)
@ -228,8 +226,7 @@ class TestFileField(MongoDBTestCase):
assert ["doc_b", "doc_e"] == grid_fs.list() assert ["doc_b", "doc_e"] == grid_fs.list()
def test_file_uniqueness(self): def test_file_uniqueness(self):
"""Ensure that each instance of a FileField is unique """Ensure that each instance of a FileField is unique"""
"""
class TestFile(Document): class TestFile(Document):
name = StringField() name = StringField()
@ -238,7 +235,7 @@ class TestFileField(MongoDBTestCase):
# First instance # First instance
test_file = TestFile() test_file = TestFile()
test_file.name = "Hello, World!" test_file.name = "Hello, World!"
test_file.the_file.put("Hello, World!".encode("latin-1")) test_file.the_file.put(b"Hello, World!")
test_file.save() test_file.save()
# Second instance # Second instance
@ -285,8 +282,7 @@ class TestFileField(MongoDBTestCase):
assert test_file.the_file.get().length == 4971 assert test_file.the_file.get().length == 4971
def test_file_boolean(self): def test_file_boolean(self):
"""Ensure that a boolean test of a FileField indicates its presence """Ensure that a boolean test of a FileField indicates its presence"""
"""
class TestFile(Document): class TestFile(Document):
the_file = FileField() the_file = FileField()
@ -295,9 +291,7 @@ class TestFileField(MongoDBTestCase):
test_file = TestFile() test_file = TestFile()
assert not bool(test_file.the_file) assert not bool(test_file.the_file)
test_file.the_file.put( test_file.the_file.put(b"Hello, World!", content_type="text/plain")
"Hello, World!".encode("latin-1"), content_type="text/plain"
)
test_file.save() test_file.save()
assert bool(test_file.the_file) assert bool(test_file.the_file)
@ -314,12 +308,12 @@ class TestFileField(MongoDBTestCase):
assert test_file.the_file not in [{"test": 1}] assert test_file.the_file not in [{"test": 1}]
def test_file_disk_space(self): def test_file_disk_space(self):
""" Test disk space usage when we delete/replace a file """ """Test disk space usage when we delete/replace a file"""
class TestFile(Document): class TestFile(Document):
the_file = FileField() the_file = FileField()
text = "Hello, World!".encode("latin-1") text = b"Hello, World!"
content_type = "text/plain" content_type = "text/plain"
testfile = TestFile() testfile = TestFile()
@ -363,7 +357,7 @@ class TestFileField(MongoDBTestCase):
testfile.the_file.put(text, content_type=content_type, filename="hello") testfile.the_file.put(text, content_type=content_type, filename="hello")
testfile.save() testfile.save()
text = "Bonjour, World!".encode("latin-1") text = b"Bonjour, World!"
testfile.the_file.replace(text, content_type=content_type, filename="hello") testfile.the_file.replace(text, content_type=content_type, filename="hello")
testfile.save() testfile.save()
@ -387,7 +381,7 @@ class TestFileField(MongoDBTestCase):
TestImage.drop_collection() TestImage.drop_collection()
with tempfile.TemporaryFile() as f: with tempfile.TemporaryFile() as f:
f.write("Hello World!".encode("latin-1")) f.write(b"Hello World!")
f.flush() f.flush()
t = TestImage() t = TestImage()
@ -429,7 +423,7 @@ class TestFileField(MongoDBTestCase):
@require_pil @require_pil
def test_image_field_resize(self): def test_image_field_resize(self):
class TestImage(Document): class TestImage(Document):
image = ImageField(size=(185, 37)) image = ImageField(size=(185, 37, True))
TestImage.drop_collection() TestImage.drop_collection()
@ -471,7 +465,7 @@ class TestFileField(MongoDBTestCase):
@require_pil @require_pil
def test_image_field_thumbnail(self): def test_image_field_thumbnail(self):
class TestImage(Document): class TestImage(Document):
image = ImageField(thumbnail_size=(92, 18)) image = ImageField(thumbnail_size=(92, 18, True))
TestImage.drop_collection() TestImage.drop_collection()
@ -503,21 +497,21 @@ class TestFileField(MongoDBTestCase):
# First instance # First instance
test_file = TestFile() test_file = TestFile()
test_file.name = "Hello, World!" test_file.name = "Hello, World!"
test_file.the_file.put("Hello, World!".encode("latin-1"), name="hello.txt") test_file.the_file.put(b"Hello, World!", name="hello.txt")
test_file.save() test_file.save()
data = get_db("test_files").macumba.files.find_one() data = get_db("test_files").macumba.files.find_one()
assert data.get("name") == "hello.txt" assert data.get("name") == "hello.txt"
test_file = TestFile.objects.first() test_file = TestFile.objects.first()
assert test_file.the_file.read() == "Hello, World!".encode("latin-1") assert test_file.the_file.read() == b"Hello, World!"
test_file = TestFile.objects.first() test_file = TestFile.objects.first()
test_file.the_file = "Hello, World!".encode("latin-1") test_file.the_file = b"Hello, World!"
test_file.save() test_file.save()
test_file = TestFile.objects.first() test_file = TestFile.objects.first()
assert test_file.the_file.read() == "Hello, World!".encode("latin-1") assert test_file.the_file.read() == b"Hello, World!"
def test_copyable(self): def test_copyable(self):
class PutFile(Document): class PutFile(Document):
@ -525,7 +519,7 @@ class TestFileField(MongoDBTestCase):
PutFile.drop_collection() PutFile.drop_collection()
text = "Hello, World!".encode("latin-1") text = b"Hello, World!"
content_type = "text/plain" content_type = "text/plain"
putfile = PutFile() putfile = PutFile()

View File

@ -1,7 +1,6 @@
import pytest import pytest
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -19,8 +18,7 @@ class TestFloatField(MongoDBTestCase):
assert 1 == TestDocument.objects(float_fld__ne=1).count() assert 1 == TestDocument.objects(float_fld__ne=1).count()
def test_validation(self): def test_validation(self):
"""Ensure that invalid values cannot be assigned to float fields. """Ensure that invalid values cannot be assigned to float fields."""
"""
class Person(Document): class Person(Document):
height = FloatField(min_value=0.1, max_value=3.5) height = FloatField(min_value=0.1, max_value=3.5)

View File

@ -8,7 +8,7 @@ class TestGeoField(MongoDBTestCase):
def _test_for_expected_error(self, Cls, loc, expected): def _test_for_expected_error(self, Cls, loc, expected):
try: try:
Cls(loc=loc).validate() Cls(loc=loc).validate()
self.fail("Should not validate the location {0}".format(loc)) self.fail(f"Should not validate the location {loc}")
except ValidationError as e: except ValidationError as e:
assert expected == e.to_dict()["loc"] assert expected == e.to_dict()["loc"]
@ -290,8 +290,7 @@ class TestGeoField(MongoDBTestCase):
Location(loc=[[[[1, 2], [3, 4], [5, 6], [1, 2]]]]).validate() Location(loc=[[[[1, 2], [3, 4], [5, 6], [1, 2]]]]).validate()
def test_indexes_geopoint(self): def test_indexes_geopoint(self):
"""Ensure that indexes are created automatically for GeoPointFields. """Ensure that indexes are created automatically for GeoPointFields."""
"""
class Event(Document): class Event(Document):
title = StringField() title = StringField()
@ -317,8 +316,7 @@ class TestGeoField(MongoDBTestCase):
assert geo_indicies == [{"fields": [("venue.location", "2d")]}] assert geo_indicies == [{"fields": [("venue.location", "2d")]}]
def test_indexes_2dsphere(self): def test_indexes_2dsphere(self):
"""Ensure that indexes are created automatically for GeoPointFields. """Ensure that indexes are created automatically for GeoPointFields."""
"""
class Event(Document): class Event(Document):
title = StringField() title = StringField()
@ -332,8 +330,7 @@ class TestGeoField(MongoDBTestCase):
assert {"fields": [("point", "2dsphere")]} in geo_indicies assert {"fields": [("point", "2dsphere")]} in geo_indicies
def test_indexes_2dsphere_embedded(self): def test_indexes_2dsphere_embedded(self):
"""Ensure that indexes are created automatically for GeoPointFields. """Ensure that indexes are created automatically for GeoPointFields."""
"""
class Venue(EmbeddedDocument): class Venue(EmbeddedDocument):
name = StringField() name = StringField()

View File

@ -1,14 +1,12 @@
import pytest import pytest
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
class TestIntField(MongoDBTestCase): class TestIntField(MongoDBTestCase):
def test_int_validation(self): def test_int_validation(self):
"""Ensure that invalid values cannot be assigned to int fields. """Ensure that invalid values cannot be assigned to int fields."""
"""
class Person(Document): class Person(Document):
age = IntField(min_value=0, max_value=110) age = IntField(min_value=0, max_value=110)

View File

@ -1,9 +1,9 @@
from bson import DBRef, ObjectId
import pytest import pytest
from bson import DBRef, ObjectId
from mongoengine import * from mongoengine import *
from mongoengine.base import LazyReference from mongoengine.base import LazyReference
from mongoengine.context_managers import query_counter
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -330,6 +330,70 @@ class TestLazyReferenceField(MongoDBTestCase):
occ.in_embedded.in_list = [animal1.id, animal2.id] occ.in_embedded.in_list = [animal1.id, animal2.id]
check_fields_type(occ) check_fields_type(occ)
def test_lazy_reference_embedded_dereferencing(self):
# Test case for #2375
# -- Test documents
class Author(Document):
name = StringField()
class AuthorReference(EmbeddedDocument):
author = LazyReferenceField(Author)
class Book(Document):
authors = EmbeddedDocumentListField(AuthorReference)
# -- Cleanup
Author.drop_collection()
Book.drop_collection()
# -- Create test data
author_1 = Author(name="A1").save()
author_2 = Author(name="A2").save()
author_3 = Author(name="A3").save()
book = Book(
authors=[
AuthorReference(author=author_1),
AuthorReference(author=author_2),
AuthorReference(author=author_3),
]
).save()
with query_counter() as qc:
book = Book.objects.first()
# Accessing the list must not trigger dereferencing.
book.authors
assert qc == 1
for ref in book.authors:
with pytest.raises(AttributeError):
ref["author"].name
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): class TestGenericLazyReferenceField(MongoDBTestCase):
def test_generic_lazy_reference_simple(self): def test_generic_lazy_reference_simple(self):

View File

@ -1,13 +1,28 @@
from bson.int64 import Int64
import pytest import pytest
from bson.int64 import Int64
from mongoengine import * from mongoengine import *
from mongoengine.connection import get_db from mongoengine.connection import get_db
from tests.utils import MongoDBTestCase, get_as_pymongo
from tests.utils import MongoDBTestCase
class TestLongField(MongoDBTestCase): class TestLongField(MongoDBTestCase):
def test_storage(self):
class Person(Document):
value = LongField()
Person.drop_collection()
person = Person(value=5000)
person.save()
assert get_as_pymongo(person) == {"_id": person.id, "value": 5000}
def test_construction_does_not_fail_with_invalid_value(self):
class Person(Document):
value = LongField()
person = Person(value="not_an_int")
assert person.value == "not_an_int"
def test_long_field_is_considered_as_int64(self): def test_long_field_is_considered_as_int64(self):
""" """
Tests that long fields are stored as long in mongo, even if long Tests that long fields are stored as long in mongo, even if long
@ -25,25 +40,21 @@ class TestLongField(MongoDBTestCase):
assert isinstance(doc.some_long, int) assert isinstance(doc.some_long, int)
def test_long_validation(self): def test_long_validation(self):
"""Ensure that invalid values cannot be assigned to long fields. """Ensure that invalid values cannot be assigned to long fields."""
"""
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):
@ -54,4 +65,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

@ -135,11 +135,11 @@ class TestMapField(MongoDBTestCase):
BlogPost.drop_collection() BlogPost.drop_collection()
tree = BlogPost(info_dict={u"éééé": {"description": u"VALUE: éééé"}}) tree = BlogPost(info_dict={"éééé": {"description": "VALUE: éééé"}})
tree.save() tree.save()
assert ( assert (
BlogPost.objects.get(id=tree.id).info_dict[u"éééé"].description BlogPost.objects.get(id=tree.id).info_dict["éééé"].description
== u"VALUE: éééé" == "VALUE: éééé"
) )

View File

@ -1,5 +1,5 @@
from bson import DBRef, SON
import pytest import pytest
from bson import SON, DBRef
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -87,7 +87,7 @@ class TestReferenceField(MongoDBTestCase):
parent = ReferenceField("self", dbref=False) parent = ReferenceField("self", dbref=False)
p = Person(name="Steve", parent=DBRef("person", "abcdefghijklmnop")) p = Person(name="Steve", parent=DBRef("person", "abcdefghijklmnop"))
assert p.to_mongo() == SON([("name", u"Steve"), ("parent", "abcdefghijklmnop")]) assert p.to_mongo() == SON([("name", "Steve"), ("parent", "abcdefghijklmnop")])
def test_objectid_reference_fields(self): def test_objectid_reference_fields(self):
class Person(Document): class Person(Document):
@ -107,8 +107,7 @@ class TestReferenceField(MongoDBTestCase):
assert p.parent == p1 assert p.parent == p1
def test_undefined_reference(self): def test_undefined_reference(self):
"""Ensure that ReferenceFields may reference undefined Documents. """Ensure that ReferenceFields may reference undefined Documents."""
"""
class Product(Document): class Product(Document):
name = StringField() name = StringField()

View File

@ -1,5 +1,4 @@
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -166,8 +165,8 @@ class TestSequenceField(MongoDBTestCase):
ids = [i.id for i in Person.objects] ids = [i.id for i in Person.objects]
assert ids == list(range(1, 11)) assert ids == list(range(1, 11))
id = [i.id for i in Animal.objects] _id = [i.id for i in Animal.objects]
assert id == list(range(1, 11)) assert _id == list(range(1, 11))
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"}) c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
assert c["next"] == 10 assert c["next"] == 10
@ -274,3 +273,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

@ -26,7 +26,7 @@ class TestURLField(MongoDBTestCase):
url = URLField() url = URLField()
link = Link() link = Link()
link.url = u"http://привет.com" link.url = "http://привет.com"
# TODO fix URL validation - this *IS* a valid URL # TODO fix URL validation - this *IS* a valid URL
# For now we just want to make sure that the error message is correct # For now we just want to make sure that the error message is correct
@ -34,12 +34,11 @@ class TestURLField(MongoDBTestCase):
link.validate() link.validate()
assert ( assert (
str(exc_info.value) str(exc_info.value)
== u"ValidationError (Link:None) (Invalid URL: http://\u043f\u0440\u0438\u0432\u0435\u0442.com: ['url'])" == "ValidationError (Link:None) (Invalid URL: http://\u043f\u0440\u0438\u0432\u0435\u0442.com: ['url'])"
) )
def test_url_scheme_validation(self): def test_url_scheme_validation(self):
"""Ensure that URLFields validate urls with specific schemes properly. """Ensure that URLFields validate urls with specific schemes properly."""
"""
class Link(Document): class Link(Document):
url = URLField() url = URLField()

View File

@ -17,8 +17,7 @@ class TestUUIDField(MongoDBTestCase):
assert get_as_pymongo(person) == {"_id": person.id, "api_key": str(uid)} assert get_as_pymongo(person) == {"_id": person.id, "api_key": str(uid)}
def test_field_string(self): def test_field_string(self):
"""Test UUID fields storing as String """Test UUID fields storing as String"""
"""
Person.drop_collection() Person.drop_collection()
uu = uuid.uuid4() uu = uuid.uuid4()

View File

@ -53,7 +53,7 @@ signals.post_save.connect(PickleSignalsTest.post_save, sender=PickleSignalsTest)
signals.post_delete.connect(PickleSignalsTest.post_delete, sender=PickleSignalsTest) signals.post_delete.connect(PickleSignalsTest.post_delete, sender=PickleSignalsTest)
class Mixin(object): class Mixin:
name = StringField() name = StringField()

View File

@ -148,8 +148,7 @@ class TestOnlyExcludeAll(unittest.TestCase):
assert qs._loaded_fields.as_dict() == {"c": {"$slice": 2}, "a": 1} assert qs._loaded_fields.as_dict() == {"c": {"$slice": 2}, "a": 1}
def test_only(self): def test_only(self):
"""Ensure that QuerySet.only only returns the requested fields. """Ensure that QuerySet.only only returns the requested fields."""
"""
person = self.Person(name="test", age=25) person = self.Person(name="test", age=25)
person.save() person.save()
@ -365,8 +364,7 @@ class TestOnlyExcludeAll(unittest.TestCase):
Email.drop_collection() Email.drop_collection()
def test_slicing_fields(self): def test_slicing_fields(self):
"""Ensure that query slicing an array works. """Ensure that query slicing an array works."""
"""
class Numbers(Document): class Numbers(Document):
n = ListField(IntField()) n = ListField(IntField())
@ -401,8 +399,7 @@ class TestOnlyExcludeAll(unittest.TestCase):
assert numbers.n == [-5, -4, -3, -2, -1] assert numbers.n == [-5, -4, -3, -2, -1]
def test_slicing_nested_fields(self): def test_slicing_nested_fields(self):
"""Ensure that query slicing an embedded array works. """Ensure that query slicing an embedded array works."""
"""
class EmbeddedNumber(EmbeddedDocument): class EmbeddedNumber(EmbeddedDocument):
n = ListField(IntField()) n = ListField(IntField())

View File

@ -2,7 +2,6 @@ import datetime
import unittest import unittest
from mongoengine import * from mongoengine import *
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -496,8 +495,8 @@ class TestGeoQueries(MongoDBTestCase):
p.save() p.save()
qs = Place.objects().only("location") qs = Place.objects().only("location")
assert qs.as_pymongo()[0]["location"] == { assert qs.as_pymongo()[0]["location"] == {
u"type": u"Point", "type": "Point",
u"coordinates": [24.946861267089844, 60.16311983618494], "coordinates": [24.946861267089844, 60.16311983618494],
} }
def test_2dsphere_point_sets_correctly(self): def test_2dsphere_point_sets_correctly(self):

View File

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

View File

@ -1,8 +1,6 @@
import pickle import pickle
import unittest
from mongoengine import Document, IntField, StringField from mongoengine import Document, IntField, StringField
from mongoengine.connection import connect
from tests.utils import MongoDBTestCase from tests.utils import MongoDBTestCase
@ -18,18 +16,15 @@ class TestQuerysetPickable(MongoDBTestCase):
""" """
def setUp(self): def setUp(self):
super(TestQuerysetPickable, self).setUp() super().setUp()
self.john = Person.objects.create(name="John", age=21) self.john = Person.objects.create(name="John", age=21)
def test_picke_simple_qs(self): def test_picke_simple_qs(self):
qs = Person.objects.all() qs = Person.objects.all()
pickle.dumps(qs) pickle.dumps(qs)
def _get_loaded(self, qs): def _get_loaded(self, qs):
s = pickle.dumps(qs) s = pickle.dumps(qs)
return pickle.loads(s) return pickle.loads(s)
def test_unpickle(self): def test_unpickle(self):

File diff suppressed because it is too large Load Diff

View File

@ -64,7 +64,7 @@ class TestQuerysetAggregate(MongoDBTestCase):
pipeline = [{"$match": {"name": "Isabella Luanna"}}] pipeline = [{"$match": {"name": "Isabella Luanna"}}]
data = Person.objects().aggregate(pipeline) data = Person.objects().aggregate(pipeline)
assert list(data) == [{u"_id": p1.pk, u"age": 16, u"name": u"Isabella Luanna"}] assert list(data) == [{"_id": p1.pk, "age": 16, "name": "Isabella Luanna"}]
def test_queryset_aggregation_with_skip(self): def test_queryset_aggregation_with_skip(self):
class Person(Document): class Person(Document):
@ -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,7 +1,7 @@
import unittest import unittest
from bson.son import SON
import pytest import pytest
from bson.son import SON
from mongoengine import * from mongoengine import *
from mongoengine.queryset import Q, transform from mongoengine.queryset import Q, transform
@ -12,8 +12,7 @@ class TestTransform(unittest.TestCase):
connect(db="mongoenginetest") connect(db="mongoenginetest")
def test_transform_query(self): def test_transform_query(self):
"""Ensure that the _transform_query function operates correctly. """Ensure that the _transform_query function operates correctly."""
"""
assert transform.query(name="test", age=30) == {"name": "test", "age": 30} assert transform.query(name="test", age=30) == {"name": "test", "age": 30}
assert transform.query(age__lt=30) == {"age": {"$lt": 30}} assert transform.query(age__lt=30) == {"age": {"$lt": 30}}
assert transform.query(age__gt=20, age__lt=50) == { assert transform.query(age__gt=20, age__lt=50) == {
@ -88,8 +87,7 @@ class TestTransform(unittest.TestCase):
assert update == {"$set": {"tags": ["mongo", "db"]}} assert update == {"$set": {"tags": ["mongo", "db"]}}
def test_query_field_name(self): def test_query_field_name(self):
"""Ensure that the correct field name is used when querying. """Ensure that the correct field name is used when querying."""
"""
class Comment(EmbeddedDocument): class Comment(EmbeddedDocument):
content = StringField(db_field="commentContent") content = StringField(db_field="commentContent")
@ -106,18 +104,17 @@ class TestTransform(unittest.TestCase):
post = BlogPost(**data) post = BlogPost(**data)
post.save() post.save()
assert "postTitle" in BlogPost.objects(title=data["title"])._query qs = BlogPost.objects(title=data["title"])
assert not ("title" in BlogPost.objects(title=data["title"])._query) assert qs._query == {"postTitle": data["title"]}
assert BlogPost.objects(title=data["title"]).count() == 1 assert qs.count() == 1
assert "_id" in BlogPost.objects(pk=post.id)._query qs = BlogPost.objects(pk=post.id)
assert BlogPost.objects(pk=post.id).count() == 1 assert qs._query == {"_id": post.id}
assert qs.count() == 1
assert ( qs = BlogPost.objects(comments__content="test")
"postComments.commentContent" assert qs._query == {"postComments.commentContent": "test"}
in BlogPost.objects(comments__content="test")._query assert qs.count() == 1
)
assert BlogPost.objects(comments__content="test").count() == 1
BlogPost.drop_collection() BlogPost.drop_collection()
@ -330,7 +327,7 @@ class TestTransform(unittest.TestCase):
word = Word(word="abc", index=1) word = Word(word="abc", index=1)
update = transform.update(MainDoc, pull__content__text=word) update = transform.update(MainDoc, pull__content__text=word)
assert update == { assert update == {
"$pull": {"content.text": SON([("word", u"abc"), ("index", 1)])} "$pull": {"content.text": SON([("word", "abc"), ("index", 1)])}
} }
update = transform.update(MainDoc, pull__content__heading="xyz") update = transform.update(MainDoc, pull__content__heading="xyz")

View File

@ -2,8 +2,8 @@ import datetime
import re import re
import unittest import unittest
from bson import ObjectId
import pytest import pytest
from bson import ObjectId
from mongoengine import * from mongoengine import *
from mongoengine.errors import InvalidQueryError from mongoengine.errors import InvalidQueryError
@ -23,8 +23,7 @@ class TestQ(unittest.TestCase):
self.Person = Person self.Person = Person
def test_empty_q(self): def test_empty_q(self):
"""Ensure that empty Q objects won't hurt. """Ensure that empty Q objects won't hurt."""
"""
q1 = Q() q1 = Q()
q2 = Q(age__gte=18) q2 = Q(age__gte=18)
q3 = Q() q3 = Q()
@ -58,8 +57,7 @@ class TestQ(unittest.TestCase):
assert Post.objects.filter(Q(created_user=user)).count() == 1 assert Post.objects.filter(Q(created_user=user)).count() == 1
def test_and_combination(self): def test_and_combination(self):
"""Ensure that Q-objects correctly AND together. """Ensure that Q-objects correctly AND together."""
"""
class TestDoc(Document): class TestDoc(Document):
x = IntField() x = IntField()
@ -89,8 +87,7 @@ class TestQ(unittest.TestCase):
assert query.to_query(TestDoc) == mongo_query assert query.to_query(TestDoc) == mongo_query
def test_or_combination(self): def test_or_combination(self):
"""Ensure that Q-objects correctly OR together. """Ensure that Q-objects correctly OR together."""
"""
class TestDoc(Document): class TestDoc(Document):
x = IntField() x = IntField()
@ -101,8 +98,7 @@ class TestQ(unittest.TestCase):
assert query == {"$or": [{"x": {"$lt": 3}}, {"x": {"$gt": 7}}]} assert query == {"$or": [{"x": {"$lt": 3}}, {"x": {"$gt": 7}}]}
def test_and_or_combination(self): def test_and_or_combination(self):
"""Ensure that Q-objects handle ANDing ORed components. """Ensure that Q-objects handle ANDing ORed components."""
"""
class TestDoc(Document): class TestDoc(Document):
x = IntField() x = IntField()
@ -136,8 +132,7 @@ class TestQ(unittest.TestCase):
assert 2 == TestDoc.objects(q1 & q2).count() assert 2 == TestDoc.objects(q1 & q2).count()
def test_or_and_or_combination(self): def test_or_and_or_combination(self):
"""Ensure that Q-objects handle ORing ANDed ORed components. :) """Ensure that Q-objects handle ORing ANDed ORed components. :)"""
"""
class TestDoc(Document): class TestDoc(Document):
x = IntField() x = IntField()
@ -208,8 +203,7 @@ class TestQ(unittest.TestCase):
assert test.count() == 3 assert test.count() == 3
def test_q(self): def test_q(self):
"""Ensure that Q objects may be used to query for documents. """Ensure that Q objects may be used to query for documents."""
"""
class BlogPost(Document): class BlogPost(Document):
title = StringField() title = StringField()
@ -286,8 +280,7 @@ class TestQ(unittest.TestCase):
self.Person.objects.filter("user1") self.Person.objects.filter("user1")
def test_q_regex(self): def test_q_regex(self):
"""Ensure that Q objects can be queried using regexes. """Ensure that Q objects can be queried using regexes."""
"""
person = self.Person(name="Guido van Rossum") person = self.Person(name="Guido van Rossum")
person.save() person.save()
@ -320,8 +313,7 @@ class TestQ(unittest.TestCase):
) )
def test_q_lists(self): def test_q_lists(self):
"""Ensure that Q objects query ListFields correctly. """Ensure that Q objects query ListFields correctly."""
"""
class BlogPost(Document): class BlogPost(Document):
tags = ListField(StringField()) tags = ListField(StringField())

View File

@ -1,5 +1,3 @@
import unittest
import pytest import pytest
from mongoengine import Document from mongoengine import Document

View File

@ -1,16 +1,11 @@
import datetime import datetime
import unittest
from bson.tz_util import utc
import pymongo import pymongo
import pytest
from bson.tz_util import utc
from pymongo import MongoClient, ReadPreference from pymongo import MongoClient, ReadPreference
from pymongo.errors import InvalidName, OperationFailure from pymongo.errors import InvalidName, OperationFailure
import pytest
try:
import unittest2 as unittest
except ImportError:
import unittest
import mongoengine.connection import mongoengine.connection
from mongoengine import ( from mongoengine import (
@ -22,8 +17,8 @@ from mongoengine import (
register_connection, register_connection,
) )
from mongoengine.connection import ( from mongoengine.connection import (
ConnectionFailure,
DEFAULT_DATABASE_NAME, DEFAULT_DATABASE_NAME,
ConnectionFailure,
disconnect, disconnect,
get_connection, get_connection,
get_db, get_db,
@ -34,18 +29,6 @@ def get_tz_awareness(connection):
return connection.codec_options.tz_aware return connection.codec_options.tz_aware
try:
import mongomock
MONGOMOCK_INSTALLED = True
except ImportError:
MONGOMOCK_INSTALLED = False
require_mongomock = pytest.mark.skipif(
not MONGOMOCK_INSTALLED, reason="you need mongomock installed to run this testcase"
)
class ConnectionTest(unittest.TestCase): class ConnectionTest(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@ -194,14 +177,12 @@ class ConnectionTest(unittest.TestCase):
assert len(mongoengine.connection._connections) == 3 assert len(mongoengine.connection._connections) == 3
def test_connect_with_invalid_db_name(self): def test_connect_with_invalid_db_name(self):
"""Ensure that connect() method fails fast if db name is invalid """Ensure that connect() method fails fast if db name is invalid"""
"""
with pytest.raises(InvalidName): with pytest.raises(InvalidName):
connect("mongomock://localhost") connect("mongodb://localhost")
def test_connect_with_db_name_external(self): def test_connect_with_db_name_external(self):
"""Ensure that connect() works if db name is $external """Ensure that connect() works if db name is $external"""
"""
"""Ensure that the connect() method works properly.""" """Ensure that the connect() method works properly."""
connect("$external") connect("$external")
@ -217,112 +198,11 @@ class ConnectionTest(unittest.TestCase):
assert isinstance(conn, pymongo.mongo_client.MongoClient) assert isinstance(conn, pymongo.mongo_client.MongoClient)
def test_connect_with_invalid_db_name_type(self): def test_connect_with_invalid_db_name_type(self):
"""Ensure that connect() method fails fast if db name has invalid type """Ensure that connect() method fails fast if db name has invalid type"""
"""
with pytest.raises(TypeError): with pytest.raises(TypeError):
non_string_db_name = ["e. g. list instead of a string"] non_string_db_name = ["e. g. list instead of a string"]
connect(non_string_db_name) connect(non_string_db_name)
@require_mongomock
def test_connect_in_mocking(self):
"""Ensure that the connect() method works properly in mocking.
"""
connect("mongoenginetest", host="mongomock://localhost")
conn = get_connection()
assert isinstance(conn, mongomock.MongoClient)
connect("mongoenginetest2", host="mongomock://localhost", alias="testdb2")
conn = get_connection("testdb2")
assert isinstance(conn, mongomock.MongoClient)
connect(
"mongoenginetest3",
host="mongodb://localhost",
is_mock=True,
alias="testdb3",
)
conn = get_connection("testdb3")
assert isinstance(conn, mongomock.MongoClient)
connect("mongoenginetest4", is_mock=True, alias="testdb4")
conn = get_connection("testdb4")
assert isinstance(conn, mongomock.MongoClient)
connect(
host="mongodb://localhost:27017/mongoenginetest5",
is_mock=True,
alias="testdb5",
)
conn = get_connection("testdb5")
assert isinstance(conn, mongomock.MongoClient)
connect(host="mongomock://localhost:27017/mongoenginetest6", alias="testdb6")
conn = get_connection("testdb6")
assert isinstance(conn, mongomock.MongoClient)
connect(
host="mongomock://localhost:27017/mongoenginetest7",
is_mock=True,
alias="testdb7",
)
conn = get_connection("testdb7")
assert isinstance(conn, mongomock.MongoClient)
@require_mongomock
def test_default_database_with_mocking(self):
"""Ensure that the default database is correctly set when using mongomock.
"""
disconnect_all()
class SomeDocument(Document):
pass
conn = connect(host="mongomock://localhost:27017/mongoenginetest")
some_document = SomeDocument()
# database won't exist until we save a document
some_document.save()
assert conn.get_default_database().name == "mongoenginetest"
assert conn.list_database_names()[0] == "mongoenginetest"
@require_mongomock
def test_connect_with_host_list(self):
"""Ensure that the connect() method works when host is a list
Uses mongomock to test w/o needing multiple mongod/mongos processes
"""
connect(host=["mongomock://localhost"])
conn = get_connection()
assert isinstance(conn, mongomock.MongoClient)
connect(host=["mongodb://localhost"], is_mock=True, alias="testdb2")
conn = get_connection("testdb2")
assert isinstance(conn, mongomock.MongoClient)
connect(host=["localhost"], is_mock=True, alias="testdb3")
conn = get_connection("testdb3")
assert isinstance(conn, mongomock.MongoClient)
connect(
host=["mongomock://localhost:27017", "mongomock://localhost:27018"],
alias="testdb4",
)
conn = get_connection("testdb4")
assert isinstance(conn, mongomock.MongoClient)
connect(
host=["mongodb://localhost:27017", "mongodb://localhost:27018"],
is_mock=True,
alias="testdb5",
)
conn = get_connection("testdb5")
assert isinstance(conn, mongomock.MongoClient)
connect(
host=["localhost:27017", "localhost:27018"], is_mock=True, alias="testdb6"
)
conn = get_connection("testdb6")
assert isinstance(conn, mongomock.MongoClient)
def test_disconnect_cleans_globals(self): def test_disconnect_cleans_globals(self):
"""Ensure that the disconnect() method cleans the globals objects""" """Ensure that the disconnect() method cleans the globals objects"""
connections = mongoengine.connection._connections connections = mongoengine.connection._connections
@ -452,8 +332,7 @@ class ConnectionTest(unittest.TestCase):
disconnect_all() disconnect_all()
def test_sharing_connections(self): def test_sharing_connections(self):
"""Ensure that connections are shared when the connection settings are exactly the same """Ensure that connections are shared when the connection settings are exactly the same"""
"""
connect("mongoenginetests", alias="testdb1") connect("mongoenginetests", alias="testdb1")
expected_connection = get_connection("testdb1") expected_connection = get_connection("testdb1")
@ -564,8 +443,7 @@ class ConnectionTest(unittest.TestCase):
authd_conn.admin.system.users.delete_many({}) authd_conn.admin.system.users.delete_many({})
def test_register_connection(self): def test_register_connection(self):
"""Ensure that connections with different aliases may be registered. """Ensure that connections with different aliases may be registered."""
"""
register_connection("testdb", "mongoenginetest2") register_connection("testdb", "mongoenginetest2")
with pytest.raises(ConnectionFailure): with pytest.raises(ConnectionFailure):
@ -578,8 +456,7 @@ class ConnectionTest(unittest.TestCase):
assert db.name == "mongoenginetest2" assert db.name == "mongoenginetest2"
def test_register_connection_defaults(self): def test_register_connection_defaults(self):
"""Ensure that defaults are used when the host and port are None. """Ensure that defaults are used when the host and port are None."""
"""
register_connection("testdb", "mongoenginetest", host=None, port=None) register_connection("testdb", "mongoenginetest", host=None, port=None)
conn = get_connection("testdb") conn = get_connection("testdb")

View File

@ -0,0 +1,167 @@
import unittest
import pytest
import mongoengine.connection
from mongoengine import Document, StringField, connect, disconnect_all
from mongoengine.connection import get_connection
try:
import mongomock
MONGOMOCK_INSTALLED = True
except ImportError:
MONGOMOCK_INSTALLED = False
require_mongomock = pytest.mark.skipif(
not MONGOMOCK_INSTALLED, reason="you need mongomock installed to run this testcase"
)
class MongoMockConnectionTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
disconnect_all()
@classmethod
def tearDownClass(cls):
disconnect_all()
def tearDown(self):
mongoengine.connection._connection_settings = {}
mongoengine.connection._connections = {}
mongoengine.connection._dbs = {}
@require_mongomock
def test_connect_in_mocking(self):
"""Ensure that the connect() method works properly in mocking."""
connect("mongoenginetest", host="mongomock://localhost")
conn = get_connection()
assert isinstance(conn, mongomock.MongoClient)
connect("mongoenginetest2", host="mongomock://localhost", alias="testdb2")
conn = get_connection("testdb2")
assert isinstance(conn, mongomock.MongoClient)
connect(
"mongoenginetest3",
host="mongodb://localhost",
is_mock=True,
alias="testdb3",
)
conn = get_connection("testdb3")
assert isinstance(conn, mongomock.MongoClient)
connect("mongoenginetest4", is_mock=True, alias="testdb4")
conn = get_connection("testdb4")
assert isinstance(conn, mongomock.MongoClient)
connect(
host="mongodb://localhost:27017/mongoenginetest5",
is_mock=True,
alias="testdb5",
)
conn = get_connection("testdb5")
assert isinstance(conn, mongomock.MongoClient)
connect(host="mongomock://localhost:27017/mongoenginetest6", alias="testdb6")
conn = get_connection("testdb6")
assert isinstance(conn, mongomock.MongoClient)
connect(
host="mongomock://localhost:27017/mongoenginetest7",
is_mock=True,
alias="testdb7",
)
conn = get_connection("testdb7")
assert isinstance(conn, mongomock.MongoClient)
@require_mongomock
def test_default_database_with_mocking(self):
"""Ensure that the default database is correctly set when using mongomock."""
disconnect_all()
class SomeDocument(Document):
pass
conn = connect(host="mongomock://localhost:27017/mongoenginetest")
some_document = SomeDocument()
# database won't exist until we save a document
some_document.save()
assert SomeDocument.objects.count() == 1
assert conn.get_default_database().name == "mongoenginetest"
assert conn.list_database_names()[0] == "mongoenginetest"
@require_mongomock
def test_basic_queries_against_mongomock(self):
disconnect_all()
connect(host="mongomock://localhost:27017/mongoenginetest")
class Person(Document):
name = StringField()
Person.drop_collection()
assert Person.objects.count() == 0
bob = Person(name="Bob").save()
john = Person(name="John").save()
assert Person.objects.count() == 2
qs = Person.objects(name="Bob")
assert qs.count() == 1
assert qs.first() == bob
assert list(qs.as_pymongo()) == [{"_id": bob.id, "name": "Bob"}]
pipeline = [{"$project": {"name": {"$toUpper": "$name"}}}]
data = Person.objects.order_by("name").aggregate(pipeline)
assert list(data) == [
{"_id": bob.id, "name": "BOB"},
{"_id": john.id, "name": "JOHN"},
]
Person.drop_collection()
assert Person.objects.count() == 0
@require_mongomock
def test_connect_with_host_list(self):
"""Ensure that the connect() method works when host is a list
Uses mongomock to test w/o needing multiple mongod/mongos processes
"""
connect(host=["mongomock://localhost"])
conn = get_connection()
assert isinstance(conn, mongomock.MongoClient)
connect(host=["mongodb://localhost"], is_mock=True, alias="testdb2")
conn = get_connection("testdb2")
assert isinstance(conn, mongomock.MongoClient)
connect(host=["localhost"], is_mock=True, alias="testdb3")
conn = get_connection("testdb3")
assert isinstance(conn, mongomock.MongoClient)
connect(
host=["mongomock://localhost:27017", "mongomock://localhost:27018"],
alias="testdb4",
)
conn = get_connection("testdb4")
assert isinstance(conn, mongomock.MongoClient)
connect(
host=["mongodb://localhost:27017", "mongodb://localhost:27018"],
is_mock=True,
alias="testdb5",
)
conn = get_connection("testdb5")
assert isinstance(conn, mongomock.MongoClient)
connect(
host=["localhost:27017", "localhost:27018"], is_mock=True, alias="testdb6"
)
conn = get_connection("testdb6")
assert isinstance(conn, mongomock.MongoClient)
if __name__ == "__main__":
unittest.main()

View File

@ -117,8 +117,7 @@ class TestContextManagers:
assert 1 == Group.objects.count() assert 1 == Group.objects.count()
def test_no_dereference_context_manager_object_id(self): def test_no_dereference_context_manager_object_id(self):
"""Ensure that DBRef items in ListFields aren't dereferenced. """Ensure that DBRef items in ListFields aren't dereferenced."""
"""
connect("mongoenginetest") connect("mongoenginetest")
class User(Document): class User(Document):
@ -155,8 +154,7 @@ class TestContextManagers:
assert isinstance(group.generic, User) assert isinstance(group.generic, User)
def test_no_dereference_context_manager_dbref(self): def test_no_dereference_context_manager_dbref(self):
"""Ensure that DBRef items in ListFields aren't dereferenced. """Ensure that DBRef items in ListFields aren't dereferenced."""
"""
connect("mongoenginetest") connect("mongoenginetest")
class User(Document): class User(Document):
@ -182,11 +180,11 @@ class TestContextManagers:
with no_dereference(Group) as Group: with no_dereference(Group) as Group:
group = Group.objects.first() group = Group.objects.first()
assert all([not isinstance(m, User) for m in group.members]) assert all(not isinstance(m, User) for m in group.members)
assert not isinstance(group.ref, User) assert not isinstance(group.ref, User)
assert not isinstance(group.generic, User) assert not isinstance(group.generic, User)
assert all([isinstance(m, User) for m in group.members]) assert all(isinstance(m, User) for m in group.members)
assert isinstance(group.ref, User) assert isinstance(group.ref, User)
assert isinstance(group.generic, User) assert isinstance(group.generic, User)

View File

@ -3,10 +3,14 @@ import unittest
import pytest import pytest
from mongoengine import Document from mongoengine import Document
from mongoengine.base.datastructures import BaseDict, BaseList, StrictDict from mongoengine.base.datastructures import (
BaseDict,
BaseList,
StrictDict,
)
class DocumentStub(object): class DocumentStub:
def __init__(self): def __init__(self):
self._changed_fields = [] self._changed_fields = []
self._unset_fields = [] self._unset_fields = []
@ -421,7 +425,7 @@ class TestStrictDict(unittest.TestCase):
d.a = 1 d.a = 1
assert d.a == 1 assert d.a == 1
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
getattr(d, "b") d.b
def test_setattr_raises_on_nonexisting_attr(self): def test_setattr_raises_on_nonexisting_attr(self):
d = self.dtype() d = self.dtype()

View File

@ -16,8 +16,7 @@ class FieldTest(unittest.TestCase):
cls.db.drop_database("mongoenginetest") cls.db.drop_database("mongoenginetest")
def test_list_item_dereference(self): def test_list_item_dereference(self):
"""Ensure that DBRef items in ListFields are dereferenced. """Ensure that DBRef items in ListFields are dereferenced."""
"""
class User(Document): class User(Document):
name = StringField() name = StringField()
@ -50,7 +49,7 @@ class FieldTest(unittest.TestCase):
len(group_obj.members) len(group_obj.members)
assert q == 2 assert q == 2
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
# Document select_related # Document select_related
@ -59,7 +58,7 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first().select_related() group_obj = Group.objects.first().select_related()
assert q == 2 assert q == 2
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
# Queryset select_related # Queryset select_related
@ -68,15 +67,14 @@ class FieldTest(unittest.TestCase):
group_objs = Group.objects.select_related() group_objs = Group.objects.select_related()
assert q == 2 assert q == 2
for group_obj in group_objs: for group_obj in group_objs:
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
User.drop_collection() User.drop_collection()
Group.drop_collection() Group.drop_collection()
def test_list_item_dereference_dref_false(self): def test_list_item_dereference_dref_false(self):
"""Ensure that DBRef items in ListFields are dereferenced. """Ensure that DBRef items in ListFields are dereferenced."""
"""
class User(Document): class User(Document):
name = StringField() name = StringField()
@ -101,14 +99,14 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first() group_obj = Group.objects.first()
assert q == 1 assert q == 1
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
assert group_obj._data["members"]._dereferenced assert group_obj._data["members"]._dereferenced
# verifies that no additional queries gets executed # verifies that no additional queries gets executed
# if we re-iterate over the ListField once it is # if we re-iterate over the ListField once it is
# dereferenced # dereferenced
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
assert group_obj._data["members"]._dereferenced assert group_obj._data["members"]._dereferenced
@ -119,7 +117,7 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first().select_related() group_obj = Group.objects.first().select_related()
assert q == 2 assert q == 2
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
# Queryset select_related # Queryset select_related
@ -128,12 +126,11 @@ class FieldTest(unittest.TestCase):
group_objs = Group.objects.select_related() group_objs = Group.objects.select_related()
assert q == 2 assert q == 2
for group_obj in group_objs: for group_obj in group_objs:
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
def test_list_item_dereference_orphan_dbref(self): def test_list_item_dereference_orphan_dbref(self):
"""Ensure that orphan DBRef items in ListFields are dereferenced. """Ensure that orphan DBRef items in ListFields are dereferenced."""
"""
class User(Document): class User(Document):
name = StringField() name = StringField()
@ -161,14 +158,14 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first() group_obj = Group.objects.first()
assert q == 1 assert q == 1
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
assert group_obj._data["members"]._dereferenced assert group_obj._data["members"]._dereferenced
# verifies that no additional queries gets executed # verifies that no additional queries gets executed
# if we re-iterate over the ListField once it is # if we re-iterate over the ListField once it is
# dereferenced # dereferenced
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
assert group_obj._data["members"]._dereferenced assert group_obj._data["members"]._dereferenced
@ -176,8 +173,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection() Group.drop_collection()
def test_list_item_dereference_dref_false_stores_as_type(self): def test_list_item_dereference_dref_false_stores_as_type(self):
"""Ensure that DBRef items are stored as their type """Ensure that DBRef items are stored as their type"""
"""
class User(Document): class User(Document):
my_id = IntField(primary_key=True) my_id = IntField(primary_key=True)
@ -198,8 +194,7 @@ class FieldTest(unittest.TestCase):
assert group.members == [user] assert group.members == [user]
def test_handle_old_style_references(self): def test_handle_old_style_references(self):
"""Ensure that DBRef items in ListFields are dereferenced. """Ensure that DBRef items in ListFields are dereferenced."""
"""
class User(Document): class User(Document):
name = StringField() name = StringField()
@ -232,8 +227,7 @@ class FieldTest(unittest.TestCase):
assert group.members[-1].name == "String!" assert group.members[-1].name == "String!"
def test_migrate_references(self): def test_migrate_references(self):
"""Example of migrating ReferenceField storage """Example of migrating ReferenceField storage"""
"""
# Create some sample data # Create some sample data
class User(Document): class User(Document):
@ -278,8 +272,7 @@ class FieldTest(unittest.TestCase):
assert isinstance(raw_data["members"][0], ObjectId) assert isinstance(raw_data["members"][0], ObjectId)
def test_recursive_reference(self): def test_recursive_reference(self):
"""Ensure that ReferenceFields can reference their own documents. """Ensure that ReferenceFields can reference their own documents."""
"""
class Employee(Document): class Employee(Document):
name = StringField() name = StringField()
@ -402,8 +395,7 @@ class FieldTest(unittest.TestCase):
assert "[<Person: Mother>, <Person: Daughter>]" == "%s" % Person.objects() assert "[<Person: Mother>, <Person: Daughter>]" == "%s" % Person.objects()
def test_circular_reference_on_self(self): def test_circular_reference_on_self(self):
"""Ensure you can handle circular references """Ensure you can handle circular references"""
"""
class Person(Document): class Person(Document):
name = StringField() name = StringField()
@ -430,8 +422,7 @@ class FieldTest(unittest.TestCase):
assert "[<Person: Mother>, <Person: Daughter>]" == "%s" % Person.objects() assert "[<Person: Mother>, <Person: Daughter>]" == "%s" % Person.objects()
def test_circular_tree_reference(self): def test_circular_tree_reference(self):
"""Ensure you can handle circular references with more than one level """Ensure you can handle circular references with more than one level"""
"""
class Other(EmbeddedDocument): class Other(EmbeddedDocument):
name = StringField() name = StringField()
@ -514,10 +505,10 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first() group_obj = Group.objects.first()
assert q == 1 assert q == 1
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
for m in group_obj.members: for m in group_obj.members:
@ -530,10 +521,10 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first().select_related() group_obj = Group.objects.first().select_related()
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
for m in group_obj.members: for m in group_obj.members:
@ -547,18 +538,17 @@ class FieldTest(unittest.TestCase):
assert q == 4 assert q == 4
for group_obj in group_objs: for group_obj in group_objs:
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
for m in group_obj.members: for m in group_obj.members:
assert "User" in m.__class__.__name__ assert "User" in m.__class__.__name__
def test_generic_reference_orphan_dbref(self): def test_generic_reference_orphan_dbref(self):
"""Ensure that generic orphan DBRef items in ListFields are dereferenced. """Ensure that generic orphan DBRef items in ListFields are dereferenced."""
"""
class UserA(Document): class UserA(Document):
name = StringField() name = StringField()
@ -602,11 +592,11 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first() group_obj = Group.objects.first()
assert q == 1 assert q == 1
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
assert group_obj._data["members"]._dereferenced assert group_obj._data["members"]._dereferenced
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
assert group_obj._data["members"]._dereferenced assert group_obj._data["members"]._dereferenced
@ -658,10 +648,10 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first() group_obj = Group.objects.first()
assert q == 1 assert q == 1
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
for m in group_obj.members: for m in group_obj.members:
@ -674,10 +664,10 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first().select_related() group_obj = Group.objects.first().select_related()
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
for m in group_obj.members: for m in group_obj.members:
@ -691,10 +681,10 @@ class FieldTest(unittest.TestCase):
assert q == 4 assert q == 4
for group_obj in group_objs: for group_obj in group_objs:
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
for m in group_obj.members: for m in group_obj.members:
@ -733,10 +723,10 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first() group_obj = Group.objects.first()
assert q == 1 assert q == 1
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
for k, m in group_obj.members.items(): for _, m in group_obj.members.items():
assert isinstance(m, User) assert isinstance(m, User)
# Document select_related # Document select_related
@ -746,7 +736,7 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first().select_related() group_obj = Group.objects.first().select_related()
assert q == 2 assert q == 2
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
for k, m in group_obj.members.items(): for k, m in group_obj.members.items():
@ -760,7 +750,7 @@ class FieldTest(unittest.TestCase):
assert q == 2 assert q == 2
for group_obj in group_objs: for group_obj in group_objs:
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
for k, m in group_obj.members.items(): for k, m in group_obj.members.items():
@ -811,10 +801,10 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first() group_obj = Group.objects.first()
assert q == 1 assert q == 1
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
for k, m in group_obj.members.items(): for k, m in group_obj.members.items():
@ -827,10 +817,10 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first().select_related() group_obj = Group.objects.first().select_related()
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
for k, m in group_obj.members.items(): for k, m in group_obj.members.items():
@ -844,10 +834,10 @@ class FieldTest(unittest.TestCase):
assert q == 4 assert q == 4
for group_obj in group_objs: for group_obj in group_objs:
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
for k, m in group_obj.members.items(): for k, m in group_obj.members.items():
@ -862,7 +852,7 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first() group_obj = Group.objects.first()
assert q == 1 assert q == 1
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 1 assert q == 1
assert group_obj.members == {} assert group_obj.members == {}
@ -901,10 +891,10 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first() group_obj = Group.objects.first()
assert q == 1 assert q == 1
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
for k, m in group_obj.members.items(): for k, m in group_obj.members.items():
@ -917,10 +907,10 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first().select_related() group_obj = Group.objects.first().select_related()
assert q == 2 assert q == 2
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
for k, m in group_obj.members.items(): for k, m in group_obj.members.items():
@ -934,13 +924,13 @@ class FieldTest(unittest.TestCase):
assert q == 2 assert q == 2
for group_obj in group_objs: for group_obj in group_objs:
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 2 assert q == 2
for k, m in group_obj.members.items(): for _, m in group_obj.members.items():
assert isinstance(m, UserA) assert isinstance(m, UserA)
UserA.drop_collection() UserA.drop_collection()
@ -988,13 +978,13 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first() group_obj = Group.objects.first()
assert q == 1 assert q == 1
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
for k, m in group_obj.members.items(): for _, m in group_obj.members.items():
assert "User" in m.__class__.__name__ assert "User" in m.__class__.__name__
# Document select_related # Document select_related
@ -1004,13 +994,13 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first().select_related() group_obj = Group.objects.first().select_related()
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
for k, m in group_obj.members.items(): for _, m in group_obj.members.items():
assert "User" in m.__class__.__name__ assert "User" in m.__class__.__name__
# Queryset select_related # Queryset select_related
@ -1021,13 +1011,13 @@ class FieldTest(unittest.TestCase):
assert q == 4 assert q == 4
for group_obj in group_objs: for group_obj in group_objs:
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 4 assert q == 4
for k, m in group_obj.members.items(): for _, m in group_obj.members.items():
assert "User" in m.__class__.__name__ assert "User" in m.__class__.__name__
Group.objects.delete() Group.objects.delete()
@ -1039,7 +1029,7 @@ class FieldTest(unittest.TestCase):
group_obj = Group.objects.first() group_obj = Group.objects.first()
assert q == 1 assert q == 1
[m for m in group_obj.members] _ = [m for m in group_obj.members]
assert q == 1 assert q == 1
UserA.drop_collection() UserA.drop_collection()
@ -1168,8 +1158,7 @@ class FieldTest(unittest.TestCase):
assert msg.author.name == "new-name" assert msg.author.name == "new-name"
def test_list_lookup_not_checked_in_map(self): def test_list_lookup_not_checked_in_map(self):
"""Ensure we dereference list data correctly """Ensure we dereference list data correctly"""
"""
class Comment(Document): class Comment(Document):
id = IntField(primary_key=True) id = IntField(primary_key=True)
@ -1191,8 +1180,7 @@ class FieldTest(unittest.TestCase):
assert 1 == msg.comments[1].id assert 1 == msg.comments[1].id
def test_list_item_dereference_dref_false_save_doesnt_cause_extra_queries(self): def test_list_item_dereference_dref_false_save_doesnt_cause_extra_queries(self):
"""Ensure that DBRef items in ListFields are dereferenced. """Ensure that DBRef items in ListFields are dereferenced."""
"""
class User(Document): class User(Document):
name = StringField() name = StringField()
@ -1221,8 +1209,7 @@ class FieldTest(unittest.TestCase):
assert q == 2 assert q == 2
def test_list_item_dereference_dref_true_save_doesnt_cause_extra_queries(self): def test_list_item_dereference_dref_true_save_doesnt_cause_extra_queries(self):
"""Ensure that DBRef items in ListFields are dereferenced. """Ensure that DBRef items in ListFields are dereferenced."""
"""
class User(Document): class User(Document):
name = StringField() name = StringField()
@ -1334,7 +1321,7 @@ class FieldTest(unittest.TestCase):
BrandGroup.drop_collection() BrandGroup.drop_collection()
brand1 = Brand(title="Moschino").save() brand1 = Brand(title="Moschino").save()
brand2 = Brand(title=u"Денис Симачёв").save() brand2 = Brand(title="Денис Симачёв").save()
BrandGroup(title="top_brands", brands=[brand1, brand2]).save() BrandGroup(title="top_brands", brands=[brand1, brand2]).save()
brand_groups = BrandGroup.objects().all() brand_groups = BrandGroup.objects().all()

View File

@ -5,7 +5,6 @@ from pymongo import MongoClient, ReadPreference
import mongoengine import mongoengine
from mongoengine.connection import ConnectionFailure from mongoengine.connection import ConnectionFailure
CONN_CLASS = MongoClient CONN_CLASS = MongoClient
READ_PREF = ReadPreference.SECONDARY READ_PREF = ReadPreference.SECONDARY
@ -22,8 +21,7 @@ class ConnectionTest(unittest.TestCase):
mongoengine.connection._dbs = {} mongoengine.connection._dbs = {}
def test_replicaset_uri_passes_read_preference(self): def test_replicaset_uri_passes_read_preference(self):
"""Requires a replica set called "rs" on port 27017 """Requires a replica set called "rs" on port 27017"""
"""
try: try:
conn = mongoengine.connect( conn = mongoengine.connect(
db="mongoenginetest", db="mongoenginetest",

View File

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

View File

@ -1,5 +1,4 @@
import re import re
import unittest
import pytest import pytest
@ -10,7 +9,7 @@ signal_output = []
class TestLazyRegexCompiler: class TestLazyRegexCompiler:
def test_lazy_regex_compiler_verify_laziness_of_descriptor(self): def test_lazy_regex_compiler_verify_laziness_of_descriptor(self):
class UserEmail(object): class UserEmail:
EMAIL_REGEX = LazyRegexCompiler("@", flags=32) EMAIL_REGEX = LazyRegexCompiler("@", flags=32)
descriptor = UserEmail.__dict__["EMAIL_REGEX"] descriptor = UserEmail.__dict__["EMAIL_REGEX"]
@ -24,7 +23,7 @@ class TestLazyRegexCompiler:
assert user_email.EMAIL_REGEX is UserEmail.EMAIL_REGEX assert user_email.EMAIL_REGEX is UserEmail.EMAIL_REGEX
def test_lazy_regex_compiler_verify_cannot_set_descriptor_on_instance(self): def test_lazy_regex_compiler_verify_cannot_set_descriptor_on_instance(self):
class UserEmail(object): class UserEmail:
EMAIL_REGEX = LazyRegexCompiler("@") EMAIL_REGEX = LazyRegexCompiler("@")
user_email = UserEmail() user_email = UserEmail()
@ -32,7 +31,7 @@ class TestLazyRegexCompiler:
user_email.EMAIL_REGEX = re.compile("@") user_email.EMAIL_REGEX = re.compile("@")
def test_lazy_regex_compiler_verify_can_override_class_attr(self): def test_lazy_regex_compiler_verify_can_override_class_attr(self):
class UserEmail(object): class UserEmail:
EMAIL_REGEX = LazyRegexCompiler("@") EMAIL_REGEX = LazyRegexCompiler("@")
UserEmail.EMAIL_REGEX = re.compile("cookies") UserEmail.EMAIL_REGEX = re.compile("cookies")

View File

@ -1,3 +1,4 @@
import operator
import unittest import unittest
import pytest import pytest
@ -6,7 +7,6 @@ from mongoengine import connect
from mongoengine.connection import disconnect_all, get_db from mongoengine.connection import disconnect_all, get_db
from mongoengine.mongodb_support import get_mongodb_version from mongoengine.mongodb_support import get_mongodb_version
MONGO_TEST_DB = "mongoenginetest" # standard name for the test database MONGO_TEST_DB = "mongoenginetest" # standard name for the test database
@ -33,6 +33,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.
@ -59,7 +67,7 @@ def _decorated_with_ver_requirement(func, mongo_version_req, oper):
return func(*args, **kwargs) return func(*args, **kwargs)
pretty_version = ".".join(str(n) for n in mongo_version_req) pretty_version = ".".join(str(n) for n in mongo_version_req)
pytest.skip("Needs MongoDB v{}+".format(pretty_version)) pytest.skip(f"Needs MongoDB v{pretty_version}+")
_inner.__name__ = func.__name__ _inner.__name__ = func.__name__
_inner.__doc__ = func.__doc__ _inner.__doc__ = func.__doc__

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 =