Compare commits

..

224 Commits

Author SHA1 Message Date
Bastien Gérard
3db9d58dac Merge pull request #2319 from bagerard/prepare_0_20_0
Bump version to 0.20.0 and update CHANGELOG accordingly
2020-05-01 13:24:01 +02:00
Bastien Gérard
3fbe9c3cdd Bump version to 0.20.0 and update CHANGELOG accordingly 2020-05-01 13:13:30 +02:00
Bastien Gérard
130e9c519c Merge pull request #2314 from bagerard/abarto-feature/allow-setting-read-concern-queryset
Abarto feature/allow setting read concern queryset
2020-04-26 22:57:57 +02:00
Bastien Gérard
78c9e9745d fix linting + update changelog & contributors 2020-04-26 22:51:56 +02:00
Bastien Gérard
38ebb5abf4 Merge branch 'master' into feature/allow-setting-read-concern-queryset 2020-04-26 22:36:47 +02:00
Bastien Gérard
9b73be26ab Merge pull request #2313 from bagerard/DonQueso89-master
Improve docstring EmbeddedDocumentList (clone)
2020-04-26 22:33:39 +02:00
Bastien Gérard
fd0095b73f improve recent docstring of EmbeddedDocumentList.create 2020-04-26 22:28:14 +02:00
Bastien Gérard
226049f66a Merge branch 'master' of github.com:MongoEngine/mongoengine into DonQueso89-master 2020-04-26 22:27:11 +02:00
Bastien Gérard
dc1cf88ca6 Merge branch 'master' of git://github.com/DonQueso89/mongoengine into DonQueso89-master 2020-04-26 22:27:02 +02:00
Bastien Gérard
f5f8b730b5 Merge pull request #2254 from bagerard/fix_complex_datetime_field_invalid_string_set
fix complex datetime field invalid string set
2020-04-25 22:17:20 +02:00
Bastien Gérard
e8f6b42316 Merge branch 'master' of github.com:MongoEngine/mongoengine into fix_complex_datetime_field_invalid_string_set 2020-04-25 22:12:35 +02:00
Bastien Gérard
49b0d73654 Merge pull request #2310 from bagerard/Pacu2-add-dev-tools
Pacu2 add dev tools
2020-04-25 21:53:50 +02:00
Bastien Gérard
394da67cf1 fix travis env var 2020-04-25 21:47:37 +02:00
Bastien Gérard
ef7da36ac6 Update pre-commit and fix existing flake8 warnings once for all 2020-04-25 21:36:07 +02:00
Bastien Gérard
1312100bc7 Merge branch 'master' of github.com:MongoEngine/mongoengine into Pacu2-fix/add-dev-tools 2020-04-25 20:53:35 +02:00
Bastien Gérard
4085bc2152 Merge branch 'fix/add-dev-tools' of git://github.com/Pacu2/mongoengine into Pacu2-fix/add-dev-tools 2020-04-25 20:46:44 +02:00
Bastien Gérard
f4d7e72426 improve .gitignore 2020-04-25 20:43:55 +02:00
Bastien Gérard
ece63ad071 Merge pull request #2279 from bagerard/drop_py2_support
Drop python 2 support
2020-04-25 14:22:23 +02:00
Bastien Gérard
a9550b8243 Merge pull request #2232 from bagerard/bump_development_status_classifier
Bump development status classifier
2020-04-25 14:17:46 +02:00
Bastien Gérard
43724e40b2 improve doc related to dropping Py2 support 2020-04-25 14:16:56 +02:00
Bastien Gérard
1bfa40e926 Merge branch 'master' of github.com:MongoEngine/mongoengine into bump_development_status_classifier 2020-04-25 14:07:43 +02:00
Bastien Gérard
d493f71c4e Merge branch 'drop_py2_support' of github.com:bagerard/mongoengine into drop_py2_support 2020-04-25 14:05:30 +02:00
Bastien Gérard
87f4d1a323 Merge branch 'master' of github.com:MongoEngine/mongoengine into drop_py2_support 2020-04-25 13:51:21 +02:00
Bastien Gérard
0a0e6114f5 Merge branch 'master' into drop_py2_support 2020-04-19 23:13:36 +02:00
Bastien Gérard
41d36fa3bf Merge pull request #2304 from bagerard/fix_py2_configparser
fix subdependencies that dropped Py2 support
2020-04-09 09:20:39 +02:00
Bastien Gérard
707923e3f5 fix subdependencies that dropped Py2 support 2020-04-08 21:54:26 +02:00
Bastien Gérard
d9b9581df2 Merge branch 'master' of github.com:MongoEngine/mongoengine into fix_complex_datetime_field_invalid_string_set 2020-04-08 21:33:58 +02:00
Bastien Gérard
463e7c66af Merge pull request #2287 from bagerard/fix_queryset_get_error_message_multiple_match
Improve MultipleObjectsReturned message from queryset.get
2020-04-08 21:28:56 +02:00
Filip Kucharczyk
2be28a22a7 Merge branch 'master' into fix/add-dev-tools 2020-03-27 15:15:14 +01:00
Kes
d73f0bb1af fix typo 2020-03-26 17:25:25 +01:00
Kes
ce74978b1e Correct and improve docstring on EmbeddedDocumentList.create() 2020-03-26 17:23:16 +01:00
Bastien Gérard
2b0157aecd Improve Queryset.get to avoid confusing message in case multiple match are found 2020-03-22 14:08:29 +01:00
Bastien Gérard
f49baf5d90 Merge pull request #2285 from bagerard/remove_drop_dups
Remove drop_dups that was deprecated with Mongo3
2020-03-18 21:02:03 +01:00
Agustin Barto
7cc964c7d8 Add missing import 2020-03-17 21:58:36 -03:00
Agustin Barto
bc77322c2f Update unit tests 2020-03-17 21:49:17 -03:00
Agustin Barto
8913a74a86 Allow setting the read concern to None 2020-03-17 21:31:05 -03:00
Agustin Barto
af35b25d15 Refactor read_preference to accept a dictionary instead of a ReadPreference instance. 2020-03-17 21:28:08 -03:00
Bastien Gérard
476b07af6e reformat changelog 2020-03-17 23:59:54 +01:00
Bastien Gérard
e2b9a02531 Merge branch 'master' of github.com:MongoEngine/mongoengine into remove_drop_dups 2020-03-17 23:58:44 +01:00
Bastien Gérard
6cc6229066 Merge pull request #2245 from bagerard/remove_old_deprecated_method
[>= 0.19.0] Remove old deprecated method
2020-03-17 23:51:42 +01:00
Agustin Barto
4c62a060f0 Add tests for set_write_conern and set_read_write_concern 2020-03-17 19:37:21 -03:00
Agustin Barto
3d80637fa4 Refactor set_read_write_concern so read_conern is consistent with the write_concerns argument. 2020-03-17 19:08:43 -03:00
Bastien Gérard
68be9fe979 Merge pull request #2284 from bagerard/remove_field_name_attribute_deprecated
Remove name param in Field constructor (replaced by db_field)
2020-03-17 21:46:38 +01:00
Bastien Gérard
547cd4a3ae Merge branch 'master' of github.com:MongoEngine/mongoengine into remove_old_deprecated_method 2020-03-17 21:39:36 +01:00
Bastien Gérard
ee2d50b2d1 remove drop_dups index option, deprecated with MongoDB3 2020-03-17 21:38:50 +01:00
Bastien Gérard
15c3ddece8 Merge branch 'master' of github.com:MongoEngine/mongoengine into remove_field_name_attribute_deprecated 2020-03-17 21:29:47 +01:00
Bastien Gérard
beaa9744b7 Merge pull request #2283 from bagerard/remove_slave_okay_deprecated
remove qs.slave_okay() that is deprecated since pymongo3
2020-03-17 21:27:50 +01:00
Bastien Gérard
8eb51790b5 Remove Field(name='...') which was deprecated when db_field was introduced a while ago 2020-03-17 21:26:41 +01:00
Bastien Gérard
aadc6262ed remove qs.slave_okay() that is deprecated since pymongo3 2020-03-17 21:10:52 +01:00
Bastien Gérard
00ae6298d4 Merge pull request #2242 from bagerard/fix_dictfield_validation
Fix bug introduced in 0.19 related to DictField validation
2020-03-17 20:26:54 +01:00
Bastien Gérard
ad0669a326 update changelog 2020-03-17 10:15:47 +01:00
Bastien Gérard
85df76c623 Merge branch 'master' of github.com:MongoEngine/mongoengine into drop_py2_support 2020-03-17 09:24:32 +01:00
Bastien Gérard
87512246cb Merge branch 'master' of github.com:MongoEngine/mongoengine into fix_dictfield_validation 2020-03-16 22:30:48 +01:00
Bastien Gérard
a3f9016ae9 reformat import for flake8 2020-03-15 22:27:19 +01:00
Bastien Gérard
4e58e9f8d1 Merge branch 'master' of github.com:MongoEngine/mongoengine into feature/allow-setting-read-concern-queryset 2020-03-15 22:25:59 +01:00
Bastien Gérard
7c533394fd Merge pull request #2267 from abarto/support-elemmatch-projection
Add support for the elemMatch projection operator
2020-03-15 22:25:08 +01:00
Bastien Gérard
333e014f13 Merge branch 'master' of github.com:MongoEngine/mongoengine into fix_complex_datetime_field_invalid_string_set 2020-03-15 22:20:46 +01:00
Bastien Gérard
c0c0efce18 improve docstring related to #2267 and document the change in the changelog 2020-03-15 22:14:26 +01:00
Bastien Gérard
beabaee345 Merge branch 'master' of github.com:MongoEngine/mongoengine into support-elemmatch-projection 2020-03-15 22:08:20 +01:00
Bastien Gérard
c937af3919 Merge pull request #2281 from bagerard/fix_py38_deprecation_warn
Fix invalid escape seq in codebase
2020-03-15 21:16:54 +01:00
Bastien Gérard
aa4a6ae023 Fix invalid escape seq in codebase 2020-03-15 21:02:44 +01:00
Bastien Gérard
b57946ec98 remove virtualenv installation in travis 2020-03-14 21:39:47 +01:00
Bastien Gérard
1e110a2c41 run pyupgrade --py3-plus 2020-03-11 23:34:43 +01:00
Bastien Gérard
b234aa48e4 run pyupgrade 2020-03-11 23:21:38 +01:00
Bastien Gérard
8086576677 get rid of six 2020-03-11 23:07:03 +01:00
Bastien Gérard
03e34299f0 clean code related to Py2 + six.text_type & six.string_types 2020-03-11 21:50:50 +01:00
Bastien Gérard
421e3f324f Drop python2 support 2020-03-09 00:18:52 +01:00
Bastien Gérard
a0b803959c Merge branch 'master' of github.com:MongoEngine/mongoengine into py2py3_improve_compat 2020-03-08 14:44:39 +01:00
Bastien Gérard
ff4d57032a reformat w black 2020-03-03 23:51:11 +01:00
Bastien Gérard
ba34589065 Merge branch 'master' of github.com:MongoEngine/mongoengine into abarto-feature/allow-setting-read-concern-queryset 2020-03-03 23:50:36 +01:00
Bastien Gérard
a4d11eef46 Merge pull request #2264 from Pacu2/2263/combining-raw-and-regular-queries
Fix for combining raw and regular filters
2020-03-03 23:48:31 +01:00
Filip Kucharczyk
fda2e2b47a Update changelog 2020-03-03 15:18:58 +01:00
Filip Kucharczyk
d287f480e5 Fix for combining raw and regular filters 2020-03-03 15:18:58 +01:00
Bastien Gérard
d85f0e6226 Merge branch 'feature/allow-setting-read-concern-queryset' of git://github.com/abarto/mongoengine into abarto-feature/allow-setting-read-concern-queryset 2020-03-02 23:04:42 +01:00
Bastien Gérard
cfb4943986 reformat with black 2020-03-02 22:49:21 +01:00
Bastien Gérard
b453a96211 Merge pull request #2272 from leodmgs/add-return-docstr-to-save-function
Add return info in the save function docstring
2020-03-02 21:27:39 +01:00
Leonardo Domingues
81f9b351b3 Add return info in the save function docstring 2020-02-21 19:14:34 -03:00
Agustin Barto
4bca3de42f Add support for the elemMatch projection operator. Add basic tests to the fields queryset method. 2020-02-14 16:43:07 -03:00
Bastien Gérard
235b1a3679 Merge pull request #2265 from bagerard/fix_ci_lib_drop_python2_support
fix indirect library version (zipp) that dropped python2 support recently
2020-02-08 13:45:13 +01:00
Bastien Gérard
450658d7ac fix indirect library version that dropped python2 support recently 2020-02-04 22:51:02 +01:00
Agustin
8e17e42e26 Allow setting read_concern 2020-01-24 13:11:07 -03:00
Bastien Gérard
2d6a4c4b90 update changelog 2020-01-23 23:36:03 +01:00
Bastien Gérard
38703acc29 fix complex datetime field invalid string set 2020-01-23 23:33:23 +01:00
Bastien Gérard
095217e797 remove methods that were derecated in 2013... 2020-01-13 23:53:24 +01:00
Bastien Gérard
86e965f854 remove very old deprecated method 2020-01-13 23:45:42 +01:00
Bastien Gérard
57db68dc04 update changelog 2020-01-13 23:34:43 +01:00
Bastien Gérard
72de6d67c7 Bump dev status classifier 2020-01-13 23:33:02 +01:00
Bastien Gérard
b2c3acd025 Merge pull request #2244 from bagerard/mongodb_40
Mongodb 40
2020-01-13 23:26:11 +01:00
Bastien Gérard
605de59bd0 improve travis + fix tox mg310 2020-01-12 21:37:32 +01:00
Bastien Gérard
e0565ddac5 update changelog 2020-01-12 21:31:28 +01:00
Bastien Gérard
18b68f1b80 update travis mongo 4.0 to latest 4.0.13 2020-01-12 21:29:18 +01:00
Bastien Gérard
ea88806630 Merge branch 'master' of github.com:MongoEngine/mongoengine into mongodb_40 2020-01-12 21:16:48 +01:00
Bastien Gérard
d738462139 Fix bug introduced in -1.19 related to DictField validate failing without default connection 2020-01-11 23:17:05 +01:00
Bastien Gérard
9490ad2bf7 Merge pull request #2238 from bagerard/test_latest_pymongo
include latest pymongo version in travis
2020-01-07 09:35:49 +01:00
Bastien Gérard
705c55ce24 update tox file to account for mg310 2020-01-05 21:01:17 +01:00
Bastien Gérard
59fbd505a0 include latest pymongo version in travis 2020-01-05 20:20:13 +01:00
Bastien Gérard
1cc20c9770 Merge pull request #2237 from timgates42/bugfix/typo_thoroughly
Fix simple typo: thorougly -> thoroughly
2020-01-05 11:17:49 +01:00
Tim Gates
f8f267a880 Fix simple typo: thorougly -> thoroughly
Closes #2236
2020-01-05 20:48:16 +11:00
erdenezul
80ea1f6883 Merge pull request #2233 from bagerard/release_0_19_1
Bump version to 0.19.1 + force pillow to be < 7.0.0
2020-01-03 21:11:17 +01:00
Bastien Gérard
75ee282a3d black setup.py to please CI 2020-01-03 14:15:24 +01:00
Bastien Gérard
4edad4601c Bump version to 0.19.1 + force pillow to be < 7.0.0 2020-01-03 14:03:17 +01:00
Bastien Gérard
152b51fd33 improve gridfs example (properly opening file) 2019-12-29 14:36:50 +01:00
erdenezul
66a0fca4ad Merge pull request #2229 from bagerard/improve_doc_gridfs
improve doc of GridFS
2019-12-27 16:46:38 +01:00
Bastien Gérard
e7c7a66cd1 improve doc of GridFS, emphasize that subsequent call to read() requires to rewind the file with seek(0) 2019-12-27 10:38:03 +01:00
Bastien Gérard
b3dbb87c3c improve doc of aggregate kwargs 2019-12-27 10:06:27 +01:00
erdenezul
3d45538998 Merge pull request #2227 from bagerard/test_py38
Test py38
2019-12-27 07:05:12 +01:00
erdenezul
8df9d3fef9 Merge pull request #2228 from bagerard/change_aggregate_interface
change & deprecate .aggregate interface to mimic pymongo's interface
2019-12-27 07:04:25 +01:00
Bastien Gérard
99e660c66d update changelog 2019-12-27 09:32:05 +01:00
Bastien Gérard
aa02f87b69 change & deprecate .aggregate api to mimic pymongo's interface + separate the aggregation tests from the large test_queryset.py file 2019-12-27 09:23:15 +01:00
Bastien Gérard
f0d1ee2cb4 update travis config + improve readmecode 2019-12-26 21:03:34 +01:00
Bastien Gérard
ca4967311d update python 3.8 config 2019-12-26 21:00:34 +01:00
Bastien Gérard
65eb6ab611 Merge branch 'master' of github.com:MongoEngine/mongoengine into test_py38 2019-12-26 20:58:31 +01:00
Bastien Gérard
1cb2f7814c Merge branch 'mcsimps2-allow-periods-in-dict' 2019-12-26 20:53:50 +01:00
Bastien Gérard
b5485b16e6 Merge branch 'allow-periods-in-dict' of https://github.com/mcsimps2/mongoengine into mcsimps2-allow-periods-in-dict 2019-12-26 20:47:46 +01:00
Bastien Gérard
62c8597a3b fix pypi deployment version match 2019-12-24 11:15:23 +01:00
Bastien Gérard
488604ff2e test python 3.8 2019-12-24 00:00:15 +01:00
erdenezul
bd88a17b8e Merge pull request #2224 from bagerard/release_0_19_0
Release 0 19 0
2019-12-22 18:46:38 +01:00
Bastien Gérard
8e892dccfe document recent merged PR in changelog 2019-12-20 23:51:01 +01:00
Bastien Gérard
c22eb34017 Merge branch 'master' of github.com:MongoEngine/mongoengine into release_0_19_0 2019-12-20 23:49:41 +01:00
Bastien Gérard
dcf3edb03e Merge pull request #2094 from divomen/negative_indexes_in_list
Supported updates of an array by negative index
2019-12-20 23:48:00 +01:00
Bastien Gérard
c85b59d3b5 Merge branch 'master' of github.com:MongoEngine/mongoengine into negative_indexes_in_list 2019-12-20 23:27:51 +01:00
Bastien Gérard
1170de1e8e added explicit doc for order_by #2117 2019-12-20 23:20:15 +01:00
Bastien Gérard
332bd767d4 minor fixes in tests 2019-12-20 23:20:15 +01:00
erdenezul
0053b30237 Merge pull request #2217 from bagerard/improve_inheritance_doc
provide additional details on how inheritance works in doc
2019-12-20 22:43:45 +01:00
Bastien Gérard
d44533d956 completed the changelog with missing details 2019-12-20 22:41:22 +01:00
Bastien Gérard
12d8bd5a22 bump version to 0.19.0 2019-12-20 22:11:43 +01:00
Bastien Gérard
ae326678ec updated changelog for upcoming 0.19.0 2019-12-20 22:09:04 +01:00
Bastien Gérard
8d31f165c0 Merge pull request #2182 from daewok/fix-embedded-document-equality-with-lazy-refs
Fix EmbeddedDocument equality with LazyReferenceFields
2019-12-16 23:57:50 +01:00
Eric Timmons
cfd4d6a161 Add breaking change to changelog for LazyReferenceField representation in _data 2019-12-15 20:46:48 -05:00
Eric Timmons
329f030a41 Always store a DBRef, Document, or EmbeddedDocument in LazyReferenceField._data
This is required to handle the case of equality tests on a LazyReferenceField
with dbref=True when comparing against a field instantiated with an ObjectId.
2019-12-15 20:46:48 -05:00
Eric Timmons
68dc2925fb Add LazyReferenceField with dbref=True to embedded_document equality test 2019-12-15 20:21:22 -05:00
Eric Timmons
0d4e61d489 Add daewok to AUTHORS per contributing guidelines 2019-12-15 20:21:18 -05:00
Eric Timmons
dc7b96a569 Make python value for LazyReferenceFields be a DBRef
Previously, when reading a LazyReferenceField from the DB, it was stored
internally in the parent document's _data field as an ObjectId. However, this
meant that equality tests using an enclosing EmbeddedDocument would not return
True when the EmbeddedDocument being compared to contained a DBRef or Document
in _data. Enclosing Documents were largely unaffected because they look at the
primary key for equality (which EmbeddedDocuments lack).

This makes the internal Python representation of a LazyReferenceField (before
the LazyReference itself has been constructed) a DBRef, using code identical to
ReferenceField.
2019-12-15 20:20:59 -05:00
Eric Timmons
50882e5bb0 Add failing test
Test that __eq__ for EmbeddedDocuments with LazyReferenceFields works as
expected.
2019-12-15 20:20:59 -05:00
Bastien Gérard
280a73af3b minor fix in doc of NULLIFY to improve #834 2019-12-14 21:44:59 +01:00
Bastien Gérard
d8c0631dab added entry in changelog for py2py3 compatibility 2019-12-14 21:23:28 +01:00
Bastien Gérard
9166ba91d7 fix minor styling issue 2019-12-14 16:11:59 +01:00
Bastien Gérard
6bc4e602bb additional fix 2019-12-14 00:05:48 +01:00
Bastien Gérard
45a7520fc3 make use past.builtins.long 2019-12-13 23:57:18 +01:00
Bastien Gérard
64c0cace85 fix from code review regarding py2py3 compat 2019-12-13 23:57:18 +01:00
Bastien Gérard
82af5e4a19 fix small finding from review on py2py3 compat 2019-12-13 23:54:42 +01:00
Bastien Gérard
7e0ba1b335 clean remaining references to 2to3 2019-12-13 23:52:59 +01:00
Bastien Gérard
44b7f792fe fix benchmarks prints 2019-12-13 23:52:59 +01:00
Bastien Gérard
a3e432eb68 remove references to '2to3' in doc, travis, etc 2019-12-13 23:46:13 +01:00
Bastien Gérard
009f9a2b14 set dist as xenial to avoid relying on flaky travis default dist 2019-12-13 23:43:04 +01:00
Bastien Gérard
2ca905b6e5 Finalize python2/3 codebase compatibility and get rid of 2to3 2019-12-13 23:43:04 +01:00
Bastien Gérard
3b099f936a provide additional details on how inheritance works in doc 2019-12-13 21:32:45 +01:00
Bastien Gérard
4d6ddb070e Merge pull request #2210 from Pacu2/fix/implement-bool-on-q-and-qcombination
Add __bool__ to Q and QCombination
2019-12-13 20:53:33 +01:00
Matt Simpson
b205314424 Add ability for dict keys to have . or $ in MongoDB >= 3.6
Starting in MongoDB >= 3.6, it is valid for dictionary keys to have $ or . in them as long as they don't start with $.  Additional tests added.
2019-12-10 11:09:29 -05:00
Filip Kucharczyk
e83132f32c Note deprecation of 'empty' in changelog 2019-12-10 11:54:52 +01:00
Filip Kucharczyk
1b38309d70 Revert 'empty' usage to it's previous state 2019-12-06 10:14:22 +01:00
Filip Kucharczyk
6e8196d475 Python 2.x compatibility 2019-12-05 01:41:48 +01:00
Filip Kucharczyk
90fecc56dd Reformat with black 2019-12-05 01:33:35 +01:00
Filip Kucharczyk
d3d7f0e670 Changelog 2019-12-05 01:18:29 +01:00
Filip Kucharczyk
37ffeafeff Replace 'pip' with 'python -m pip install' in docs 2019-12-05 01:13:05 +01:00
Filip Kucharczyk
abc159b7b9 Update Contributing, changelog 2019-12-05 01:10:54 +01:00
Filip Kucharczyk
648b28876d Rename requirements-lint.txt to requirements-dev.txt 2019-12-05 00:57:17 +01:00
Filip Kucharczyk
5b9f2bac87 Add pre-commit 2019-12-05 00:54:20 +01:00
Filip Kucharczyk
17151f67c2 Reformat repo with pre-commit hooks 2019-12-05 00:53:39 +01:00
Filip Kucharczyk
5f14d958ac Sort imports 2019-12-05 00:46:57 +01:00
Filip Kucharczyk
bd6c52e025 Changelog 2019-12-05 00:30:03 +01:00
Filip Kucharczyk
cb77bb6b69 Implement __bool__ on Q and QCombination 2019-12-05 00:27:41 +01:00
Bastien Gérard
78b240b740 updated changelog + improved query_counter test 2019-12-04 21:51:07 +01:00
Bastien Gérard
7e30f00178 Merge pull request #2195 from EloiZalczer/master
Added alias parameter in query_counter
2019-12-04 21:50:41 +01:00
Bastien Gérard
35310dbc73 Merge pull request #2204 from Pacu2/2203/ability-to-compare-q-objects
Add __eq__ to Q and Q operations
2019-12-04 20:58:04 +01:00
Filip Kucharczyk
af82c07acc Reformat with black 2019-12-03 09:19:02 +01:00
Filip Kucharczyk
3f75f30f26 Run black 2019-12-03 09:03:49 +01:00
Filip Kucharczyk
f7f0e10d4d Update changelog 2019-12-03 00:54:53 +01:00
Filip Kucharczyk
091238a2cf Update Authors 2019-12-03 00:54:46 +01:00
Filip Kucharczyk
0458ef869e Add __eq__ to Q and Q operations 2019-12-03 00:44:13 +01:00
Eloi Zalczer
0bf08db7b9 Added test case for query_counter alias 2019-12-02 10:07:33 +01:00
Eloi Zalczer
d3420918cd Added alias parameter in query_counter 2019-11-18 17:16:06 +01:00
Bastien Gérard
138e759161 Merge pull request #2187 from bagerard/improve_error_message_InvalidDocumentError
Improve error message from InvalidDocumentError
2019-11-08 21:27:17 +01:00
Bastien Gérard
f1d6ce7d12 Merge pull request #2189 from jangop/patch-1
Specify version of requirement
2019-11-08 21:25:41 +01:00
Jan Philip Göpfert
ff749a7a0a Specify version of requirement
In `README.rst`, a version of `six` of at least `1.10.0` is specified. This was missing from the requirements, potentially leading to broken installations.
2019-11-06 10:35:16 +01:00
Bastien Gérard
bff78ca8dd Merge pull request #2188 from bagerard/convert_assertions
Convert assertions
2019-11-05 20:06:59 +01:00
Bastien Gérard
81647d67a0 fix recent tests update with unittest2pytest 2019-10-31 23:06:40 +01:00
Bastien Gérard
d8924ed892 remove inheritance from unittest.TestCase on basic test classes 2019-10-31 23:02:28 +01:00
Bastien Gérard
799cdafae6 remove references to nose 2019-10-31 23:02:28 +01:00
Bastien Gérard
bc0c55e49a improve tests health (flake8 warnings) 2019-10-31 23:01:27 +01:00
Bastien Gérard
c61c6a8525 fix == None assertions 2019-10-31 22:59:49 +01:00
Bastien Gérard
3e764d068c fix remaining assertRaises 2019-10-31 22:59:49 +01:00
Bastien Gérard
ac25f4b98b ran unittest2pytest 2019-10-31 22:59:49 +01:00
Bastien Gérard
aa6ff8c84a Merge pull request #2157 from bagerard/switch_pytest
Switch to pytest as runner
2019-10-31 22:50:39 +01:00
Bastien Gérard
37ca79e9c5 fix black formatting 2019-10-31 22:39:53 +01:00
Bastien Gérard
6040b4b494 update changelog 2019-10-31 21:33:19 +01:00
Bastien Gérard
51ea3e3c6f fix for recent coverage/coveralls compatibility issue 2019-10-31 20:39:49 +01:00
Bastien Gérard
5a16dda50d fix coverage for pytest runner 2019-10-31 20:39:49 +01:00
Bastien Gérard
bbfa978861 switch test runner from nose to pytest 2019-10-31 20:39:49 +01:00
Bastien Gérard
54ca7bf09f fix associated test to avoid discrepencies btw py2 and py3 2019-10-28 23:33:37 +01:00
Bastien Gérard
8bf5370b6c Improve error message from InvalidDocumentError whenever an embedded document has a bad shape (e.g due to migration) 2019-10-28 22:05:13 +01:00
Bastien Gérard
ecefa05e03 Merge pull request #2174 from bagerard/upgrade_latest_pymongo_travis
add latest pymongo 3.9 as part of the CI
2019-10-28 21:22:00 +01:00
erdenezul
e013494fb2 Merge pull request #2177 from bagerard/fix_few_deprecation_warnings
fix 2 pymongo deprecation warnings
2019-10-22 15:56:39 +02:00
erdenezul
4853f74dbf Merge pull request #2184 from bagerard/docs_logging
Documented how pymongo.monitoring can be used with MongoEngine
2019-10-21 20:07:42 +08:00
erdenezul
6f45ee6813 Merge pull request #2176 from bagerard/fix_docstring_pycharm
Fix docstring format to improve pycharm inspection
2019-10-21 20:06:52 +08:00
Bastien Gérard
c60ed32f3a Documented how pymongo.monitoring can be used with MongoEngine 2019-10-16 21:25:17 +02:00
Bastien Gérard
178851589d Merge pull request #2175 from bagerard/document_no_async_driver_support
Document no async driver support
2019-10-16 20:34:26 +02:00
Bastien Gérard
5bcc679194 fix 2 pymongo deprecation warnings 2019-10-10 22:55:44 +02:00
Bastien Gérard
1e17b5ac66 Fix docstring format to improve pycharm inspection 2019-10-05 14:24:54 +02:00
Bastien Gérard
19f12f3f2f document pymongo in RTD and make it point to github 2019-10-04 21:51:12 +02:00
Bastien Gérard
71e8d9a490 Added a FAQ to doc and Document the fact that we dont support motor 2019-10-04 21:51:12 +02:00
Bastien Gérard
e3cd553f82 add latest pymongo 3.9 as part of the CI 2019-10-04 21:46:47 +02:00
Bastien Gérard
b61c8cd104 fix tox envs 2019-10-01 22:17:19 +02:00
Bastien Gérard
8f288fe458 add mongodb 4.0 to travis and docs 2019-09-29 22:50:14 +02:00
erdenezul
02a920feea Merge pull request #2166 from bagerard/add_aggregate_tutorial
Document Model.objects.aggregate entrypoint with an example
2019-09-17 21:07:42 +02:00
Bastien Gérard
be2c4f2b3c fix formatting and improve doc based on review 2019-09-16 21:15:35 +02:00
Bastien Gérard
7ac74b1c1f Document Model.objects.aggregate entrypoint with an example 2019-09-15 23:27:34 +02:00
Bastien Gérard
933cb1d5c7 Merge pull request #2144 from 0a69911a/master
Implement collation for queryset
2019-09-11 23:27:25 +02:00
Bastien Gérard
6203e30152 Merge pull request #2160 from vainu-arto/fix-trust-default-cursor-timeout
Only set no_cursor_timeout when requested
2019-09-11 22:09:56 +02:00
Bastien Gérard
7d94af0e31 add test coverage for no_cursor_timeout to support recent fix 2019-09-11 21:53:30 +02:00
erdenezul
564a2b5f1e Merge pull request #2153 from erdenezul/misleading_bulkwrite_error
Change misleading error message
2019-09-04 09:42:10 +02:00
Erdenezul Batmunkh
1dbe7a3163 Add log in changelog 2019-09-03 16:17:09 +02:00
Arto Jantunen
47f8a126ca Only set no_cursor_timeout when requested
Previously this was always set for all requests. The parameter is only
documented as supported for certain queries, so this was probably wrong.

Mongo version 4.2 fails update queries that have this parameter set making
mongoengine unusable there. Fixes #2148.
2019-09-03 14:36:06 +03:00
Bastien Gérard
693195f70b fix test_pickable that was brought back to life recently 2019-08-31 22:28:31 +03:00
Bastien Gérard
2267b7e7d7 rename remaining files for pytest migration 2019-08-30 16:27:56 +03:00
otrofimov
47c67ecc99 Merge branch 'master' of https://github.com/MongoEngine/mongoengine 2019-08-29 11:48:07 +03:00
Bastien Gérard
4c4b7cbeae Merge pull request #2156 from bagerard/rename_test_files_for_pytest_migration
rename all test files so that they are prefixed by test
2019-08-28 16:17:38 +03:00
Bastien Gérard
ddececbfea rename all test files so that they are prefixed by test_{orginal_filename}.py 2019-08-28 16:01:44 +03:00
otrofimov
71a6f3d1a4 test_collation:
Added test with `pymongo.collation.Collation` object
Readable list of BlogPost names for test
2019-08-21 18:26:10 +03:00
otrofimov
fbb3bf869c compatibility with black 2019-08-08 15:56:20 +03:00
otrofimov
b887ea9623 Implement collation for queryset 2019-08-08 11:55:45 +03:00
Bastien Gérard
c68e3e1238 Add test case for list update by negative index 2019-07-24 21:37:16 +02:00
Bastien Gérard
c5080e4030 Merge branch 'master' of github.com:MongoEngine/mongoengine into negative_indexes_in_list 2019-07-24 21:14:55 +02:00
Dmitry Voronenkov
6a4c342e45 Supported updates of an array by negative index 2019-06-18 19:45:37 +03:00
108 changed files with 6251 additions and 5849 deletions

11
.gitignore vendored
View File

@@ -1,8 +1,15 @@
.*
!.gitignore
*~
*.py[co]
.*.sw[po]
.cache/
.coverage
.coveragerc
.env
.idea/
.pytest_cache/
.tox/
.eggs/
*.egg
docs/.build
docs/_build
@@ -13,8 +20,6 @@ env/
.settings
.project
.pydevproject
tests/test_bugfix.py
htmlcov/
venv
venv3
scratchpad

View File

@@ -5,17 +5,12 @@ pylint:
options:
additional-builtins:
# add xrange and long as valid built-ins. In Python 3, xrange is
# translated into range and long is translated into int via 2to3 (see
# "use_2to3" in setup.py). This should be removed when we drop Python
# 2 support (which probably won't happen any time soon).
- xrange
# add long as valid built-ins.
- long
pyflakes:
disable:
# undefined variables are already covered by pylint (and exclude
# xrange & long)
# undefined variables are already covered by pylint (and exclude long)
- F821
ignore-paths:

12
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,12 @@
fail_fast: false
repos:
- repo: https://github.com/ambv/black
rev: 19.10b0
hooks:
- id: black
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.0a2
hooks:
- id: flake8
additional_dependencies:
- flake8-import-order

View File

@@ -1,13 +1,10 @@
# 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:
# * MongoDB v3.4 & the latest PyMongo v3.x is currently the "main" setup,
# tested against Python v2.7, v3.5, v3.6, and PyPy.
# * Besides that, we test the lowest actively supported Python/MongoDB/PyMongo
# combination: MongoDB v3.4, PyMongo v3.4, Python v2.7.
# * MongoDB v3.6 is tested against Python v3.6, and PyMongo v3.6, v3.7, v3.8.
#
# 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:
@@ -16,38 +13,43 @@
#
# Reminder: Update README.rst if you change MongoDB versions we test.
language: python
dist: xenial
python:
- 2.7
- 3.5
- 3.6
- 3.7
- pypy
- 3.8
- pypy3
dist: xenial
env:
global:
- MONGODB_3_4=3.4.17
- MONGODB_3_6=3.6.12
- PYMONGO_3_6=3.6
- MONGODB_4_0=4.0.13
- PYMONGO_3_4=3.4
- PYMONGO_3_6=3.6
- PYMONGO_3_9=3.9
- PYMONGO_3_10=3.10
- MAIN_PYTHON_VERSION=3.7
matrix:
- MONGODB=${MONGODB_3_4} PYMONGO=${PYMONGO_3_6}
- MONGODB=${MONGODB_3_4} PYMONGO=${PYMONGO_3_10}
matrix:
# Finish the build as soon as one job fails
fast_finish: true
include:
- python: 2.7
env: MONGODB=${MONGODB_3_4} PYMONGO=${PYMONGO_3_4}
- 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_10}
- python: 3.8
env: MONGODB=${MONGODB_4_0} PYMONGO=${PYMONGO_3_10}
install:
# Install Mongo
@@ -57,29 +59,23 @@ install:
# Install Python dependencies.
- pip install --upgrade pip
- pip install coveralls
- pip install flake8 flake8-import-order
- pip install tox # tox 3.11.0 has requirement virtualenv>=14.0.0
- pip install virtualenv # virtualenv>=14.0.0 has dropped Python 3.2 support (and pypy3 is based on py32)
# Install the tox venv.
- tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -e test
# Install black for Python v3.7 only.
- if [[ $TRAVIS_PYTHON_VERSION == '3.7' ]]; then pip install black; fi
- 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
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then flake8 .; else echo "flake8 only runs on py27"; fi # Run flake8 for Python 2.7 only
- if [[ $TRAVIS_PYTHON_VERSION == '3.7' ]]; then black --check .; else echo "black only runs on py37"; fi # Run black for Python 3.7 only
# 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/') -- --with-coverage
- tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -a "--cov=mongoengine"
# For now only submit coveralls for Python v2.7. Python v3.x currently shows
# 0% coverage. That's caused by 'use_2to3', which builds the py3-compatible
# code in a separate dir and runs tests on that.
after_success:
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --verbose; fi
- - if [[ $TRAVIS_PYTHON_VERSION == $MAIN_PYTHON_VERSION ]]; then coveralls --verbose; else echo "coveralls only sent for py37"; fi
notifications:
irc: irc.freenode.org#mongoengine
@@ -101,11 +97,11 @@ deploy:
distributions: "sdist bdist_wheel"
# Only deploy on tagged commits (aka GitHub releases) and only for the parent
# repo's builds running Python v2.7 along with PyMongo v3.x and MongoDB v3.4.
# 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_6}) && ($MONGODB = ${MONGODB_3_4})
python: 2.7
condition: ($PYMONGO = ${PYMONGO_3_10}) && ($MONGODB = ${MONGODB_3_4})
python: 3.7

View File

@@ -252,3 +252,8 @@ that much better:
* Paulo Amaral (https://github.com/pauloAmaral)
* Gaurav Dadhania (https://github.com/GVRV)
* Yurii Andrieiev (https://github.com/yandrieiev)
* Filip Kucharczyk (https://github.com/Pacu2)
* Eric Timmons (https://github.com/daewok)
* Matthew Simpson (https://github.com/mcsimps2)
* Leonardo Domingues (https://github.com/leodmgs)
* Agustin Barto (https://github.com/abarto)

View File

@@ -20,19 +20,43 @@ post to the `user group <http://groups.google.com/group/mongoengine-users>`
Supported Interpreters
----------------------
MongoEngine supports CPython 2.7 and newer. Language
features not supported by all interpreters can not be used.
The codebase is written in python 2 so you must be using python 2
when developing new features. Compatibility of the library with Python 3
relies on the 2to3 package that gets executed as part of the installation
build. You should ensure that your code is properly converted by
`2to3 <http://docs.python.org/library/2to3.html>`_.
MongoEngine supports CPython 3.5 and newer as well as Pypy3.
Language features not supported by all interpreters can not be used.
Python3 codebase
----------------------
Since 0.20, the codebase is exclusively Python 3.
Earlier versions were exclusively Python2, and were relying on 2to3 to support Python3 installs.
Travis runs the tests against the main Python 3.x versions.
Style Guide
-----------
MongoEngine uses `black <https://github.com/python/black>`_ for code
formatting.
MongoEngine's codebase is formatted with `black <https://github.com/python/black>`_, other tools like
flake8 are also used. Those tools will run as part of the CI and will fail in case the code is not formatted properly.
To install all development tools, simply run the following commands:
.. code-block:: console
$ python -m pip install -r requirements-dev.txt
You can install `pre-commit <https://pre-commit.com/>`_ into your git hooks,
to automatically check and fix any formatting issue before creating a
git commit.
To enable ``pre-commit`` simply run:
.. code-block:: console
$ pre-commit install
See the ``.pre-commit-config.yaml`` configuration file for more information
on how it works.
Testing
-------
@@ -54,7 +78,7 @@ General Guidelines
should adapt to the breaking change in docs/upgrade.rst.
- Write inline documentation for new classes and methods.
- Write tests and make sure they pass (make sure you have a mongod
running on the default port, then execute ``python setup.py nosetests``
running on the default port, then execute ``python setup.py test``
from the cmd line to run the test suite).
- Ensure tests pass on all supported Python, PyMongo, and MongoDB versions.
You can test various Python and PyMongo versions locally by executing

View File

@@ -26,15 +26,15 @@ an `API reference <https://mongoengine-odm.readthedocs.io/apireference.html>`_.
Supported MongoDB Versions
==========================
MongoEngine is currently tested against MongoDB v3.4 and v3.6. Future versions
MongoEngine is currently tested against MongoDB v3.4, v3.6 and v4.0. Future versions
should be supported as well, but aren't actively tested at the moment. Make
sure to open an issue or submit a pull request if you experience any problems
with MongoDB version > 3.6.
with MongoDB version > 4.0.
Installation
============
We recommend the use of `virtualenv <https://virtualenv.pypa.io/>`_ and of
`pip <https://pip.pypa.io/>`_. You can then use ``pip install -U mongoengine``.
`pip <https://pip.pypa.io/>`_. You can then use ``python -m pip install -U mongoengine``.
You may also have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
and thus you can use ``easy_install -U mongoengine``. Another option is
`pipenv <https://docs.pipenv.org/>`_. You can then use ``pipenv install mongoengine``
@@ -42,13 +42,14 @@ to both create the virtual environment and install the package. Otherwise, you c
download the source from `GitHub <http://github.com/MongoEngine/mongoengine>`_ and
run ``python setup.py install``.
The support for Python2 was dropped with MongoEngine 0.20.0
Dependencies
============
All of the dependencies can easily be installed via `pip <https://pip.pypa.io/>`_.
All of the dependencies can easily be installed via `python -m pip <https://pip.pypa.io/>`_.
At the very least, you'll need these two packages to use MongoEngine:
- pymongo>=3.4
- six>=1.10.0
If you utilize a ``DateTimeField``, you might also use a more flexible date parser:
@@ -58,6 +59,10 @@ If you need to use an ``ImageField`` or ``ImageGridFsProxy``:
- Pillow>=2.0.0
If you need to use signals:
- blinker>=1.3
Examples
========
Some simple examples of what MongoEngine code looks like:
@@ -91,12 +96,11 @@ Some simple examples of what MongoEngine code looks like:
# Iterate over all posts using the BlogPost superclass
>>> for post in BlogPost.objects:
... print '===', post.title, '==='
... print('===', post.title, '===')
... if isinstance(post, TextPost):
... print post.content
... print(post.content)
... elif isinstance(post, LinkPost):
... print 'Link:', post.url
... print
... print('Link:', post.url)
...
# Count all blog posts and its subtypes
@@ -116,7 +120,8 @@ Some simple examples of what MongoEngine code looks like:
Tests
=====
To run the test suite, ensure you are running a local instance of MongoDB on
the standard port and have ``nose`` installed. Then, run ``python setup.py nosetests``.
the standard port and have ``pytest`` installed. Then, run ``python setup.py test``
or simply ``pytest``.
To run the test suite on every supported Python and PyMongo version, you can
use ``tox``. You'll need to make sure you have each supported Python version
@@ -125,20 +130,18 @@ installed in your environment and then:
.. code-block:: shell
# Install tox
$ pip install tox
$ python -m pip install tox
# Run the test suites
$ tox
If you wish to run a subset of tests, use the nosetests convention:
If you wish to run a subset of tests, use the pytest convention:
.. code-block:: shell
# Run all the tests in a particular test file
$ python setup.py nosetests --tests tests/fields/fields.py
$ pytest tests/fields/test_fields.py
# Run only particular test class in that file
$ python setup.py nosetests --tests tests/fields/fields.py:FieldTest
# Use the -s option if you want to print some debug statements or use pdb
$ python setup.py nosetests --tests tests/fields/fields.py:FieldTest -s
$ pytest tests/fields/test_fields.py::TestField
Community
=========

View File

@@ -4,12 +4,14 @@ import timeit
def main():
setup = """
from pymongo import MongoClient
connection = MongoClient()
connection.drop_database('mongoengine_benchmark_test')
"""
stmt = """
from pymongo import MongoClient
connection = MongoClient()
db = connection.mongoengine_benchmark_test
@@ -56,6 +58,7 @@ myNoddys = noddy.find()
setup = """
from pymongo import MongoClient
connection = MongoClient()
connection.drop_database('mongoengine_benchmark_test')
connection.close()

View File

@@ -6,6 +6,34 @@ Changelog
Development
===========
- (Fill this out as you fix issues and develop your features).
Changes in 0.20.0
=================
- ATTENTION: Drop support for Python2
- Add Mongo 4.0 to Travis
- Fix error when setting a string as a ComplexDateTimeField #2253
- Bump development Status classifier to Production/Stable #2232
- Improve Queryset.get to avoid confusing MultipleObjectsReturned message in case multiple match are found #630
- Fixed a bug causing inaccurate query results, while combining ``__raw__`` and regular filters for the same field #2264
- Add support for the `elemMatch` projection operator in .fields() (e.g BlogPost.objects.fields(elemMatch__comments="test")) #2267
- DictField validate failed without default connection (bug introduced in 0.19.0) #2239
- Remove methods that were deprecated years ago:
- name parameter in Field constructor e.g `StringField(name="...")`, was replaced by db_field
- Queryset.slave_okay() was deprecated since pymongo3
- dropDups was dropped with MongoDB3
- ``Queryset._ensure_indexes`` and ``Queryset.ensure_indexes``, the right method to use is ``Document.ensure_indexes``
- Added pre-commit for development/CI #2212
- Renamed requirements-lint.txt to requirements-dev.txt #2212
- Support for setting ReadConcern #2255
Changes in 0.19.1
=================
- Requires Pillow < 7.0.0 as it dropped Python2 support
- DEPRECATION: The interface of ``QuerySet.aggregate`` method was changed, it no longer takes an unpacked list of
pipeline steps (*pipeline) but simply takes the pipeline list just like ``pymongo.Collection.aggregate`` does. #2079
Changes in 0.19.0
=================
- BREAKING CHANGE: ``class_check`` and ``read_preference`` keyword arguments are no longer available when filtering a ``QuerySet``. #2112
- Instead of ``Doc.objects(foo=bar, read_preference=...)`` use ``Doc.objects(foo=bar).read_preference(...)``.
- Instead of ``Doc.objects(foo=bar, class_check=False)`` use ``Doc.objects(foo=bar).clear_cls_query(...)``.
@@ -15,9 +43,23 @@ Development
- If you catch/use ``MongoEngineConnectionError`` in your code, you'll have to rename it.
- BREAKING CHANGE: Positional arguments when instantiating a document are no longer supported. #2103
- From now on keyword arguments (e.g. ``Doc(field_name=value)``) are required.
- BREAKING CHANGE: A ``LazyReferenceField`` is now stored in the ``_data`` field of its parent as a ``DBRef``, ``Document``, or ``EmbeddedDocument`` (``ObjectId`` is no longer allowed). #2182
- DEPRECATION: ``Q.empty`` & ``QNode.empty`` are marked as deprecated and will be removed in a next version of MongoEngine. #2210
- Added ability to check if Q or QNode are empty by parsing them to bool.
- Instead of ``Q(name="John").empty`` use ``not Q(name="John")``.
- Fix updating/modifying/deleting/reloading a document that's sharded by a field with ``db_field`` specified. #2125
- Only set no_cursor_timeout when requested (fixes an incompatibility with MongoDB 4.2) #2148
- ``ListField`` now accepts an optional ``max_length`` parameter. #2110
- Improve error message related to InvalidDocumentError #2180
- Added BulkWriteError to replace NotUniqueError which was misleading in bulk write insert #2152
- Added ability to compare Q and Q operations #2204
- Added ability to use a db alias on query_counter #2194
- Added ability to specify collations for querysets with ``Doc.objects.collation`` #2024
- Fix updates of a list field by negative index #2094
- Switch from nosetest to pytest as test runner #2114
- The codebase is now formatted using ``black``. #2109
- Documentation improvements:
- Documented how `pymongo.monitoring` can be used to log all queries issued by MongoEngine to the driver.
Changes in 0.18.2
=================

View File

@@ -11,7 +11,8 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
import os
import sys
import sphinx_rtd_theme

View File

@@ -13,7 +13,7 @@ Help Wanted!
The MongoEngine team is looking for help contributing and maintaining a new
Django extension for MongoEngine! If you have Django experience and would like
to help contribute to the project, please get in touch on the
`mailing list <http://groups.google.com/group/mongoengine-users>`_ or by
to help contribute to the project, please get in touch on the
`mailing list <http://groups.google.com/group/mongoengine-users>`_ or by
simply contributing on
`GitHub <https://github.com/MongoEngine/django-mongoengine>`_.

12
docs/faq.rst Normal file
View File

@@ -0,0 +1,12 @@
==========================
Frequently Asked Questions
==========================
Does MongoEngine support asynchronous drivers (Motor, TxMongo)?
---------------------------------------------------------------
No, MongoEngine is exclusively based on PyMongo and isn't designed to support other driver.
If this is a requirement for your project, check the alternative: `uMongo`_ and `MotorEngine`_.
.. _uMongo: https://umongo.readthedocs.io/
.. _MotorEngine: https://motorengine.readthedocs.io/

View File

@@ -86,7 +86,7 @@ using 3 different databases to store data::
connect(alias='user-db-alias', db='user-db')
connect(alias='book-db-alias', db='book-db')
connect(alias='users-books-db-alias', db='users-books-db')
class User(Document):
name = StringField()

View File

@@ -352,7 +352,7 @@ Its value can take any of the following constants:
Deletion is denied if there still exist references to the object being
deleted.
:const:`mongoengine.NULLIFY`
Any object's fields still referring to the object being deleted are removed
Any object's fields still referring to the object being deleted are set to None
(using MongoDB's "unset" operation), effectively nullifying the relationship.
:const:`mongoengine.CASCADE`
Any object containing fields that are referring to the object being deleted
@@ -555,7 +555,6 @@ There are a few top level defaults for all indexes that can be set::
'index_background': True,
'index_cls': False,
'auto_create_index': True,
'index_drop_dups': True,
}
@@ -574,11 +573,6 @@ There are a few top level defaults for all indexes that can be set::
in systems where indexes are managed separately. Disabling this will improve
performance.
:attr:`index_drop_dups` (Optional)
Set the default value for if an index should drop duplicates
Since MongoDB 3.0 drop_dups is not supported anymore. Raises a Warning
and has no effect
Compound Indexes and Indexing sub documents
-------------------------------------------
@@ -744,7 +738,7 @@ Document inheritance
To create a specialised type of a :class:`~mongoengine.Document` you have
defined, you may subclass it and add any extra fields or methods you may need.
As this is new class is not a direct subclass of
As this new class is not a direct subclass of
:class:`~mongoengine.Document`, it will not be stored in its own collection; it
will use the same collection as its superclass uses. This allows for more
convenient and efficient retrieval of related documents -- all you need do is
@@ -767,6 +761,27 @@ document.::
Setting :attr:`allow_inheritance` to True should also be used in
:class:`~mongoengine.EmbeddedDocument` class in case you need to subclass it
When it comes to querying using :attr:`.objects()`, querying `Page.objects()` will query
both `Page` and `DatedPage` whereas querying `DatedPage` will only query the `DatedPage` documents.
Behind the scenes, MongoEngine deals with inheritance by adding a :attr:`_cls` attribute that contains
the class name in every documents. When a document is loaded, MongoEngine checks
it's :attr:`_cls` attribute and use that class to construct the instance.::
Page(title='a funky title').save()
DatedPage(title='another title', date=datetime.utcnow()).save()
print(Page.objects().count()) # 2
print(DatedPage.objects().count()) # 1
# print documents in their native form
# we remove 'id' to avoid polluting the output with unnecessary detail
qs = Page.objects.exclude('id').as_pymongo()
print(list(qs))
# [
# {'_cls': u 'Page', 'title': 'a funky title'},
# {'_cls': u 'Page.DatedPage', 'title': u 'another title', 'date': datetime.datetime(2019, 12, 13, 20, 16, 59, 993000)}
# ]
Working with existing data
--------------------------
As MongoEngine no longer defaults to needing :attr:`_cls`, you can quickly and

View File

@@ -10,8 +10,9 @@ Writing
GridFS support comes in the form of the :class:`~mongoengine.fields.FileField` field
object. This field acts as a file-like object and provides a couple of
different ways of inserting and retrieving data. Arbitrary metadata such as
content type can also be stored alongside the files. In the following example,
a document is created to store details about animals, including a photo::
content type can also be stored alongside the files. The object returned when accessing a
FileField is a proxy to `Pymongo's GridFS <https://api.mongodb.com/python/current/examples/gridfs.html#gridfs-example>`_
In the following example, a document is created to store details about animals, including a photo::
class Animal(Document):
genus = StringField()
@@ -20,8 +21,8 @@ a document is created to store details about animals, including a photo::
marmot = Animal(genus='Marmota', family='Sciuridae')
marmot_photo = open('marmot.jpg', 'rb')
marmot.photo.put(marmot_photo, content_type = 'image/jpeg')
with open('marmot.jpg', 'rb') as fd:
marmot.photo.put(fd, content_type = 'image/jpeg')
marmot.save()
Retrieval
@@ -34,6 +35,20 @@ field. The file can also be retrieved just as easily::
photo = marmot.photo.read()
content_type = marmot.photo.content_type
.. note:: If you need to read() the content of a file multiple times, you'll need to "rewind"
the file-like object using `seek`::
marmot = Animal.objects(genus='Marmota').first()
content1 = marmot.photo.read()
assert content1 != ""
content2 = marmot.photo.read() # will be empty
assert content2 == ""
marmot.photo.seek(0) # rewind the file by setting the current position of the cursor in the file to 0
content3 = marmot.photo.read()
assert content3 == content1
Streaming
---------

View File

@@ -13,4 +13,5 @@ User Guide
gridfs
signals
text-indexes
logging-monitoring
mongomock

View File

@@ -12,7 +12,7 @@ MongoEngine is available on PyPI, so you can use :program:`pip`:
.. code-block:: console
$ pip install mongoengine
$ python -m pip install mongoengine
Alternatively, if you don't have setuptools installed, `download it from PyPi
<http://pypi.python.org/pypi/mongoengine/>`_ and run

View File

@@ -0,0 +1,80 @@
==================
Logging/Monitoring
==================
It is possible to use `pymongo.monitoring <https://api.mongodb.com/python/current/api/pymongo/monitoring.html>`_ to monitor
the driver events (e.g: queries, connections, etc). This can be handy if you want to monitor the queries issued by
MongoEngine to the driver.
To use `pymongo.monitoring` with MongoEngine, you need to make sure that you are registering the listeners
**before** establishing the database connection (i.e calling `connect`):
The following snippet provides a basic logging of all command events:
.. code-block:: python
import logging
from pymongo import monitoring
from mongoengine import *
log = logging.getLogger()
log.setLevel(logging.DEBUG)
logging.basicConfig(level=logging.DEBUG)
class CommandLogger(monitoring.CommandListener):
def started(self, event):
log.debug("Command {0.command_name} with request id "
"{0.request_id} started on server "
"{0.connection_id}".format(event))
def succeeded(self, event):
log.debug("Command {0.command_name} with request id "
"{0.request_id} on server {0.connection_id} "
"succeeded in {0.duration_micros} "
"microseconds".format(event))
def failed(self, event):
log.debug("Command {0.command_name} with request id "
"{0.request_id} on server {0.connection_id} "
"failed in {0.duration_micros} "
"microseconds".format(event))
monitoring.register(CommandLogger())
class Jedi(Document):
name = StringField()
connect()
log.info('GO!')
log.info('Saving an item through MongoEngine...')
Jedi(name='Obi-Wan Kenobii').save()
log.info('Querying through MongoEngine...')
obiwan = Jedi.objects.first()
log.info('Updating through MongoEngine...')
obiwan.name = 'Obi-Wan Kenobi'
obiwan.save()
Executing this prints the following output::
INFO:root:GO!
INFO:root:Saving an item through MongoEngine...
DEBUG:root:Command insert with request id 1681692777 started on server ('localhost', 27017)
DEBUG:root:Command insert with request id 1681692777 on server ('localhost', 27017) succeeded in 562 microseconds
INFO:root:Querying through MongoEngine...
DEBUG:root:Command find with request id 1714636915 started on server ('localhost', 27017)
DEBUG:root:Command find with request id 1714636915 on server ('localhost', 27017) succeeded in 341 microseconds
INFO:root:Updating through MongoEngine...
DEBUG:root:Command update with request id 1957747793 started on server ('localhost', 27017)
DEBUG:root:Command update with request id 1957747793 on server ('localhost', 27017) succeeded in 455 microseconds
More details can of course be obtained by checking the `event` argument from the `CommandListener`.

View File

@@ -2,10 +2,10 @@
Use mongomock for testing
==============================
`mongomock <https://github.com/vmalloc/mongomock/>`_ is a package to do just
`mongomock <https://github.com/vmalloc/mongomock/>`_ is a package to do just
what the name implies, mocking a mongo database.
To use with mongoengine, simply specify mongomock when connecting with
To use with mongoengine, simply specify mongomock when connecting with
mongoengine:
.. code-block:: python
@@ -21,7 +21,7 @@ or with an alias:
conn = get_connection('testdb')
Example of test file:
--------
---------------------
.. code-block:: python
import unittest
@@ -45,4 +45,4 @@ Example of test file:
pers.save()
fresh_pers = Person.objects().first()
self.assertEqual(fresh_pers.name, 'John')
assert fresh_pers.name == 'John'

View File

@@ -222,6 +222,18 @@ keyword argument::
.. versionadded:: 0.4
Sorting/Ordering results
========================
It is possible to order the results by 1 or more keys using :meth:`~mongoengine.queryset.QuerySet.order_by`.
The order may be specified by prepending each of the keys by "+" or "-". Ascending order is assumed if there's no prefix.::
# Order by ascending date
blogs = BlogPost.objects().order_by('date') # equivalent to .order_by('+date')
# Order by ascending date first, then descending title
blogs = BlogPost.objects().order_by('+date', '-title')
Limiting and skipping results
=============================
Just as with traditional ORMs, you may limit the number of results returned or
@@ -349,9 +361,9 @@ Just as with limiting and skipping results, there is a method on a
You could technically use ``len(User.objects)`` to get the same result, but it
would be significantly slower than :meth:`~mongoengine.queryset.QuerySet.count`.
When you execute a server-side count query, you let MongoDB do the heavy
lifting and you receive a single integer over the wire. Meanwhile, len()
lifting and you receive a single integer over the wire. Meanwhile, ``len()``
retrieves all the results, places them in a local cache, and finally counts
them. If we compare the performance of the two operations, len() is much slower
them. If we compare the performance of the two operations, ``len()`` is much slower
than :meth:`~mongoengine.queryset.QuerySet.count`.
Further aggregation
@@ -386,6 +398,25 @@ would be generating "tag-clouds"::
top_tags = sorted(tag_freqs.items(), key=itemgetter(1), reverse=True)[:10]
MongoDB aggregation API
-----------------------
If you need to run aggregation pipelines, MongoEngine provides an entry point to `Pymongo's aggregation framework <https://api.mongodb.com/python/current/examples/aggregation.html#aggregation-framework>`_
through :meth:`~mongoengine.queryset.QuerySet.aggregate`. Check out Pymongo's documentation for the syntax and pipeline.
An example of its use would be::
class Person(Document):
name = StringField()
Person(name='John').save()
Person(name='Bob').save()
pipeline = [
{"$sort" : {"name" : -1}},
{"$project": {"_id": 0, "name": {"$toUpper": "$name"}}}
]
data = Person.objects().aggregate(pipeline)
assert data == [{'name': 'BOB'}, {'name': 'JOHN'}]
Query efficiency and performance
================================
@@ -566,7 +597,8 @@ cannot use the `$` syntax in keyword arguments it has been mapped to `S`::
['database', 'mongodb']
From MongoDB version 2.6, push operator supports $position value which allows
to push values with index.
to push values with index::
>>> post = BlogPost(title="Test", tags=["mongo"])
>>> post.save()
>>> post.update(push__tags__0=["database", "code"])

View File

@@ -44,8 +44,8 @@ Available signals include:
`post_save`
Called within :meth:`~mongoengine.Document.save` after most actions
(validation, insert/update, and cascades, but not clearing dirty flags) have
completed successfully. Passed the additional boolean keyword argument
(validation, insert/update, and cascades, but not clearing dirty flags) have
completed successfully. Passed the additional boolean keyword argument
`created` to indicate if the save was an insert or an update.
`pre_delete`

View File

@@ -8,7 +8,7 @@ After MongoDB 2.4 version, supports search documents by text indexes.
Defining a Document with text index
===================================
Use the *$* prefix to set a text index, Look the declaration::
class News(Document):
title = StringField()
content = StringField()
@@ -35,10 +35,10 @@ Saving a document::
content="Various improvements").save()
Next, start a text search using :attr:`QuerySet.search_text` method::
document = News.objects.search_text('testing').first()
document.title # may be: "Using mongodb text search"
document = News.objects.search_text('released').first()
document.title # may be: "MongoEngine 0.9 released"

View File

@@ -7,7 +7,7 @@ MongoDB. To install it, simply run
.. code-block:: console
$ pip install -U mongoengine
$ python -m pip install -U mongoengine
:doc:`tutorial`
A quick tutorial building a tumblelog to get you up and running with
@@ -23,9 +23,18 @@ MongoDB. To install it, simply run
:doc:`upgrade`
How to upgrade MongoEngine.
:doc:`faq`
Frequently Asked Questions
:doc:`django`
Using MongoEngine and Django
MongoDB and driver support
--------------------------
MongoEngine is based on the PyMongo driver and tested against multiple versions of MongoDB.
For further details, please refer to the `readme <https://github.com/MongoEngine/mongoengine#mongoengine>`_.
Community
---------
@@ -73,6 +82,7 @@ formats for offline reading.
apireference
changelog
upgrade
faq
django
Indices and tables
@@ -81,4 +91,3 @@ Indices and tables
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@@ -18,7 +18,7 @@ location --- running it locally will be easier, but if that is not an option
then it may be run on a remote server. If you haven't installed MongoEngine,
simply use pip to install it like so::
$ pip install mongoengine
$ python -m pip install mongoengine
Before we can start using MongoEngine, we need to tell it how to connect to our
instance of :program:`mongod`. For this we use the :func:`~mongoengine.connect`

View File

@@ -52,7 +52,7 @@ rename its occurrences.
This release includes a major rehaul of MongoEngine's code quality and
introduces a few breaking changes. It also touches many different parts of
the package and although all the changes have been tested and scrutinized,
you're encouraged to thorougly test the upgrade.
you're encouraged to thoroughly test the upgrade.
First breaking change involves renaming `ConnectionError` to `MongoEngineConnectionError`.
If you import or catch this exception, you'll need to rename it in your code.
@@ -85,10 +85,10 @@ by default from now on.
The 0.8.7 package on pypi was corrupted. If upgrading from 0.8.7 to 0.9.0 please follow: ::
pip uninstall pymongo
pip uninstall mongoengine
pip install pymongo==2.8
pip install mongoengine
python -m pip uninstall pymongo
python -m pip uninstall mongoengine
python -m pip install pymongo==2.8
python -m pip install mongoengine
0.8.7
*****
@@ -153,7 +153,7 @@ inherited classes like so: ::
# 4. Remove indexes
info = collection.index_information()
indexes_to_drop = [key for key, value in info.iteritems()
indexes_to_drop = [key for key, value in info.items()
if '_types' in dict(value['key'])]
for index in indexes_to_drop:
collection.drop_index(index)

View File

@@ -28,7 +28,7 @@ __all__ = (
)
VERSION = (0, 18, 2)
VERSION = (0, 20, 0)
def get_version():

View File

@@ -1,8 +1,6 @@
import weakref
from bson import DBRef
import six
from six import iteritems
from mongoengine.common import _import_class
from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
@@ -53,7 +51,7 @@ class BaseDict(dict):
if isinstance(instance, BaseDocument):
self._instance = weakref.proxy(instance)
self._name = name
super(BaseDict, self).__init__(dict_items)
super().__init__(dict_items)
def get(self, key, default=None):
# get does not use __getitem__ by default so we must override it as well
@@ -63,18 +61,18 @@ class BaseDict(dict):
return default
def __getitem__(self, key):
value = super(BaseDict, self).__getitem__(key)
value = super().__getitem__(key)
EmbeddedDocument = _import_class("EmbeddedDocument")
if isinstance(value, EmbeddedDocument) and value._instance is None:
value._instance = self._instance
elif isinstance(value, dict) and not isinstance(value, BaseDict):
value = BaseDict(value, None, "%s.%s" % (self._name, key))
super(BaseDict, self).__setitem__(key, value)
value = BaseDict(value, None, "{}.{}".format(self._name, key))
super().__setitem__(key, value)
value._instance = self._instance
elif isinstance(value, list) and not isinstance(value, BaseList):
value = BaseList(value, None, "%s.%s" % (self._name, key))
super(BaseDict, self).__setitem__(key, value)
value = BaseList(value, None, "{}.{}".format(self._name, key))
super().__setitem__(key, value)
value._instance = self._instance
return value
@@ -99,7 +97,7 @@ class BaseDict(dict):
def _mark_as_changed(self, key=None):
if hasattr(self._instance, "_mark_as_changed"):
if key:
self._instance._mark_as_changed("%s.%s" % (self._name, key))
self._instance._mark_as_changed("{}.{}".format(self._name, key))
else:
self._instance._mark_as_changed(self._name)
@@ -117,10 +115,13 @@ class BaseList(list):
if isinstance(instance, BaseDocument):
self._instance = weakref.proxy(instance)
self._name = name
super(BaseList, self).__init__(list_items)
super().__init__(list_items)
def __getitem__(self, key):
value = super(BaseList, self).__getitem__(key)
# change index to positive value because MongoDB does not support negative one
if isinstance(key, int) and key < 0:
key = len(self) + key
value = super().__getitem__(key)
if isinstance(key, slice):
# When receiving a slice operator, we don't convert the structure and bind
@@ -132,19 +133,18 @@ class BaseList(list):
value._instance = self._instance
elif isinstance(value, dict) and not isinstance(value, BaseDict):
# Replace dict by BaseDict
value = BaseDict(value, None, "%s.%s" % (self._name, key))
super(BaseList, self).__setitem__(key, value)
value = BaseDict(value, None, "{}.{}".format(self._name, key))
super().__setitem__(key, value)
value._instance = self._instance
elif isinstance(value, list) and not isinstance(value, BaseList):
# Replace list by BaseList
value = BaseList(value, None, "%s.%s" % (self._name, key))
super(BaseList, self).__setitem__(key, value)
value = BaseList(value, None, "{}.{}".format(self._name, key))
super().__setitem__(key, value)
value._instance = self._instance
return value
def __iter__(self):
for v in super(BaseList, self).__iter__():
yield v
yield from super().__iter__()
def __getstate__(self):
self.instance = None
@@ -162,7 +162,7 @@ class BaseList(list):
# instead, we simply marks the whole list as changed
changed_key = None
result = super(BaseList, self).__setitem__(key, value)
result = super().__setitem__(key, value)
self._mark_as_changed(changed_key)
return result
@@ -177,30 +177,19 @@ class BaseList(list):
__iadd__ = mark_as_changed_wrapper(list.__iadd__)
__imul__ = mark_as_changed_wrapper(list.__imul__)
if six.PY2:
# Under py3 __setslice__, __delslice__ and __getslice__
# are replaced by __setitem__, __delitem__ and __getitem__ with a slice as parameter
# so we mimic this under python 2
def __setslice__(self, i, j, sequence):
return self.__setitem__(slice(i, j), sequence)
def __delslice__(self, i, j):
return self.__delitem__(slice(i, j))
def __getslice__(self, i, j):
return self.__getitem__(slice(i, j))
def _mark_as_changed(self, key=None):
if hasattr(self._instance, "_mark_as_changed"):
if key:
self._instance._mark_as_changed("%s.%s" % (self._name, key % len(self)))
self._instance._mark_as_changed(
"{}.{}".format(self._name, key % len(self))
)
else:
self._instance._mark_as_changed(self._name)
class EmbeddedDocumentList(BaseList):
def __init__(self, list_items, instance, name):
super(EmbeddedDocumentList, self).__init__(list_items, instance, name)
super().__init__(list_items, instance, name)
self._instance = instance
@classmethod
@@ -210,7 +199,7 @@ class EmbeddedDocumentList(BaseList):
"""
for key, expected_value in kwargs.items():
doc_val = getattr(embedded_doc, key)
if doc_val != expected_value and six.text_type(doc_val) != expected_value:
if doc_val != expected_value and str(doc_val) != expected_value:
return False
return True
@@ -300,11 +289,11 @@ class EmbeddedDocumentList(BaseList):
def create(self, **values):
"""
Creates a new embedded document and saves it to the database.
Creates a new instance of the EmbeddedDocument and appends it to this EmbeddedDocumentList.
.. note::
The embedded document changes are not automatically saved
to the database after calling this method.
the instance of the EmbeddedDocument is not automatically saved to the database.
You still need to call .save() on the parent Document.
:param values: A dictionary of values for the embedded document.
:return: The new embedded document instance.
@@ -365,13 +354,13 @@ class EmbeddedDocumentList(BaseList):
return len(values)
class StrictDict(object):
class StrictDict:
__slots__ = ()
_special_fields = {"get", "pop", "iteritems", "items", "keys", "create"}
_classes = {}
def __init__(self, **kwargs):
for k, v in iteritems(kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
def __getitem__(self, key):
@@ -419,13 +408,13 @@ class StrictDict(object):
return (key for key in self.__slots__ if hasattr(self, key))
def __len__(self):
return len(list(iteritems(self)))
return len(list(self.items()))
def __eq__(self, other):
return self.items() == other.items()
return list(self.items()) == list(other.items())
def __ne__(self, other):
return self.items() != other.items()
return not (self == other)
@classmethod
def create(cls, allowed_keys):
@@ -440,7 +429,7 @@ class StrictDict(object):
def __repr__(self):
return "{%s}" % ", ".join(
'"{0!s}": {1!r}'.format(k, v) for k, v in self.items()
'"{!s}": {!r}'.format(k, v) for k, v in self.items()
)
cls._classes[allowed_keys] = SpecificStrictDict
@@ -465,9 +454,7 @@ class LazyReference(DBRef):
self.document_type = document_type
self._cached_doc = cached_doc
self.passthrough = passthrough
super(LazyReference, self).__init__(
self.document_type._get_collection_name(), pk
)
super().__init__(self.document_type._get_collection_name(), pk)
def __getitem__(self, name):
if not self.passthrough:
@@ -485,4 +472,4 @@ class LazyReference(DBRef):
raise AttributeError()
def __repr__(self):
return "<LazyReference(%s, %r)>" % (self.document_type, self.pk)
return "<LazyReference({}, {!r})>".format(self.document_type, self.pk)

View File

@@ -1,11 +1,10 @@
import copy
import numbers
from functools import partial
from bson import DBRef, ObjectId, SON, json_util
import pymongo
import six
from six import iteritems
from mongoengine import signals
from mongoengine.base.common import get_document
@@ -25,14 +24,13 @@ from mongoengine.errors import (
OperationError,
ValidationError,
)
from mongoengine.python_support import Hashable
__all__ = ("BaseDocument", "NON_FIELD_ERRORS")
NON_FIELD_ERRORS = "__all__"
class BaseDocument(object):
class BaseDocument:
# TODO simplify how `_changed_fields` is used.
# Currently, handling of `_changed_fields` seems unnecessarily convoluted:
# 1. `BaseDocument` defines `_changed_fields` in its `__slots__`, yet it's
@@ -62,13 +60,13 @@ class BaseDocument(object):
"""
Initialise a document or an embedded document.
:param dict values: A dictionary of keys and values for the document.
:param values: A dictionary of keys and values for the document.
It may contain additional reserved keywords, e.g. "__auto_convert".
:param bool __auto_convert: If True, supplied values will be converted
:param __auto_convert: If True, supplied values will be converted
to Python-type values via each field's `to_python` method.
:param set __only_fields: A set of fields that have been loaded for
:param __only_fields: A set of fields that have been loaded for
this document. Empty if all fields have been loaded.
:param bool _created: Indicates whether this is a brand new document
:param _created: Indicates whether this is a brand new document
or whether it's already been persisted before. Defaults to true.
"""
self._initialised = False
@@ -92,10 +90,10 @@ class BaseDocument(object):
# if so raise an Exception.
if not self._dynamic and (self._meta.get("strict", True) or _created):
_undefined_fields = set(values.keys()) - set(
self._fields.keys() + ["id", "pk", "_cls", "_text_score"]
list(self._fields.keys()) + ["id", "pk", "_cls", "_text_score"]
)
if _undefined_fields:
msg = ('The fields "{0}" do not exist on the document "{1}"').format(
msg = ('The fields "{}" do not exist on the document "{}"').format(
_undefined_fields, self._class_name
)
raise FieldDoesNotExist(msg)
@@ -110,7 +108,7 @@ class BaseDocument(object):
# Assign default values to the instance.
# We set default values only for fields loaded from DB. See
# https://github.com/mongoengine/mongoengine/issues/399 for more info.
for key, field in iteritems(self._fields):
for key, field in self._fields.items():
if self._db_field_map.get(key, key) in __only_fields:
continue
value = getattr(self, key, None)
@@ -122,14 +120,14 @@ class BaseDocument(object):
# Set passed values after initialisation
if self._dynamic:
dynamic_data = {}
for key, value in iteritems(values):
for key, value in values.items():
if key in self._fields or key == "_id":
setattr(self, key, value)
else:
dynamic_data[key] = value
else:
FileField = _import_class("FileField")
for key, value in iteritems(values):
for key, value in values.items():
key = self._reverse_db_field_map.get(key, key)
if key in self._fields or key in ("id", "pk", "_cls"):
if __auto_convert and value is not None:
@@ -145,7 +143,7 @@ class BaseDocument(object):
if self._dynamic:
self._dynamic_lock = False
for key, value in iteritems(dynamic_data):
for key, value in dynamic_data.items():
setattr(self, key, value)
# Flag initialised
@@ -163,7 +161,7 @@ class BaseDocument(object):
default = default()
setattr(self, field_name, default)
else:
super(BaseDocument, self).__delattr__(*args, **kwargs)
super().__delattr__(*args, **kwargs)
def __setattr__(self, name, value):
# Handle dynamic data only if an initialised dynamic document
@@ -210,9 +208,9 @@ class BaseDocument(object):
and self__created
and name == self._meta.get("id_field")
):
super(BaseDocument, self).__setattr__("_created", False)
super().__setattr__("_created", False)
super(BaseDocument, self).__setattr__(name, value)
super().__setattr__(name, value)
def __getstate__(self):
data = {}
@@ -288,16 +286,13 @@ class BaseDocument(object):
except (UnicodeEncodeError, UnicodeDecodeError):
u = "[Bad Unicode data]"
repr_type = str if u is None else type(u)
return repr_type("<%s: %s>" % (self.__class__.__name__, u))
return repr_type("<{}: {}>".format(self.__class__.__name__, u))
def __str__(self):
# TODO this could be simpler?
if hasattr(self, "__unicode__"):
if six.PY3:
return self.__unicode__()
else:
return six.text_type(self).encode("utf-8")
return six.text_type("%s object" % self.__class__.__name__)
return self.__unicode__()
return "%s object" % self.__class__.__name__
def __eq__(self, other):
if (
@@ -446,7 +441,7 @@ class BaseDocument(object):
pk = self.pk
elif self._instance and hasattr(self._instance, "pk"):
pk = self._instance.pk
message = "ValidationError (%s:%s) " % (self._class_name, pk)
message = "ValidationError ({}:{}) ".format(self._class_name, pk)
raise ValidationError(message, errors=errors)
def to_json(self, *args, **kwargs):
@@ -519,7 +514,7 @@ class BaseDocument(object):
if "." in key:
key, rest = key.split(".", 1)
key = self._db_field_map.get(key, key)
key = "%s.%s" % (key, rest)
key = "{}.{}".format(key, rest)
else:
key = self._db_field_map.get(key, key)
@@ -578,10 +573,10 @@ class BaseDocument(object):
if not hasattr(data, "items"):
iterator = enumerate(data)
else:
iterator = iteritems(data)
iterator = data.items()
for index_or_key, value in iterator:
item_key = "%s%s." % (base_key, index_or_key)
item_key = "{}{}.".format(base_key, index_or_key)
# don't check anything lower if this key is already marked
# as changed.
if item_key[:-1] in changed_fields:
@@ -589,7 +584,7 @@ class BaseDocument(object):
if hasattr(value, "_get_changed_fields"):
changed = value._get_changed_fields()
changed_fields += ["%s%s" % (item_key, k) for k in changed if k]
changed_fields += ["{}{}".format(item_key, k) for k in changed if k]
elif isinstance(value, (list, tuple, dict)):
self._nestable_types_changed_fields(changed_fields, item_key, value)
@@ -620,7 +615,7 @@ class BaseDocument(object):
if isinstance(data, EmbeddedDocument):
# Find all embedded fields that have been changed
changed = data._get_changed_fields()
changed_fields += ["%s%s" % (key, k) for k in changed if k]
changed_fields += ["{}{}".format(key, k) for k in changed if k]
elif isinstance(data, (list, tuple, dict)):
if hasattr(field, "field") and isinstance(
field.field, (ReferenceField, GenericReferenceField)
@@ -670,7 +665,7 @@ class BaseDocument(object):
del set_data["_id"]
# Determine if any changed items were actually unset.
for path, value in set_data.items():
for path, value in list(set_data.items()):
if value or isinstance(
value, (numbers.Number, bool)
): # Account for 0 and True that are truthy
@@ -732,7 +727,10 @@ class BaseDocument(object):
only_fields = []
if son and not isinstance(son, dict):
raise ValueError("The source SON object needs to be of type 'dict'")
raise ValueError(
"The source SON object needs to be of type 'dict' but a '%s' was found"
% type(son)
)
# Get the class name from the document, falling back to the given
# class if unavailable
@@ -741,7 +739,7 @@ class BaseDocument(object):
# Convert SON to a data dict, making sure each key is a string and
# corresponds to the right db field.
data = {}
for key, value in iteritems(son):
for key, value in son.items():
key = str(key)
key = cls._db_field_map.get(key, key)
data[key] = value
@@ -756,7 +754,7 @@ class BaseDocument(object):
if not _auto_dereference:
fields = copy.deepcopy(fields)
for field_name, field in iteritems(fields):
for field_name, field in fields.items():
field._auto_dereference = _auto_dereference
if field.db_field in data:
value = data[field.db_field]
@@ -770,16 +768,17 @@ class BaseDocument(object):
errors_dict[field_name] = e
if errors_dict:
errors = "\n".join(["%s - %s" % (k, v) for k, v in errors_dict.items()])
msg = "Invalid data to create a `%s` instance.\n%s" % (
cls._class_name,
errors,
errors = "\n".join(
["Field '{}' - {}".format(k, v) for k, v in errors_dict.items()]
)
msg = "Invalid data to create a `{}` instance.\n{}".format(
cls._class_name, errors,
)
raise InvalidDocumentError(msg)
# In STRICT documents, remove any keys that aren't in cls._fields
if cls.STRICT:
data = {k: v for k, v in iteritems(data) if k in cls._fields}
data = {k: v for k, v in data.items() if k in cls._fields}
obj = cls(
__auto_convert=False, _created=created, __only_fields=only_fields, **data
@@ -826,7 +825,7 @@ class BaseDocument(object):
@classmethod
def _build_index_spec(cls, spec):
"""Build a PyMongo index spec from a MongoEngine index spec."""
if isinstance(spec, six.string_types):
if isinstance(spec, str):
spec = {"fields": [spec]}
elif isinstance(spec, (list, tuple)):
spec = {"fields": list(spec)}
@@ -923,7 +922,7 @@ class BaseDocument(object):
# Add any unique_with fields to the back of the index spec
if field.unique_with:
if isinstance(field.unique_with, six.string_types):
if isinstance(field.unique_with, str):
field.unique_with = [field.unique_with]
# Convert unique_with field names to real field names
@@ -944,7 +943,8 @@ class BaseDocument(object):
# Add the new index to the list
fields = [
("%s%s" % (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}
unique_indexes.append(index)
@@ -1001,7 +1001,7 @@ class BaseDocument(object):
elif field._geo_index:
field_name = field.db_field
if parent_field:
field_name = "%s.%s" % (parent_field, field_name)
field_name = "{}.{}".format(parent_field, field_name)
geo_indices.append({"fields": [(field_name, field._geo_index)]})
return geo_indices
@@ -1170,9 +1170,6 @@ class BaseDocument(object):
else [value]
)
return sep.join(
[
six.text_type(dict(field.choices).get(val, val))
for val in values or []
]
[str(dict(field.choices).get(val, val)) for val in values or []]
)
return value

View File

@@ -4,8 +4,6 @@ import weakref
from bson import DBRef, ObjectId, SON
import pymongo
import six
from six import iteritems
from mongoengine.base.common import UPDATE_OPERATORS
from mongoengine.base.datastructures import BaseDict, BaseList, EmbeddedDocumentList
@@ -15,7 +13,7 @@ from mongoengine.errors import DeprecatedError, ValidationError
__all__ = ("BaseField", "ComplexBaseField", "ObjectIdField", "GeoJsonBaseField")
class BaseField(object):
class BaseField:
"""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.
@@ -36,7 +34,6 @@ class BaseField(object):
def __init__(
self,
db_field=None,
name=None,
required=False,
default=None,
unique=False,
@@ -51,7 +48,6 @@ class BaseField(object):
"""
:param db_field: The database field to store this field in
(defaults to the name of the field)
:param name: Deprecated - use db_field
:param required: If the field is required. Whether it has to have a
value or not. Defaults to False.
:param default: (optional) The default value for this field if no value
@@ -75,11 +71,8 @@ class BaseField(object):
existing attributes. Common metadata includes `verbose_name` and
`help_text`.
"""
self.db_field = (db_field or name) if not primary_key else "_id"
self.db_field = db_field if not primary_key else "_id"
if name:
msg = 'Field\'s "name" attribute deprecated in favour of "db_field"'
warnings.warn(msg, DeprecationWarning)
self.required = required or primary_key
self.default = default
self.unique = bool(unique or unique_with)
@@ -92,13 +85,11 @@ class BaseField(object):
self._owner_document = None
# Make sure db_field is a string (if it's explicitly defined).
if self.db_field is not None and not isinstance(
self.db_field, six.string_types
):
if self.db_field is not None and not isinstance(self.db_field, str):
raise TypeError("db_field should be a string.")
# Make sure db_field doesn't contain any forbidden characters.
if isinstance(self.db_field, six.string_types) and (
if isinstance(self.db_field, str) and (
"." in self.db_field
or "\0" in self.db_field
or self.db_field.startswith("$")
@@ -221,14 +212,12 @@ class BaseField(object):
# Choices which are other types of Documents
if isinstance(value, (Document, EmbeddedDocument)):
if not any(isinstance(value, c) for c in choice_list):
self.error(
"Value must be an instance of %s" % (six.text_type(choice_list))
)
self.error("Value must be an instance of %s" % (choice_list))
# Choices which are types other than Documents
else:
values = value if isinstance(value, (list, tuple)) else [value]
if len(set(values) - set(choice_list)):
self.error("Value must be one of %s" % six.text_type(choice_list))
self.error("Value must be one of %s" % str(choice_list))
def _validate(self, value, **kwargs):
# Check the Choices Constraint
@@ -316,7 +305,7 @@ class ComplexBaseField(BaseField):
if hasattr(instance._data[self.name], "_dereferenced"):
instance._data[self.name]._dereferenced = True
value = super(ComplexBaseField, self).__get__(instance, owner)
value = super().__get__(instance, owner)
# Convert lists / values so we can watch for any changes on them
if isinstance(value, (list, tuple)):
@@ -345,7 +334,7 @@ class ComplexBaseField(BaseField):
def to_python(self, value):
"""Convert a MongoDB-compatible type to a Python type."""
if isinstance(value, six.string_types):
if isinstance(value, str):
return value
if hasattr(value, "to_python"):
@@ -399,7 +388,7 @@ class ComplexBaseField(BaseField):
EmbeddedDocument = _import_class("EmbeddedDocument")
GenericReferenceField = _import_class("GenericReferenceField")
if isinstance(value, six.string_types):
if isinstance(value, str):
return value
if hasattr(value, "to_mongo"):
@@ -423,11 +412,11 @@ class ComplexBaseField(BaseField):
if self.field:
value_dict = {
key: self.field._to_mongo_safe_call(item, use_db_field, fields)
for key, item in iteritems(value)
for key, item in value.items()
}
else:
value_dict = {}
for k, v in iteritems(value):
for k, v in value.items():
if isinstance(v, Document):
# We need the id from the saved object to create the DBRef
if v.pk is None:
@@ -466,8 +455,8 @@ class ComplexBaseField(BaseField):
"""If field is provided ensure the value is valid."""
errors = {}
if self.field:
if hasattr(value, "iteritems") or hasattr(value, "items"):
sequence = iteritems(value)
if hasattr(value, "items"):
sequence = value.items()
else:
sequence = enumerate(value)
for k, v in sequence:
@@ -480,7 +469,9 @@ class ComplexBaseField(BaseField):
if errors:
field_class = self.field.__class__.__name__
self.error("Invalid %s item (%s)" % (field_class, value), errors=errors)
self.error(
"Invalid {} item ({})".format(field_class, value), errors=errors
)
# Don't allow empty values if required
if self.required and not value:
self.error("Field is required and cannot be empty")
@@ -513,10 +504,9 @@ class ObjectIdField(BaseField):
def to_mongo(self, value):
if not isinstance(value, ObjectId):
try:
return ObjectId(six.text_type(value))
return ObjectId(str(value))
except Exception as e:
# e.message attribute has been deprecated since Python 2.6
self.error(six.text_type(e))
self.error(str(e))
return value
def prepare_query_value(self, op, value):
@@ -524,9 +514,9 @@ class ObjectIdField(BaseField):
def validate(self, value):
try:
ObjectId(six.text_type(value))
ObjectId(str(value))
except Exception:
self.error("Invalid Object ID")
self.error("Invalid ObjectID")
class GeoJsonBaseField(BaseField):
@@ -546,14 +536,14 @@ class GeoJsonBaseField(BaseField):
self._name = "%sField" % self._type
if not auto_index:
self._geo_index = False
super(GeoJsonBaseField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def validate(self, value):
"""Validate the GeoJson object based on its type."""
if isinstance(value, dict):
if set(value.keys()) == {"type", "coordinates"}:
if value["type"] != self._type:
self.error('%s type must be "%s"' % (self._name, self._type))
self.error('{} type must be "{}"'.format(self._name, self._type))
return self.validate(value["coordinates"])
else:
self.error(

View File

@@ -1,9 +1,6 @@
import itertools
import warnings
import six
from six import iteritems, itervalues
from mongoengine.base.common import _document_registry
from mongoengine.base.fields import BaseField, ComplexBaseField, ObjectIdField
from mongoengine.common import _import_class
@@ -25,7 +22,7 @@ class DocumentMetaclass(type):
# TODO lower complexity of this method
def __new__(mcs, name, bases, attrs):
flattened_bases = mcs._get_bases(bases)
super_new = super(DocumentMetaclass, mcs).__new__
super_new = super().__new__
# If a base class just call super
metaclass = attrs.get("my_metaclass")
@@ -69,7 +66,7 @@ class DocumentMetaclass(type):
# Standard object mixin - merge in any Fields
if not hasattr(base, "_meta"):
base_fields = {}
for attr_name, attr_value in iteritems(base.__dict__):
for attr_name, attr_value in base.__dict__.items():
if not isinstance(attr_value, BaseField):
continue
attr_value.name = attr_name
@@ -81,7 +78,7 @@ class DocumentMetaclass(type):
# Discover any document fields
field_names = {}
for attr_name, attr_value in iteritems(attrs):
for attr_name, attr_value in attrs.items():
if not isinstance(attr_value, BaseField):
continue
attr_value.name = attr_name
@@ -111,9 +108,7 @@ class DocumentMetaclass(type):
attrs["_fields_ordered"] = tuple(
i[1]
for i in sorted(
(v.creation_counter, v.name) for v in itervalues(doc_fields)
)
for i in sorted((v.creation_counter, v.name) for v in doc_fields.values())
)
#
@@ -173,24 +168,8 @@ class DocumentMetaclass(type):
# Add class to the _document_registry
_document_registry[new_class._class_name] = new_class
# In Python 2, User-defined methods objects have special read-only
# attributes 'im_func' and 'im_self' which contain the function obj
# and class instance object respectively. With Python 3 these special
# attributes have been replaced by __func__ and __self__. The Blinker
# module continues to use im_func and im_self, so the code below
# copies __func__ into im_func and __self__ into im_self for
# classmethod objects in Document derived classes.
if six.PY3:
for val in new_class.__dict__.values():
if isinstance(val, classmethod):
f = val.__get__(new_class)
if hasattr(f, "__func__") and not hasattr(f, "im_func"):
f.__dict__.update({"im_func": getattr(f, "__func__")})
if hasattr(f, "__self__") and not hasattr(f, "im_self"):
f.__dict__.update({"im_self": getattr(f, "__self__")})
# Handle delete rules
for field in itervalues(new_class._fields):
for field in new_class._fields.values():
f = field
if f.owner_document is None:
f.owner_document = new_class
@@ -252,8 +231,7 @@ class DocumentMetaclass(type):
if base is object:
continue
yield base
for child_base in mcs.__get_bases(base.__bases__):
yield child_base
yield from mcs.__get_bases(base.__bases__)
@classmethod
def _import_classes(mcs):
@@ -271,7 +249,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
def __new__(mcs, name, bases, attrs):
flattened_bases = mcs._get_bases(bases)
super_new = super(TopLevelDocumentMetaclass, mcs).__new__
super_new = super().__new__
# Set default _meta data if base class, otherwise get user defined meta
if attrs.get("my_metaclass") == TopLevelDocumentMetaclass:
@@ -284,7 +262,6 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
"indexes": [], # indexes to be ensured at runtime
"id_field": None,
"index_background": False,
"index_drop_dups": False,
"index_opts": None,
"delete_rules": None,
# allow_inheritance can be True, False, and None. True means
@@ -399,7 +376,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
new_class.objects = QuerySetManager()
# Validate the fields and set primary key if needed
for field_name, field in iteritems(new_class._fields):
for field_name, field in new_class._fields.items():
if field.primary_key:
# Ensure only one primary key is set
current_pk = new_class._meta.get("id_field")
@@ -462,8 +439,8 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
id_basename, id_db_basename, i = ("auto_id", "_auto_id", 0)
for i in itertools.count():
id_name = "{0}_{1}".format(id_basename, i)
id_db_name = "{0}_{1}".format(id_db_basename, i)
id_name = "{}_{}".format(id_basename, i)
id_db_name = "{}_{}".format(id_db_basename, i)
if id_name not in existing_fields and id_db_name not in existing_db_fields:
return id_name, id_db_name
@@ -476,7 +453,7 @@ class MetaDict(dict):
_merge_options = ("indexes",)
def merge(self, new_options):
for k, v in iteritems(new_options):
for k, v in new_options.items():
if k in self._merge_options:
self[k] = self.get(k, []) + v
else:

View File

@@ -1,7 +1,7 @@
import re
class LazyRegexCompiler(object):
class LazyRegexCompiler:
"""Descriptor to allow lazy compilation of regex"""
def __init__(self, pattern, flags=0):

View File

@@ -1,6 +1,5 @@
from pymongo import MongoClient, ReadPreference, uri_parser
from pymongo.database import _check_name
import six
__all__ = [
"DEFAULT_CONNECTION_NAME",
@@ -39,8 +38,8 @@ def _check_db_name(name):
"""Check if a database name is valid.
This functionality is copied from pymongo Database class constructor.
"""
if not isinstance(name, six.string_types):
raise TypeError("name must be an instance of %s" % six.string_types)
if not isinstance(name, str):
raise TypeError("name must be an instance of %s" % str)
elif name != "$external":
_check_name(name)
@@ -93,7 +92,7 @@ def _get_connection_settings(
conn_host = conn_settings["host"]
# Host can be a list or a string, so if string, force to a list.
if isinstance(conn_host, six.string_types):
if isinstance(conn_host, str):
conn_host = [conn_host]
resolved_hosts = []
@@ -148,7 +147,7 @@ def _get_connection_settings(
# TODO simplify the code below once we drop support for
# PyMongo v3.4.
read_pf_mode = uri_options["readpreference"]
if isinstance(read_pf_mode, six.string_types):
if isinstance(read_pf_mode, str):
read_pf_mode = read_pf_mode.lower()
for preference in read_preferences:
if (
@@ -318,7 +317,7 @@ def _create_connection(alias, connection_class, **connection_settings):
try:
return connection_class(**connection_settings)
except Exception as e:
raise ConnectionFailure("Cannot connect to database %s :\n%s" % (alias, e))
raise ConnectionFailure("Cannot connect to database {} :\n{}".format(alias, e))
def _find_existing_connection(connection_settings):
@@ -396,8 +395,8 @@ def connect(db=None, alias=DEFAULT_CONNECTION_NAME, **kwargs):
if new_conn_settings != prev_conn_setting:
err_msg = (
u"A different connection with alias `{}` was already "
u"registered. Use disconnect() first"
"A different connection with alias `{}` was already "
"registered. Use disconnect() first"
).format(alias)
raise ConnectionFailure(err_msg)
else:

View File

@@ -1,7 +1,7 @@
from contextlib import contextmanager
from pymongo.read_concern import ReadConcern
from pymongo.write_concern import WriteConcern
from six import iteritems
from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
@@ -14,10 +14,11 @@ __all__ = (
"no_sub_classes",
"query_counter",
"set_write_concern",
"set_read_write_concern",
)
class switch_db(object):
class switch_db:
"""switch_db alias context manager.
Example ::
@@ -58,7 +59,7 @@ class switch_db(object):
self.cls._collection = self.collection
class switch_collection(object):
class switch_collection:
"""switch_collection alias context manager.
Example ::
@@ -100,7 +101,7 @@ class switch_collection(object):
self.cls._get_collection_name = self.ori_get_collection_name
class no_dereference(object):
class no_dereference:
"""no_dereference context manager.
Turns off all dereferencing in Documents for the duration of the context
@@ -123,7 +124,7 @@ class no_dereference(object):
self.deref_fields = [
k
for k, v in iteritems(self.cls._fields)
for k, v in self.cls._fields.items()
if isinstance(v, (ReferenceField, GenericReferenceField, ComplexBaseField))
]
@@ -140,7 +141,7 @@ class no_dereference(object):
return self.cls
class no_sub_classes(object):
class no_sub_classes:
"""no_sub_classes context manager.
Only returns instances of this class and no sub (inherited) classes::
@@ -168,10 +169,10 @@ class no_sub_classes(object):
self.cls._subclasses = self.cls_initial_subclasses
class query_counter(object):
class query_counter:
"""Query_counter context manager to get the number of queries.
This works by updating the `profiling_level` of the database so that all queries get logged,
resetting the db.system.profile collection at the beginnig of the context and counting the new entries.
resetting the db.system.profile collection at the beginning of the context and counting the new entries.
This was designed for debugging purpose. In fact it is a global counter so queries issued by other threads/processes
can interfere with it
@@ -182,10 +183,10 @@ class query_counter(object):
- Some queries are ignored by default by the counter (killcursors, db.system.indexes)
"""
def __init__(self):
def __init__(self, alias=DEFAULT_CONNECTION_NAME):
"""Construct the query_counter
"""
self.db = get_db()
self.db = get_db(alias=alias)
self.initial_profiling_level = None
self._ctx_query_counter = 0 # number of queries issued by the context
@@ -235,7 +236,7 @@ class query_counter(object):
def __repr__(self):
"""repr query_counter as the number of queries."""
return u"%s" % self._get_count()
return "%s" % self._get_count()
def _get_count(self):
"""Get the number of queries by counting the current number of entries in db.system.profile
@@ -247,8 +248,8 @@ class query_counter(object):
- self._ctx_query_counter
)
self._ctx_query_counter += (
1
) # Account for the query we just issued to gather the information
1 # Account for the query we just issued to gather the information
)
return count
@@ -257,3 +258,21 @@ def set_write_concern(collection, write_concerns):
combined_concerns = dict(collection.write_concern.document.items())
combined_concerns.update(write_concerns)
yield collection.with_options(write_concern=WriteConcern(**combined_concerns))
@contextmanager
def set_read_write_concern(collection, write_concerns, read_concerns):
combined_write_concerns = dict(collection.write_concern.document.items())
if write_concerns is not None:
combined_write_concerns.update(write_concerns)
combined_read_concerns = dict(collection.read_concern.document.items())
if read_concerns is not None:
combined_read_concerns.update(read_concerns)
yield collection.with_options(
write_concern=WriteConcern(**combined_write_concerns),
read_concern=ReadConcern(**combined_read_concerns),
)

View File

@@ -1,6 +1,4 @@
from bson import DBRef, SON
import six
from six import iteritems
from mongoengine.base import (
BaseDict,
@@ -16,7 +14,7 @@ from mongoengine.fields import DictField, ListField, MapField, ReferenceField
from mongoengine.queryset import QuerySet
class DeReference(object):
class DeReference:
def __call__(self, items, max_depth=1, instance=None, name=None):
"""
Cheaply dereferences the items to a set depth.
@@ -30,7 +28,7 @@ class DeReference(object):
:class:`~mongoengine.base.ComplexBaseField`
:param get: A boolean determining if being called by __get__
"""
if items is None or isinstance(items, six.string_types):
if items is None or isinstance(items, str):
return items
# cheapest way to convert a queryset to a list
@@ -79,7 +77,7 @@ class DeReference(object):
def _get_items_from_dict(items):
new_items = {}
for k, v in iteritems(items):
for k, v in items.items():
value = v
if isinstance(v, list):
value = _get_items_from_list(v)
@@ -120,7 +118,7 @@ class DeReference(object):
depth += 1
for item in iterator:
if isinstance(item, (Document, EmbeddedDocument)):
for field_name, field in iteritems(item._fields):
for field_name, field in item._fields.items():
v = item._data.get(field_name, None)
if isinstance(v, LazyReference):
# LazyReference inherits DBRef but should not be dereferenced here !
@@ -136,7 +134,7 @@ class DeReference(object):
getattr(field, "field", None), "document_type", None
)
references = self._find_references(v, depth)
for key, refs in iteritems(references):
for key, refs in references.items():
if isinstance(
field_cls, (Document, TopLevelDocumentMetaclass)
):
@@ -153,7 +151,7 @@ class DeReference(object):
)
elif isinstance(item, (dict, list, tuple)) and depth - 1 <= self.max_depth:
references = self._find_references(item, depth - 1)
for key, refs in iteritems(references):
for key, refs in references.items():
reference_map.setdefault(key, set()).update(refs)
return reference_map
@@ -162,7 +160,7 @@ class DeReference(object):
"""Fetch all references and convert to their document objects
"""
object_map = {}
for collection, dbrefs in iteritems(self.reference_map):
for collection, dbrefs in self.reference_map.items():
# we use getattr instead of hasattr because hasattr swallows any exception under python2
# so it could hide nasty things without raising exceptions (cfr bug #1688))
@@ -174,7 +172,7 @@ class DeReference(object):
dbref for dbref in dbrefs if (col_name, dbref) not in object_map
]
references = collection.objects.in_bulk(refs)
for key, doc in iteritems(references):
for key, doc in references.items():
object_map[(col_name, key)] = doc
else: # Generic reference: use the refs data to convert to document
if isinstance(doc_type, (ListField, DictField, MapField)):
@@ -250,7 +248,7 @@ class DeReference(object):
data = []
else:
is_list = False
iterator = iteritems(items)
iterator = items.items()
data = {}
depth += 1
@@ -274,14 +272,12 @@ class DeReference(object):
(v["_ref"].collection, v["_ref"].id), v
)
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
item_name = six.text_type("{0}.{1}.{2}").format(
name, k, field_name
)
item_name = "{}.{}.{}".format(name, k, field_name)
data[k]._data[field_name] = self._attach_objects(
v, depth, instance=instance, name=item_name
)
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
item_name = "%s.%s" % (name, k) if name else name
item_name = "{}.{}".format(name, k) if name else name
data[k] = self._attach_objects(
v, depth - 1, instance=instance, name=item_name
)

View File

@@ -4,8 +4,6 @@ import warnings
from bson.dbref import DBRef
import pymongo
from pymongo.read_preferences import ReadPreference
import six
from six import iteritems
from mongoengine import signals
from mongoengine.base import (
@@ -44,7 +42,7 @@ def includes_cls(fields):
"""Helper function used for ensuring and comparing indexes."""
first_field = None
if len(fields):
if isinstance(fields[0], six.string_types):
if isinstance(fields[0], str):
first_field = fields[0]
elif isinstance(fields[0], (list, tuple)) and len(fields[0]):
first_field = fields[0][0]
@@ -55,8 +53,8 @@ class InvalidCollectionError(Exception):
pass
class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
"""A :class:`~mongoengine.Document` that isn't stored in its own
class EmbeddedDocument(BaseDocument, metaclass=DocumentMetaclass):
r"""A :class:`~mongoengine.Document` that isn't stored in its own
collection. :class:`~mongoengine.EmbeddedDocument`\ s should be used as
fields on :class:`~mongoengine.Document`\ s through the
:class:`~mongoengine.EmbeddedDocumentField` field type.
@@ -71,7 +69,6 @@ class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
__slots__ = ("_instance",)
# The __metaclass__ attribute is removed by 2to3 when running with Python3
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
my_metaclass = DocumentMetaclass
@@ -82,7 +79,7 @@ class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
__hash__ = None
def __init__(self, *args, **kwargs):
super(EmbeddedDocument, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self._instance = None
self._changed_fields = []
@@ -95,7 +92,7 @@ class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
return not self.__eq__(other)
def to_mongo(self, *args, **kwargs):
data = super(EmbeddedDocument, self).to_mongo(*args, **kwargs)
data = super().to_mongo(*args, **kwargs)
# remove _id from the SON if it's in it and it's None
if "_id" in data and data["_id"] is None:
@@ -104,7 +101,7 @@ class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
return data
class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
"""The base class used for defining the structure and properties of
collections of documents stored in MongoDB. Inherit from this class, and
add fields as class attributes to define a document's structure.
@@ -156,7 +153,6 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
in the :attr:`meta` dictionary.
"""
# The __metaclass__ attribute is removed by 2to3 when running with Python3
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
my_metaclass = TopLevelDocumentMetaclass
@@ -260,7 +256,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
return db.create_collection(collection_name, **opts)
def to_mongo(self, *args, **kwargs):
data = super(Document, self).to_mongo(*args, **kwargs)
data = super().to_mongo(*args, **kwargs)
# If '_id' is None, try and set it from self._data. If that
# doesn't exist either, remove '_id' from the SON completely.
@@ -332,7 +328,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
):
"""Save the :class:`~mongoengine.Document` to the database. If the
document already exists, it will be updated, otherwise it will be
created.
created. Returns the saved object instance.
:param force_insert: only try to create a new document, don't allow
updates of existing documents.
@@ -431,16 +427,16 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
self.cascade_save(**kwargs)
except pymongo.errors.DuplicateKeyError as err:
message = u"Tried to save duplicate unique keys (%s)"
raise NotUniqueError(message % six.text_type(err))
message = "Tried to save duplicate unique keys (%s)"
raise NotUniqueError(message % err)
except pymongo.errors.OperationFailure as err:
message = "Could not save document (%s)"
if re.match("^E1100[01] duplicate key", six.text_type(err)):
if re.match("^E1100[01] duplicate key", str(err)):
# E11000 - duplicate key error index
# E11001 - duplicate key on update
message = u"Tried to save duplicate unique keys (%s)"
raise NotUniqueError(message % six.text_type(err))
raise OperationError(message % six.text_type(err))
message = "Tried to save duplicate unique keys (%s)"
raise NotUniqueError(message % err)
raise OperationError(message % err)
# Make sure we store the PK on this document now that it's saved
id_field = self._meta["id_field"]
@@ -559,7 +555,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
if not getattr(ref, "_changed_fields", True):
continue
ref_id = "%s,%s" % (ref.__class__.__name__, str(ref._data))
ref_id = "{},{}".format(ref.__class__.__name__, str(ref._data))
if ref and ref_id not in _refs:
_refs.append(ref_id)
kwargs["_refs"] = _refs
@@ -634,7 +630,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
# Delete FileFields separately
FileField = _import_class("FileField")
for name, field in iteritems(self._fields):
for name, field in self._fields.items():
if isinstance(field, FileField):
getattr(self, name).delete()
@@ -643,7 +639,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
write_concern=write_concern, _from_doc_delete=True
)
except pymongo.errors.OperationFailure as err:
message = u"Could not delete document (%s)" % err.message
message = "Could not delete document (%s)" % err.message
raise OperationError(message)
signals.post_delete.send(self.__class__, document=self, **signal_kwargs)
@@ -851,17 +847,13 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
index_spec = cls._build_index_spec(keys)
index_spec = index_spec.copy()
fields = index_spec.pop("fields")
drop_dups = kwargs.get("drop_dups", False)
if drop_dups:
msg = "drop_dups is deprecated and is removed when using PyMongo 3+."
warnings.warn(msg, DeprecationWarning)
index_spec["background"] = background
index_spec.update(kwargs)
return cls._get_collection().create_index(fields, **index_spec)
@classmethod
def ensure_index(cls, key_or_list, drop_dups=False, background=False, **kwargs):
def ensure_index(cls, key_or_list, background=False, **kwargs):
"""Ensure that the given indexes are in place. Deprecated in favour
of create_index.
@@ -869,12 +861,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
construct a multi-field index); keys may be prefixed with a **+**
or a **-** to determine the index ordering
:param background: Allows index creation in the background
:param drop_dups: Was removed/ignored with MongoDB >2.7.5. The value
will be removed if PyMongo3+ is used
"""
if drop_dups:
msg = "drop_dups is deprecated and is removed when using PyMongo 3+."
warnings.warn(msg, DeprecationWarning)
return cls.create_index(key_or_list, background=background, **kwargs)
@classmethod
@@ -887,12 +874,8 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
`auto_create_index` to False in the documents meta data
"""
background = cls._meta.get("index_background", False)
drop_dups = cls._meta.get("index_drop_dups", False)
index_opts = cls._meta.get("index_opts") or {}
index_cls = cls._meta.get("index_cls", True)
if drop_dups:
msg = "drop_dups is deprecated and is removed when using PyMongo 3+."
warnings.warn(msg, DeprecationWarning)
collection = cls._get_collection()
# 746: when connection is via mongos, the read preference is not necessarily an indication that
@@ -992,10 +975,10 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
indexes.append(index)
# finish up by appending { '_id': 1 } and { '_cls': 1 }, if needed
if [(u"_id", 1)] not in indexes:
indexes.append([(u"_id", 1)])
if [("_id", 1)] not in indexes:
indexes.append([("_id", 1)])
if cls._meta.get("index_cls", True) and cls._meta.get("allow_inheritance"):
indexes.append([(u"_cls", 1)])
indexes.append([("_cls", 1)])
return indexes
@@ -1019,19 +1002,19 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
extra = [index for index in existing if index not in required]
# if { _cls: 1 } is missing, make sure it's *really* necessary
if [(u"_cls", 1)] in missing:
if [("_cls", 1)] in missing:
cls_obsolete = False
for index in existing:
if includes_cls(index) and index not in extra:
cls_obsolete = True
break
if cls_obsolete:
missing.remove([(u"_cls", 1)])
missing.remove([("_cls", 1)])
return {"missing": missing, "extra": extra}
class DynamicDocument(six.with_metaclass(TopLevelDocumentMetaclass, Document)):
class DynamicDocument(Document, metaclass=TopLevelDocumentMetaclass):
"""A Dynamic Document class allowing flexible, expandable and uncontrolled
schemas. As a :class:`~mongoengine.Document` subclass, acts in the same
way as an ordinary document but has expanded style properties. Any data
@@ -1045,7 +1028,6 @@ class DynamicDocument(six.with_metaclass(TopLevelDocumentMetaclass, Document)):
There is one caveat on Dynamic Documents: undeclared fields cannot start with `_`
"""
# The __metaclass__ attribute is removed by 2to3 when running with Python3
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
my_metaclass = TopLevelDocumentMetaclass
@@ -1060,16 +1042,15 @@ class DynamicDocument(six.with_metaclass(TopLevelDocumentMetaclass, Document)):
setattr(self, field_name, None)
self._dynamic_fields[field_name].null = False
else:
super(DynamicDocument, self).__delattr__(*args, **kwargs)
super().__delattr__(*args, **kwargs)
class DynamicEmbeddedDocument(six.with_metaclass(DocumentMetaclass, EmbeddedDocument)):
class DynamicEmbeddedDocument(EmbeddedDocument, metaclass=DocumentMetaclass):
"""A Dynamic Embedded Document class allowing flexible, expandable and
uncontrolled schemas. See :class:`~mongoengine.DynamicDocument` for more
information about dynamic documents.
"""
# The __metaclass__ attribute is removed by 2to3 when running with Python3
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
my_metaclass = DocumentMetaclass
@@ -1089,7 +1070,7 @@ class DynamicEmbeddedDocument(six.with_metaclass(DocumentMetaclass, EmbeddedDocu
setattr(self, field_name, None)
class MapReduceDocument(object):
class MapReduceDocument:
"""A document returned from a map/reduce query.
:param collection: An instance of :class:`~pymongo.Collection`

View File

@@ -1,7 +1,5 @@
from collections import defaultdict
import six
from six import iteritems
__all__ = (
"NotRegistered",
@@ -87,24 +85,24 @@ class ValidationError(AssertionError):
_message = None
def __init__(self, message="", **kwargs):
super(ValidationError, self).__init__(message)
super().__init__(message)
self.errors = kwargs.get("errors", {})
self.field_name = kwargs.get("field_name")
self.message = message
def __str__(self):
return six.text_type(self.message)
return str(self.message)
def __repr__(self):
return "%s(%s,)" % (self.__class__.__name__, self.message)
return "{}({},)".format(self.__class__.__name__, self.message)
def __getattribute__(self, name):
message = super(ValidationError, self).__getattribute__(name)
message = super().__getattribute__(name)
if name == "message":
if self.field_name:
message = "%s" % message
if self.errors:
message = "%s(%s)" % (message, self._format_errors())
message = "{}({})".format(message, self._format_errors())
return message
def _get_message(self):
@@ -126,12 +124,12 @@ class ValidationError(AssertionError):
def build_dict(source):
errors_dict = {}
if isinstance(source, dict):
for field_name, error in iteritems(source):
for field_name, error in source.items():
errors_dict[field_name] = build_dict(error)
elif isinstance(source, ValidationError) and source.errors:
return build_dict(source.errors)
else:
return six.text_type(source)
return str(source)
return errors_dict
@@ -147,15 +145,15 @@ class ValidationError(AssertionError):
if isinstance(value, list):
value = " ".join([generate_key(k) for k in value])
elif isinstance(value, dict):
value = " ".join([generate_key(v, k) for k, v in iteritems(value)])
value = " ".join([generate_key(v, k) for k, v in value.items()])
results = "%s.%s" % (prefix, value) if prefix else value
results = "{}.{}".format(prefix, value) if prefix else value
return results
error_dict = defaultdict(list)
for k, v in iteritems(self.to_dict()):
for k, v in self.to_dict().items():
error_dict[generate_key(v)].append(k)
return " ".join(["%s: %s" % (k, v) for k, v in iteritems(error_dict)])
return " ".join(["{}: {}".format(k, v) for k, v in error_dict.items()])
class DeprecatedError(Exception):

View File

@@ -5,14 +5,14 @@ import re
import socket
import time
import uuid
from io import BytesIO
from operator import itemgetter
from bson import Binary, DBRef, ObjectId, SON
from bson.int64 import Int64
import gridfs
import pymongo
from pymongo import ReturnDocument
import six
from six import iteritems
try:
import dateutil
@@ -21,11 +21,6 @@ except ImportError:
else:
import dateutil.parser
try:
from bson.int64 import Int64
except ImportError:
Int64 = long
from mongoengine.base import (
BaseDocument,
@@ -41,7 +36,7 @@ from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.document import Document, EmbeddedDocument
from mongoengine.errors import DoesNotExist, InvalidQueryError, ValidationError
from mongoengine.python_support import StringIO
from mongoengine.mongodb_support import MONGODB_36, get_mongodb_version
from mongoengine.queryset import DO_NOTHING
from mongoengine.queryset.base import BaseQuerySet
from mongoengine.queryset.transform import STRING_OPERATORS
@@ -52,11 +47,6 @@ except ImportError:
Image = None
ImageOps = None
if six.PY3:
# Useless as long as 2to3 gets executed
# as it turns `long` into `int` blindly
long = int
__all__ = (
"StringField",
@@ -113,10 +103,10 @@ class StringField(BaseField):
self.regex = re.compile(regex) if regex else None
self.max_length = max_length
self.min_length = min_length
super(StringField, self).__init__(**kwargs)
super().__init__(**kwargs)
def to_python(self, value):
if isinstance(value, six.text_type):
if isinstance(value, str):
return value
try:
value = value.decode("utf-8")
@@ -125,7 +115,7 @@ class StringField(BaseField):
return value
def validate(self, value):
if not isinstance(value, six.string_types):
if not isinstance(value, str):
self.error("StringField only accepts string values")
if self.max_length is not None and len(value) > self.max_length:
@@ -141,7 +131,7 @@ class StringField(BaseField):
return None
def prepare_query_value(self, op, value):
if not isinstance(op, six.string_types):
if not isinstance(op, str):
return value
if op in STRING_OPERATORS:
@@ -161,7 +151,7 @@ class StringField(BaseField):
# escape unsafe characters which could lead to a re.error
value = re.escape(value)
value = re.compile(regex % value, flags)
return super(StringField, self).prepare_query_value(op, value)
return super().prepare_query_value(op, value)
class URLField(StringField):
@@ -185,17 +175,17 @@ class URLField(StringField):
def __init__(self, url_regex=None, schemes=None, **kwargs):
self.url_regex = url_regex or self._URL_REGEX
self.schemes = schemes or self._URL_SCHEMES
super(URLField, self).__init__(**kwargs)
super().__init__(**kwargs)
def validate(self, value):
# Check first if the scheme is valid
scheme = value.split("://")[0].lower()
if scheme not in self.schemes:
self.error(u"Invalid scheme {} in URL: {}".format(scheme, value))
self.error("Invalid scheme {} in URL: {}".format(scheme, value))
# Then check full URL
if not self.url_regex.match(value):
self.error(u"Invalid URL: {}".format(value))
self.error("Invalid URL: {}".format(value))
class EmailField(StringField):
@@ -213,7 +203,7 @@ class EmailField(StringField):
)
UTF8_USER_REGEX = LazyRegexCompiler(
six.u(
(
# RFC 6531 Section 3.3 extends `atext` (used by dot-atom) to
# include `UTF8-non-ascii`.
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z\u0080-\U0010FFFF]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z\u0080-\U0010FFFF]+)*\Z"
@@ -228,7 +218,7 @@ class EmailField(StringField):
re.IGNORECASE,
)
error_msg = u"Invalid email address: %s"
error_msg = "Invalid email address: %s"
def __init__(
self,
@@ -252,7 +242,7 @@ class EmailField(StringField):
self.domain_whitelist = domain_whitelist or []
self.allow_utf8_user = allow_utf8_user
self.allow_ip_domain = allow_ip_domain
super(EmailField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def validate_user_part(self, user_part):
"""Validate the user part of the email address. Return True if
@@ -279,13 +269,13 @@ class EmailField(StringField):
try:
socket.inet_pton(addr_family, domain_part[1:-1])
return True
except (socket.error, UnicodeEncodeError):
except (OSError, UnicodeEncodeError):
pass
return False
def validate(self, value):
super(EmailField, self).validate(value)
super().validate(value)
if "@" not in value:
self.error(self.error_msg % value)
@@ -302,12 +292,16 @@ class EmailField(StringField):
domain_part = domain_part.encode("idna").decode("ascii")
except UnicodeError:
self.error(
"%s %s" % (self.error_msg % value, "(domain failed IDN encoding)")
"{} {}".format(
self.error_msg % value, "(domain failed IDN encoding)"
)
)
else:
if not self.validate_domain_part(domain_part):
self.error(
"%s %s" % (self.error_msg % value, "(domain validation failed)")
"{} {}".format(
self.error_msg % value, "(domain validation failed)"
)
)
@@ -316,7 +310,7 @@ class IntField(BaseField):
def __init__(self, min_value=None, max_value=None, **kwargs):
self.min_value, self.max_value = min_value, max_value
super(IntField, self).__init__(**kwargs)
super().__init__(**kwargs)
def to_python(self, value):
try:
@@ -341,19 +335,19 @@ class IntField(BaseField):
if value is None:
return value
return super(IntField, self).prepare_query_value(op, int(value))
return super().prepare_query_value(op, int(value))
class LongField(BaseField):
"""64-bit integer field."""
"""64-bit integer field. (Equivalent to IntField since the support to Python2 was dropped)"""
def __init__(self, min_value=None, max_value=None, **kwargs):
self.min_value, self.max_value = min_value, max_value
super(LongField, self).__init__(**kwargs)
super().__init__(**kwargs)
def to_python(self, value):
try:
value = long(value)
value = int(value)
except (TypeError, ValueError):
pass
return value
@@ -363,7 +357,7 @@ class LongField(BaseField):
def validate(self, value):
try:
value = long(value)
value = int(value)
except (TypeError, ValueError):
self.error("%s could not be converted to long" % value)
@@ -377,7 +371,7 @@ class LongField(BaseField):
if value is None:
return value
return super(LongField, self).prepare_query_value(op, long(value))
return super().prepare_query_value(op, int(value))
class FloatField(BaseField):
@@ -385,7 +379,7 @@ class FloatField(BaseField):
def __init__(self, min_value=None, max_value=None, **kwargs):
self.min_value, self.max_value = min_value, max_value
super(FloatField, self).__init__(**kwargs)
super().__init__(**kwargs)
def to_python(self, value):
try:
@@ -395,7 +389,7 @@ class FloatField(BaseField):
return value
def validate(self, value):
if isinstance(value, six.integer_types):
if isinstance(value, int):
try:
value = float(value)
except OverflowError:
@@ -414,7 +408,7 @@ class FloatField(BaseField):
if value is None:
return value
return super(FloatField, self).prepare_query_value(op, float(value))
return super().prepare_query_value(op, float(value))
class DecimalField(BaseField):
@@ -461,7 +455,7 @@ class DecimalField(BaseField):
self.precision = precision
self.rounding = rounding
super(DecimalField, self).__init__(**kwargs)
super().__init__(**kwargs)
def to_python(self, value):
if value is None:
@@ -480,13 +474,13 @@ class DecimalField(BaseField):
if value is None:
return value
if self.force_string:
return six.text_type(self.to_python(value))
return str(self.to_python(value))
return float(self.to_python(value))
def validate(self, value):
if not isinstance(value, decimal.Decimal):
if not isinstance(value, six.string_types):
value = six.text_type(value)
if not isinstance(value, str):
value = str(value)
try:
value = decimal.Decimal(value)
except (TypeError, ValueError, decimal.InvalidOperation) as exc:
@@ -499,7 +493,7 @@ class DecimalField(BaseField):
self.error("Decimal value is too large")
def prepare_query_value(self, op, value):
return super(DecimalField, self).prepare_query_value(op, self.to_mongo(value))
return super().prepare_query_value(op, self.to_mongo(value))
class BooleanField(BaseField):
@@ -539,7 +533,7 @@ class DateTimeField(BaseField):
def validate(self, value):
new_value = self.to_mongo(value)
if not isinstance(new_value, (datetime.datetime, datetime.date)):
self.error(u'cannot parse date "%s"' % value)
self.error('cannot parse date "%s"' % value)
def to_mongo(self, value):
if value is None:
@@ -551,7 +545,7 @@ class DateTimeField(BaseField):
if callable(value):
return value()
if not isinstance(value, six.string_types):
if not isinstance(value, str):
return None
return self._parse_datetime(value)
@@ -596,19 +590,19 @@ class DateTimeField(BaseField):
return None
def prepare_query_value(self, op, value):
return super(DateTimeField, self).prepare_query_value(op, self.to_mongo(value))
return super().prepare_query_value(op, self.to_mongo(value))
class DateField(DateTimeField):
def to_mongo(self, value):
value = super(DateField, self).to_mongo(value)
value = super().to_mongo(value)
# drop hours, minutes, seconds
if isinstance(value, datetime.datetime):
value = datetime.datetime(value.year, value.month, value.day)
return value
def to_python(self, value):
value = super(DateField, self).to_python(value)
value = super().to_python(value)
# convert datetime to date
if isinstance(value, datetime.datetime):
value = datetime.date(value.year, value.month, value.day)
@@ -642,7 +636,7 @@ class ComplexDateTimeField(StringField):
"""
self.separator = separator
self.format = separator.join(["%Y", "%m", "%d", "%H", "%M", "%S", "%f"])
super(ComplexDateTimeField, self).__init__(**kwargs)
super().__init__(**kwargs)
def _convert_from_datetime(self, val):
"""
@@ -673,17 +667,20 @@ class ComplexDateTimeField(StringField):
if instance is None:
return self
data = super(ComplexDateTimeField, self).__get__(instance, owner)
data = super().__get__(instance, owner)
if isinstance(data, datetime.datetime) or data is None:
return data
return self._convert_from_string(data)
def __set__(self, instance, value):
super(ComplexDateTimeField, self).__set__(instance, value)
super().__set__(instance, value)
value = instance._data[self.name]
if value is not None:
instance._data[self.name] = self._convert_from_datetime(value)
if isinstance(value, datetime.datetime):
instance._data[self.name] = self._convert_from_datetime(value)
else:
instance._data[self.name] = value
def validate(self, value):
value = self.to_python(value)
@@ -702,9 +699,7 @@ class ComplexDateTimeField(StringField):
return self._convert_from_datetime(value)
def prepare_query_value(self, op, value):
return super(ComplexDateTimeField, self).prepare_query_value(
op, self._convert_from_datetime(value)
)
return super().prepare_query_value(op, self._convert_from_datetime(value))
class EmbeddedDocumentField(BaseField):
@@ -715,7 +710,7 @@ class EmbeddedDocumentField(BaseField):
def __init__(self, document_type, **kwargs):
# XXX ValidationError raised outside of the "validate" method.
if not (
isinstance(document_type, six.string_types)
isinstance(document_type, str)
or issubclass(document_type, EmbeddedDocument)
):
self.error(
@@ -724,11 +719,11 @@ class EmbeddedDocumentField(BaseField):
)
self.document_type_obj = document_type
super(EmbeddedDocumentField, self).__init__(**kwargs)
super().__init__(**kwargs)
@property
def document_type(self):
if isinstance(self.document_type_obj, six.string_types):
if isinstance(self.document_type_obj, str):
if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT:
resolved_document_type = self.owner_document
else:
@@ -785,7 +780,7 @@ class EmbeddedDocumentField(BaseField):
"Querying the embedded document '%s' failed, due to an invalid query value"
% (self.document_type._class_name,)
)
super(EmbeddedDocumentField, self).prepare_query_value(op, value)
super().prepare_query_value(op, value)
return self.to_mongo(value)
@@ -801,9 +796,7 @@ class GenericEmbeddedDocumentField(BaseField):
"""
def prepare_query_value(self, op, value):
return super(GenericEmbeddedDocumentField, self).prepare_query_value(
op, self.to_mongo(value)
)
return super().prepare_query_value(op, self.to_mongo(value))
def to_python(self, value):
if isinstance(value, dict):
@@ -854,7 +847,7 @@ class DynamicField(BaseField):
"""Convert a Python type to a MongoDB compatible type.
"""
if isinstance(value, six.string_types):
if isinstance(value, str):
return value
if hasattr(value, "to_mongo"):
@@ -876,12 +869,12 @@ class DynamicField(BaseField):
value = {k: v for k, v in enumerate(value)}
data = {}
for k, v in iteritems(value):
for k, v in value.items():
data[k] = self.to_mongo(v, use_db_field, fields)
value = data
if is_list: # Convert back to a list
value = [v for k, v in sorted(iteritems(data), key=itemgetter(0))]
value = [v for k, v in sorted(data.items(), key=itemgetter(0))]
return value
def to_python(self, value):
@@ -891,15 +884,15 @@ class DynamicField(BaseField):
value = doc_cls._get_db().dereference(value["_ref"])
return doc_cls._from_son(value)
return super(DynamicField, self).to_python(value)
return super().to_python(value)
def lookup_member(self, member_name):
return member_name
def prepare_query_value(self, op, value):
if isinstance(value, six.string_types):
if isinstance(value, str):
return StringField().prepare_query_value(op, value)
return super(DynamicField, self).prepare_query_value(op, self.to_mongo(value))
return super().prepare_query_value(op, self.to_mongo(value))
def validate(self, value, clean=True):
if hasattr(value, "validate"):
@@ -920,7 +913,7 @@ class ListField(ComplexBaseField):
self.field = field
self.max_length = max_length
kwargs.setdefault("default", lambda: [])
super(ListField, self).__init__(**kwargs)
super().__init__(**kwargs)
def __get__(self, instance, owner):
if instance is None:
@@ -934,7 +927,7 @@ class ListField(ComplexBaseField):
and value
):
instance._data[self.name] = [self.field.build_lazyref(x) for x in value]
return super(ListField, self).__get__(instance, owner)
return super().__get__(instance, owner)
def validate(self, value):
"""Make sure that a list of valid fields is being used."""
@@ -948,7 +941,7 @@ class ListField(ComplexBaseField):
if self.max_length is not None and len(value) > self.max_length:
self.error("List is too long")
super(ListField, self).validate(value)
super().validate(value)
def prepare_query_value(self, op, value):
# Validate that the `set` operator doesn't contain more items than `max_length`.
@@ -962,14 +955,14 @@ class ListField(ComplexBaseField):
if (
op in ("set", "unset", None)
and hasattr(value, "__iter__")
and not isinstance(value, six.string_types)
and not isinstance(value, str)
and not isinstance(value, BaseDocument)
):
return [self.field.prepare_query_value(op, v) for v in value]
return self.field.prepare_query_value(op, value)
return super(ListField, self).prepare_query_value(op, value)
return super().prepare_query_value(op, value)
class EmbeddedDocumentListField(ListField):
@@ -990,9 +983,7 @@ class EmbeddedDocumentListField(ListField):
:param kwargs: Keyword arguments passed directly into the parent
:class:`~mongoengine.ListField`.
"""
super(EmbeddedDocumentListField, self).__init__(
field=EmbeddedDocumentField(document_type), **kwargs
)
super().__init__(field=EmbeddedDocumentField(document_type), **kwargs)
class SortedListField(ListField):
@@ -1018,10 +1009,10 @@ class SortedListField(ListField):
self._ordering = kwargs.pop("ordering")
if "reverse" in kwargs.keys():
self._order_reverse = kwargs.pop("reverse")
super(SortedListField, self).__init__(field, **kwargs)
super().__init__(field, **kwargs)
def to_mongo(self, value, use_db_field=True, fields=None):
value = super(SortedListField, self).to_mongo(value, use_db_field, fields)
value = super().to_mongo(value, use_db_field, fields)
if self._ordering is not None:
return sorted(
value, key=itemgetter(self._ordering), reverse=self._order_reverse
@@ -1034,9 +1025,7 @@ def key_not_string(d):
dictionary is not a string.
"""
for k, v in d.items():
if not isinstance(k, six.string_types) or (
isinstance(v, dict) and key_not_string(v)
):
if not isinstance(k, str) or (isinstance(v, dict) and key_not_string(v)):
return True
@@ -1051,6 +1040,15 @@ def key_has_dot_or_dollar(d):
return True
def key_starts_with_dollar(d):
"""Helper function to recursively determine if any key in a
dictionary starts with a dollar
"""
for k, v in d.items():
if (k.startswith("$")) or (isinstance(v, dict) and key_starts_with_dollar(v)):
return True
class DictField(ComplexBaseField):
"""A dictionary field that wraps a standard Python dictionary. This is
similar to an embedded document, but the structure is not defined.
@@ -1067,7 +1065,7 @@ class DictField(ComplexBaseField):
self._auto_dereference = False
kwargs.setdefault("default", lambda: {})
super(DictField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def validate(self, value):
"""Make sure that a list of valid fields is being used."""
@@ -1077,12 +1075,17 @@ class DictField(ComplexBaseField):
if key_not_string(value):
msg = "Invalid dictionary key - documents must have only string keys"
self.error(msg)
if key_has_dot_or_dollar(value):
# Following condition applies to MongoDB >= 3.6
# older Mongo has stricter constraints but
# it will be rejected upon insertion anyway
# Having a validation that depends on the MongoDB version
# is not straightforward as the field isn't aware of the connected Mongo
if key_starts_with_dollar(value):
self.error(
'Invalid dictionary key name - keys may not contain "."'
' or startswith "$" characters'
'Invalid dictionary key name - keys may not startswith "$" characters'
)
super(DictField, self).validate(value)
super().validate(value)
def lookup_member(self, member_name):
return DictField(db_field=member_name)
@@ -1099,7 +1102,7 @@ class DictField(ComplexBaseField):
"iexact",
]
if op in match_operators and isinstance(value, six.string_types):
if op in match_operators and isinstance(value, str):
return StringField().prepare_query_value(op, value)
if hasattr(
@@ -1111,7 +1114,7 @@ class DictField(ComplexBaseField):
}
return self.field.prepare_query_value(op, value)
return super(DictField, self).prepare_query_value(op, value)
return super().prepare_query_value(op, value)
class MapField(DictField):
@@ -1126,7 +1129,7 @@ class MapField(DictField):
# XXX ValidationError raised outside of the "validate" method.
if not isinstance(field, BaseField):
self.error("Argument to MapField constructor must be a valid field")
super(MapField, self).__init__(field=field, *args, **kwargs)
super().__init__(field=field, *args, **kwargs)
class ReferenceField(BaseField):
@@ -1186,7 +1189,7 @@ class ReferenceField(BaseField):
:class:`~pymongo.dbref.DBRef`, regardless of the value of `dbref`.
"""
# XXX ValidationError raised outside of the "validate" method.
if not isinstance(document_type, six.string_types) and not issubclass(
if not isinstance(document_type, str) and not issubclass(
document_type, Document
):
self.error(
@@ -1197,11 +1200,11 @@ class ReferenceField(BaseField):
self.dbref = dbref
self.document_type_obj = document_type
self.reverse_delete_rule = reverse_delete_rule
super(ReferenceField, self).__init__(**kwargs)
super().__init__(**kwargs)
@property
def document_type(self):
if isinstance(self.document_type_obj, six.string_types):
if isinstance(self.document_type_obj, str):
if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT:
self.document_type_obj = self.owner_document
else:
@@ -1230,7 +1233,7 @@ class ReferenceField(BaseField):
else:
instance._data[self.name] = cls._from_son(dereferenced)
return super(ReferenceField, self).__get__(instance, owner)
return super().__get__(instance, owner)
def to_mongo(self, document):
if isinstance(document, DBRef):
@@ -1281,7 +1284,7 @@ class ReferenceField(BaseField):
def prepare_query_value(self, op, value):
if value is None:
return None
super(ReferenceField, self).prepare_query_value(op, value)
super().prepare_query_value(op, value)
return self.to_mongo(value)
def validate(self, value):
@@ -1317,7 +1320,7 @@ class CachedReferenceField(BaseField):
fields = []
# XXX ValidationError raised outside of the "validate" method.
if not isinstance(document_type, six.string_types) and not issubclass(
if not isinstance(document_type, str) and not issubclass(
document_type, Document
):
self.error(
@@ -1328,7 +1331,7 @@ class CachedReferenceField(BaseField):
self.auto_sync = auto_sync
self.document_type_obj = document_type
self.fields = fields
super(CachedReferenceField, self).__init__(**kwargs)
super().__init__(**kwargs)
def start_listener(self):
from mongoengine import signals
@@ -1340,7 +1343,7 @@ class CachedReferenceField(BaseField):
return None
update_kwargs = {
"set__%s__%s" % (self.name, key): val
"set__{}__{}".format(self.name, key): val
for key, val in document._delta()[0].items()
if key in self.fields
}
@@ -1362,7 +1365,7 @@ class CachedReferenceField(BaseField):
@property
def document_type(self):
if isinstance(self.document_type_obj, six.string_types):
if isinstance(self.document_type_obj, str):
if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT:
self.document_type_obj = self.owner_document
else:
@@ -1386,7 +1389,7 @@ class CachedReferenceField(BaseField):
else:
instance._data[self.name] = self.document_type._from_son(dereferenced)
return super(CachedReferenceField, self).__get__(instance, owner)
return super().__get__(instance, owner)
def to_mongo(self, document, use_db_field=True, fields=None):
id_field_name = self.document_type._meta["id_field"]
@@ -1485,12 +1488,12 @@ class GenericReferenceField(BaseField):
def __init__(self, *args, **kwargs):
choices = kwargs.pop("choices", None)
super(GenericReferenceField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.choices = []
# Keep the choices as a list of allowed Document class names
if choices:
for choice in choices:
if isinstance(choice, six.string_types):
if isinstance(choice, str):
self.choices.append(choice)
elif isinstance(choice, type) and issubclass(choice, Document):
self.choices.append(choice._class_name)
@@ -1499,7 +1502,7 @@ class GenericReferenceField(BaseField):
# method.
self.error(
"Invalid choices provided: must be a list of"
"Document subclasses and/or six.string_typess"
"Document subclasses and/or str"
)
def _validate_choices(self, value):
@@ -1509,7 +1512,7 @@ class GenericReferenceField(BaseField):
value = value.get("_cls")
elif isinstance(value, Document):
value = value._class_name
super(GenericReferenceField, self)._validate_choices(value)
super()._validate_choices(value)
def __get__(self, instance, owner):
if instance is None:
@@ -1525,7 +1528,7 @@ class GenericReferenceField(BaseField):
else:
instance._data[self.name] = dereferenced
return super(GenericReferenceField, self).__get__(instance, owner)
return super().__get__(instance, owner)
def validate(self, value):
if not isinstance(value, (Document, DBRef, dict, SON)):
@@ -1589,22 +1592,22 @@ class BinaryField(BaseField):
def __init__(self, max_bytes=None, **kwargs):
self.max_bytes = max_bytes
super(BinaryField, self).__init__(**kwargs)
super().__init__(**kwargs)
def __set__(self, instance, value):
"""Handle bytearrays in python 3.1"""
if six.PY3 and isinstance(value, bytearray):
value = six.binary_type(value)
return super(BinaryField, self).__set__(instance, value)
if isinstance(value, bytearray):
value = bytes(value)
return super().__set__(instance, value)
def to_mongo(self, value):
return Binary(value)
def validate(self, value):
if not isinstance(value, (six.binary_type, Binary)):
if not isinstance(value, (bytes, Binary)):
self.error(
"BinaryField only accepts instances of "
"(%s, %s, Binary)" % (six.binary_type.__name__, Binary.__name__)
"(%s, %s, Binary)" % (bytes.__name__, Binary.__name__)
)
if self.max_bytes is not None and len(value) > self.max_bytes:
@@ -1613,14 +1616,14 @@ class BinaryField(BaseField):
def prepare_query_value(self, op, value):
if value is None:
return value
return super(BinaryField, self).prepare_query_value(op, self.to_mongo(value))
return super().prepare_query_value(op, self.to_mongo(value))
class GridFSError(Exception):
pass
class GridFSProxy(object):
class GridFSProxy:
"""Proxy object to handle writing and reading of files to and from GridFS
.. versionadded:: 0.4
@@ -1670,8 +1673,6 @@ class GridFSProxy(object):
def __bool__(self):
return bool(self.grid_id)
__nonzero__ = __bool__ # For Py2 support
def __getstate__(self):
self_dict = self.__dict__
self_dict["_fs"] = None
@@ -1686,12 +1687,12 @@ class GridFSProxy(object):
return self.__copy__()
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self.grid_id)
return "<{}: {}>".format(self.__class__.__name__, self.grid_id)
def __str__(self):
gridout = self.get()
filename = getattr(gridout, "filename") if gridout else "<no file>"
return "<%s: %s (%s)>" % (self.__class__.__name__, filename, self.grid_id)
return "<{}: {} ({})>".format(self.__class__.__name__, filename, self.grid_id)
def __eq__(self, other):
if isinstance(other, GridFSProxy):
@@ -1802,7 +1803,7 @@ class FileField(BaseField):
def __init__(
self, db_alias=DEFAULT_CONNECTION_NAME, collection_name="fs", **kwargs
):
super(FileField, self).__init__(**kwargs)
super().__init__(**kwargs)
self.collection_name = collection_name
self.db_alias = db_alias
@@ -1825,7 +1826,7 @@ class FileField(BaseField):
key = self.name
if (
hasattr(value, "read") and not isinstance(value, GridFSProxy)
) or isinstance(value, (six.binary_type, six.string_types)):
) or isinstance(value, (bytes, str)):
# using "FileField() = file/string" notation
grid_file = instance._data.get(self.name)
# If a file already exists, delete it
@@ -1943,11 +1944,11 @@ class ImageGridFsProxy(GridFSProxy):
w, h = img.size
io = StringIO()
io = BytesIO()
img.save(io, img_format, progressive=progressive)
io.seek(0)
return super(ImageGridFsProxy, self).put(
return super().put(
io, width=w, height=h, format=img_format, thumbnail_id=thumb_id, **kwargs
)
@@ -1957,12 +1958,12 @@ class ImageGridFsProxy(GridFSProxy):
if out and out.thumbnail_id:
self.fs.delete(out.thumbnail_id)
return super(ImageGridFsProxy, self).delete()
return super().delete()
def _put_thumbnail(self, thumbnail, format, progressive, **kwargs):
w, h = thumbnail.size
io = StringIO()
io = BytesIO()
thumbnail.save(io, format, progressive=progressive)
io.seek(0)
@@ -2032,16 +2033,11 @@ class ImageField(FileField):
for att_name, att in extra_args.items():
value = None
if isinstance(att, (tuple, list)):
if six.PY3:
value = dict(
itertools.zip_longest(params_size, att, fillvalue=None)
)
else:
value = dict(map(None, params_size, att))
value = dict(itertools.zip_longest(params_size, att, fillvalue=None))
setattr(self, att_name, value)
super(ImageField, self).__init__(collection_name=collection_name, **kwargs)
super().__init__(collection_name=collection_name, **kwargs)
class SequenceField(BaseField):
@@ -2093,14 +2089,14 @@ class SequenceField(BaseField):
self.value_decorator = (
value_decorator if callable(value_decorator) else self.VALUE_DECORATOR
)
super(SequenceField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def generate(self):
"""
Generate and Increment the counter
"""
sequence_name = self.get_sequence_name()
sequence_id = "%s.%s" % (sequence_name, self.name)
sequence_id = "{}.{}".format(sequence_name, self.name)
collection = get_db(alias=self.db_alias)[self.collection_name]
counter = collection.find_one_and_update(
@@ -2114,7 +2110,7 @@ class SequenceField(BaseField):
def set_next_value(self, value):
"""Helper method to set the next sequence value"""
sequence_name = self.get_sequence_name()
sequence_id = "%s.%s" % (sequence_name, self.name)
sequence_id = "{}.{}".format(sequence_name, self.name)
collection = get_db(alias=self.db_alias)[self.collection_name]
counter = collection.find_one_and_update(
filter={"_id": sequence_id},
@@ -2131,7 +2127,7 @@ class SequenceField(BaseField):
as it is only fixed on set.
"""
sequence_name = self.get_sequence_name()
sequence_id = "%s.%s" % (sequence_name, self.name)
sequence_id = "{}.{}".format(sequence_name, self.name)
collection = get_db(alias=self.db_alias)[self.collection_name]
data = collection.find_one({"_id": sequence_id})
@@ -2154,7 +2150,7 @@ class SequenceField(BaseField):
)
def __get__(self, instance, owner):
value = super(SequenceField, self).__get__(instance, owner)
value = super().__get__(instance, owner)
if value is None and instance._initialised:
value = self.generate()
instance._data[self.name] = value
@@ -2167,7 +2163,7 @@ class SequenceField(BaseField):
if value is None and instance._initialised:
value = self.generate()
return super(SequenceField, self).__set__(instance, value)
return super().__set__(instance, value)
def prepare_query_value(self, op, value):
"""
@@ -2201,14 +2197,14 @@ class UUIDField(BaseField):
.. versionchanged:: 0.6.19
"""
self._binary = binary
super(UUIDField, self).__init__(**kwargs)
super().__init__(**kwargs)
def to_python(self, value):
if not self._binary:
original_value = value
try:
if not isinstance(value, six.string_types):
value = six.text_type(value)
if not isinstance(value, str):
value = str(value)
return uuid.UUID(value)
except (ValueError, TypeError, AttributeError):
return original_value
@@ -2216,8 +2212,8 @@ class UUIDField(BaseField):
def to_mongo(self, value):
if not self._binary:
return six.text_type(value)
elif isinstance(value, six.string_types):
return str(value)
elif isinstance(value, str):
return uuid.UUID(value)
return value
@@ -2228,7 +2224,7 @@ class UUIDField(BaseField):
def validate(self, value):
if not isinstance(value, uuid.UUID):
if not isinstance(value, six.string_types):
if not isinstance(value, str):
value = str(value)
try:
uuid.UUID(value)
@@ -2427,7 +2423,7 @@ class LazyReferenceField(BaseField):
document. Note this only work getting field (not setting or deleting).
"""
# XXX ValidationError raised outside of the "validate" method.
if not isinstance(document_type, six.string_types) and not issubclass(
if not isinstance(document_type, str) and not issubclass(
document_type, Document
):
self.error(
@@ -2439,11 +2435,11 @@ class LazyReferenceField(BaseField):
self.passthrough = passthrough
self.document_type_obj = document_type
self.reverse_delete_rule = reverse_delete_rule
super(LazyReferenceField, self).__init__(**kwargs)
super().__init__(**kwargs)
@property
def document_type(self):
if isinstance(self.document_type_obj, six.string_types):
if isinstance(self.document_type_obj, str):
if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT:
self.document_type_obj = self.owner_document
else:
@@ -2482,7 +2478,7 @@ class LazyReferenceField(BaseField):
if value:
instance._data[self.name] = value
return super(LazyReferenceField, self).__get__(instance, owner)
return super().__get__(instance, owner)
def to_mongo(self, value):
if isinstance(value, LazyReference):
@@ -2502,6 +2498,13 @@ class LazyReferenceField(BaseField):
else:
return pk
def to_python(self, value):
"""Convert a MongoDB-compatible type to a Python type."""
if not isinstance(value, (DBRef, Document, EmbeddedDocument)):
collection = self.document_type._get_collection_name()
value = DBRef(collection, self.document_type.id.to_python(value))
return value
def validate(self, value):
if isinstance(value, LazyReference):
if value.collection != self.document_type._get_collection_name():
@@ -2539,7 +2542,7 @@ class LazyReferenceField(BaseField):
def prepare_query_value(self, op, value):
if value is None:
return None
super(LazyReferenceField, self).prepare_query_value(op, value)
super().prepare_query_value(op, value)
return self.to_mongo(value)
def lookup_member(self, member_name):
@@ -2566,12 +2569,12 @@ class GenericLazyReferenceField(GenericReferenceField):
def __init__(self, *args, **kwargs):
self.passthrough = kwargs.pop("passthrough", False)
super(GenericLazyReferenceField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def _validate_choices(self, value):
if isinstance(value, LazyReference):
value = value.document_type._class_name
super(GenericLazyReferenceField, self)._validate_choices(value)
super()._validate_choices(value)
def build_lazyref(self, value):
if isinstance(value, LazyReference):
@@ -2600,7 +2603,7 @@ class GenericLazyReferenceField(GenericReferenceField):
if value:
instance._data[self.name] = value
return super(GenericLazyReferenceField, self).__get__(instance, owner)
return super().__get__(instance, owner)
def validate(self, value):
if isinstance(value, LazyReference) and value.pk is None:
@@ -2608,7 +2611,7 @@ class GenericLazyReferenceField(GenericReferenceField):
"You can only reference documents once they have been"
" saved to the database"
)
return super(GenericLazyReferenceField, self).validate(value)
return super().validate(value)
def to_mongo(self, document):
if document is None:
@@ -2627,4 +2630,4 @@ class GenericLazyReferenceField(GenericReferenceField):
)
)
else:
return super(GenericLazyReferenceField, self).to_mongo(document)
return super().to_mongo(document)

View File

@@ -11,7 +11,7 @@ MONGODB_36 = (3, 6)
def get_mongodb_version():
"""Return the version of the connected mongoDB (first 2 digits)
"""Return the version of the default connected mongoDB (first 2 digits)
:return: tuple(int, int)
"""

View File

@@ -1,23 +0,0 @@
"""
Helper functions, constants, and types to aid with Python v2.7 - v3.x support
"""
import six
# six.BytesIO resolves to StringIO.StringIO in Py2 and io.BytesIO in Py3.
StringIO = six.BytesIO
# Additionally for Py2, try to use the faster cStringIO, if available
if not six.PY3:
try:
import cStringIO
except ImportError:
pass
else:
StringIO = cStringIO.StringIO
if six.PY3:
from collections.abc import Hashable
else:
# raises DeprecationWarnings in Python >=3.7
from collections import Hashable

View File

@@ -1,24 +1,27 @@
from __future__ import absolute_import
import copy
import itertools
import re
import warnings
from collections.abc import Mapping
from bson import SON, json_util
from bson.code import Code
import pymongo
import pymongo.errors
from pymongo.collection import ReturnDocument
from pymongo.common import validate_read_preference
import six
from six import iteritems
from pymongo.read_concern import ReadConcern
from mongoengine import signals
from mongoengine.base import get_document
from mongoengine.common import _import_class
from mongoengine.connection import get_db
from mongoengine.context_managers import set_write_concern, switch_db
from mongoengine.context_managers import (
set_read_write_concern,
set_write_concern,
switch_db,
)
from mongoengine.errors import (
BulkWriteError,
InvalidQueryError,
@@ -41,7 +44,7 @@ DENY = 3
PULL = 4
class BaseQuerySet(object):
class BaseQuerySet:
"""A set of results returned from a query. Wraps a MongoDB cursor,
providing :class:`~mongoengine.Document` objects as the results.
"""
@@ -60,8 +63,8 @@ class BaseQuerySet(object):
self._ordering = None
self._snapshot = False
self._timeout = True
self._slave_okay = False
self._read_preference = None
self._read_concern = None
self._iter = False
self._scalar = []
self._none = False
@@ -81,6 +84,7 @@ class BaseQuerySet(object):
self._limit = None
self._skip = None
self._hint = -1 # Using -1 as None is a valid value for hint
self._collation = None
self._batch_size = None
self.only_fields = []
self._max_time_ms = None
@@ -203,8 +207,6 @@ class BaseQuerySet(object):
"""Avoid to open all records in an if stmt in Py3."""
return self._has_data()
__nonzero__ = __bool__ # For Py2 support
# Core functions
def all(self):
@@ -255,20 +257,21 @@ class BaseQuerySet(object):
queryset = queryset.filter(*q_objs, **query)
try:
result = six.next(queryset)
result = next(queryset)
except StopIteration:
msg = "%s matching query does not exist." % queryset._document._class_name
raise queryset._document.DoesNotExist(msg)
try:
six.next(queryset)
# Check if there is another match
next(queryset)
except StopIteration:
return result
# If we were able to retrieve the 2nd doc, rewind the cursor and
# raise the MultipleObjectsReturned exception.
queryset.rewind()
message = u"%d items returned, instead of 1" % queryset.count()
raise queryset._document.MultipleObjectsReturned(message)
# If we were able to retrieve the 2nd doc, raise the MultipleObjectsReturned exception.
raise queryset._document.MultipleObjectsReturned(
"2 or more items returned, instead of 1"
)
def create(self, **kwargs):
"""Create new object. Returns the saved object instance.
@@ -301,7 +304,7 @@ class BaseQuerySet(object):
``insert(..., {w: 2, fsync: True})`` will wait until at least
two servers have recorded the write and will force an fsync on
each server being written to.
:parm signal_kwargs: (optional) kwargs dictionary to be passed to
:param signal_kwargs: (optional) kwargs dictionary to be passed to
the signal calls.
By default returns document instances, set ``load_bulk`` to False to
@@ -352,20 +355,20 @@ class BaseQuerySet(object):
)
except pymongo.errors.DuplicateKeyError as err:
message = "Could not save document (%s)"
raise NotUniqueError(message % six.text_type(err))
raise NotUniqueError(message % err)
except pymongo.errors.BulkWriteError as err:
# inserting documents that already have an _id field will
# give huge performance debt or raise
message = u"Bulk write error: (%s)"
raise BulkWriteError(message % six.text_type(err.details))
message = "Bulk write error: (%s)"
raise BulkWriteError(message % err.details)
except pymongo.errors.OperationFailure as err:
message = "Could not save document (%s)"
if re.match("^E1100[01] duplicate key", six.text_type(err)):
if re.match("^E1100[01] duplicate key", str(err)):
# E11000 - duplicate key error index
# E11001 - duplicate key on update
message = u"Tried to save duplicate unique keys (%s)"
raise NotUniqueError(message % six.text_type(err))
raise OperationError(message % six.text_type(err))
message = "Tried to save duplicate unique keys (%s)"
raise NotUniqueError(message % err)
raise OperationError(message % err)
# Apply inserted_ids to documents
for doc, doc_id in zip(docs, ids):
@@ -489,7 +492,13 @@ class BaseQuerySet(object):
return result.deleted_count
def update(
self, upsert=False, multi=True, write_concern=None, full_result=False, **update
self,
upsert=False,
multi=True,
write_concern=None,
read_concern=None,
full_result=False,
**update
):
"""Perform an atomic update on the fields matched by the query.
@@ -501,6 +510,7 @@ class BaseQuerySet(object):
``save(..., write_concern={w: 2, fsync: True}, ...)`` will
wait until at least two servers have recorded the write and
will force an fsync on the primary server.
:param read_concern: Override the read concern for the operation
:param full_result: Return the associated ``pymongo.UpdateResult`` rather than just the number
updated items
:param update: Django-style update keyword arguments
@@ -527,7 +537,9 @@ class BaseQuerySet(object):
else:
update["$set"] = {"_cls": queryset._document._class_name}
try:
with set_write_concern(queryset._collection, write_concern) as collection:
with set_read_write_concern(
queryset._collection, write_concern, read_concern
) as collection:
update_func = collection.update_one
if multi:
update_func = collection.update_many
@@ -537,14 +549,14 @@ class BaseQuerySet(object):
elif result.raw_result:
return result.raw_result["n"]
except pymongo.errors.DuplicateKeyError as err:
raise NotUniqueError(u"Update failed (%s)" % six.text_type(err))
raise NotUniqueError("Update failed (%s)" % err)
except pymongo.errors.OperationFailure as err:
if six.text_type(err) == u"multi not coded yet":
message = u"update() method requires MongoDB 1.1.3+"
if str(err) == "multi not coded yet":
message = "update() method requires MongoDB 1.1.3+"
raise OperationError(message)
raise OperationError(u"Update failed (%s)" % six.text_type(err))
raise OperationError("Update failed (%s)" % err)
def upsert_one(self, write_concern=None, **update):
def upsert_one(self, write_concern=None, read_concern=None, **update):
"""Overwrite or add the first document matched by the query.
:param write_concern: Extra keyword arguments are passed down which
@@ -553,6 +565,7 @@ class BaseQuerySet(object):
``save(..., write_concern={w: 2, fsync: True}, ...)`` will
wait until at least two servers have recorded the write and
will force an fsync on the primary server.
:param read_concern: Override the read concern for the operation
:param update: Django-style update keyword arguments
:returns the new or overwritten document
@@ -564,6 +577,7 @@ class BaseQuerySet(object):
multi=False,
upsert=True,
write_concern=write_concern,
read_concern=read_concern,
full_result=True,
**update
)
@@ -660,9 +674,9 @@ class BaseQuerySet(object):
**self._cursor_args
)
except pymongo.errors.DuplicateKeyError as err:
raise NotUniqueError(u"Update failed (%s)" % err)
raise NotUniqueError("Update failed (%s)" % err)
except pymongo.errors.OperationFailure as err:
raise OperationError(u"Update failed (%s)" % err)
raise OperationError("Update failed (%s)" % err)
if full_response:
if result["value"] is not None:
@@ -691,10 +705,10 @@ class BaseQuerySet(object):
return queryset.filter(pk=object_id).first()
def in_bulk(self, object_ids):
"""Retrieve a set of documents by their ids.
""""Retrieve a set of documents by their ids.
:param object_ids: a list or tuple of ``ObjectId``\ s
:rtype: dict of ObjectIds as keys and collection-specific
:param object_ids: a list or tuple of ObjectId's
:rtype: dict of ObjectId's as keys and collection-specific
Document subclasses as values.
.. versionadded:: 0.3
@@ -774,7 +788,6 @@ class BaseQuerySet(object):
"_ordering",
"_snapshot",
"_timeout",
"_slave_okay",
"_read_preference",
"_iter",
"_scalar",
@@ -782,6 +795,7 @@ class BaseQuerySet(object):
"_limit",
"_skip",
"_hint",
"_collation",
"_auto_dereference",
"_search_text",
"only_fields",
@@ -864,6 +878,32 @@ class BaseQuerySet(object):
return queryset
def collation(self, collation=None):
"""
Collation allows users to specify language-specific rules for string
comparison, such as rules for lettercase and accent marks.
:param collation: `~pymongo.collation.Collation` or dict with
following fields:
{
locale: str,
caseLevel: bool,
caseFirst: str,
strength: int,
numericOrdering: bool,
alternate: str,
maxVariable: str,
backwards: str
}
Collation should be added to indexes like in test example
"""
queryset = self.clone()
queryset._collation = collation
if queryset._cursor_obj:
queryset._cursor_obj.collation(collation)
return queryset
def batch_size(self, size):
"""Limit the number of documents returned in a single batch (each
batch requires a round trip to the server).
@@ -961,7 +1001,7 @@ class BaseQuerySet(object):
.. versionchanged:: 0.5 - Added subfield support
"""
fields = {f: QueryFieldList.ONLY for f in fields}
self.only_fields = fields.keys()
self.only_fields = list(fields.keys())
return self.fields(True, **fields)
def exclude(self, *fields):
@@ -998,9 +1038,11 @@ class BaseQuerySet(object):
posts = BlogPost.objects(...).fields(comments=0)
To retrieve a subrange of array elements:
To retrieve a subrange or sublist of array elements,
support exist for both the `slice` and `elemMatch` projection operator:
posts = BlogPost.objects(...).fields(slice__comments=5)
posts = BlogPost.objects(...).fields(elemMatch__comments="test")
:param kwargs: A set of keyword arguments identifying what to
include, exclude, or slice.
@@ -1009,7 +1051,7 @@ class BaseQuerySet(object):
"""
# Check for an operator and transform to mongo-style if there is
operators = ["slice"]
operators = ["slice", "elemMatch"]
cleaned_fields = []
for key, value in kwargs.items():
parts = key.split("__")
@@ -1112,7 +1154,7 @@ class BaseQuerySet(object):
def explain(self):
"""Return an explain plan record for the
:class:`~mongoengine.queryset.QuerySet`\ 's cursor.
:class:`~mongoengine.queryset.QuerySet` cursor.
"""
return self._cursor.explain()
@@ -1142,20 +1184,6 @@ class BaseQuerySet(object):
queryset._timeout = enabled
return queryset
# DEPRECATED. Has no more impact on PyMongo 3+
def slave_okay(self, enabled):
"""Enable or disable the slave_okay when querying.
:param enabled: whether or not the slave_okay is enabled
.. deprecated:: Ignored with PyMongo 3+
"""
msg = "slave_okay is deprecated as it has no impact when using PyMongo 3+."
warnings.warn(msg, DeprecationWarning)
queryset = self.clone()
queryset._slave_okay = enabled
return queryset
def read_preference(self, read_preference):
"""Change the read_preference when querying.
@@ -1165,9 +1193,23 @@ class BaseQuerySet(object):
validate_read_preference("read_preference", read_preference)
queryset = self.clone()
queryset._read_preference = read_preference
queryset._cursor_obj = (
None
) # we need to re-create the cursor object whenever we apply read_preference
queryset._cursor_obj = None # we need to re-create the cursor object whenever we apply read_preference
return queryset
def read_concern(self, read_concern):
"""Change the read_concern when querying.
:param read_concern: override ReplicaSetConnection-level
preference.
"""
if read_concern is not None and not isinstance(read_concern, Mapping):
raise TypeError("%r is not a valid read concern." % (read_concern,))
queryset = self.clone()
queryset._read_concern = (
ReadConcern(**read_concern) if read_concern is not None else None
)
queryset._cursor_obj = None # we need to re-create the cursor object whenever we apply read_concern
return queryset
def scalar(self, *fields):
@@ -1229,16 +1271,27 @@ class BaseQuerySet(object):
for data in son_data
]
def aggregate(self, *pipeline, **kwargs):
"""
Perform a aggregate function based in your queryset params
def aggregate(self, pipeline, *suppl_pipeline, **kwargs):
"""Perform a aggregate function based in your queryset params
:param pipeline: list of aggregation commands,\
see: http://docs.mongodb.org/manual/core/aggregation-pipeline/
:param suppl_pipeline: unpacked list of pipeline (added to support deprecation of the old interface)
parameter will be removed shortly
: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
.. versionadded:: 0.9
"""
initial_pipeline = []
using_deprecated_interface = isinstance(pipeline, dict) or bool(suppl_pipeline)
user_pipeline = [pipeline] if isinstance(pipeline, dict) else list(pipeline)
if using_deprecated_interface:
msg = "Calling .aggregate() with un unpacked list (*pipeline) is deprecated, it will soon change and will expect a list (similar to pymongo.Collection.aggregate interface), see documentation"
warnings.warn(msg, DeprecationWarning)
user_pipeline += suppl_pipeline
initial_pipeline = []
if self._query:
initial_pipeline.append({"$match": self._query})
@@ -1255,14 +1308,14 @@ class BaseQuerySet(object):
if self._skip is not None:
initial_pipeline.append({"$skip": self._skip})
pipeline = initial_pipeline + list(pipeline)
final_pipeline = initial_pipeline + user_pipeline
collection = self._collection
if self._read_preference is not None:
return self._collection.with_options(
collection = self._collection.with_options(
read_preference=self._read_preference
).aggregate(pipeline, cursor={}, **kwargs)
return self._collection.aggregate(pipeline, cursor={}, **kwargs)
)
return collection.aggregate(final_pipeline, cursor={}, **kwargs)
# JS functionality
def map_reduce(
@@ -1315,13 +1368,13 @@ class BaseQuerySet(object):
map_f_scope = {}
if isinstance(map_f, Code):
map_f_scope = map_f.scope
map_f = six.text_type(map_f)
map_f = str(map_f)
map_f = Code(queryset._sub_js_fields(map_f), map_f_scope)
reduce_f_scope = {}
if isinstance(reduce_f, Code):
reduce_f_scope = reduce_f.scope
reduce_f = six.text_type(reduce_f)
reduce_f = str(reduce_f)
reduce_f_code = queryset._sub_js_fields(reduce_f)
reduce_f = Code(reduce_f_code, reduce_f_scope)
@@ -1331,7 +1384,7 @@ class BaseQuerySet(object):
finalize_f_scope = {}
if isinstance(finalize_f, Code):
finalize_f_scope = finalize_f.scope
finalize_f = six.text_type(finalize_f)
finalize_f = str(finalize_f)
finalize_f_code = queryset._sub_js_fields(finalize_f)
finalize_f = Code(finalize_f_code, finalize_f_scope)
mr_args["finalize"] = finalize_f
@@ -1347,7 +1400,7 @@ class BaseQuerySet(object):
else:
map_reduce_function = "map_reduce"
if isinstance(output, six.string_types):
if isinstance(output, str):
mr_args["out"] = output
elif isinstance(output, dict):
@@ -1534,7 +1587,7 @@ class BaseQuerySet(object):
if self._limit == 0 or self._none:
raise StopIteration
raw_doc = six.next(self._cursor)
raw_doc = next(self._cursor)
if self._as_pymongo:
return raw_doc
@@ -1577,7 +1630,10 @@ class BaseQuerySet(object):
if self._snapshot:
msg = "The snapshot option is not anymore available with PyMongo 3+"
warnings.warn(msg, DeprecationWarning)
cursor_args = {"no_cursor_timeout": not self._timeout}
cursor_args = {}
if not self._timeout:
cursor_args["no_cursor_timeout"] = True
if self._loaded_fields:
cursor_args[fields_name] = self._loaded_fields.as_dict()
@@ -1602,12 +1658,13 @@ class BaseQuerySet(object):
# XXX In PyMongo 3+, we define the read preference on a collection
# level, not a cursor level. Thus, we need to get a cloned collection
# object using `with_options` first.
if self._read_preference is not None:
if self._read_preference is not None or self._read_concern is not None:
self._cursor_obj = self._collection.with_options(
read_preference=self._read_preference
read_preference=self._read_preference, read_concern=self._read_concern
).find(self._query, **self._cursor_args)
else:
self._cursor_obj = self._collection.find(self._query, **self._cursor_args)
# Apply "where" clauses to cursor
if self._where_clause:
where_clause = self._sub_js_fields(self._where_clause)
@@ -1637,6 +1694,9 @@ class BaseQuerySet(object):
if self._hint != -1:
self._cursor_obj.hint(self._hint)
if self._collation is not None:
self._cursor_obj.collation(self._collation)
if self._batch_size is not None:
self._cursor_obj.batch_size(self._batch_size)
@@ -1772,13 +1832,13 @@ class BaseQuerySet(object):
}
"""
total, data, types = self.exec_js(freq_func, field)
values = {types.get(k): int(v) for k, v in iteritems(data)}
values = {types.get(k): int(v) for k, v in data.items()}
if normalize:
values = {k: float(v) / total for k, v in values.items()}
frequencies = {}
for k, v in iteritems(values):
for k, v in values.items():
if isinstance(k, float):
if int(k) == k:
k = int(k)
@@ -1798,7 +1858,7 @@ class BaseQuerySet(object):
field_parts = field.split(".")
try:
field = ".".join(
f if isinstance(f, six.string_types) else f.db_field
f if isinstance(f, str) else f.db_field
for f in self._document._lookup_field(field_parts)
)
db_field_paths.append(field)
@@ -1810,7 +1870,7 @@ class BaseQuerySet(object):
for subdoc in subclasses:
try:
subfield = ".".join(
f if isinstance(f, six.string_types) else f.db_field
f if isinstance(f, str) else f.db_field
for f in subdoc._lookup_field(field_parts)
)
db_field_paths.append(subfield)
@@ -1884,7 +1944,7 @@ class BaseQuerySet(object):
field_name = match.group(1).split(".")
fields = self._document._lookup_field(field_name)
# Substitute the correct name for the field into the javascript
return u'["%s"]' % fields[-1].db_field
return '["%s"]' % fields[-1].db_field
def field_path_sub(match):
# Extract just the field name, and look up the field objects
@@ -1914,23 +1974,3 @@ class BaseQuerySet(object):
setattr(queryset, "_" + method_name, val)
return queryset
# Deprecated
def ensure_index(self, **kwargs):
"""Deprecated use :func:`Document.ensure_index`"""
msg = (
"Doc.objects()._ensure_index() is deprecated. "
"Use Doc.ensure_index() instead."
)
warnings.warn(msg, DeprecationWarning)
self._document.__class__.ensure_index(**kwargs)
return self
def _ensure_indexes(self):
"""Deprecated use :func:`~Document.ensure_indexes`"""
msg = (
"Doc.objects()._ensure_indexes() is deprecated. "
"Use Doc.ensure_indexes() instead."
)
warnings.warn(msg, DeprecationWarning)
self._document.__class__.ensure_indexes()

View File

@@ -1,7 +1,7 @@
__all__ = ("QueryFieldList",)
class QueryFieldList(object):
class QueryFieldList:
"""Object that handles combinations of .only() and .exclude() calls"""
ONLY = 1
@@ -69,8 +69,6 @@ class QueryFieldList(object):
def __bool__(self):
return bool(self.fields)
__nonzero__ = __bool__ # For Py2 support
def as_dict(self):
field_list = {field: self.value for field in self.fields}
if self.slice:
@@ -80,7 +78,7 @@ class QueryFieldList(object):
return field_list
def reset(self):
self.fields = set([])
self.fields = set()
self.slice = {}
self.value = self.ONLY

View File

@@ -4,7 +4,7 @@ from mongoengine.queryset.queryset import QuerySet
__all__ = ("queryset_manager", "QuerySetManager")
class QuerySetManager(object):
class QuerySetManager:
"""
The default QuerySet Manager.

View File

@@ -1,5 +1,3 @@
import six
from mongoengine.errors import OperationError
from mongoengine.queryset.base import (
BaseQuerySet,
@@ -127,8 +125,8 @@ class QuerySet(BaseQuerySet):
# Pull in ITER_CHUNK_SIZE docs from the database and store them in
# the result cache.
try:
for _ in six.moves.range(ITER_CHUNK_SIZE):
self._result_cache.append(six.next(self))
for _ in range(ITER_CHUNK_SIZE):
self._result_cache.append(next(self))
except StopIteration:
# Getting this exception means there are no more docs in the
# db cursor. Set _has_more to False so that we can use that
@@ -143,10 +141,10 @@ class QuerySet(BaseQuerySet):
getting the count
"""
if with_limit_and_skip is False:
return super(QuerySet, self).count(with_limit_and_skip)
return super().count(with_limit_and_skip)
if self._len is None:
self._len = super(QuerySet, self).count(with_limit_and_skip)
self._len = super().count(with_limit_and_skip)
return self._len
@@ -180,9 +178,9 @@ class QuerySetNoCache(BaseQuerySet):
return ".. queryset mid-iteration .."
data = []
for _ in six.moves.range(REPR_OUTPUT_SIZE + 1):
for _ in range(REPR_OUTPUT_SIZE + 1):
try:
data.append(six.next(self))
data.append(next(self))
except StopIteration:
break

View File

@@ -3,14 +3,12 @@ from collections import defaultdict
from bson import ObjectId, SON
from bson.dbref import DBRef
import pymongo
import six
from six import iteritems
from mongoengine.base import UPDATE_OPERATORS
from mongoengine.common import _import_class
from mongoengine.errors import InvalidQueryError
__all__ = ("query", "update")
__all__ = ("query", "update", "STRING_OPERATORS")
COMPARISON_OPERATORS = (
"ne",
@@ -101,7 +99,7 @@ def query(_doc_cls=None, **kwargs):
cleaned_fields = []
for field in fields:
append_field = True
if isinstance(field, six.string_types):
if isinstance(field, str):
parts.append(field)
append_field = False
# is last and CachedReferenceField
@@ -169,9 +167,9 @@ def query(_doc_cls=None, **kwargs):
key = ".".join(parts)
if op is None or key not in mongo_query:
if key not in mongo_query:
mongo_query[key] = value
elif key in mongo_query:
else:
if isinstance(mongo_query[key], dict) and isinstance(value, dict):
mongo_query[key].update(value)
# $max/minDistance needs to come last - convert to SON
@@ -180,7 +178,7 @@ def query(_doc_cls=None, **kwargs):
"$near" in value_dict or "$nearSphere" in value_dict
):
value_son = SON()
for k, v in iteritems(value_dict):
for k, v in value_dict.items():
if k == "$maxDistance" or k == "$minDistance":
continue
value_son[k] = v
@@ -281,7 +279,7 @@ def update(_doc_cls=None, **update):
appended_sub_field = False
for field in fields:
append_field = True
if isinstance(field, six.string_types):
if isinstance(field, str):
# Convert the S operator to $
if field == "S":
field = "$"
@@ -435,7 +433,9 @@ def _geo_operator(field, op, value):
value = {"$near": _infer_geometry(value)}
else:
raise NotImplementedError(
'Geo method "%s" has not been implemented for a %s ' % (op, field._name)
'Geo method "{}" has not been implemented for a {} '.format(
op, field._name
)
)
return value

View File

@@ -1,4 +1,5 @@
import copy
import warnings
from mongoengine.errors import InvalidQueryError
from mongoengine.queryset import transform
@@ -6,7 +7,7 @@ from mongoengine.queryset import transform
__all__ = ("Q", "QNode")
class QNodeVisitor(object):
class QNodeVisitor:
"""Base visitor class for visiting Q-object nodes in a query tree.
"""
@@ -78,7 +79,7 @@ class QueryCompilerVisitor(QNodeVisitor):
return transform.query(self.document, **query.query)
class QNode(object):
class QNode:
"""Base class for nodes in query trees."""
AND = 0
@@ -96,9 +97,11 @@ class QNode(object):
"""Combine this node with another node into a QCombination
object.
"""
# If the other Q() is empty, ignore it and just use `self`.
if getattr(other, "empty", True):
return self
# Or if this Q is empty, ignore it and just use `other`.
if self.empty:
return other
@@ -106,6 +109,8 @@ class QNode(object):
@property
def empty(self):
msg = "'empty' property is deprecated in favour of using 'not bool(filter)'"
warnings.warn(msg, DeprecationWarning)
return False
def __or__(self, other):
@@ -135,6 +140,9 @@ class QCombination(QNode):
op = " & " if self.operation is self.AND else " | "
return "(%s)" % op.join([repr(node) for node in self.children])
def __bool__(self):
return bool(self.children)
def accept(self, visitor):
for i in range(len(self.children)):
if isinstance(self.children[i], QNode):
@@ -144,8 +152,17 @@ class QCombination(QNode):
@property
def empty(self):
msg = "'empty' property is deprecated in favour of using 'not bool(filter)'"
warnings.warn(msg, DeprecationWarning)
return not bool(self.children)
def __eq__(self, other):
return (
self.__class__ == other.__class__
and self.operation == other.operation
and self.children == other.children
)
class Q(QNode):
"""A simple query object, used in a query tree to build up more complex
@@ -158,6 +175,12 @@ class Q(QNode):
def __repr__(self):
return "Q(**%s)" % repr(self.query)
def __bool__(self):
return bool(self.query)
def __eq__(self, other):
return self.__class__ == other.__class__ and self.query == other.query
def accept(self, visitor):
return visitor.visit_query(self)

View File

@@ -15,11 +15,11 @@ try:
signals_available = True
except ImportError:
class Namespace(object):
class Namespace:
def signal(self, name, doc=None):
return _FakeSignal(name, doc)
class _FakeSignal(object):
class _FakeSignal:
"""If blinker is unavailable, create a fake class with the same
interface that allows sending of signals but will fail with an
error on anything else. Instead of doing anything on send, it

View File

@@ -51,4 +51,4 @@ rm -rf $RPM_BUILD_ROOT
# %{python_sitearch}/*
%changelog
* See: http://docs.mongoengine.org/en/latest/changelog.html
* See: http://docs.mongoengine.org/en/latest/changelog.html

8
requirements-dev.txt Normal file
View File

@@ -0,0 +1,8 @@
black
flake8
flake8-import-order
pre-commit
pytest
ipdb
ipython
tox

View File

@@ -1,3 +0,0 @@
black
flake8
flake8-import-order

View File

@@ -1,5 +1,3 @@
nose
pymongo>=3.4
six==1.10.0
Sphinx==1.5.5
sphinx-rtd-theme==0.2.4

View File

@@ -1,11 +1,10 @@
[nosetests]
verbosity=2
detailed-errors=1
#tests=tests
cover-package=mongoengine
[flake8]
ignore=E501,F401,F403,F405,I201,I202,W504, W605, W503
exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests
max-complexity=47
application-import-names=mongoengine,tests
[tool:pytest]
# Limits the discovery to tests directory
# avoids that it runs for instance the benchmark
testpaths = tests

View File

@@ -1,6 +1,9 @@
import os
import sys
from pkg_resources import normalize_path
from setuptools import find_packages, setup
from setuptools.command.test import test as TestCommand
# Hack to silence atexit traceback in newer python versions
try:
@@ -24,6 +27,62 @@ def get_version(version_tuple):
return ".".join(map(str, version_tuple))
class PyTest(TestCommand):
"""Will force pytest to search for tests inside the build directory
for 2to3 converted code (used by tox), instead of the current directory.
Required as long as we need 2to3
Known Limitation: https://tox.readthedocs.io/en/latest/example/pytest.html#known-issues-and-limitations
Source: https://www.hackzine.org/python-testing-with-pytest-and-2to3-plus-tox-and-travis-ci.html
"""
# https://pytest.readthedocs.io/en/2.7.3/goodpractises.html#integration-with-setuptools-test-commands
# Allows to provide pytest command argument through the test runner command `python setup.py test`
# e.g: `python setup.py test -a "-k=test"`
# This only works for 1 argument though
user_options = [("pytest-args=", "a", "Arguments to pass to py.test")]
def initialize_options(self):
TestCommand.initialize_options(self)
self.pytest_args = ""
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = ["tests"]
self.test_suite = True
def run_tests(self):
# import here, cause outside the eggs aren't loaded
from pkg_resources import _namespace_packages
import pytest
# Purge modules under test from sys.modules. The test loader will
# re-import them from the build location. Required when 2to3 is used
# with namespace packages.
if sys.version_info >= (3,) and getattr(self.distribution, "use_2to3", False):
module = self.test_args[-1].split(".")[0]
if module in _namespace_packages:
del_modules = []
if module in sys.modules:
del_modules.append(module)
module += "."
for name in sys.modules:
if name.startswith(module):
del_modules.append(name)
map(sys.modules.__delitem__, del_modules)
# Run on the build directory for 2to3-built code
# This will prevent the old 2.x code from being found
# by py.test discovery mechanism, that apparently
# ignores sys.path..
ei_cmd = self.get_finalized_command("egg_info")
self.test_args = [normalize_path(ei_cmd.egg_base)]
cmd_args = self.test_args + ([self.pytest_args] if self.pytest_args else [])
errno = pytest.main(cmd_args)
sys.exit(errno)
# Dirty hack to get version number from monogengine/__init__.py - we can't
# import it as it depends on PyMongo and PyMongo isn't installed until this
# file is read
@@ -33,16 +92,16 @@ version_line = list(filter(lambda l: l.startswith("VERSION"), open(init)))[0]
VERSION = get_version(eval(version_line.split("=")[-1]))
CLASSIFIERS = [
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Database",
@@ -51,17 +110,20 @@ CLASSIFIERS = [
extra_opts = {
"packages": find_packages(exclude=["tests", "tests.*"]),
"tests_require": ["nose", "coverage==4.2", "blinker", "Pillow>=2.0.0"],
"tests_require": [
"pytest<5.0",
"pytest-cov",
"coverage<5.0", # recent coverage switched to sqlite format for the .coverage file which isn't handled properly by coveralls
"blinker",
"Pillow>=2.0.0, <7.0.0", # 7.0.0 dropped Python2 support
],
}
if sys.version_info[0] == 3:
extra_opts["use_2to3"] = True
if "test" in sys.argv or "nosetests" in sys.argv:
extra_opts["packages"] = find_packages()
extra_opts["package_data"] = {
"tests": ["fields/mongoengine.png", "fields/mongodb_leaf.png"]
}
else:
extra_opts["tests_require"] += ["python-dateutil"]
if "test" in sys.argv:
extra_opts["packages"] = find_packages()
extra_opts["package_data"] = {
"tests": ["fields/mongoengine.png", "fields/mongodb_leaf.png"]
}
setup(
name="mongoengine",
@@ -78,7 +140,8 @@ setup(
long_description=LONG_DESCRIPTION,
platforms=["any"],
classifiers=CLASSIFIERS,
install_requires=["pymongo>=3.4", "six"],
test_suite="nose.collector",
python_requires=">=3.5",
install_requires=["pymongo>=3.4, <4.0"],
cmdclass={"test": PyTest},
**extra_opts
)

View File

@@ -1,4 +0,0 @@
from .all_warnings import AllWarnings
from .document import *
from .queryset import *
from .fields import *

View File

@@ -1,40 +0,0 @@
"""
This test has been put into a module. This is because it tests warnings that
only get triggered on first hit. This way we can ensure its imported into the
top level and called first by the test suite.
"""
import unittest
import warnings
from mongoengine import *
__all__ = ("AllWarnings",)
class AllWarnings(unittest.TestCase):
def setUp(self):
connect(db="mongoenginetest")
self.warning_list = []
self.showwarning_default = warnings.showwarning
warnings.showwarning = self.append_to_warning_list
def append_to_warning_list(self, message, category, *args):
self.warning_list.append({"message": message, "category": category})
def tearDown(self):
# restore default handling of warnings
warnings.showwarning = self.showwarning_default
def test_document_collection_syntax_warning(self):
class NonAbstractBase(Document):
meta = {"allow_inheritance": True}
class InheritedDocumentFailTest(NonAbstractBase):
meta = {"collection": "fail"}
warning = self.warning_list[0]
self.assertEqual(SyntaxWarning, warning["category"])
self.assertEqual(
"non_abstract_base", InheritedDocumentFailTest._get_collection_name()
)

View File

@@ -0,0 +1,35 @@
"""
This test has been put into a module. This is because it tests warnings that
only get triggered on first hit. This way we can ensure its imported into the
top level and called first by the test suite.
"""
import unittest
import warnings
from mongoengine import *
class TestAllWarnings(unittest.TestCase):
def setUp(self):
connect(db="mongoenginetest")
self.warning_list = []
self.showwarning_default = warnings.showwarning
warnings.showwarning = self.append_to_warning_list
def append_to_warning_list(self, message, category, *args):
self.warning_list.append({"message": message, "category": category})
def tearDown(self):
# restore default handling of warnings
warnings.showwarning = self.showwarning_default
def test_document_collection_syntax_warning(self):
class NonAbstractBase(Document):
meta = {"allow_inheritance": True}
class InheritedDocumentFailTest(NonAbstractBase):
meta = {"collection": "fail"}
warning = self.warning_list[0]
assert SyntaxWarning == warning["category"]
assert "non_abstract_base" == InheritedDocumentFailTest._get_collection_name()

View File

@@ -1,13 +0,0 @@
import unittest
from .class_methods import *
from .delta import *
from .dynamic import *
from .indexes import *
from .inheritance import *
from .instance import *
from .json_serialisation import *
from .validation import *
if __name__ == "__main__":
unittest.main()

View File

@@ -2,15 +2,12 @@
import unittest
from mongoengine import *
from mongoengine.pymongo_support import list_collection_names
from mongoengine.queryset import NULLIFY, PULL
from mongoengine.connection import get_db
__all__ = ("ClassMethodsTest",)
from mongoengine.pymongo_support import list_collection_names
from mongoengine.queryset import NULLIFY, PULL
class ClassMethodsTest(unittest.TestCase):
class TestClassMethods(unittest.TestCase):
def setUp(self):
connect(db="mongoenginetest")
self.db = get_db()
@@ -32,43 +29,40 @@ class ClassMethodsTest(unittest.TestCase):
def test_definition(self):
"""Ensure that document may be defined using fields.
"""
self.assertEqual(
["_cls", "age", "id", "name"], sorted(self.Person._fields.keys())
)
self.assertEqual(
["IntField", "ObjectIdField", "StringField", "StringField"],
sorted([x.__class__.__name__ for x in self.Person._fields.values()]),
assert ["_cls", "age", "id", "name"] == sorted(self.Person._fields.keys())
assert ["IntField", "ObjectIdField", "StringField", "StringField"] == sorted(
[x.__class__.__name__ for x in self.Person._fields.values()]
)
def test_get_db(self):
"""Ensure that get_db returns the expected db.
"""
db = self.Person._get_db()
self.assertEqual(self.db, db)
assert self.db == db
def test_get_collection_name(self):
"""Ensure that get_collection_name returns the expected collection
name.
"""
collection_name = "person"
self.assertEqual(collection_name, self.Person._get_collection_name())
assert collection_name == self.Person._get_collection_name()
def test_get_collection(self):
"""Ensure that get_collection returns the expected collection.
"""
collection_name = "person"
collection = self.Person._get_collection()
self.assertEqual(self.db[collection_name], collection)
assert self.db[collection_name] == collection
def test_drop_collection(self):
"""Ensure that the collection may be dropped from the database.
"""
collection_name = "person"
self.Person(name="Test").save()
self.assertIn(collection_name, list_collection_names(self.db))
assert collection_name in list_collection_names(self.db)
self.Person.drop_collection()
self.assertNotIn(collection_name, list_collection_names(self.db))
assert collection_name not in list_collection_names(self.db)
def test_register_delete_rule(self):
"""Ensure that register delete rule adds a delete rule to the document
@@ -78,12 +72,10 @@ class ClassMethodsTest(unittest.TestCase):
class Job(Document):
employee = ReferenceField(self.Person)
self.assertEqual(self.Person._meta.get("delete_rules"), None)
assert self.Person._meta.get("delete_rules") is None
self.Person.register_delete_rule(Job, "employee", NULLIFY)
self.assertEqual(
self.Person._meta["delete_rules"], {(Job, "employee"): NULLIFY}
)
assert self.Person._meta["delete_rules"] == {(Job, "employee"): NULLIFY}
def test_compare_indexes(self):
""" Ensure that the indexes are properly created and that
@@ -101,22 +93,22 @@ class ClassMethodsTest(unittest.TestCase):
BlogPost.drop_collection()
BlogPost.ensure_indexes()
self.assertEqual(BlogPost.compare_indexes(), {"missing": [], "extra": []})
assert BlogPost.compare_indexes() == {"missing": [], "extra": []}
BlogPost.ensure_index(["author", "description"])
self.assertEqual(
BlogPost.compare_indexes(),
{"missing": [], "extra": [[("author", 1), ("description", 1)]]},
)
assert BlogPost.compare_indexes() == {
"missing": [],
"extra": [[("author", 1), ("description", 1)]],
}
BlogPost._get_collection().drop_index("author_1_description_1")
self.assertEqual(BlogPost.compare_indexes(), {"missing": [], "extra": []})
assert BlogPost.compare_indexes() == {"missing": [], "extra": []}
BlogPost._get_collection().drop_index("author_1_title_1")
self.assertEqual(
BlogPost.compare_indexes(),
{"missing": [[("author", 1), ("title", 1)]], "extra": []},
)
assert BlogPost.compare_indexes() == {
"missing": [[("author", 1), ("title", 1)]],
"extra": [],
}
def test_compare_indexes_inheritance(self):
""" Ensure that the indexes are properly created and that
@@ -141,22 +133,22 @@ class ClassMethodsTest(unittest.TestCase):
BlogPost.ensure_indexes()
BlogPostWithTags.ensure_indexes()
self.assertEqual(BlogPost.compare_indexes(), {"missing": [], "extra": []})
assert BlogPost.compare_indexes() == {"missing": [], "extra": []}
BlogPostWithTags.ensure_index(["author", "tag_list"])
self.assertEqual(
BlogPost.compare_indexes(),
{"missing": [], "extra": [[("_cls", 1), ("author", 1), ("tag_list", 1)]]},
)
assert BlogPost.compare_indexes() == {
"missing": [],
"extra": [[("_cls", 1), ("author", 1), ("tag_list", 1)]],
}
BlogPostWithTags._get_collection().drop_index("_cls_1_author_1_tag_list_1")
self.assertEqual(BlogPost.compare_indexes(), {"missing": [], "extra": []})
assert BlogPost.compare_indexes() == {"missing": [], "extra": []}
BlogPostWithTags._get_collection().drop_index("_cls_1_author_1_tags_1")
self.assertEqual(
BlogPost.compare_indexes(),
{"missing": [[("_cls", 1), ("author", 1), ("tags", 1)]], "extra": []},
)
assert BlogPost.compare_indexes() == {
"missing": [[("_cls", 1), ("author", 1), ("tags", 1)]],
"extra": [],
}
def test_compare_indexes_multiple_subclasses(self):
""" Ensure that compare_indexes behaves correctly if called from a
@@ -185,13 +177,9 @@ class ClassMethodsTest(unittest.TestCase):
BlogPostWithTags.ensure_indexes()
BlogPostWithCustomField.ensure_indexes()
self.assertEqual(BlogPost.compare_indexes(), {"missing": [], "extra": []})
self.assertEqual(
BlogPostWithTags.compare_indexes(), {"missing": [], "extra": []}
)
self.assertEqual(
BlogPostWithCustomField.compare_indexes(), {"missing": [], "extra": []}
)
assert BlogPost.compare_indexes() == {"missing": [], "extra": []}
assert BlogPostWithTags.compare_indexes() == {"missing": [], "extra": []}
assert BlogPostWithCustomField.compare_indexes() == {"missing": [], "extra": []}
def test_compare_indexes_for_text_indexes(self):
""" Ensure that compare_indexes behaves correctly for text indexes """
@@ -213,7 +201,7 @@ class ClassMethodsTest(unittest.TestCase):
Doc.ensure_indexes()
actual = Doc.compare_indexes()
expected = {"missing": [], "extra": []}
self.assertEqual(actual, expected)
assert actual == expected
def test_list_indexes_inheritance(self):
""" ensure that all of the indexes are listed regardless of the super-
@@ -243,19 +231,14 @@ class ClassMethodsTest(unittest.TestCase):
BlogPostWithTags.ensure_indexes()
BlogPostWithTagsAndExtraText.ensure_indexes()
self.assertEqual(BlogPost.list_indexes(), BlogPostWithTags.list_indexes())
self.assertEqual(
BlogPost.list_indexes(), BlogPostWithTagsAndExtraText.list_indexes()
)
self.assertEqual(
BlogPost.list_indexes(),
[
[("_cls", 1), ("author", 1), ("tags", 1)],
[("_cls", 1), ("author", 1), ("tags", 1), ("extra_text", 1)],
[(u"_id", 1)],
[("_cls", 1)],
],
)
assert BlogPost.list_indexes() == BlogPostWithTags.list_indexes()
assert BlogPost.list_indexes() == BlogPostWithTagsAndExtraText.list_indexes()
assert BlogPost.list_indexes() == [
[("_cls", 1), ("author", 1), ("tags", 1)],
[("_cls", 1), ("author", 1), ("tags", 1), ("extra_text", 1)],
[(u"_id", 1)],
[("_cls", 1)],
]
def test_register_delete_rule_inherited(self):
class Vaccine(Document):
@@ -274,8 +257,8 @@ class ClassMethodsTest(unittest.TestCase):
class Cat(Animal):
name = StringField(required=True)
self.assertEqual(Vaccine._meta["delete_rules"][(Animal, "vaccine_made")], PULL)
self.assertEqual(Vaccine._meta["delete_rules"][(Cat, "vaccine_made")], PULL)
assert Vaccine._meta["delete_rules"][(Animal, "vaccine_made")] == PULL
assert Vaccine._meta["delete_rules"][(Cat, "vaccine_made")] == PULL
def test_collection_naming(self):
"""Ensure that a collection with a specified name may be used.
@@ -284,19 +267,17 @@ class ClassMethodsTest(unittest.TestCase):
class DefaultNamingTest(Document):
pass
self.assertEqual(
"default_naming_test", DefaultNamingTest._get_collection_name()
)
assert "default_naming_test" == DefaultNamingTest._get_collection_name()
class CustomNamingTest(Document):
meta = {"collection": "pimp_my_collection"}
self.assertEqual("pimp_my_collection", CustomNamingTest._get_collection_name())
assert "pimp_my_collection" == CustomNamingTest._get_collection_name()
class DynamicNamingTest(Document):
meta = {"collection": lambda c: "DYNAMO"}
self.assertEqual("DYNAMO", DynamicNamingTest._get_collection_name())
assert "DYNAMO" == DynamicNamingTest._get_collection_name()
# Use Abstract class to handle backwards compatibility
class BaseDocument(Document):
@@ -305,14 +286,12 @@ class ClassMethodsTest(unittest.TestCase):
class OldNamingConvention(BaseDocument):
pass
self.assertEqual(
"oldnamingconvention", OldNamingConvention._get_collection_name()
)
assert "oldnamingconvention" == OldNamingConvention._get_collection_name()
class InheritedAbstractNamingTest(BaseDocument):
meta = {"collection": "wibble"}
self.assertEqual("wibble", InheritedAbstractNamingTest._get_collection_name())
assert "wibble" == InheritedAbstractNamingTest._get_collection_name()
# Mixin tests
class BaseMixin(object):
@@ -321,8 +300,9 @@ class ClassMethodsTest(unittest.TestCase):
class OldMixinNamingConvention(Document, BaseMixin):
pass
self.assertEqual(
"oldmixinnamingconvention", OldMixinNamingConvention._get_collection_name()
assert (
"oldmixinnamingconvention"
== OldMixinNamingConvention._get_collection_name()
)
class BaseMixin(object):
@@ -334,7 +314,7 @@ class ClassMethodsTest(unittest.TestCase):
class MyDocument(BaseDocument):
pass
self.assertEqual("basedocument", MyDocument._get_collection_name())
assert "basedocument" == MyDocument._get_collection_name()
def test_custom_collection_name_operations(self):
"""Ensure that a collection with a specified name is used as expected.
@@ -346,16 +326,16 @@ class ClassMethodsTest(unittest.TestCase):
meta = {"collection": collection_name}
Person(name="Test User").save()
self.assertIn(collection_name, list_collection_names(self.db))
assert collection_name in list_collection_names(self.db)
user_obj = self.db[collection_name].find_one()
self.assertEqual(user_obj["name"], "Test User")
assert user_obj["name"] == "Test User"
user_obj = Person.objects[0]
self.assertEqual(user_obj.name, "Test User")
assert user_obj.name == "Test User"
Person.drop_collection()
self.assertNotIn(collection_name, list_collection_names(self.db))
assert collection_name not in list_collection_names(self.db)
def test_collection_name_and_primary(self):
"""Ensure that a collection with a specified name may be used.
@@ -368,7 +348,7 @@ class ClassMethodsTest(unittest.TestCase):
Person(name="Test User").save()
user_obj = Person.objects.first()
self.assertEqual(user_obj.name, "Test User")
assert user_obj.name == "Test User"
Person.drop_collection()

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
import unittest
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase
@@ -25,15 +27,15 @@ class TestDynamicDocument(MongoDBTestCase):
p.name = "James"
p.age = 34
self.assertEqual(p.to_mongo(), {"_cls": "Person", "name": "James", "age": 34})
self.assertEqual(p.to_mongo().keys(), ["_cls", "name", "age"])
assert p.to_mongo() == {"_cls": "Person", "name": "James", "age": 34}
assert p.to_mongo().keys() == ["_cls", "name", "age"]
p.save()
self.assertEqual(p.to_mongo().keys(), ["_id", "_cls", "name", "age"])
assert p.to_mongo().keys() == ["_id", "_cls", "name", "age"]
self.assertEqual(self.Person.objects.first().age, 34)
assert self.Person.objects.first().age == 34
# Confirm no changes to self.Person
self.assertFalse(hasattr(self.Person, "age"))
assert not hasattr(self.Person, "age")
def test_change_scope_of_variable(self):
"""Test changing the scope of a dynamic field has no adverse effects"""
@@ -47,7 +49,7 @@ class TestDynamicDocument(MongoDBTestCase):
p.save()
p = self.Person.objects.get()
self.assertEqual(p.misc, {"hello": "world"})
assert p.misc == {"hello": "world"}
def test_delete_dynamic_field(self):
"""Test deleting a dynamic field works"""
@@ -62,19 +64,19 @@ class TestDynamicDocument(MongoDBTestCase):
p.save()
p = self.Person.objects.get()
self.assertEqual(p.misc, {"hello": "world"})
assert p.misc == {"hello": "world"}
collection = self.db[self.Person._get_collection_name()]
obj = collection.find_one()
self.assertEqual(sorted(obj.keys()), ["_cls", "_id", "misc", "name"])
assert sorted(obj.keys()) == ["_cls", "_id", "misc", "name"]
del p.misc
p.save()
p = self.Person.objects.get()
self.assertFalse(hasattr(p, "misc"))
assert not hasattr(p, "misc")
obj = collection.find_one()
self.assertEqual(sorted(obj.keys()), ["_cls", "_id", "name"])
assert sorted(obj.keys()) == ["_cls", "_id", "name"]
def test_reload_after_unsetting(self):
p = self.Person()
@@ -88,12 +90,12 @@ class TestDynamicDocument(MongoDBTestCase):
p = self.Person.objects.create()
p.update(age=1)
self.assertEqual(len(p._data), 3)
self.assertEqual(sorted(p._data.keys()), ["_cls", "id", "name"])
assert len(p._data) == 3
assert sorted(p._data.keys()) == ["_cls", "id", "name"]
p.reload()
self.assertEqual(len(p._data), 4)
self.assertEqual(sorted(p._data.keys()), ["_cls", "age", "id", "name"])
assert len(p._data) == 4
assert sorted(p._data.keys()) == ["_cls", "age", "id", "name"]
def test_fields_without_underscore(self):
"""Ensure we can query dynamic fields"""
@@ -103,16 +105,18 @@ class TestDynamicDocument(MongoDBTestCase):
p.save()
raw_p = Person.objects.as_pymongo().get(id=p.id)
self.assertEqual(raw_p, {"_cls": u"Person", "_id": p.id, "name": u"Dean"})
assert raw_p == {"_cls": u"Person", "_id": p.id, "name": u"Dean"}
p.name = "OldDean"
p.newattr = "garbage"
p.save()
raw_p = Person.objects.as_pymongo().get(id=p.id)
self.assertEqual(
raw_p,
{"_cls": u"Person", "_id": p.id, "name": "OldDean", "newattr": u"garbage"},
)
assert raw_p == {
"_cls": u"Person",
"_id": p.id,
"name": "OldDean",
"newattr": u"garbage",
}
def test_fields_containing_underscore(self):
"""Ensure we can query dynamic fields"""
@@ -127,14 +131,14 @@ class TestDynamicDocument(MongoDBTestCase):
p.save()
raw_p = WeirdPerson.objects.as_pymongo().get(id=p.id)
self.assertEqual(raw_p, {"_id": p.id, "_name": u"Dean", "name": u"Dean"})
assert raw_p == {"_id": p.id, "_name": u"Dean", "name": u"Dean"}
p.name = "OldDean"
p._name = "NewDean"
p._newattr1 = "garbage" # Unknown fields won't be added
p.save()
raw_p = WeirdPerson.objects.as_pymongo().get(id=p.id)
self.assertEqual(raw_p, {"_id": p.id, "_name": u"NewDean", "name": u"OldDean"})
assert raw_p == {"_id": p.id, "_name": u"NewDean", "name": u"OldDean"}
def test_dynamic_document_queries(self):
"""Ensure we can query dynamic fields"""
@@ -143,10 +147,10 @@ class TestDynamicDocument(MongoDBTestCase):
p.age = 22
p.save()
self.assertEqual(1, self.Person.objects(age=22).count())
assert 1 == self.Person.objects(age=22).count()
p = self.Person.objects(age=22)
p = p.get()
self.assertEqual(22, p.age)
assert 22 == p.age
def test_complex_dynamic_document_queries(self):
class Person(DynamicDocument):
@@ -166,8 +170,8 @@ class TestDynamicDocument(MongoDBTestCase):
p2.age = 10
p2.save()
self.assertEqual(Person.objects(age__icontains="ten").count(), 2)
self.assertEqual(Person.objects(age__gte=10).count(), 1)
assert Person.objects(age__icontains="ten").count() == 2
assert Person.objects(age__gte=10).count() == 1
def test_complex_data_lookups(self):
"""Ensure you can query dynamic document dynamic fields"""
@@ -175,12 +179,12 @@ class TestDynamicDocument(MongoDBTestCase):
p.misc = {"hello": "world"}
p.save()
self.assertEqual(1, self.Person.objects(misc__hello="world").count())
assert 1 == self.Person.objects(misc__hello="world").count()
def test_three_level_complex_data_lookups(self):
"""Ensure you can query three level document dynamic fields"""
p = self.Person.objects.create(misc={"hello": {"hello2": "world"}})
self.assertEqual(1, self.Person.objects(misc__hello__hello2="world").count())
self.Person.objects.create(misc={"hello": {"hello2": "world"}})
assert 1 == self.Person.objects(misc__hello__hello2="world").count()
def test_complex_embedded_document_validation(self):
"""Ensure embedded dynamic documents may be validated"""
@@ -198,11 +202,13 @@ class TestDynamicDocument(MongoDBTestCase):
embedded_doc_1.validate()
embedded_doc_2 = Embedded(content="this is not a url")
self.assertRaises(ValidationError, embedded_doc_2.validate)
with pytest.raises(ValidationError):
embedded_doc_2.validate()
doc.embedded_field_1 = embedded_doc_1
doc.embedded_field_2 = embedded_doc_2
self.assertRaises(ValidationError, doc.validate)
with pytest.raises(ValidationError):
doc.validate()
def test_inheritance(self):
"""Ensure that dynamic document plays nice with inheritance"""
@@ -212,11 +218,9 @@ class TestDynamicDocument(MongoDBTestCase):
Employee.drop_collection()
self.assertIn("name", Employee._fields)
self.assertIn("salary", Employee._fields)
self.assertEqual(
Employee._get_collection_name(), self.Person._get_collection_name()
)
assert "name" in Employee._fields
assert "salary" in Employee._fields
assert Employee._get_collection_name() == self.Person._get_collection_name()
joe_bloggs = Employee()
joe_bloggs.name = "Joe Bloggs"
@@ -224,11 +228,11 @@ class TestDynamicDocument(MongoDBTestCase):
joe_bloggs.age = 20
joe_bloggs.save()
self.assertEqual(1, self.Person.objects(age=20).count())
self.assertEqual(1, Employee.objects(age=20).count())
assert 1 == self.Person.objects(age=20).count()
assert 1 == Employee.objects(age=20).count()
joe_bloggs = self.Person.objects.first()
self.assertIsInstance(joe_bloggs, Employee)
assert isinstance(joe_bloggs, Employee)
def test_embedded_dynamic_document(self):
"""Test dynamic embedded documents"""
@@ -249,26 +253,23 @@ class TestDynamicDocument(MongoDBTestCase):
embedded_1.list_field = ["1", 2, {"hello": "world"}]
doc.embedded_field = embedded_1
self.assertEqual(
doc.to_mongo(),
{
"embedded_field": {
"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ["1", 2, {"hello": "world"}],
}
},
)
assert doc.to_mongo() == {
"embedded_field": {
"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ["1", 2, {"hello": "world"}],
}
}
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc.embedded_field.__class__, Embedded)
self.assertEqual(doc.embedded_field.string_field, "hello")
self.assertEqual(doc.embedded_field.int_field, 1)
self.assertEqual(doc.embedded_field.dict_field, {"hello": "world"})
self.assertEqual(doc.embedded_field.list_field, ["1", 2, {"hello": "world"}])
assert doc.embedded_field.__class__ == Embedded
assert doc.embedded_field.string_field == "hello"
assert doc.embedded_field.int_field == 1
assert doc.embedded_field.dict_field == {"hello": "world"}
assert doc.embedded_field.list_field == ["1", 2, {"hello": "world"}]
def test_complex_embedded_documents(self):
"""Test complex dynamic embedded documents setups"""
@@ -296,44 +297,41 @@ class TestDynamicDocument(MongoDBTestCase):
embedded_1.list_field = ["1", 2, embedded_2]
doc.embedded_field = embedded_1
self.assertEqual(
doc.to_mongo(),
{
"embedded_field": {
"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": [
"1",
2,
{
"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ["1", 2, {"hello": "world"}],
},
],
}
},
)
assert doc.to_mongo() == {
"embedded_field": {
"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": [
"1",
2,
{
"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ["1", 2, {"hello": "world"}],
},
],
}
}
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc.embedded_field.__class__, Embedded)
self.assertEqual(doc.embedded_field.string_field, "hello")
self.assertEqual(doc.embedded_field.int_field, 1)
self.assertEqual(doc.embedded_field.dict_field, {"hello": "world"})
self.assertEqual(doc.embedded_field.list_field[0], "1")
self.assertEqual(doc.embedded_field.list_field[1], 2)
assert doc.embedded_field.__class__ == Embedded
assert doc.embedded_field.string_field == "hello"
assert doc.embedded_field.int_field == 1
assert doc.embedded_field.dict_field == {"hello": "world"}
assert doc.embedded_field.list_field[0] == "1"
assert doc.embedded_field.list_field[1] == 2
embedded_field = doc.embedded_field.list_field[2]
self.assertEqual(embedded_field.__class__, Embedded)
self.assertEqual(embedded_field.string_field, "hello")
self.assertEqual(embedded_field.int_field, 1)
self.assertEqual(embedded_field.dict_field, {"hello": "world"})
self.assertEqual(embedded_field.list_field, ["1", 2, {"hello": "world"}])
assert embedded_field.__class__ == Embedded
assert embedded_field.string_field == "hello"
assert embedded_field.int_field == 1
assert embedded_field.dict_field == {"hello": "world"}
assert embedded_field.list_field == ["1", 2, {"hello": "world"}]
def test_dynamic_and_embedded(self):
"""Ensure embedded documents play nicely"""
@@ -352,18 +350,18 @@ class TestDynamicDocument(MongoDBTestCase):
person.address.city = "Lundenne"
person.save()
self.assertEqual(Person.objects.first().address.city, "Lundenne")
assert Person.objects.first().address.city == "Lundenne"
person = Person.objects.first()
person.address = Address(city="Londinium")
person.save()
self.assertEqual(Person.objects.first().address.city, "Londinium")
assert Person.objects.first().address.city == "Londinium"
person = Person.objects.first()
person.age = 35
person.save()
self.assertEqual(Person.objects.first().age, 35)
assert Person.objects.first().age == 35
def test_dynamic_embedded_works_with_only(self):
"""Ensure custom fieldnames on a dynamic embedded document are found by qs.only()"""
@@ -380,10 +378,10 @@ class TestDynamicDocument(MongoDBTestCase):
name="Eric", address=Address(city="San Francisco", street_number="1337")
).save()
self.assertEqual(Person.objects.first().address.street_number, "1337")
self.assertEqual(
Person.objects.only("address__street_number").first().address.street_number,
"1337",
assert Person.objects.first().address.street_number == "1337"
assert (
Person.objects.only("address__street_number").first().address.street_number
== "1337"
)
def test_dynamic_and_embedded_dict_access(self):
@@ -408,20 +406,20 @@ class TestDynamicDocument(MongoDBTestCase):
person["address"]["city"] = "Lundenne"
person.save()
self.assertEqual(Person.objects.first().address.city, "Lundenne")
assert Person.objects.first().address.city == "Lundenne"
self.assertEqual(Person.objects.first().phone, "555-1212")
assert Person.objects.first().phone == "555-1212"
person = Person.objects.first()
person.address = Address(city="Londinium")
person.save()
self.assertEqual(Person.objects.first().address.city, "Londinium")
assert Person.objects.first().address.city == "Londinium"
person = Person.objects.first()
person["age"] = 35
person.save()
self.assertEqual(Person.objects.first().age, 35)
assert Person.objects.first().age == 35
if __name__ == "__main__":

View File

@@ -2,18 +2,15 @@
import unittest
from datetime import datetime
from nose.plugins.skip import SkipTest
from pymongo.collation import Collation
from pymongo.errors import OperationFailure
import pymongo
from six import iteritems
import pytest
from mongoengine import *
from mongoengine.connection import get_db
__all__ = ("IndexesTest",)
class IndexesTest(unittest.TestCase):
class TestIndexes(unittest.TestCase):
def setUp(self):
self.connection = connect(db="mongoenginetest")
self.db = get_db()
@@ -55,15 +52,15 @@ class IndexesTest(unittest.TestCase):
{"fields": [("tags", 1)]},
{"fields": [("category", 1), ("addDate", -1)]},
]
self.assertEqual(expected_specs, BlogPost._meta["index_specs"])
assert expected_specs == BlogPost._meta["index_specs"]
BlogPost.ensure_indexes()
info = BlogPost.objects._collection.index_information()
# _id, '-date', 'tags', ('cat', 'date')
self.assertEqual(len(info), 4)
info = [value["key"] for key, value in iteritems(info)]
assert len(info) == 4
info = [value["key"] for key, value in info.items()]
for expected in expected_specs:
self.assertIn(expected["fields"], info)
assert expected["fields"] in info
def _index_test_inheritance(self, InheritFrom):
class BlogPost(InheritFrom):
@@ -80,7 +77,7 @@ class IndexesTest(unittest.TestCase):
{"fields": [("_cls", 1), ("tags", 1)]},
{"fields": [("_cls", 1), ("category", 1), ("addDate", -1)]},
]
self.assertEqual(expected_specs, BlogPost._meta["index_specs"])
assert expected_specs == BlogPost._meta["index_specs"]
BlogPost.ensure_indexes()
info = BlogPost.objects._collection.index_information()
@@ -88,25 +85,25 @@ class IndexesTest(unittest.TestCase):
# NB: there is no index on _cls by itself, since
# the indices on -date and tags will both contain
# _cls as first element in the key
self.assertEqual(len(info), 4)
info = [value["key"] for key, value in iteritems(info)]
assert len(info) == 4
info = [value["key"] for key, value in info.items()]
for expected in expected_specs:
self.assertIn(expected["fields"], info)
assert expected["fields"] in info
class ExtendedBlogPost(BlogPost):
title = StringField()
meta = {"indexes": ["title"]}
expected_specs.append({"fields": [("_cls", 1), ("title", 1)]})
self.assertEqual(expected_specs, ExtendedBlogPost._meta["index_specs"])
assert expected_specs == ExtendedBlogPost._meta["index_specs"]
BlogPost.drop_collection()
ExtendedBlogPost.ensure_indexes()
info = ExtendedBlogPost.objects._collection.index_information()
info = [value["key"] for key, value in iteritems(info)]
info = [value["key"] for key, value in info.items()]
for expected in expected_specs:
self.assertIn(expected["fields"], info)
assert expected["fields"] in info
def test_indexes_document_inheritance(self):
"""Ensure that indexes are used when meta[indexes] is specified for
@@ -130,10 +127,8 @@ class IndexesTest(unittest.TestCase):
class B(A):
description = StringField()
self.assertEqual(A._meta["index_specs"], B._meta["index_specs"])
self.assertEqual(
[{"fields": [("_cls", 1), ("title", 1)]}], A._meta["index_specs"]
)
assert A._meta["index_specs"] == B._meta["index_specs"]
assert [{"fields": [("_cls", 1), ("title", 1)]}] == A._meta["index_specs"]
def test_index_no_cls(self):
"""Ensure index specs are inhertited correctly"""
@@ -146,11 +141,11 @@ class IndexesTest(unittest.TestCase):
"index_cls": False,
}
self.assertEqual([("title", 1)], A._meta["index_specs"][0]["fields"])
assert [("title", 1)] == A._meta["index_specs"][0]["fields"]
A._get_collection().drop_indexes()
A.ensure_indexes()
info = A._get_collection().index_information()
self.assertEqual(len(info.keys()), 2)
assert len(info.keys()) == 2
class B(A):
c = StringField()
@@ -160,8 +155,8 @@ class IndexesTest(unittest.TestCase):
"allow_inheritance": True,
}
self.assertEqual([("c", 1)], B._meta["index_specs"][1]["fields"])
self.assertEqual([("_cls", 1), ("d", 1)], B._meta["index_specs"][2]["fields"])
assert [("c", 1)] == B._meta["index_specs"][1]["fields"]
assert [("_cls", 1), ("d", 1)] == B._meta["index_specs"][2]["fields"]
def test_build_index_spec_is_not_destructive(self):
class MyDoc(Document):
@@ -169,12 +164,12 @@ class IndexesTest(unittest.TestCase):
meta = {"indexes": ["keywords"], "allow_inheritance": False}
self.assertEqual(MyDoc._meta["index_specs"], [{"fields": [("keywords", 1)]}])
assert MyDoc._meta["index_specs"] == [{"fields": [("keywords", 1)]}]
# Force index creation
MyDoc.ensure_indexes()
self.assertEqual(MyDoc._meta["index_specs"], [{"fields": [("keywords", 1)]}])
assert MyDoc._meta["index_specs"] == [{"fields": [("keywords", 1)]}]
def test_embedded_document_index_meta(self):
"""Ensure that embedded document indexes are created explicitly
@@ -189,15 +184,15 @@ class IndexesTest(unittest.TestCase):
meta = {"indexes": ["rank.title"], "allow_inheritance": False}
self.assertEqual([{"fields": [("rank.title", 1)]}], Person._meta["index_specs"])
assert [{"fields": [("rank.title", 1)]}] == Person._meta["index_specs"]
Person.drop_collection()
# Indexes are lazy so use list() to perform query
list(Person.objects)
info = Person.objects._collection.index_information()
info = [value["key"] for key, value in iteritems(info)]
self.assertIn([("rank.title", 1)], info)
info = [value["key"] for key, value in info.items()]
assert [("rank.title", 1)] in info
def test_explicit_geo2d_index(self):
"""Ensure that geo2d indexes work when created via meta[indexes]
@@ -207,14 +202,12 @@ class IndexesTest(unittest.TestCase):
location = DictField()
meta = {"allow_inheritance": True, "indexes": ["*location.point"]}
self.assertEqual(
[{"fields": [("location.point", "2d")]}], Place._meta["index_specs"]
)
assert [{"fields": [("location.point", "2d")]}] == Place._meta["index_specs"]
Place.ensure_indexes()
info = Place._get_collection().index_information()
info = [value["key"] for key, value in iteritems(info)]
self.assertIn([("location.point", "2d")], info)
info = [value["key"] for key, value in info.items()]
assert [("location.point", "2d")] in info
def test_explicit_geo2d_index_embedded(self):
"""Ensure that geo2d indexes work when created via meta[indexes]
@@ -227,14 +220,14 @@ class IndexesTest(unittest.TestCase):
current = DictField(field=EmbeddedDocumentField("EmbeddedLocation"))
meta = {"allow_inheritance": True, "indexes": ["*current.location.point"]}
self.assertEqual(
[{"fields": [("current.location.point", "2d")]}], Place._meta["index_specs"]
)
assert [{"fields": [("current.location.point", "2d")]}] == Place._meta[
"index_specs"
]
Place.ensure_indexes()
info = Place._get_collection().index_information()
info = [value["key"] for key, value in iteritems(info)]
self.assertIn([("current.location.point", "2d")], info)
info = [value["key"] for key, value in info.items()]
assert [("current.location.point", "2d")] in info
def test_explicit_geosphere_index(self):
"""Ensure that geosphere indexes work when created via meta[indexes]
@@ -244,19 +237,19 @@ class IndexesTest(unittest.TestCase):
location = DictField()
meta = {"allow_inheritance": True, "indexes": ["(location.point"]}
self.assertEqual(
[{"fields": [("location.point", "2dsphere")]}], Place._meta["index_specs"]
)
assert [{"fields": [("location.point", "2dsphere")]}] == Place._meta[
"index_specs"
]
Place.ensure_indexes()
info = Place._get_collection().index_information()
info = [value["key"] for key, value in iteritems(info)]
self.assertIn([("location.point", "2dsphere")], info)
info = [value["key"] for key, value in info.items()]
assert [("location.point", "2dsphere")] in info
def test_explicit_geohaystack_index(self):
"""Ensure that geohaystack indexes work when created via meta[indexes]
"""
raise SkipTest(
pytest.skip(
"GeoHaystack index creation is not supported for now"
"from meta, as it requires a bucketSize parameter."
)
@@ -266,15 +259,14 @@ class IndexesTest(unittest.TestCase):
name = StringField()
meta = {"indexes": [(")location.point", "name")]}
self.assertEqual(
[{"fields": [("location.point", "geoHaystack"), ("name", 1)]}],
Place._meta["index_specs"],
)
assert [
{"fields": [("location.point", "geoHaystack"), ("name", 1)]}
] == Place._meta["index_specs"]
Place.ensure_indexes()
info = Place._get_collection().index_information()
info = [value["key"] for key, value in iteritems(info)]
self.assertIn([("location.point", "geoHaystack")], info)
info = [value["key"] for key, value in info.items()]
assert [("location.point", "geoHaystack")] in info
def test_create_geohaystack_index(self):
"""Ensure that geohaystack indexes can be created
@@ -286,8 +278,8 @@ class IndexesTest(unittest.TestCase):
Place.create_index({"fields": (")location.point", "name")}, bucketSize=10)
info = Place._get_collection().index_information()
info = [value["key"] for key, value in iteritems(info)]
self.assertIn([("location.point", "geoHaystack"), ("name", 1)], info)
info = [value["key"] for key, value in info.items()]
assert [("location.point", "geoHaystack"), ("name", 1)] in info
def test_dictionary_indexes(self):
"""Ensure that indexes are used when meta[indexes] contains
@@ -300,25 +292,24 @@ class IndexesTest(unittest.TestCase):
tags = ListField(StringField())
meta = {"indexes": [{"fields": ["-date"], "unique": True, "sparse": True}]}
self.assertEqual(
[{"fields": [("addDate", -1)], "unique": True, "sparse": True}],
BlogPost._meta["index_specs"],
)
assert [
{"fields": [("addDate", -1)], "unique": True, "sparse": True}
] == BlogPost._meta["index_specs"]
BlogPost.drop_collection()
info = BlogPost.objects._collection.index_information()
# _id, '-date'
self.assertEqual(len(info), 2)
assert len(info) == 2
# Indexes are lazy so use list() to perform query
list(BlogPost.objects)
info = BlogPost.objects._collection.index_information()
info = [
(value["key"], value.get("unique", False), value.get("sparse", False))
for key, value in iteritems(info)
for key, value in info.items()
]
self.assertIn(([("addDate", -1)], True, True), info)
assert ([("addDate", -1)], True, True) in info
BlogPost.drop_collection()
@@ -340,11 +331,9 @@ class IndexesTest(unittest.TestCase):
Person(name="test", user_guid="123").save()
self.assertEqual(1, Person.objects.count())
assert 1 == Person.objects.count()
info = Person.objects._collection.index_information()
self.assertEqual(
sorted(info.keys()), ["_cls_1_name_1", "_cls_1_user_guid_1", "_id_"]
)
assert sorted(info.keys()) == ["_cls_1_name_1", "_cls_1_user_guid_1", "_id_"]
def test_disable_index_creation(self):
"""Tests setting auto_create_index to False on the connection will
@@ -367,13 +356,13 @@ class IndexesTest(unittest.TestCase):
User(user_guid="123").save()
MongoUser(user_guid="123").save()
self.assertEqual(2, User.objects.count())
assert 2 == User.objects.count()
info = User.objects._collection.index_information()
self.assertEqual(list(info.keys()), ["_id_"])
assert list(info.keys()) == ["_id_"]
User.ensure_indexes()
info = User.objects._collection.index_information()
self.assertEqual(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):
"""Tests settings an index on an embedded document
@@ -391,7 +380,7 @@ class IndexesTest(unittest.TestCase):
BlogPost.drop_collection()
info = BlogPost.objects._collection.index_information()
self.assertEqual(sorted(info.keys()), ["_id_", "date.yr_-1"])
assert sorted(info.keys()) == ["_id_", "date.yr_-1"]
def test_list_embedded_document_index(self):
"""Ensure list embedded documents can be indexed
@@ -410,7 +399,7 @@ class IndexesTest(unittest.TestCase):
info = BlogPost.objects._collection.index_information()
# we don't use _cls in with list fields by default
self.assertEqual(sorted(info.keys()), ["_id_", "tags.tag_1"])
assert sorted(info.keys()) == ["_id_", "tags.tag_1"]
post1 = BlogPost(
title="Embedded Indexes tests in place",
@@ -428,7 +417,7 @@ class IndexesTest(unittest.TestCase):
RecursiveDocument.ensure_indexes()
info = RecursiveDocument._get_collection().index_information()
self.assertEqual(sorted(info.keys()), ["_cls_1", "_id_"])
assert sorted(info.keys()) == ["_cls_1", "_id_"]
def test_covered_index(self):
"""Ensure that covered indexes can be used
@@ -448,46 +437,45 @@ class IndexesTest(unittest.TestCase):
# Need to be explicit about covered indexes as mongoDB doesn't know if
# the documents returned might have more keys in that here.
query_plan = Test.objects(id=obj.id).exclude("a").explain()
self.assertEqual(
assert (
query_plan.get("queryPlanner")
.get("winningPlan")
.get("inputStage")
.get("stage"),
"IDHACK",
.get("stage")
== "IDHACK"
)
query_plan = Test.objects(id=obj.id).only("id").explain()
self.assertEqual(
assert (
query_plan.get("queryPlanner")
.get("winningPlan")
.get("inputStage")
.get("stage"),
"IDHACK",
.get("stage")
== "IDHACK"
)
query_plan = Test.objects(a=1).only("a").exclude("id").explain()
self.assertEqual(
assert (
query_plan.get("queryPlanner")
.get("winningPlan")
.get("inputStage")
.get("stage"),
"IXSCAN",
.get("stage")
== "IXSCAN"
)
self.assertEqual(
query_plan.get("queryPlanner").get("winningPlan").get("stage"), "PROJECTION"
assert (
query_plan.get("queryPlanner").get("winningPlan").get("stage")
== "PROJECTION"
)
query_plan = Test.objects(a=1).explain()
self.assertEqual(
assert (
query_plan.get("queryPlanner")
.get("winningPlan")
.get("inputStage")
.get("stage"),
"IXSCAN",
)
self.assertEqual(
query_plan.get("queryPlanner").get("winningPlan").get("stage"), "FETCH"
.get("stage")
== "IXSCAN"
)
assert query_plan.get("queryPlanner").get("winningPlan").get("stage") == "FETCH"
def test_index_on_id(self):
class BlogPost(Document):
@@ -500,9 +488,7 @@ class IndexesTest(unittest.TestCase):
BlogPost.drop_collection()
indexes = BlogPost.objects._collection.index_information()
self.assertEqual(
indexes["categories_1__id_1"]["key"], [("categories", 1), ("_id", 1)]
)
assert indexes["categories_1__id_1"]["key"] == [("categories", 1), ("_id", 1)]
def test_hint(self):
TAGS_INDEX_NAME = "tags_1"
@@ -518,27 +504,59 @@ class IndexesTest(unittest.TestCase):
BlogPost(tags=tags).save()
# Hinting by shape should work.
self.assertEqual(BlogPost.objects.hint([("tags", 1)]).count(), 10)
assert BlogPost.objects.hint([("tags", 1)]).count() == 10
# Hinting by index name should work.
self.assertEqual(BlogPost.objects.hint(TAGS_INDEX_NAME).count(), 10)
assert BlogPost.objects.hint(TAGS_INDEX_NAME).count() == 10
# Clearing the hint should work fine.
self.assertEqual(BlogPost.objects.hint().count(), 10)
self.assertEqual(BlogPost.objects.hint([("ZZ", 1)]).hint().count(), 10)
assert BlogPost.objects.hint().count() == 10
assert BlogPost.objects.hint([("ZZ", 1)]).hint().count() == 10
# Hinting on a non-existent index shape should fail.
with self.assertRaises(OperationFailure):
with pytest.raises(OperationFailure):
BlogPost.objects.hint([("ZZ", 1)]).count()
# Hinting on a non-existent index name should fail.
with self.assertRaises(OperationFailure):
with pytest.raises(OperationFailure):
BlogPost.objects.hint("Bad Name").count()
# Invalid shape argument (missing list brackets) should fail.
with self.assertRaises(ValueError):
with pytest.raises(ValueError):
BlogPost.objects.hint(("tags", 1)).count()
def test_collation(self):
base = {"locale": "en", "strength": 2}
class BlogPost(Document):
name = StringField()
meta = {
"indexes": [
{"fields": ["name"], "name": "name_index", "collation": base}
]
}
BlogPost.drop_collection()
names = ["tag1", "Tag2", "tag3", "Tag4", "tag5"]
for name in names:
BlogPost(name=name).save()
query_result = BlogPost.objects.collation(base).order_by("name")
assert [x.name for x in query_result] == sorted(names, key=lambda x: x.lower())
assert 5 == query_result.count()
query_result = BlogPost.objects.collation(Collation(**base)).order_by("name")
assert [x.name for x in query_result] == sorted(names, key=lambda x: x.lower())
assert 5 == query_result.count()
incorrect_collation = {"arndom": "wrdo"}
with pytest.raises(OperationFailure):
BlogPost.objects.collation(incorrect_collation).count()
query_result = BlogPost.objects.collation({}).order_by("name")
assert [x.name for x in query_result] == sorted(names)
def test_unique(self):
"""Ensure that uniqueness constraints are applied to fields.
"""
@@ -554,11 +572,14 @@ class IndexesTest(unittest.TestCase):
# Two posts with the same slug is not allowed
post2 = BlogPost(title="test2", slug="test")
self.assertRaises(NotUniqueError, post2.save)
self.assertRaises(NotUniqueError, BlogPost.objects.insert, post2)
with pytest.raises(NotUniqueError):
post2.save()
with pytest.raises(NotUniqueError):
BlogPost.objects.insert(post2)
# Ensure backwards compatibility for errors
self.assertRaises(OperationError, post2.save)
with pytest.raises(OperationError):
post2.save()
def test_primary_key_unique_not_working(self):
"""Relates to #1445"""
@@ -568,23 +589,21 @@ class IndexesTest(unittest.TestCase):
Blog.drop_collection()
with self.assertRaises(OperationFailure) as ctx_err:
with pytest.raises(OperationFailure) as exc_info:
Blog(id="garbage").save()
# One of the errors below should happen. Which one depends on the
# PyMongo version and dict order.
err_msg = str(ctx_err.exception)
self.assertTrue(
any(
[
"The field 'unique' is not valid for an _id index specification"
in err_msg,
"The field 'background' is not valid for an _id index specification"
in err_msg,
"The field 'sparse' is not valid for an _id index specification"
in err_msg,
]
)
err_msg = str(exc_info.value)
assert any(
[
"The field 'unique' is not valid for an _id index specification"
in err_msg,
"The field 'background' is not valid for an _id index specification"
in err_msg,
"The field 'sparse' is not valid for an _id index specification"
in err_msg,
]
)
def test_unique_with(self):
@@ -610,7 +629,8 @@ class IndexesTest(unittest.TestCase):
# Now there will be two docs with the same slug and the same day: fail
post3 = BlogPost(title="test3", date=Date(year=2010), slug="test")
self.assertRaises(OperationError, post3.save)
with pytest.raises(OperationError):
post3.save()
def test_unique_embedded_document(self):
"""Ensure that uniqueness constraints are applied to fields on embedded documents.
@@ -635,7 +655,8 @@ class IndexesTest(unittest.TestCase):
# Now there will be two docs with the same sub.slug
post3 = BlogPost(title="test3", sub=SubDocument(year=2010, slug="test"))
self.assertRaises(NotUniqueError, post3.save)
with pytest.raises(NotUniqueError):
post3.save()
def test_unique_embedded_document_in_list(self):
"""
@@ -665,7 +686,8 @@ class IndexesTest(unittest.TestCase):
post2 = BlogPost(title="test2", subs=[SubDocument(year=2014, slug="conflict")])
self.assertRaises(NotUniqueError, post2.save)
with pytest.raises(NotUniqueError):
post2.save()
def test_unique_embedded_document_in_sorted_list(self):
"""
@@ -695,12 +717,13 @@ class IndexesTest(unittest.TestCase):
# confirm that the unique index is created
indexes = BlogPost._get_collection().index_information()
self.assertIn("subs.slug_1", indexes)
self.assertTrue(indexes["subs.slug_1"]["unique"])
assert "subs.slug_1" in indexes
assert indexes["subs.slug_1"]["unique"]
post2 = BlogPost(title="test2", subs=[SubDocument(year=2014, slug="conflict")])
self.assertRaises(NotUniqueError, post2.save)
with pytest.raises(NotUniqueError):
post2.save()
def test_unique_embedded_document_in_embedded_document_list(self):
"""
@@ -730,12 +753,13 @@ class IndexesTest(unittest.TestCase):
# confirm that the unique index is created
indexes = BlogPost._get_collection().index_information()
self.assertIn("subs.slug_1", indexes)
self.assertTrue(indexes["subs.slug_1"]["unique"])
assert "subs.slug_1" in indexes
assert indexes["subs.slug_1"]["unique"]
post2 = BlogPost(title="test2", subs=[SubDocument(year=2014, slug="conflict")])
self.assertRaises(NotUniqueError, post2.save)
with pytest.raises(NotUniqueError):
post2.save()
def test_unique_with_embedded_document_and_embedded_unique(self):
"""Ensure that uniqueness constraints are applied to fields on
@@ -761,11 +785,13 @@ class IndexesTest(unittest.TestCase):
# Now there will be two docs with the same sub.slug
post3 = BlogPost(title="test3", sub=SubDocument(year=2010, slug="test"))
self.assertRaises(NotUniqueError, post3.save)
with pytest.raises(NotUniqueError):
post3.save()
# Now there will be two docs with the same title and year
post3 = BlogPost(title="test1", sub=SubDocument(year=2009, slug="test-1"))
self.assertRaises(NotUniqueError, post3.save)
with pytest.raises(NotUniqueError):
post3.save()
def test_ttl_indexes(self):
class Log(Document):
@@ -777,19 +803,7 @@ class IndexesTest(unittest.TestCase):
# Indexes are lazy so use list() to perform query
list(Log.objects)
info = Log.objects._collection.index_information()
self.assertEqual(3600, info["created_1"]["expireAfterSeconds"])
def test_index_drop_dups_silently_ignored(self):
class Customer(Document):
cust_id = IntField(unique=True, required=True)
meta = {
"indexes": ["cust_id"],
"index_drop_dups": True,
"allow_inheritance": False,
}
Customer.drop_collection()
Customer.objects.first()
assert 3600 == info["created_1"]["expireAfterSeconds"]
def test_unique_and_indexes(self):
"""Ensure that 'unique' constraints aren't overridden by
@@ -805,14 +819,14 @@ class IndexesTest(unittest.TestCase):
cust.save()
cust_dupe = Customer(cust_id=1)
with self.assertRaises(NotUniqueError):
with pytest.raises(NotUniqueError):
cust_dupe.save()
cust = Customer(cust_id=2)
cust.save()
# duplicate key on update
with self.assertRaises(NotUniqueError):
with pytest.raises(NotUniqueError):
cust.cust_id = 1
cust.save()
@@ -833,8 +847,8 @@ class IndexesTest(unittest.TestCase):
user = User(name="huangz", password="secret2")
user.save()
self.assertEqual(User.objects.count(), 1)
self.assertEqual(User.objects.get().password, "secret2")
assert User.objects.count() == 1
assert User.objects.get().password == "secret2"
def test_unique_and_primary_create(self):
"""Create a new record with a duplicate primary key
@@ -848,11 +862,11 @@ class IndexesTest(unittest.TestCase):
User.drop_collection()
User.objects.create(name="huangz", password="secret")
with self.assertRaises(NotUniqueError):
with pytest.raises(NotUniqueError):
User.objects.create(name="huangz", password="secret2")
self.assertEqual(User.objects.count(), 1)
self.assertEqual(User.objects.get().password, "secret")
assert User.objects.count() == 1
assert User.objects.get().password == "secret"
def test_index_with_pk(self):
"""Ensure you can use `pk` as part of a query"""
@@ -874,9 +888,9 @@ class IndexesTest(unittest.TestCase):
self.fail("Unbound local error at index + pk definition")
info = BlogPost.objects._collection.index_information()
info = [value["key"] for key, value in iteritems(info)]
info = [value["key"] for key, value in info.items()]
index_item = [("_id", 1), ("comments.comment_id", 1)]
self.assertIn(index_item, info)
assert index_item in info
def test_compound_key_embedded(self):
class CompoundKey(EmbeddedDocument):
@@ -890,10 +904,8 @@ class IndexesTest(unittest.TestCase):
my_key = CompoundKey(name="n", term="ok")
report = ReportEmbedded(text="OK", key=my_key).save()
self.assertEqual(
{"text": "OK", "_id": {"term": "ok", "name": "n"}}, report.to_mongo()
)
self.assertEqual(report, ReportEmbedded.objects.get(pk=my_key))
assert {"text": "OK", "_id": {"term": "ok", "name": "n"}} == report.to_mongo()
assert report == ReportEmbedded.objects.get(pk=my_key)
def test_compound_key_dictfield(self):
class ReportDictField(Document):
@@ -903,15 +915,13 @@ class IndexesTest(unittest.TestCase):
my_key = {"name": "n", "term": "ok"}
report = ReportDictField(text="OK", key=my_key).save()
self.assertEqual(
{"text": "OK", "_id": {"term": "ok", "name": "n"}}, report.to_mongo()
)
assert {"text": "OK", "_id": {"term": "ok", "name": "n"}} == report.to_mongo()
# We can't directly call ReportDictField.objects.get(pk=my_key),
# because dicts are unordered, and if the order in MongoDB is
# different than the one in `my_key`, this test will fail.
self.assertEqual(report, ReportDictField.objects.get(pk__name=my_key["name"]))
self.assertEqual(report, ReportDictField.objects.get(pk__term=my_key["term"]))
assert report == ReportDictField.objects.get(pk__name=my_key["name"])
assert report == ReportDictField.objects.get(pk__term=my_key["term"])
def test_string_indexes(self):
class MyDoc(Document):
@@ -919,9 +929,9 @@ class IndexesTest(unittest.TestCase):
meta = {"indexes": ["provider_ids.foo", "provider_ids.bar"]}
info = MyDoc.objects._collection.index_information()
info = [value["key"] for key, value in iteritems(info)]
self.assertIn([("provider_ids.foo", 1)], info)
self.assertIn([("provider_ids.bar", 1)], info)
info = [value["key"] for key, value in info.items()]
assert [("provider_ids.foo", 1)] in info
assert [("provider_ids.bar", 1)] in info
def test_sparse_compound_indexes(self):
class MyDoc(Document):
@@ -933,11 +943,10 @@ class IndexesTest(unittest.TestCase):
}
info = MyDoc.objects._collection.index_information()
self.assertEqual(
[("provider_ids.foo", 1), ("provider_ids.bar", 1)],
info["provider_ids.foo_1_provider_ids.bar_1"]["key"],
)
self.assertTrue(info["provider_ids.foo_1_provider_ids.bar_1"]["sparse"])
assert [("provider_ids.foo", 1), ("provider_ids.bar", 1)] == info[
"provider_ids.foo_1_provider_ids.bar_1"
]["key"]
assert info["provider_ids.foo_1_provider_ids.bar_1"]["sparse"]
def test_text_indexes(self):
class Book(Document):
@@ -945,9 +954,9 @@ class IndexesTest(unittest.TestCase):
meta = {"indexes": ["$title"]}
indexes = Book.objects._collection.index_information()
self.assertIn("title_text", indexes)
assert "title_text" in indexes
key = indexes["title_text"]["key"]
self.assertIn(("_fts", "text"), key)
assert ("_fts", "text") in key
def test_hashed_indexes(self):
class Book(Document):
@@ -955,8 +964,8 @@ class IndexesTest(unittest.TestCase):
meta = {"indexes": ["#ref_id"]}
indexes = Book.objects._collection.index_information()
self.assertIn("ref_id_hashed", indexes)
self.assertIn(("ref_id", "hashed"), indexes["ref_id_hashed"]["key"])
assert "ref_id_hashed" in indexes
assert ("ref_id", "hashed") in indexes["ref_id_hashed"]["key"]
def test_indexes_after_database_drop(self):
"""
@@ -993,7 +1002,8 @@ class IndexesTest(unittest.TestCase):
# Create Post #2
post2 = BlogPost(title="test2", slug="test")
self.assertRaises(NotUniqueError, post2.save)
with pytest.raises(NotUniqueError):
post2.save()
finally:
# Drop the temporary database at the end
connection.drop_database("tempdatabase")
@@ -1035,20 +1045,13 @@ class IndexesTest(unittest.TestCase):
del index_info[key][
"ns"
] # drop the index namespace - we don't care about that here, MongoDB 3+
if "dropDups" in index_info[key]:
del index_info[key][
"dropDups"
] # drop the index dropDups - it is deprecated in MongoDB 3+
self.assertEqual(
index_info,
{
"txt_1": {"key": [("txt", 1)], "background": False},
"_id_": {"key": [("_id", 1)]},
"txt2_1": {"key": [("txt2", 1)], "background": False},
"_cls_1": {"key": [("_cls", 1)], "background": False},
},
)
assert index_info == {
"txt_1": {"key": [("txt", 1)], "background": False},
"_id_": {"key": [("_id", 1)]},
"txt2_1": {"key": [("txt2", 1)], "background": False},
"_cls_1": {"key": [("_cls", 1)], "background": False},
}
def test_compound_index_underscore_cls_not_overwritten(self):
"""
@@ -1071,7 +1074,7 @@ class IndexesTest(unittest.TestCase):
TestDoc.ensure_indexes()
index_info = TestDoc._get_collection().index_information()
self.assertIn("shard_1_1__cls_1_txt_1_1", index_info)
assert "shard_1_1__cls_1_txt_1_1" in index_info
if __name__ == "__main__":

View File

@@ -2,7 +2,7 @@
import unittest
import warnings
from six import iteritems
import pytest
from mongoengine import (
BooleanField,
@@ -15,13 +15,11 @@ from mongoengine import (
StringField,
)
from mongoengine.pymongo_support import list_collection_names
from tests.utils import MongoDBTestCase
from tests.fixtures import Base
__all__ = ("InheritanceTest",)
from tests.utils import MongoDBTestCase
class InheritanceTest(MongoDBTestCase):
class TestInheritance(MongoDBTestCase):
def tearDown(self):
for collection in list_collection_names(self.db):
self.db.drop_collection(collection)
@@ -39,12 +37,12 @@ class InheritanceTest(MongoDBTestCase):
meta = {"allow_inheritance": True}
test_doc = DataDoc(name="test", embed=EmbedData(data="data"))
self.assertEqual(test_doc._cls, "DataDoc")
self.assertEqual(test_doc.embed._cls, "EmbedData")
assert test_doc._cls == "DataDoc"
assert test_doc.embed._cls == "EmbedData"
test_doc.save()
saved_doc = DataDoc.objects.with_id(test_doc.id)
self.assertEqual(test_doc._cls, saved_doc._cls)
self.assertEqual(test_doc.embed._cls, saved_doc.embed._cls)
assert test_doc._cls == saved_doc._cls
assert test_doc.embed._cls == saved_doc.embed._cls
test_doc.delete()
def test_superclasses(self):
@@ -69,12 +67,12 @@ class InheritanceTest(MongoDBTestCase):
class Human(Mammal):
pass
self.assertEqual(Animal._superclasses, ())
self.assertEqual(Fish._superclasses, ("Animal",))
self.assertEqual(Guppy._superclasses, ("Animal", "Animal.Fish"))
self.assertEqual(Mammal._superclasses, ("Animal",))
self.assertEqual(Dog._superclasses, ("Animal", "Animal.Mammal"))
self.assertEqual(Human._superclasses, ("Animal", "Animal.Mammal"))
assert Animal._superclasses == ()
assert Fish._superclasses == ("Animal",)
assert Guppy._superclasses == ("Animal", "Animal.Fish")
assert Mammal._superclasses == ("Animal",)
assert Dog._superclasses == ("Animal", "Animal.Mammal")
assert Human._superclasses == ("Animal", "Animal.Mammal")
def test_external_superclasses(self):
"""Ensure that the correct list of super classes is assembled when
@@ -99,18 +97,12 @@ class InheritanceTest(MongoDBTestCase):
class Human(Mammal):
pass
self.assertEqual(Animal._superclasses, ("Base",))
self.assertEqual(Fish._superclasses, ("Base", "Base.Animal"))
self.assertEqual(
Guppy._superclasses, ("Base", "Base.Animal", "Base.Animal.Fish")
)
self.assertEqual(Mammal._superclasses, ("Base", "Base.Animal"))
self.assertEqual(
Dog._superclasses, ("Base", "Base.Animal", "Base.Animal.Mammal")
)
self.assertEqual(
Human._superclasses, ("Base", "Base.Animal", "Base.Animal.Mammal")
)
assert Animal._superclasses == ("Base",)
assert Fish._superclasses == ("Base", "Base.Animal")
assert Guppy._superclasses == ("Base", "Base.Animal", "Base.Animal.Fish")
assert Mammal._superclasses == ("Base", "Base.Animal")
assert Dog._superclasses == ("Base", "Base.Animal", "Base.Animal.Mammal")
assert Human._superclasses == ("Base", "Base.Animal", "Base.Animal.Mammal")
def test_subclasses(self):
"""Ensure that the correct list of _subclasses (subclasses) is
@@ -135,24 +127,22 @@ class InheritanceTest(MongoDBTestCase):
class Human(Mammal):
pass
self.assertEqual(
Animal._subclasses,
(
"Animal",
"Animal.Fish",
"Animal.Fish.Guppy",
"Animal.Mammal",
"Animal.Mammal.Dog",
"Animal.Mammal.Human",
),
assert Animal._subclasses == (
"Animal",
"Animal.Fish",
"Animal.Fish.Guppy",
"Animal.Mammal",
"Animal.Mammal.Dog",
"Animal.Mammal.Human",
)
self.assertEqual(Fish._subclasses, ("Animal.Fish", "Animal.Fish.Guppy"))
self.assertEqual(Guppy._subclasses, ("Animal.Fish.Guppy",))
self.assertEqual(
Mammal._subclasses,
("Animal.Mammal", "Animal.Mammal.Dog", "Animal.Mammal.Human"),
assert Fish._subclasses == ("Animal.Fish", "Animal.Fish.Guppy")
assert Guppy._subclasses == ("Animal.Fish.Guppy",)
assert Mammal._subclasses == (
"Animal.Mammal",
"Animal.Mammal.Dog",
"Animal.Mammal.Human",
)
self.assertEqual(Human._subclasses, ("Animal.Mammal.Human",))
assert Human._subclasses == ("Animal.Mammal.Human",)
def test_external_subclasses(self):
"""Ensure that the correct list of _subclasses (subclasses) is
@@ -177,30 +167,22 @@ class InheritanceTest(MongoDBTestCase):
class Human(Mammal):
pass
self.assertEqual(
Animal._subclasses,
(
"Base.Animal",
"Base.Animal.Fish",
"Base.Animal.Fish.Guppy",
"Base.Animal.Mammal",
"Base.Animal.Mammal.Dog",
"Base.Animal.Mammal.Human",
),
assert Animal._subclasses == (
"Base.Animal",
"Base.Animal.Fish",
"Base.Animal.Fish.Guppy",
"Base.Animal.Mammal",
"Base.Animal.Mammal.Dog",
"Base.Animal.Mammal.Human",
)
self.assertEqual(
Fish._subclasses, ("Base.Animal.Fish", "Base.Animal.Fish.Guppy")
assert Fish._subclasses == ("Base.Animal.Fish", "Base.Animal.Fish.Guppy")
assert Guppy._subclasses == ("Base.Animal.Fish.Guppy",)
assert Mammal._subclasses == (
"Base.Animal.Mammal",
"Base.Animal.Mammal.Dog",
"Base.Animal.Mammal.Human",
)
self.assertEqual(Guppy._subclasses, ("Base.Animal.Fish.Guppy",))
self.assertEqual(
Mammal._subclasses,
(
"Base.Animal.Mammal",
"Base.Animal.Mammal.Dog",
"Base.Animal.Mammal.Human",
),
)
self.assertEqual(Human._subclasses, ("Base.Animal.Mammal.Human",))
assert Human._subclasses == ("Base.Animal.Mammal.Human",)
def test_dynamic_declarations(self):
"""Test that declaring an extra class updates meta data"""
@@ -208,33 +190,31 @@ class InheritanceTest(MongoDBTestCase):
class Animal(Document):
meta = {"allow_inheritance": True}
self.assertEqual(Animal._superclasses, ())
self.assertEqual(Animal._subclasses, ("Animal",))
assert Animal._superclasses == ()
assert Animal._subclasses == ("Animal",)
# Test dynamically adding a class changes the meta data
class Fish(Animal):
pass
self.assertEqual(Animal._superclasses, ())
self.assertEqual(Animal._subclasses, ("Animal", "Animal.Fish"))
assert Animal._superclasses == ()
assert Animal._subclasses == ("Animal", "Animal.Fish")
self.assertEqual(Fish._superclasses, ("Animal",))
self.assertEqual(Fish._subclasses, ("Animal.Fish",))
assert Fish._superclasses == ("Animal",)
assert Fish._subclasses == ("Animal.Fish",)
# Test dynamically adding an inherited class changes the meta data
class Pike(Fish):
pass
self.assertEqual(Animal._superclasses, ())
self.assertEqual(
Animal._subclasses, ("Animal", "Animal.Fish", "Animal.Fish.Pike")
)
assert Animal._superclasses == ()
assert Animal._subclasses == ("Animal", "Animal.Fish", "Animal.Fish.Pike")
self.assertEqual(Fish._superclasses, ("Animal",))
self.assertEqual(Fish._subclasses, ("Animal.Fish", "Animal.Fish.Pike"))
assert Fish._superclasses == ("Animal",)
assert Fish._subclasses == ("Animal.Fish", "Animal.Fish.Pike")
self.assertEqual(Pike._superclasses, ("Animal", "Animal.Fish"))
self.assertEqual(Pike._subclasses, ("Animal.Fish.Pike",))
assert Pike._superclasses == ("Animal", "Animal.Fish")
assert Pike._subclasses == ("Animal.Fish.Pike",)
def test_inheritance_meta_data(self):
"""Ensure that document may inherit fields from a superclass document.
@@ -249,10 +229,10 @@ class InheritanceTest(MongoDBTestCase):
class Employee(Person):
salary = IntField()
self.assertEqual(
["_cls", "age", "id", "name", "salary"], sorted(Employee._fields.keys())
assert ["_cls", "age", "id", "name", "salary"] == sorted(
Employee._fields.keys()
)
self.assertEqual(Employee._get_collection_name(), Person._get_collection_name())
assert Employee._get_collection_name() == Person._get_collection_name()
def test_inheritance_to_mongo_keys(self):
"""Ensure that document may inherit fields from a superclass document.
@@ -267,17 +247,17 @@ class InheritanceTest(MongoDBTestCase):
class Employee(Person):
salary = IntField()
self.assertEqual(
["_cls", "age", "id", "name", "salary"], sorted(Employee._fields.keys())
assert ["_cls", "age", "id", "name", "salary"] == sorted(
Employee._fields.keys()
)
self.assertEqual(
Person(name="Bob", age=35).to_mongo().keys(), ["_cls", "name", "age"]
)
self.assertEqual(
Employee(name="Bob", age=35, salary=0).to_mongo().keys(),
["_cls", "name", "age", "salary"],
)
self.assertEqual(Employee._get_collection_name(), Person._get_collection_name())
assert Person(name="Bob", age=35).to_mongo().keys() == ["_cls", "name", "age"]
assert Employee(name="Bob", age=35, salary=0).to_mongo().keys() == [
"_cls",
"name",
"age",
"salary",
]
assert Employee._get_collection_name() == Person._get_collection_name()
def test_indexes_and_multiple_inheritance(self):
""" Ensure that all of the indexes are created for a document with
@@ -303,13 +283,10 @@ class InheritanceTest(MongoDBTestCase):
C.ensure_indexes()
self.assertEqual(
sorted(
[idx["key"] for idx in C._get_collection().index_information().values()]
),
sorted(
[[(u"_cls", 1), (u"b", 1)], [(u"_id", 1)], [(u"_cls", 1), (u"a", 1)]]
),
assert sorted(
[idx["key"] for idx in C._get_collection().index_information().values()]
) == sorted(
[[(u"_cls", 1), (u"b", 1)], [(u"_id", 1)], [(u"_cls", 1), (u"a", 1)]]
)
def test_polymorphic_queries(self):
@@ -340,13 +317,13 @@ class InheritanceTest(MongoDBTestCase):
Human().save()
classes = [obj.__class__ for obj in Animal.objects]
self.assertEqual(classes, [Animal, Fish, Mammal, Dog, Human])
assert classes == [Animal, Fish, Mammal, Dog, Human]
classes = [obj.__class__ for obj in Mammal.objects]
self.assertEqual(classes, [Mammal, Dog, Human])
assert classes == [Mammal, Dog, Human]
classes = [obj.__class__ for obj in Human.objects]
self.assertEqual(classes, [Human])
assert classes == [Human]
def test_allow_inheritance(self):
"""Ensure that inheritance is disabled by default on simple
@@ -357,20 +334,18 @@ class InheritanceTest(MongoDBTestCase):
name = StringField()
# can't inherit because Animal didn't explicitly allow inheritance
with self.assertRaises(ValueError) as cm:
with pytest.raises(ValueError, match="Document Animal may not be subclassed"):
class Dog(Animal):
pass
self.assertIn("Document Animal may not be subclassed", str(cm.exception))
# Check that _cls etc aren't present on simple documents
dog = Animal(name="dog").save()
self.assertEqual(dog.to_mongo().keys(), ["_id", "name"])
assert dog.to_mongo().keys() == ["_id", "name"]
collection = self.db[Animal._get_collection_name()]
obj = collection.find_one()
self.assertNotIn("_cls", obj)
assert "_cls" not in obj
def test_cant_turn_off_inheritance_on_subclass(self):
"""Ensure if inheritance is on in a subclass you cant turn it off.
@@ -380,14 +355,14 @@ class InheritanceTest(MongoDBTestCase):
name = StringField()
meta = {"allow_inheritance": True}
with self.assertRaises(ValueError) as cm:
with pytest.raises(ValueError) as exc_info:
class Mammal(Animal):
meta = {"allow_inheritance": False}
self.assertEqual(
str(cm.exception),
'Only direct subclasses of Document may set "allow_inheritance" to False',
assert (
str(exc_info.value)
== 'Only direct subclasses of Document may set "allow_inheritance" to False'
)
def test_allow_inheritance_abstract_document(self):
@@ -401,14 +376,14 @@ class InheritanceTest(MongoDBTestCase):
class Animal(FinalDocument):
name = StringField()
with self.assertRaises(ValueError) as cm:
with pytest.raises(ValueError):
class Mammal(Animal):
pass
# Check that _cls isn't present in simple documents
doc = Animal(name="dog")
self.assertNotIn("_cls", doc.to_mongo())
assert "_cls" not in doc.to_mongo()
def test_using_abstract_class_in_reference_field(self):
# Ensures no regression of #1920
@@ -454,10 +429,10 @@ class InheritanceTest(MongoDBTestCase):
name = StringField()
berlin = EuropeanCity(name="Berlin", continent="Europe")
self.assertEqual(len(berlin._db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._reverse_db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._fields_ordered), 3)
self.assertEqual(berlin._fields_ordered[0], "id")
assert len(berlin._db_field_map) == len(berlin._fields_ordered)
assert len(berlin._reverse_db_field_map) == len(berlin._fields_ordered)
assert len(berlin._fields_ordered) == 3
assert berlin._fields_ordered[0] == "id"
def test_auto_id_not_set_if_specific_in_parent_class(self):
class City(Document):
@@ -469,10 +444,10 @@ class InheritanceTest(MongoDBTestCase):
name = StringField()
berlin = EuropeanCity(name="Berlin", continent="Europe")
self.assertEqual(len(berlin._db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._reverse_db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._fields_ordered), 3)
self.assertEqual(berlin._fields_ordered[0], "city_id")
assert len(berlin._db_field_map) == len(berlin._fields_ordered)
assert len(berlin._reverse_db_field_map) == len(berlin._fields_ordered)
assert len(berlin._fields_ordered) == 3
assert berlin._fields_ordered[0] == "city_id"
def test_auto_id_vs_non_pk_id_field(self):
class City(Document):
@@ -484,12 +459,12 @@ class InheritanceTest(MongoDBTestCase):
name = StringField()
berlin = EuropeanCity(name="Berlin", continent="Europe")
self.assertEqual(len(berlin._db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._reverse_db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._fields_ordered), 4)
self.assertEqual(berlin._fields_ordered[0], "auto_id_0")
assert len(berlin._db_field_map) == len(berlin._fields_ordered)
assert len(berlin._reverse_db_field_map) == len(berlin._fields_ordered)
assert len(berlin._fields_ordered) == 4
assert berlin._fields_ordered[0] == "auto_id_0"
berlin.save()
self.assertEqual(berlin.pk, berlin.auto_id_0)
assert berlin.pk == berlin.auto_id_0
def test_abstract_document_creation_does_not_fail(self):
class City(Document):
@@ -497,9 +472,9 @@ class InheritanceTest(MongoDBTestCase):
meta = {"abstract": True, "allow_inheritance": False}
city = City(continent="asia")
self.assertEqual(None, city.pk)
assert city.pk is None
# TODO: expected error? Shouldn't we create a new error type?
with self.assertRaises(KeyError):
with pytest.raises(KeyError):
setattr(city, "pk", 1)
def test_allow_inheritance_embedded_document(self):
@@ -508,20 +483,20 @@ class InheritanceTest(MongoDBTestCase):
class Comment(EmbeddedDocument):
content = StringField()
with self.assertRaises(ValueError):
with pytest.raises(ValueError):
class SpecialComment(Comment):
pass
doc = Comment(content="test")
self.assertNotIn("_cls", doc.to_mongo())
assert "_cls" not in doc.to_mongo()
class Comment(EmbeddedDocument):
content = StringField()
meta = {"allow_inheritance": True}
doc = Comment(content="test")
self.assertIn("_cls", doc.to_mongo())
assert "_cls" in doc.to_mongo()
def test_document_inheritance(self):
"""Ensure mutliple inheritance of abstract documents
@@ -539,7 +514,7 @@ class InheritanceTest(MongoDBTestCase):
pass
except Exception:
self.assertTrue(False, "Couldn't create MyDocument class")
assert False, "Couldn't create MyDocument class"
def test_abstract_documents(self):
"""Ensure that a document superclass can be marked as abstract
@@ -547,7 +522,6 @@ class InheritanceTest(MongoDBTestCase):
defaults = {
"index_background": True,
"index_drop_dups": True,
"index_opts": {"hello": "world"},
"allow_inheritance": True,
"queryset_class": "QuerySet",
@@ -574,22 +548,22 @@ class InheritanceTest(MongoDBTestCase):
class Human(Mammal):
pass
for k, v in iteritems(defaults):
for k, v in defaults.items():
for cls in [Animal, Fish, Guppy]:
self.assertEqual(cls._meta[k], v)
assert cls._meta[k] == v
self.assertNotIn("collection", Animal._meta)
self.assertNotIn("collection", Mammal._meta)
assert "collection" not in Animal._meta
assert "collection" not in Mammal._meta
self.assertEqual(Animal._get_collection_name(), None)
self.assertEqual(Mammal._get_collection_name(), None)
assert Animal._get_collection_name() is None
assert Mammal._get_collection_name() is None
self.assertEqual(Fish._get_collection_name(), "fish")
self.assertEqual(Guppy._get_collection_name(), "fish")
self.assertEqual(Human._get_collection_name(), "human")
assert Fish._get_collection_name() == "fish"
assert Guppy._get_collection_name() == "fish"
assert Human._get_collection_name() == "human"
# ensure that a subclass of a non-abstract class can't be abstract
with self.assertRaises(ValueError):
with pytest.raises(ValueError):
class EvilHuman(Human):
evil = BooleanField(default=True)
@@ -603,7 +577,7 @@ class InheritanceTest(MongoDBTestCase):
class B(A):
pass
self.assertFalse(B._meta["abstract"])
assert not B._meta["abstract"]
def test_inherited_collections(self):
"""Ensure that subclassed documents don't override parents'
@@ -649,8 +623,8 @@ class InheritanceTest(MongoDBTestCase):
real_person = Drinker(drink=beer)
real_person.save()
self.assertEqual(Drinker.objects[0].drink.name, red_bull.name)
self.assertEqual(Drinker.objects[1].drink.name, beer.name)
assert Drinker.objects[0].drink.name == red_bull.name
assert Drinker.objects[1].drink.name == beer.name
if __name__ == "__main__":

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,14 @@
import unittest
import uuid
from nose.plugins.skip import SkipTest
from datetime import datetime
from bson import ObjectId
import pymongo
from mongoengine import *
__all__ = ("TestJson",)
from tests.utils import MongoDBTestCase
class TestJson(unittest.TestCase):
def setUp(self):
connect(db="mongoenginetest")
class TestJson(MongoDBTestCase):
def test_json_names(self):
"""
Going to test reported issue:
@@ -39,7 +32,7 @@ class TestJson(unittest.TestCase):
expected_json = """{"embedded":{"string":"Inner Hello"},"string":"Hello"}"""
self.assertEqual(doc_json, expected_json)
assert doc_json == expected_json
def test_json_simple(self):
class Embedded(EmbeddedDocument):
@@ -59,9 +52,9 @@ class TestJson(unittest.TestCase):
doc_json = doc.to_json(sort_keys=True, separators=(",", ":"))
expected_json = """{"embedded_field":{"string":"Hi"},"string":"Hi"}"""
self.assertEqual(doc_json, expected_json)
assert doc_json == expected_json
self.assertEqual(doc, Doc.from_json(doc.to_json()))
assert doc == Doc.from_json(doc.to_json())
def test_json_complex(self):
class EmbeddedDoc(EmbeddedDocument):
@@ -106,7 +99,7 @@ class TestJson(unittest.TestCase):
return json.loads(self.to_json()) == json.loads(other.to_json())
doc = Doc()
self.assertEqual(doc, Doc.from_json(doc.to_json()))
assert doc == Doc.from_json(doc.to_json())
if __name__ == "__main__":

View File

@@ -2,25 +2,23 @@
import unittest
from datetime import datetime
import pytest
from mongoengine import *
__all__ = ("ValidatorErrorTest",)
from tests.utils import MongoDBTestCase
class ValidatorErrorTest(unittest.TestCase):
def setUp(self):
connect(db="mongoenginetest")
class TestValidatorError(MongoDBTestCase):
def test_to_dict(self):
"""Ensure a ValidationError handles error to_dict correctly.
"""
error = ValidationError("root")
self.assertEqual(error.to_dict(), {})
assert error.to_dict() == {}
# 1st level error schema
error.errors = {"1st": ValidationError("bad 1st")}
self.assertIn("1st", error.to_dict())
self.assertEqual(error.to_dict()["1st"], "bad 1st")
assert "1st" in error.to_dict()
assert error.to_dict()["1st"] == "bad 1st"
# 2nd level error schema
error.errors = {
@@ -28,10 +26,10 @@ class ValidatorErrorTest(unittest.TestCase):
"bad 1st", errors={"2nd": ValidationError("bad 2nd")}
)
}
self.assertIn("1st", error.to_dict())
self.assertIsInstance(error.to_dict()["1st"], dict)
self.assertIn("2nd", error.to_dict()["1st"])
self.assertEqual(error.to_dict()["1st"]["2nd"], "bad 2nd")
assert "1st" in error.to_dict()
assert isinstance(error.to_dict()["1st"], dict)
assert "2nd" in error.to_dict()["1st"]
assert error.to_dict()["1st"]["2nd"] == "bad 2nd"
# moar levels
error.errors = {
@@ -49,13 +47,13 @@ class ValidatorErrorTest(unittest.TestCase):
},
)
}
self.assertIn("1st", error.to_dict())
self.assertIn("2nd", error.to_dict()["1st"])
self.assertIn("3rd", error.to_dict()["1st"]["2nd"])
self.assertIn("4th", error.to_dict()["1st"]["2nd"]["3rd"])
self.assertEqual(error.to_dict()["1st"]["2nd"]["3rd"]["4th"], "Inception")
assert "1st" in error.to_dict()
assert "2nd" in error.to_dict()["1st"]
assert "3rd" in error.to_dict()["1st"]["2nd"]
assert "4th" in error.to_dict()["1st"]["2nd"]["3rd"]
assert error.to_dict()["1st"]["2nd"]["3rd"]["4th"] == "Inception"
self.assertEqual(error.message, "root(2nd.3rd.4th.Inception: ['1st'])")
assert error.message == "root(2nd.3rd.4th.Inception: ['1st'])"
def test_model_validation(self):
class User(Document):
@@ -65,19 +63,19 @@ class ValidatorErrorTest(unittest.TestCase):
try:
User().validate()
except ValidationError as e:
self.assertIn("User:None", e.message)
self.assertEqual(
e.to_dict(),
{"username": "Field is required", "name": "Field is required"},
)
assert "User:None" in e.message
assert e.to_dict() == {
"username": "Field is required",
"name": "Field is required",
}
user = User(username="RossC0", name="Ross").save()
user.name = None
try:
user.save()
except ValidationError as e:
self.assertIn("User:RossC0", e.message)
self.assertEqual(e.to_dict(), {"name": "Field is required"})
assert "User:RossC0" in e.message
assert e.to_dict() == {"name": "Field is required"}
def test_fields_rewrite(self):
class BasePerson(Document):
@@ -89,7 +87,8 @@ class ValidatorErrorTest(unittest.TestCase):
name = StringField(required=True)
p = Person(age=15)
self.assertRaises(ValidationError, p.validate)
with pytest.raises(ValidationError):
p.validate()
def test_embedded_document_validation(self):
"""Ensure that embedded documents may be validated.
@@ -100,17 +99,19 @@ class ValidatorErrorTest(unittest.TestCase):
content = StringField(required=True)
comment = Comment()
self.assertRaises(ValidationError, comment.validate)
with pytest.raises(ValidationError):
comment.validate()
comment.content = "test"
comment.validate()
comment.date = 4
self.assertRaises(ValidationError, comment.validate)
with pytest.raises(ValidationError):
comment.validate()
comment.date = datetime.now()
comment.validate()
self.assertEqual(comment._instance, None)
assert comment._instance is None
def test_embedded_db_field_validate(self):
class SubDoc(EmbeddedDocument):
@@ -123,10 +124,8 @@ class ValidatorErrorTest(unittest.TestCase):
try:
Doc(id="bad").validate()
except ValidationError as e:
self.assertIn("SubDoc:None", e.message)
self.assertEqual(
e.to_dict(), {"e": {"val": "OK could not be converted to int"}}
)
assert "SubDoc:None" in e.message
assert e.to_dict() == {"e": {"val": "OK could not be converted to int"}}
Doc.drop_collection()
@@ -134,18 +133,16 @@ class ValidatorErrorTest(unittest.TestCase):
doc = Doc.objects.first()
keys = doc._data.keys()
self.assertEqual(2, len(keys))
self.assertIn("e", keys)
self.assertIn("id", keys)
assert 2 == len(keys)
assert "e" in keys
assert "id" in keys
doc.e.val = "OK"
try:
doc.save()
except ValidationError as e:
self.assertIn("Doc:test", e.message)
self.assertEqual(
e.to_dict(), {"e": {"val": "OK could not be converted to int"}}
)
assert "Doc:test" in e.message
assert e.to_dict() == {"e": {"val": "OK could not be converted to int"}}
def test_embedded_weakref(self):
class SubDoc(EmbeddedDocument):
@@ -161,14 +158,16 @@ class ValidatorErrorTest(unittest.TestCase):
s = SubDoc()
self.assertRaises(ValidationError, s.validate)
with pytest.raises(ValidationError):
s.validate()
d1.e = s
d2.e = s
del d1
self.assertRaises(ValidationError, d2.validate)
with pytest.raises(ValidationError):
d2.validate()
def test_parent_reference_in_child_document(self):
"""

View File

@@ -1,3 +0,0 @@
from .fields import *
from .file_tests import *
from .geo import *

View File

@@ -1,16 +1,14 @@
# -*- coding: utf-8 -*-
import uuid
from nose.plugins.skip import SkipTest
import six
from bson import Binary
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase
BIN_VALUE = six.b(
"\xa9\xf3\x8d(\xd7\x03\x84\xb4k[\x0f\xe3\xa2\x19\x85p[J\xa3\xd2>\xde\xe6\x87\xb1\x7f\xc6\xe6\xd9r\x18\xf5"
BIN_VALUE = "\xa9\xf3\x8d(\xd7\x03\x84\xb4k[\x0f\xe3\xa2\x19\x85p[J\xa3\xd2>\xde\xe6\x87\xb1\x7f\xc6\xe6\xd9r\x18\xf5".encode(
"latin-1"
)
@@ -23,7 +21,7 @@ class TestBinaryField(MongoDBTestCase):
content_type = StringField()
blob = BinaryField()
BLOB = six.b("\xe6\x00\xc4\xff\x07")
BLOB = "\xe6\x00\xc4\xff\x07".encode("latin-1")
MIME_TYPE = "application/octet-stream"
Attachment.drop_collection()
@@ -32,8 +30,8 @@ class TestBinaryField(MongoDBTestCase):
attachment.save()
attachment_1 = Attachment.objects().first()
self.assertEqual(MIME_TYPE, attachment_1.content_type)
self.assertEqual(BLOB, six.binary_type(attachment_1.blob))
assert MIME_TYPE == attachment_1.content_type
assert BLOB == bytes(attachment_1.blob)
def test_validation_succeeds(self):
"""Ensure that valid values can be assigned to binary fields.
@@ -46,13 +44,15 @@ class TestBinaryField(MongoDBTestCase):
blob = BinaryField(max_bytes=4)
attachment_required = AttachmentRequired()
self.assertRaises(ValidationError, attachment_required.validate)
attachment_required.blob = Binary(six.b("\xe6\x00\xc4\xff\x07"))
with pytest.raises(ValidationError):
attachment_required.validate()
attachment_required.blob = Binary("\xe6\x00\xc4\xff\x07".encode("latin-1"))
attachment_required.validate()
_5_BYTES = six.b("\xe6\x00\xc4\xff\x07")
_4_BYTES = six.b("\xe6\x00\xc4\xff")
self.assertRaises(ValidationError, AttachmentSizeLimit(blob=_5_BYTES).validate)
_5_BYTES = "\xe6\x00\xc4\xff\x07".encode("latin-1")
_4_BYTES = "\xe6\x00\xc4\xff".encode("latin-1")
with pytest.raises(ValidationError):
AttachmentSizeLimit(blob=_5_BYTES).validate()
AttachmentSizeLimit(blob=_4_BYTES).validate()
def test_validation_fails(self):
@@ -62,7 +62,8 @@ class TestBinaryField(MongoDBTestCase):
blob = BinaryField()
for invalid_data in (2, u"Im_a_unicode", ["some_str"]):
self.assertRaises(ValidationError, Attachment(blob=invalid_data).validate)
with pytest.raises(ValidationError):
Attachment(blob=invalid_data).validate()
def test__primary(self):
class Attachment(Document):
@@ -71,23 +72,21 @@ class TestBinaryField(MongoDBTestCase):
Attachment.drop_collection()
binary_id = uuid.uuid4().bytes
att = Attachment(id=binary_id).save()
self.assertEqual(1, Attachment.objects.count())
self.assertEqual(1, Attachment.objects.filter(id=att.id).count())
assert 1 == Attachment.objects.count()
assert 1 == Attachment.objects.filter(id=att.id).count()
att.delete()
self.assertEqual(0, Attachment.objects.count())
assert 0 == Attachment.objects.count()
def test_primary_filter_by_binary_pk_as_str(self):
raise SkipTest("Querying by id as string is not currently supported")
class Attachment(Document):
id = BinaryField(primary_key=True)
Attachment.drop_collection()
binary_id = uuid.uuid4().bytes
att = Attachment(id=binary_id).save()
self.assertEqual(1, Attachment.objects.filter(id=binary_id).count())
assert 1 == Attachment.objects.filter(id=binary_id).count()
att.delete()
self.assertEqual(0, Attachment.objects.count())
assert 0 == Attachment.objects.count()
def test_match_querying_with_bytes(self):
class MyDocument(Document):
@@ -97,7 +96,7 @@ class TestBinaryField(MongoDBTestCase):
doc = MyDocument(bin_field=BIN_VALUE).save()
matched_doc = MyDocument.objects(bin_field=BIN_VALUE).first()
self.assertEqual(matched_doc.id, doc.id)
assert matched_doc.id == doc.id
def test_match_querying_with_binary(self):
class MyDocument(Document):
@@ -108,7 +107,7 @@ class TestBinaryField(MongoDBTestCase):
doc = MyDocument(bin_field=BIN_VALUE).save()
matched_doc = MyDocument.objects(bin_field=Binary(BIN_VALUE)).first()
self.assertEqual(matched_doc.id, doc.id)
assert matched_doc.id == doc.id
def test_modify_operation__set(self):
"""Ensures no regression of bug #1127"""
@@ -122,11 +121,8 @@ class TestBinaryField(MongoDBTestCase):
doc = MyDocument.objects(some_field="test").modify(
upsert=True, new=True, set__bin_field=BIN_VALUE
)
self.assertEqual(doc.some_field, "test")
if six.PY3:
self.assertEqual(doc.bin_field, BIN_VALUE)
else:
self.assertEqual(doc.bin_field, Binary(BIN_VALUE))
assert doc.some_field == "test"
assert doc.bin_field == BIN_VALUE
def test_update_one(self):
"""Ensures no regression of bug #1127"""
@@ -136,15 +132,12 @@ class TestBinaryField(MongoDBTestCase):
MyDocument.drop_collection()
bin_data = six.b("\xe6\x00\xc4\xff\x07")
bin_data = "\xe6\x00\xc4\xff\x07".encode("latin-1")
doc = MyDocument(bin_field=bin_data).save()
n_updated = MyDocument.objects(bin_field=bin_data).update_one(
bin_field=BIN_VALUE
)
self.assertEqual(n_updated, 1)
assert n_updated == 1
fetched = MyDocument.objects.with_id(doc.id)
if six.PY3:
self.assertEqual(fetched.bin_field, BIN_VALUE)
else:
self.assertEqual(fetched.bin_field, Binary(BIN_VALUE))
assert fetched.bin_field == BIN_VALUE

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from mongoengine import *
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase, get_as_pymongo
@@ -11,7 +12,7 @@ class TestBooleanField(MongoDBTestCase):
person = Person(admin=True)
person.save()
self.assertEqual(get_as_pymongo(person), {"_id": person.id, "admin": True})
assert get_as_pymongo(person) == {"_id": person.id, "admin": True}
def test_validation(self):
"""Ensure that invalid values cannot be assigned to boolean
@@ -26,11 +27,14 @@ class TestBooleanField(MongoDBTestCase):
person.validate()
person.admin = 2
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
person.admin = "Yes"
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
person.admin = "False"
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
def test_weirdness_constructor(self):
"""When attribute is set in contructor, it gets cast into a bool
@@ -42,7 +46,7 @@ class TestBooleanField(MongoDBTestCase):
admin = BooleanField()
new_person = Person(admin="False")
self.assertTrue(new_person.admin)
assert new_person.admin
new_person = Person(admin="0")
self.assertTrue(new_person.admin)
assert new_person.admin

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
from decimal import Decimal
from mongoengine import *
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase
@@ -46,29 +47,29 @@ class TestCachedReferenceField(MongoDBTestCase):
a = Animal(name="Leopard", tag="heavy")
a.save()
self.assertEqual(Animal._cached_reference_fields, [Ocorrence.animal])
assert Animal._cached_reference_fields == [Ocorrence.animal]
o = Ocorrence(person="teste", animal=a)
o.save()
p = Ocorrence(person="Wilson")
p.save()
self.assertEqual(Ocorrence.objects(animal=None).count(), 1)
assert Ocorrence.objects(animal=None).count() == 1
self.assertEqual(a.to_mongo(fields=["tag"]), {"tag": "heavy", "_id": a.pk})
assert a.to_mongo(fields=["tag"]) == {"tag": "heavy", "_id": a.pk}
self.assertEqual(o.to_mongo()["animal"]["tag"], "heavy")
assert o.to_mongo()["animal"]["tag"] == "heavy"
# counts
Ocorrence(person="teste 2").save()
Ocorrence(person="teste 3").save()
count = Ocorrence.objects(animal__tag="heavy").count()
self.assertEqual(count, 1)
assert count == 1
ocorrence = Ocorrence.objects(animal__tag="heavy").first()
self.assertEqual(ocorrence.person, "teste")
self.assertIsInstance(ocorrence.animal, Animal)
assert ocorrence.person == "teste"
assert isinstance(ocorrence.animal, Animal)
def test_with_decimal(self):
class PersonAuto(Document):
@@ -88,10 +89,11 @@ class TestCachedReferenceField(MongoDBTestCase):
s = SocialTest(group="dev", person=p)
s.save()
self.assertEqual(
SocialTest.objects._collection.find_one({"person.salary": 7000.00}),
{"_id": s.pk, "group": s.group, "person": {"_id": p.pk, "salary": 7000.00}},
)
assert SocialTest.objects._collection.find_one({"person.salary": 7000.00}) == {
"_id": s.pk,
"group": s.group,
"person": {"_id": p.pk, "salary": 7000.00},
}
def test_cached_reference_field_reference(self):
class Group(Document):
@@ -131,18 +133,15 @@ class TestCachedReferenceField(MongoDBTestCase):
s2 = SocialData(obs="testing 321", person=p3, tags=["tag3", "tag4"])
s2.save()
self.assertEqual(
SocialData.objects._collection.find_one({"tags": "tag2"}),
{
"_id": s1.pk,
"obs": "testing 123",
"tags": ["tag1", "tag2"],
"person": {"_id": p1.pk, "group": g1.pk},
},
)
assert SocialData.objects._collection.find_one({"tags": "tag2"}) == {
"_id": s1.pk,
"obs": "testing 123",
"tags": ["tag1", "tag2"],
"person": {"_id": p1.pk, "group": g1.pk},
}
self.assertEqual(SocialData.objects(person__group=g2).count(), 1)
self.assertEqual(SocialData.objects(person__group=g2).first(), s2)
assert SocialData.objects(person__group=g2).count() == 1
assert SocialData.objects(person__group=g2).first() == s2
def test_cached_reference_field_push_with_fields(self):
class Product(Document):
@@ -157,26 +156,20 @@ class TestCachedReferenceField(MongoDBTestCase):
product1 = Product(name="abc").save()
product2 = Product(name="def").save()
basket = Basket(products=[product1]).save()
self.assertEqual(
Basket.objects._collection.find_one(),
{
"_id": basket.pk,
"products": [{"_id": product1.pk, "name": product1.name}],
},
)
assert Basket.objects._collection.find_one() == {
"_id": basket.pk,
"products": [{"_id": product1.pk, "name": product1.name}],
}
# push to list
basket.update(push__products=product2)
basket.reload()
self.assertEqual(
Basket.objects._collection.find_one(),
{
"_id": basket.pk,
"products": [
{"_id": product1.pk, "name": product1.name},
{"_id": product2.pk, "name": product2.name},
],
},
)
assert Basket.objects._collection.find_one() == {
"_id": basket.pk,
"products": [
{"_id": product1.pk, "name": product1.name},
{"_id": product2.pk, "name": product2.name},
],
}
def test_cached_reference_field_update_all(self):
class Person(Document):
@@ -194,37 +187,31 @@ class TestCachedReferenceField(MongoDBTestCase):
a2.save()
a2 = Person.objects.with_id(a2.id)
self.assertEqual(a2.father.tp, a1.tp)
assert a2.father.tp == a1.tp
self.assertEqual(
dict(a2.to_mongo()),
{
"_id": a2.pk,
"name": u"Wilson Junior",
"tp": u"pf",
"father": {"_id": a1.pk, "tp": u"pj"},
},
)
assert dict(a2.to_mongo()) == {
"_id": a2.pk,
"name": u"Wilson Junior",
"tp": u"pf",
"father": {"_id": a1.pk, "tp": u"pj"},
}
self.assertEqual(Person.objects(father=a1)._query, {"father._id": a1.pk})
self.assertEqual(Person.objects(father=a1).count(), 1)
assert Person.objects(father=a1)._query == {"father._id": a1.pk}
assert Person.objects(father=a1).count() == 1
Person.objects.update(set__tp="pf")
Person.father.sync_all()
a2.reload()
self.assertEqual(
dict(a2.to_mongo()),
{
"_id": a2.pk,
"name": u"Wilson Junior",
"tp": u"pf",
"father": {"_id": a1.pk, "tp": u"pf"},
},
)
assert dict(a2.to_mongo()) == {
"_id": a2.pk,
"name": u"Wilson Junior",
"tp": u"pf",
"father": {"_id": a1.pk, "tp": u"pf"},
}
def test_cached_reference_fields_on_embedded_documents(self):
with self.assertRaises(InvalidDocumentError):
with pytest.raises(InvalidDocumentError):
class Test(Document):
name = StringField()
@@ -255,15 +242,12 @@ class TestCachedReferenceField(MongoDBTestCase):
a1.save()
a2.reload()
self.assertEqual(
dict(a2.to_mongo()),
{
"_id": a2.pk,
"name": "Wilson Junior",
"tp": "pf",
"father": {"_id": a1.pk, "tp": "pf"},
},
)
assert dict(a2.to_mongo()) == {
"_id": a2.pk,
"name": "Wilson Junior",
"tp": "pf",
"father": {"_id": a1.pk, "tp": "pf"},
}
def test_cached_reference_auto_sync_disabled(self):
class Persone(Document):
@@ -284,15 +268,12 @@ class TestCachedReferenceField(MongoDBTestCase):
a1.tp = "pf"
a1.save()
self.assertEqual(
Persone.objects._collection.find_one({"_id": a2.pk}),
{
"_id": a2.pk,
"name": "Wilson Junior",
"tp": "pf",
"father": {"_id": a1.pk, "tp": "pj"},
},
)
assert Persone.objects._collection.find_one({"_id": a2.pk}) == {
"_id": a2.pk,
"name": "Wilson Junior",
"tp": "pf",
"father": {"_id": a1.pk, "tp": "pj"},
}
def test_cached_reference_embedded_fields(self):
class Owner(EmbeddedDocument):
@@ -320,28 +301,29 @@ class TestCachedReferenceField(MongoDBTestCase):
o = Ocorrence(person="teste", animal=a)
o.save()
self.assertEqual(
dict(a.to_mongo(fields=["tag", "owner.tp"])),
{"_id": a.pk, "tag": "heavy", "owner": {"t": "u"}},
)
self.assertEqual(o.to_mongo()["animal"]["tag"], "heavy")
self.assertEqual(o.to_mongo()["animal"]["owner"]["t"], "u")
assert dict(a.to_mongo(fields=["tag", "owner.tp"])) == {
"_id": a.pk,
"tag": "heavy",
"owner": {"t": "u"},
}
assert o.to_mongo()["animal"]["tag"] == "heavy"
assert o.to_mongo()["animal"]["owner"]["t"] == "u"
# Check to_mongo with fields
self.assertNotIn("animal", o.to_mongo(fields=["person"]))
assert "animal" not in o.to_mongo(fields=["person"])
# counts
Ocorrence(person="teste 2").save()
Ocorrence(person="teste 3").save()
count = Ocorrence.objects(animal__tag="heavy", animal__owner__tp="u").count()
self.assertEqual(count, 1)
assert count == 1
ocorrence = Ocorrence.objects(
animal__tag="heavy", animal__owner__tp="u"
).first()
self.assertEqual(ocorrence.person, "teste")
self.assertIsInstance(ocorrence.animal, Animal)
assert ocorrence.person == "teste"
assert isinstance(ocorrence.animal, Animal)
def test_cached_reference_embedded_list_fields(self):
class Owner(EmbeddedDocument):
@@ -370,13 +352,14 @@ class TestCachedReferenceField(MongoDBTestCase):
o = Ocorrence(person="teste 2", animal=a)
o.save()
self.assertEqual(
dict(a.to_mongo(fields=["tag", "owner.tags"])),
{"_id": a.pk, "tag": "heavy", "owner": {"tags": ["cool", "funny"]}},
)
assert dict(a.to_mongo(fields=["tag", "owner.tags"])) == {
"_id": a.pk,
"tag": "heavy",
"owner": {"tags": ["cool", "funny"]},
}
self.assertEqual(o.to_mongo()["animal"]["tag"], "heavy")
self.assertEqual(o.to_mongo()["animal"]["owner"]["tags"], ["cool", "funny"])
assert o.to_mongo()["animal"]["tag"] == "heavy"
assert o.to_mongo()["animal"]["owner"]["tags"] == ["cool", "funny"]
# counts
Ocorrence(person="teste 2").save()
@@ -385,10 +368,10 @@ class TestCachedReferenceField(MongoDBTestCase):
query = Ocorrence.objects(
animal__tag="heavy", animal__owner__tags="cool"
)._query
self.assertEqual(query, {"animal.owner.tags": "cool", "animal.tag": "heavy"})
assert query == {"animal.owner.tags": "cool", "animal.tag": "heavy"}
ocorrence = Ocorrence.objects(
animal__tag="heavy", animal__owner__tags="cool"
).first()
self.assertEqual(ocorrence.person, "teste 2")
self.assertIsInstance(ocorrence.animal, Animal)
assert ocorrence.person == "teste 2"
assert isinstance(ocorrence.animal, Animal)

View File

@@ -1,9 +1,11 @@
# -*- coding: utf-8 -*-
import datetime
import math
import itertools
import math
import re
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase
@@ -28,7 +30,7 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1)
assert log.date == d1
# Post UTC - microseconds are rounded (down) nearest millisecond - with
# default datetimefields
@@ -36,7 +38,7 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1)
assert log.date == d1
# Pre UTC dates microseconds below 1000 are dropped - with default
# datetimefields
@@ -44,7 +46,7 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1)
assert log.date == d1
# Pre UTC microseconds above 1000 is wonky - with default datetimefields
# log.date has an invalid microsecond value so I can't construct
@@ -54,9 +56,9 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1)
assert log.date == d1
log1 = LogEntry.objects.get(date=d1)
self.assertEqual(log, log1)
assert log == log1
# Test string padding
microsecond = map(int, [math.pow(10, x) for x in range(6)])
@@ -64,8 +66,8 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
for values in itertools.product([2014], mm, dd, hh, ii, ss, microsecond):
stored = LogEntry(date=datetime.datetime(*values)).to_mongo()["date"]
self.assertTrue(
re.match("^\d{4},\d{2},\d{2},\d{2},\d{2},\d{2},\d{6}$", stored)
assert (
re.match(r"^\d{4},\d{2},\d{2},\d{2},\d{2},\d{2},\d{6}$", stored)
is not None
)
@@ -73,8 +75,8 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
stored = LogEntry(date_with_dots=datetime.datetime(2014, 1, 1)).to_mongo()[
"date_with_dots"
]
self.assertTrue(
re.match("^\d{4}.\d{2}.\d{2}.\d{2}.\d{2}.\d{2}.\d{6}$", stored) is not None
assert (
re.match(r"^\d{4}.\d{2}.\d{2}.\d{2}.\d{2}.\d{2}.\d{6}$", stored) is not None
)
def test_complexdatetime_usage(self):
@@ -93,40 +95,40 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
log.save()
log1 = LogEntry.objects.get(date=d1)
self.assertEqual(log, log1)
assert log == log1
# create extra 59 log entries for a total of 60
for i in range(1951, 2010):
d = datetime.datetime(i, 1, 1, 0, 0, 1, 999)
LogEntry(date=d).save()
self.assertEqual(LogEntry.objects.count(), 60)
assert LogEntry.objects.count() == 60
# Test ordering
logs = LogEntry.objects.order_by("date")
i = 0
while i < 59:
self.assertTrue(logs[i].date <= logs[i + 1].date)
assert logs[i].date <= logs[i + 1].date
i += 1
logs = LogEntry.objects.order_by("-date")
i = 0
while i < 59:
self.assertTrue(logs[i].date >= logs[i + 1].date)
assert logs[i].date >= logs[i + 1].date
i += 1
# Test searching
logs = LogEntry.objects.filter(date__gte=datetime.datetime(1980, 1, 1))
self.assertEqual(logs.count(), 30)
assert logs.count() == 30
logs = LogEntry.objects.filter(date__lte=datetime.datetime(1980, 1, 1))
self.assertEqual(logs.count(), 30)
assert logs.count() == 30
logs = LogEntry.objects.filter(
date__lte=datetime.datetime(2011, 1, 1),
date__gte=datetime.datetime(2000, 1, 1),
)
self.assertEqual(logs.count(), 10)
assert logs.count() == 10
LogEntry.drop_collection()
@@ -137,17 +139,17 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
logs = list(LogEntry.objects.order_by("date"))
for next_idx, log in enumerate(logs[:-1], start=1):
next_log = logs[next_idx]
self.assertTrue(log.date < next_log.date)
assert log.date < next_log.date
logs = list(LogEntry.objects.order_by("-date"))
for next_idx, log in enumerate(logs[:-1], start=1):
next_log = logs[next_idx]
self.assertTrue(log.date > next_log.date)
assert log.date > next_log.date
logs = LogEntry.objects.filter(
date__lte=datetime.datetime(2015, 1, 1, 0, 0, 0, 10000)
)
self.assertEqual(logs.count(), 4)
assert logs.count() == 4
def test_no_default_value(self):
class Log(Document):
@@ -156,11 +158,11 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
Log.drop_collection()
log = Log()
self.assertIsNone(log.timestamp)
assert log.timestamp is None
log.save()
fetched_log = Log.objects.with_id(log.id)
self.assertIsNone(fetched_log.timestamp)
assert fetched_log.timestamp is None
def test_default_static_value(self):
NOW = datetime.datetime.utcnow()
@@ -171,11 +173,11 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
Log.drop_collection()
log = Log()
self.assertEqual(log.timestamp, NOW)
assert log.timestamp == NOW
log.save()
fetched_log = Log.objects.with_id(log.id)
self.assertEqual(fetched_log.timestamp, NOW)
assert fetched_log.timestamp == NOW
def test_default_callable(self):
NOW = datetime.datetime.utcnow()
@@ -186,8 +188,23 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
Log.drop_collection()
log = Log()
self.assertGreaterEqual(log.timestamp, NOW)
assert log.timestamp >= NOW
log.save()
fetched_log = Log.objects.with_id(log.id)
self.assertGreaterEqual(fetched_log.timestamp, NOW)
assert fetched_log.timestamp >= NOW
def test_setting_bad_value_does_not_raise_unless_validate_is_called(self):
# test regression of #2253
class Log(Document):
timestamp = ComplexDateTimeField()
Log.drop_collection()
log = Log(timestamp="garbage")
with pytest.raises(ValidationError):
log.validate()
with pytest.raises(ValidationError):
log.save()

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import datetime
import six
import pytest
try:
import dateutil
@@ -8,7 +9,6 @@ except ImportError:
dateutil = None
from mongoengine import *
from tests.utils import MongoDBTestCase
@@ -23,7 +23,8 @@ class TestDateField(MongoDBTestCase):
dt = DateField()
md = MyDoc(dt="")
self.assertRaises(ValidationError, md.save)
with pytest.raises(ValidationError):
md.save()
def test_date_from_whitespace_string(self):
"""
@@ -35,7 +36,8 @@ class TestDateField(MongoDBTestCase):
dt = DateField()
md = MyDoc(dt=" ")
self.assertRaises(ValidationError, md.save)
with pytest.raises(ValidationError):
md.save()
def test_default_values_today(self):
"""Ensure that default field values are used when creating
@@ -47,9 +49,9 @@ class TestDateField(MongoDBTestCase):
person = Person()
person.validate()
self.assertEqual(person.day, person.day)
self.assertEqual(person.day, datetime.date.today())
self.assertEqual(person._data["day"], person.day)
assert person.day == person.day
assert person.day == datetime.date.today()
assert person._data["day"] == person.day
def test_date(self):
"""Tests showing pymongo date fields
@@ -67,7 +69,7 @@ class TestDateField(MongoDBTestCase):
log.date = datetime.date.today()
log.save()
log.reload()
self.assertEqual(log.date, datetime.date.today())
assert log.date == datetime.date.today()
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 999)
d2 = datetime.datetime(1970, 1, 1, 0, 0, 1)
@@ -75,27 +77,16 @@ class TestDateField(MongoDBTestCase):
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1.date())
self.assertEqual(log.date, d2.date())
assert log.date == d1.date()
assert log.date == d2.date()
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9999)
d2 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9000)
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1.date())
self.assertEqual(log.date, d2.date())
if not six.PY3:
# Pre UTC dates microseconds below 1000 are dropped
# This does not seem to be true in PY3
d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, 999)
d2 = datetime.datetime(1969, 12, 31, 23, 59, 59)
log.date = d1
log.save()
log.reload()
self.assertEqual(log.date, d1.date())
self.assertEqual(log.date, d2.date())
assert log.date == d1.date()
assert log.date == d2.date()
def test_regular_usage(self):
"""Tests for regular datetime fields"""
@@ -113,35 +104,35 @@ class TestDateField(MongoDBTestCase):
for query in (d1, d1.isoformat(" ")):
log1 = LogEntry.objects.get(date=query)
self.assertEqual(log, log1)
assert log == log1
if dateutil:
log1 = LogEntry.objects.get(date=d1.isoformat("T"))
self.assertEqual(log, log1)
assert log == log1
# create additional 19 log entries for a total of 20
for i in range(1971, 1990):
d = datetime.datetime(i, 1, 1, 0, 0, 1)
LogEntry(date=d).save()
self.assertEqual(LogEntry.objects.count(), 20)
assert LogEntry.objects.count() == 20
# Test ordering
logs = LogEntry.objects.order_by("date")
i = 0
while i < 19:
self.assertTrue(logs[i].date <= logs[i + 1].date)
assert logs[i].date <= logs[i + 1].date
i += 1
logs = LogEntry.objects.order_by("-date")
i = 0
while i < 19:
self.assertTrue(logs[i].date >= logs[i + 1].date)
assert logs[i].date >= logs[i + 1].date
i += 1
# Test searching
logs = LogEntry.objects.filter(date__gte=datetime.datetime(1980, 1, 1))
self.assertEqual(logs.count(), 10)
assert logs.count() == 10
def test_validation(self):
"""Ensure that invalid values cannot be assigned to datetime
@@ -166,6 +157,8 @@ class TestDateField(MongoDBTestCase):
log.validate()
log.time = -1
self.assertRaises(ValidationError, log.validate)
with pytest.raises(ValidationError):
log.validate()
log.time = "ABC"
self.assertRaises(ValidationError, log.validate)
with pytest.raises(ValidationError):
log.validate()

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import datetime as dt
import six
import pytest
try:
import dateutil
@@ -24,7 +25,8 @@ class TestDateTimeField(MongoDBTestCase):
dt = DateTimeField()
md = MyDoc(dt="")
self.assertRaises(ValidationError, md.save)
with pytest.raises(ValidationError):
md.save()
def test_datetime_from_whitespace_string(self):
"""
@@ -36,7 +38,8 @@ class TestDateTimeField(MongoDBTestCase):
dt = DateTimeField()
md = MyDoc(dt=" ")
self.assertRaises(ValidationError, md.save)
with pytest.raises(ValidationError):
md.save()
def test_default_value_utcnow(self):
"""Ensure that default field values are used when creating
@@ -50,11 +53,9 @@ class TestDateTimeField(MongoDBTestCase):
person = Person()
person.validate()
person_created_t0 = person.created
self.assertLess(person.created - utcnow, dt.timedelta(seconds=1))
self.assertEqual(
person_created_t0, person.created
) # make sure it does not change
self.assertEqual(person._data["created"], person.created)
assert person.created - utcnow < dt.timedelta(seconds=1)
assert person_created_t0 == person.created # make sure it does not change
assert person._data["created"] == person.created
def test_handling_microseconds(self):
"""Tests showing pymongo datetime fields handling of microseconds.
@@ -74,7 +75,7 @@ class TestDateTimeField(MongoDBTestCase):
log.date = dt.date.today()
log.save()
log.reload()
self.assertEqual(log.date.date(), dt.date.today())
assert log.date.date() == dt.date.today()
# Post UTC - microseconds are rounded (down) nearest millisecond and
# dropped
@@ -84,8 +85,8 @@ class TestDateTimeField(MongoDBTestCase):
log.date = d1
log.save()
log.reload()
self.assertNotEqual(log.date, d1)
self.assertEqual(log.date, d2)
assert log.date != d1
assert log.date == d2
# Post UTC - microseconds are rounded (down) nearest millisecond
d1 = dt.datetime(1970, 1, 1, 0, 0, 1, 9999)
@@ -93,19 +94,8 @@ class TestDateTimeField(MongoDBTestCase):
log.date = d1
log.save()
log.reload()
self.assertNotEqual(log.date, d1)
self.assertEqual(log.date, d2)
if not six.PY3:
# Pre UTC dates microseconds below 1000 are dropped
# This does not seem to be true in PY3
d1 = dt.datetime(1969, 12, 31, 23, 59, 59, 999)
d2 = dt.datetime(1969, 12, 31, 23, 59, 59)
log.date = d1
log.save()
log.reload()
self.assertNotEqual(log.date, d1)
self.assertEqual(log.date, d2)
assert log.date != d1
assert log.date == d2
def test_regular_usage(self):
"""Tests for regular datetime fields"""
@@ -123,43 +113,43 @@ class TestDateTimeField(MongoDBTestCase):
for query in (d1, d1.isoformat(" ")):
log1 = LogEntry.objects.get(date=query)
self.assertEqual(log, log1)
assert log == log1
if dateutil:
log1 = LogEntry.objects.get(date=d1.isoformat("T"))
self.assertEqual(log, log1)
assert log == log1
# create additional 19 log entries for a total of 20
for i in range(1971, 1990):
d = dt.datetime(i, 1, 1, 0, 0, 1)
LogEntry(date=d).save()
self.assertEqual(LogEntry.objects.count(), 20)
assert LogEntry.objects.count() == 20
# Test ordering
logs = LogEntry.objects.order_by("date")
i = 0
while i < 19:
self.assertTrue(logs[i].date <= logs[i + 1].date)
assert logs[i].date <= logs[i + 1].date
i += 1
logs = LogEntry.objects.order_by("-date")
i = 0
while i < 19:
self.assertTrue(logs[i].date >= logs[i + 1].date)
assert logs[i].date >= logs[i + 1].date
i += 1
# Test searching
logs = LogEntry.objects.filter(date__gte=dt.datetime(1980, 1, 1))
self.assertEqual(logs.count(), 10)
assert logs.count() == 10
logs = LogEntry.objects.filter(date__lte=dt.datetime(1980, 1, 1))
self.assertEqual(logs.count(), 10)
assert logs.count() == 10
logs = LogEntry.objects.filter(
date__lte=dt.datetime(1980, 1, 1), date__gte=dt.datetime(1975, 1, 1)
)
self.assertEqual(logs.count(), 5)
assert logs.count() == 5
def test_datetime_validation(self):
"""Ensure that invalid values cannot be assigned to datetime
@@ -187,15 +177,20 @@ class TestDateTimeField(MongoDBTestCase):
log.validate()
log.time = -1
self.assertRaises(ValidationError, log.validate)
with pytest.raises(ValidationError):
log.validate()
log.time = "ABC"
self.assertRaises(ValidationError, log.validate)
with pytest.raises(ValidationError):
log.validate()
log.time = "2019-05-16 21:GARBAGE:12"
self.assertRaises(ValidationError, log.validate)
with pytest.raises(ValidationError):
log.validate()
log.time = "2019-05-16 21:42:57.GARBAGE"
self.assertRaises(ValidationError, log.validate)
with pytest.raises(ValidationError):
log.validate()
log.time = "2019-05-16 21:42:57.123.456"
self.assertRaises(ValidationError, log.validate)
with pytest.raises(ValidationError):
log.validate()
def test_parse_datetime_as_str(self):
class DTDoc(Document):
@@ -206,15 +201,16 @@ class TestDateTimeField(MongoDBTestCase):
# make sure that passing a parsable datetime works
dtd = DTDoc()
dtd.date = date_str
self.assertIsInstance(dtd.date, six.string_types)
assert isinstance(dtd.date, str)
dtd.save()
dtd.reload()
self.assertIsInstance(dtd.date, dt.datetime)
self.assertEqual(str(dtd.date), date_str)
assert isinstance(dtd.date, dt.datetime)
assert str(dtd.date) == date_str
dtd.date = "January 1st, 9999999999"
self.assertRaises(ValidationError, dtd.validate)
with pytest.raises(ValidationError):
dtd.validate()
class TestDateTimeTzAware(MongoDBTestCase):
@@ -235,4 +231,4 @@ class TestDateTimeTzAware(MongoDBTestCase):
log = LogEntry.objects.first()
log.time = dt.datetime(2013, 1, 1, 0, 0, 0)
self.assertEqual(["time"], log._changed_fields)
assert ["time"] == log._changed_fields

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
from decimal import Decimal
from mongoengine import *
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase
@@ -18,21 +19,26 @@ class TestDecimalField(MongoDBTestCase):
Person(height=Decimal("1.89")).save()
person = Person.objects.first()
self.assertEqual(person.height, Decimal("1.89"))
assert person.height == Decimal("1.89")
person.height = "2.0"
person.save()
person.height = 0.01
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
person.height = Decimal("0.01")
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
person.height = Decimal("4.0")
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
person.height = "something invalid"
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
person_2 = Person(height="something invalid")
self.assertRaises(ValidationError, person_2.validate)
with pytest.raises(ValidationError):
person_2.validate()
def test_comparison(self):
class Person(Document):
@@ -45,11 +51,11 @@ class TestDecimalField(MongoDBTestCase):
Person(money=8).save()
Person(money=10).save()
self.assertEqual(2, Person.objects(money__gt=Decimal("7")).count())
self.assertEqual(2, Person.objects(money__gt=7).count())
self.assertEqual(2, Person.objects(money__gt="7").count())
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()
self.assertEqual(3, Person.objects(money__gte="7").count())
assert 3 == Person.objects(money__gte="7").count()
def test_storage(self):
class Person(Document):
@@ -87,7 +93,7 @@ class TestDecimalField(MongoDBTestCase):
]
expected.extend(expected)
actual = list(Person.objects.exclude("id").as_pymongo())
self.assertEqual(expected, actual)
assert expected == actual
# How it comes out locally
expected = [
@@ -101,4 +107,4 @@ class TestDecimalField(MongoDBTestCase):
expected.extend(expected)
for field_name in ["float_value", "string_value"]:
actual = list(Person.objects().scalar(field_name))
self.assertEqual(expected, actual)
assert expected == actual

View File

@@ -1,6 +1,10 @@
# -*- coding: utf-8 -*-
from bson import InvalidDocument
import pytest
from mongoengine import *
from mongoengine.base import BaseDict
from mongoengine.mongodb_support import MONGODB_36, get_mongodb_version
from tests.utils import MongoDBTestCase, get_as_pymongo
@@ -14,7 +18,57 @@ class TestDictField(MongoDBTestCase):
info = {"testkey": "testvalue"}
post = BlogPost(info=info).save()
self.assertEqual(get_as_pymongo(post), {"_id": post.id, "info": info})
assert get_as_pymongo(post) == {"_id": post.id, "info": info}
def test_validate_invalid_type(self):
class BlogPost(Document):
info = DictField()
BlogPost.drop_collection()
invalid_infos = ["my post", ["test", "test"], {1: "test"}]
for invalid_info in invalid_infos:
with pytest.raises(ValidationError):
BlogPost(info=invalid_info).validate()
def test_keys_with_dots_or_dollars(self):
class BlogPost(Document):
info = DictField()
BlogPost.drop_collection()
post = BlogPost()
post.info = {"$title": "test"}
with pytest.raises(ValidationError):
post.validate()
post.info = {"nested": {"$title": "test"}}
with pytest.raises(ValidationError):
post.validate()
post.info = {"$title.test": "test"}
with pytest.raises(ValidationError):
post.validate()
post.info = {"nested": {"the.title": "test"}}
if get_mongodb_version() < MONGODB_36:
# MongoDB < 3.6 rejects dots
# To avoid checking the mongodb version from the DictField class
# we rely on MongoDB to reject the data during the save
post.validate()
with pytest.raises(InvalidDocument):
post.save()
else:
post.validate()
post.info = {"dollar_and_dot": {"te$st.test": "test"}}
if get_mongodb_version() < MONGODB_36:
post.validate()
with pytest.raises(InvalidDocument):
post.save()
else:
post.validate()
def test_general_things(self):
"""Ensure that dict types work as expected."""
@@ -24,29 +78,7 @@ class TestDictField(MongoDBTestCase):
BlogPost.drop_collection()
post = BlogPost()
post.info = "my post"
self.assertRaises(ValidationError, post.validate)
post.info = ["test", "test"]
self.assertRaises(ValidationError, post.validate)
post.info = {"$title": "test"}
self.assertRaises(ValidationError, post.validate)
post.info = {"nested": {"$title": "test"}}
self.assertRaises(ValidationError, post.validate)
post.info = {"the.title": "test"}
self.assertRaises(ValidationError, post.validate)
post.info = {"nested": {"the.title": "test"}}
self.assertRaises(ValidationError, post.validate)
post.info = {1: "test"}
self.assertRaises(ValidationError, post.validate)
post.info = {"title": "test"}
post = BlogPost(info={"title": "test"})
post.save()
post = BlogPost()
@@ -61,33 +93,27 @@ class TestDictField(MongoDBTestCase):
post.info = {"details": {"test": 3}}
post.save()
self.assertEqual(BlogPost.objects.count(), 4)
self.assertEqual(BlogPost.objects.filter(info__title__exact="test").count(), 1)
self.assertEqual(
BlogPost.objects.filter(info__details__test__exact="test").count(), 1
)
assert BlogPost.objects.count() == 4
assert BlogPost.objects.filter(info__title__exact="test").count() == 1
assert BlogPost.objects.filter(info__details__test__exact="test").count() == 1
post = BlogPost.objects.filter(info__title__exact="dollar_sign").first()
self.assertIn("te$t", post["info"]["details"])
assert "te$t" in post["info"]["details"]
# Confirm handles non strings or non existing keys
self.assertEqual(
BlogPost.objects.filter(info__details__test__exact=5).count(), 0
)
self.assertEqual(
BlogPost.objects.filter(info__made_up__test__exact="test").count(), 0
)
assert BlogPost.objects.filter(info__details__test__exact=5).count() == 0
assert BlogPost.objects.filter(info__made_up__test__exact="test").count() == 0
post = BlogPost.objects.create(info={"title": "original"})
post.info.update({"title": "updated"})
post.save()
post.reload()
self.assertEqual("updated", post.info["title"])
assert "updated" == post.info["title"]
post.info.setdefault("authors", [])
post.save()
post.reload()
self.assertEqual([], post.info["authors"])
assert [] == post.info["authors"]
def test_dictfield_dump_document(self):
"""Ensure a DictField can handle another document's dump."""
@@ -114,10 +140,8 @@ class TestDictField(MongoDBTestCase):
).save()
doc = Doc(field=to_embed.to_mongo().to_dict())
doc.save()
self.assertIsInstance(doc.field, dict)
self.assertEqual(
doc.field, {"_id": 2, "recursive": {"_id": 1, "recursive": {}}}
)
assert isinstance(doc.field, dict)
assert doc.field == {"_id": 2, "recursive": {"_id": 1, "recursive": {}}}
# Same thing with a Document with a _cls field
to_embed_recursive = ToEmbedChild(id=1).save()
to_embed_child = ToEmbedChild(
@@ -125,7 +149,7 @@ class TestDictField(MongoDBTestCase):
).save()
doc = Doc(field=to_embed_child.to_mongo().to_dict())
doc.save()
self.assertIsInstance(doc.field, dict)
assert isinstance(doc.field, dict)
expected = {
"_id": 2,
"_cls": "ToEmbedParent.ToEmbedChild",
@@ -135,7 +159,7 @@ class TestDictField(MongoDBTestCase):
"recursive": {},
},
}
self.assertEqual(doc.field, expected)
assert doc.field == expected
def test_dictfield_strict(self):
"""Ensure that dict field handles validation if provided a strict field type."""
@@ -150,7 +174,7 @@ class TestDictField(MongoDBTestCase):
e.save()
# try creating an invalid mapping
with self.assertRaises(ValidationError):
with pytest.raises(ValidationError):
e.mapping["somestring"] = "abc"
e.save()
@@ -184,22 +208,21 @@ class TestDictField(MongoDBTestCase):
e.save()
e2 = Simple.objects.get(id=e.id)
self.assertIsInstance(e2.mapping["somestring"], StringSetting)
self.assertIsInstance(e2.mapping["someint"], IntegerSetting)
assert isinstance(e2.mapping["somestring"], StringSetting)
assert isinstance(e2.mapping["someint"], IntegerSetting)
# Test querying
self.assertEqual(Simple.objects.filter(mapping__someint__value=42).count(), 1)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__number=1).count(), 1
assert Simple.objects.filter(mapping__someint__value=42).count() == 1
assert Simple.objects.filter(mapping__nested_dict__number=1).count() == 1
assert (
Simple.objects.filter(mapping__nested_dict__complex__value=42).count() == 1
)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__complex__value=42).count(), 1
assert (
Simple.objects.filter(mapping__nested_dict__list__0__value=42).count() == 1
)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__list__0__value=42).count(), 1
)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__list__1__value="foo").count(), 1
assert (
Simple.objects.filter(mapping__nested_dict__list__1__value="foo").count()
== 1
)
# Confirm can update
@@ -207,11 +230,13 @@ class TestDictField(MongoDBTestCase):
Simple.objects().update(
set__mapping__nested_dict__list__1=StringSetting(value="Boo")
)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__list__1__value="foo").count(), 0
assert (
Simple.objects.filter(mapping__nested_dict__list__1__value="foo").count()
== 0
)
self.assertEqual(
Simple.objects.filter(mapping__nested_dict__list__1__value="Boo").count(), 1
assert (
Simple.objects.filter(mapping__nested_dict__list__1__value="Boo").count()
== 1
)
def test_push_dict(self):
@@ -221,12 +246,12 @@ class TestDictField(MongoDBTestCase):
doc = MyModel(events=[{"a": 1}]).save()
raw_doc = get_as_pymongo(doc)
expected_raw_doc = {"_id": doc.id, "events": [{"a": 1}]}
self.assertEqual(raw_doc, expected_raw_doc)
assert raw_doc == expected_raw_doc
MyModel.objects(id=doc.id).update(push__events={})
raw_doc = get_as_pymongo(doc)
expected_raw_doc = {"_id": doc.id, "events": [{"a": 1}, {}]}
self.assertEqual(raw_doc, expected_raw_doc)
assert raw_doc == expected_raw_doc
def test_ensure_unique_default_instances(self):
"""Ensure that every field has it's own unique default instance."""
@@ -239,8 +264,8 @@ class TestDictField(MongoDBTestCase):
d1.data["foo"] = "bar"
d1.data2["foo"] = "bar"
d2 = D()
self.assertEqual(d2.data, {})
self.assertEqual(d2.data2, {})
assert d2.data == {}
assert d2.data2 == {}
def test_dict_field_invalid_dict_value(self):
class DictFieldTest(Document):
@@ -250,11 +275,13 @@ class TestDictField(MongoDBTestCase):
test = DictFieldTest(dictionary=None)
test.dictionary # Just access to test getter
self.assertRaises(ValidationError, test.validate)
with pytest.raises(ValidationError):
test.validate()
test = DictFieldTest(dictionary=False)
test.dictionary # Just access to test getter
self.assertRaises(ValidationError, test.validate)
with pytest.raises(ValidationError):
test.validate()
def test_dict_field_raises_validation_error_if_wrongly_assign_embedded_doc(self):
class DictFieldTest(Document):
@@ -267,12 +294,12 @@ class TestDictField(MongoDBTestCase):
embed = Embedded(name="garbage")
doc = DictFieldTest(dictionary=embed)
with self.assertRaises(ValidationError) as ctx_err:
with pytest.raises(ValidationError) as exc_info:
doc.validate()
self.assertIn("'dictionary'", str(ctx_err.exception))
self.assertIn(
"Only dictionaries may be used in a DictField", str(ctx_err.exception)
)
error_msg = str(exc_info.value)
assert "'dictionary'" in error_msg
assert "Only dictionaries may be used in a DictField" in error_msg
def test_atomic_update_dict_field(self):
"""Ensure that the entire DictField can be atomically updated."""
@@ -287,11 +314,11 @@ class TestDictField(MongoDBTestCase):
e.save()
e.update(set__mapping={"ints": [3, 4]})
e.reload()
self.assertEqual(BaseDict, type(e.mapping))
self.assertEqual({"ints": [3, 4]}, e.mapping)
assert isinstance(e.mapping, BaseDict)
assert {"ints": [3, 4]} == e.mapping
# try creating an invalid mapping
with self.assertRaises(ValueError):
with pytest.raises(ValueError):
e.update(set__mapping={"somestrings": ["foo", "bar"]})
def test_dictfield_with_referencefield_complex_nesting_cases(self):
@@ -329,13 +356,13 @@ class TestDictField(MongoDBTestCase):
e.save()
s = Simple.objects.first()
self.assertIsInstance(s.mapping0["someint"], Doc)
self.assertIsInstance(s.mapping1["someint"], Doc)
self.assertIsInstance(s.mapping2["someint"][0], Doc)
self.assertIsInstance(s.mapping3["someint"][0], Doc)
self.assertIsInstance(s.mapping4["someint"]["d"], Doc)
self.assertIsInstance(s.mapping5["someint"]["d"], Doc)
self.assertIsInstance(s.mapping6["someint"][0]["d"], Doc)
self.assertIsInstance(s.mapping7["someint"][0]["d"], Doc)
self.assertIsInstance(s.mapping8["someint"][0]["d"][0], Doc)
self.assertIsInstance(s.mapping9["someint"][0]["d"][0], Doc)
assert isinstance(s.mapping0["someint"], Doc)
assert isinstance(s.mapping1["someint"], Doc)
assert isinstance(s.mapping2["someint"][0], Doc)
assert isinstance(s.mapping3["someint"][0], Doc)
assert isinstance(s.mapping4["someint"]["d"], Doc)
assert isinstance(s.mapping5["someint"]["d"], Doc)
assert isinstance(s.mapping6["someint"][0]["d"], Doc)
assert isinstance(s.mapping7["someint"][0]["d"], Doc)
assert isinstance(s.mapping8["someint"][0]["d"][0], Doc)
assert isinstance(s.mapping9["someint"][0]["d"][0], Doc)

View File

@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
import sys
from unittest import SkipTest
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase
@@ -27,7 +27,8 @@ class TestEmailField(MongoDBTestCase):
user.validate()
user = User(email="ross@example.com.")
self.assertRaises(ValidationError, user.validate)
with pytest.raises(ValidationError):
user.validate()
# unicode domain
user = User(email=u"user@пример.рф")
@@ -35,24 +36,22 @@ class TestEmailField(MongoDBTestCase):
# invalid unicode domain
user = User(email=u"user@пример")
self.assertRaises(ValidationError, user.validate)
with pytest.raises(ValidationError):
user.validate()
# invalid data type
user = User(email=123)
self.assertRaises(ValidationError, user.validate)
with pytest.raises(ValidationError):
user.validate()
def test_email_field_unicode_user(self):
# Don't run this test on pypy3, which doesn't support unicode regex:
# https://bitbucket.org/pypy/pypy/issues/1821/regular-expression-doesnt-find-unicode
if sys.version_info[:2] == (3, 2):
raise SkipTest("unicode email addresses are not supported on PyPy 3")
class User(Document):
email = EmailField()
# unicode user shouldn't validate by default...
user = User(email=u"Dörte@Sörensen.example.com")
self.assertRaises(ValidationError, user.validate)
with pytest.raises(ValidationError):
user.validate()
# ...but it should be fine with allow_utf8_user set to True
class User(Document):
@@ -67,7 +66,8 @@ class TestEmailField(MongoDBTestCase):
# localhost domain shouldn't validate by default...
user = User(email="me@localhost")
self.assertRaises(ValidationError, user.validate)
with pytest.raises(ValidationError):
user.validate()
# ...but it should be fine if it's whitelisted
class User(Document):
@@ -82,9 +82,10 @@ class TestEmailField(MongoDBTestCase):
invalid_idn = ".google.com"
user = User(email="me@%s" % invalid_idn)
with self.assertRaises(ValidationError) as ctx_err:
with pytest.raises(ValidationError) as exc_info:
user.validate()
self.assertIn("domain failed IDN encoding", str(ctx_err.exception))
assert "domain failed IDN encoding" in str(exc_info.value)
def test_email_field_ip_domain(self):
class User(Document):
@@ -96,13 +97,16 @@ class TestEmailField(MongoDBTestCase):
# IP address as a domain shouldn't validate by default...
user = User(email=valid_ipv4)
self.assertRaises(ValidationError, user.validate)
with pytest.raises(ValidationError):
user.validate()
user = User(email=valid_ipv6)
self.assertRaises(ValidationError, user.validate)
with pytest.raises(ValidationError):
user.validate()
user = User(email=invalid_ip)
self.assertRaises(ValidationError, user.validate)
with pytest.raises(ValidationError):
user.validate()
# ...but it should be fine with allow_ip_domain set to True
class User(Document):
@@ -116,7 +120,8 @@ class TestEmailField(MongoDBTestCase):
# invalid IP should still fail validation
user = User(email=invalid_ip)
self.assertRaises(ValidationError, user.validate)
with pytest.raises(ValidationError):
user.validate()
def test_email_field_honors_regex(self):
class User(Document):
@@ -124,8 +129,9 @@ class TestEmailField(MongoDBTestCase):
# Fails regex validation
user = User(email="me@foo.com")
self.assertRaises(ValidationError, user.validate)
with pytest.raises(ValidationError):
user.validate()
# Passes regex validation
user = User(email="me@example.com")
self.assertIsNone(user.validate())
assert user.validate() is None

View File

@@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
import pytest
from mongoengine import (
Document,
StringField,
ValidationError,
EmbeddedDocument,
EmbeddedDocumentField,
InvalidQueryError,
LookUpError,
IntField,
GenericEmbeddedDocumentField,
IntField,
InvalidQueryError,
ListField,
EmbeddedDocumentListField,
ReferenceField,
LookUpError,
StringField,
ValidationError,
)
from tests.utils import MongoDBTestCase
@@ -23,13 +23,13 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
name = StringField()
field = EmbeddedDocumentField(MyDoc)
self.assertEqual(field.document_type_obj, MyDoc)
assert field.document_type_obj == MyDoc
field2 = EmbeddedDocumentField("MyDoc")
self.assertEqual(field2.document_type_obj, "MyDoc")
assert field2.document_type_obj == "MyDoc"
def test___init___throw_error_if_document_type_is_not_EmbeddedDocument(self):
with self.assertRaises(ValidationError):
with pytest.raises(ValidationError):
EmbeddedDocumentField(dict)
def test_document_type_throw_error_if_not_EmbeddedDocument_subclass(self):
@@ -37,11 +37,11 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
name = StringField()
emb = EmbeddedDocumentField("MyDoc")
with self.assertRaises(ValidationError) as ctx:
with pytest.raises(ValidationError) as exc_info:
emb.document_type
self.assertIn(
"Invalid embedded document class provided to an EmbeddedDocumentField",
str(ctx.exception),
assert (
"Invalid embedded document class provided to an EmbeddedDocumentField"
in str(exc_info.value)
)
def test_embedded_document_field_only_allow_subclasses_of_embedded_document(self):
@@ -49,12 +49,12 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
class MyDoc(Document):
name = StringField()
with self.assertRaises(ValidationError):
with pytest.raises(ValidationError):
class MyFailingDoc(Document):
emb = EmbeddedDocumentField(MyDoc)
with self.assertRaises(ValidationError):
with pytest.raises(ValidationError):
class MyFailingdoc2(Document):
emb = EmbeddedDocumentField("MyDoc")
@@ -73,24 +73,24 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
p = Person(settings=AdminSettings(foo1="bar1", foo2="bar2"), name="John").save()
# Test non exiting attribute
with self.assertRaises(InvalidQueryError) as ctx_err:
with pytest.raises(InvalidQueryError) as exc_info:
Person.objects(settings__notexist="bar").first()
self.assertEqual(unicode(ctx_err.exception), u'Cannot resolve field "notexist"')
assert str(exc_info.value) == u'Cannot resolve field "notexist"'
with self.assertRaises(LookUpError):
with pytest.raises(LookUpError):
Person.objects.only("settings.notexist")
# Test existing attribute
self.assertEqual(Person.objects(settings__foo1="bar1").first().id, p.id)
assert Person.objects(settings__foo1="bar1").first().id == p.id
only_p = Person.objects.only("settings.foo1").first()
self.assertEqual(only_p.settings.foo1, p.settings.foo1)
self.assertIsNone(only_p.settings.foo2)
self.assertIsNone(only_p.name)
assert only_p.settings.foo1 == p.settings.foo1
assert only_p.settings.foo2 is None
assert only_p.name is None
exclude_p = Person.objects.exclude("settings.foo1").first()
self.assertIsNone(exclude_p.settings.foo1)
self.assertEqual(exclude_p.settings.foo2, p.settings.foo2)
self.assertEqual(exclude_p.name, p.name)
assert exclude_p.settings.foo1 is None
assert exclude_p.settings.foo2 == p.settings.foo2
assert exclude_p.name == p.name
def test_query_embedded_document_attribute_with_inheritance(self):
class BaseSettings(EmbeddedDocument):
@@ -109,17 +109,17 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
p.save()
# Test non exiting attribute
with self.assertRaises(InvalidQueryError) as ctx_err:
self.assertEqual(Person.objects(settings__notexist="bar").first().id, p.id)
self.assertEqual(unicode(ctx_err.exception), u'Cannot resolve field "notexist"')
with pytest.raises(InvalidQueryError) as exc_info:
assert Person.objects(settings__notexist="bar").first().id == p.id
assert str(exc_info.value) == u'Cannot resolve field "notexist"'
# Test existing attribute
self.assertEqual(Person.objects(settings__base_foo="basefoo").first().id, p.id)
self.assertEqual(Person.objects(settings__sub_foo="subfoo").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
only_p = Person.objects.only("settings.base_foo", "settings._cls").first()
self.assertEqual(only_p.settings.base_foo, "basefoo")
self.assertIsNone(only_p.settings.sub_foo)
assert only_p.settings.base_foo == "basefoo"
assert only_p.settings.sub_foo is None
def test_query_list_embedded_document_with_inheritance(self):
class Post(EmbeddedDocument):
@@ -139,14 +139,14 @@ class TestEmbeddedDocumentField(MongoDBTestCase):
record_text = Record(posts=[TextPost(content="a", title="foo")]).save()
records = list(Record.objects(posts__author=record_movie.posts[0].author))
self.assertEqual(len(records), 1)
self.assertEqual(records[0].id, record_movie.id)
assert len(records) == 1
assert records[0].id == record_movie.id
records = list(Record.objects(posts__content=record_text.posts[0].content))
self.assertEqual(len(records), 1)
self.assertEqual(records[0].id, record_text.id)
assert len(records) == 1
assert records[0].id == record_text.id
self.assertEqual(Record.objects(posts__title="foo").count(), 2)
assert Record.objects(posts__title="foo").count() == 2
class TestGenericEmbeddedDocumentField(MongoDBTestCase):
@@ -169,13 +169,13 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
person.save()
person = Person.objects.first()
self.assertIsInstance(person.like, Car)
assert isinstance(person.like, Car)
person.like = Dish(food="arroz", number=15)
person.save()
person = Person.objects.first()
self.assertIsInstance(person.like, Dish)
assert isinstance(person.like, Dish)
def test_generic_embedded_document_choices(self):
"""Ensure you can limit GenericEmbeddedDocument choices."""
@@ -195,13 +195,14 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
person = Person(name="Test User")
person.like = Car(name="Fiat")
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
person.like = Dish(food="arroz", number=15)
person.save()
person = Person.objects.first()
self.assertIsInstance(person.like, Dish)
assert isinstance(person.like, Dish)
def test_generic_list_embedded_document_choices(self):
"""Ensure you can limit GenericEmbeddedDocument choices inside
@@ -223,13 +224,14 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
person = Person(name="Test User")
person.likes = [Car(name="Fiat")]
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
person.likes = [Dish(food="arroz", number=15)]
person.save()
person = Person.objects.first()
self.assertIsInstance(person.likes[0], Dish)
assert isinstance(person.likes[0], Dish)
def test_choices_validation_documents(self):
"""
@@ -265,7 +267,8 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
# Single Entry Failure
post = BlogPost(comments=[ModeratorComments(author="mod1", message="message1")])
self.assertRaises(ValidationError, post.save)
with pytest.raises(ValidationError):
post.save()
# Mixed Entry Failure
post = BlogPost(
@@ -274,7 +277,8 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
UserComments(author="user2", message="message2"),
]
)
self.assertRaises(ValidationError, post.save)
with pytest.raises(ValidationError):
post.save()
def test_choices_validation_documents_inheritance(self):
"""
@@ -313,16 +317,16 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
p2 = Person(settings=NonAdminSettings(foo2="bar2")).save()
# Test non exiting attribute
with self.assertRaises(InvalidQueryError) as ctx_err:
with pytest.raises(InvalidQueryError) as exc_info:
Person.objects(settings__notexist="bar").first()
self.assertEqual(unicode(ctx_err.exception), u'Cannot resolve field "notexist"')
assert str(exc_info.value) == u'Cannot resolve field "notexist"'
with self.assertRaises(LookUpError):
with pytest.raises(LookUpError):
Person.objects.only("settings.notexist")
# Test existing attribute
self.assertEqual(Person.objects(settings__foo1="bar1").first().id, p1.id)
self.assertEqual(Person.objects(settings__foo2="bar2").first().id, p2.id)
assert Person.objects(settings__foo1="bar1").first().id == p1.id
assert Person.objects(settings__foo2="bar2").first().id == p2.id
def test_query_generic_embedded_document_attribute_with_inheritance(self):
class BaseSettings(EmbeddedDocument):
@@ -341,10 +345,10 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
p.save()
# Test non exiting attribute
with self.assertRaises(InvalidQueryError) as ctx_err:
self.assertEqual(Person.objects(settings__notexist="bar").first().id, p.id)
self.assertEqual(unicode(ctx_err.exception), u'Cannot resolve field "notexist"')
with pytest.raises(InvalidQueryError) as exc_info:
assert Person.objects(settings__notexist="bar").first().id == p.id
assert str(exc_info.value) == u'Cannot resolve field "notexist"'
# Test existing attribute
self.assertEqual(Person.objects(settings__base_foo="basefoo").first().id, p.id)
self.assertEqual(Person.objects(settings__sub_foo="subfoo").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

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,15 @@
# -*- coding: utf-8 -*-
import copy
import os
import unittest
import tempfile
import unittest
from io import BytesIO
import gridfs
import six
import pytest
from nose.plugins.skip import SkipTest
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.python_support import StringIO
try:
from PIL import Image
@@ -21,6 +20,8 @@ except ImportError:
from tests.utils import MongoDBTestCase
require_pil = pytest.mark.skipif(not HAS_PIL, reason="PIL not installed")
TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__), "mongoengine.png")
TEST_IMAGE2_PATH = os.path.join(os.path.dirname(__file__), "mongodb_leaf.png")
@@ -28,14 +29,14 @@ TEST_IMAGE2_PATH = os.path.join(os.path.dirname(__file__), "mongodb_leaf.png")
def get_file(path):
"""Use a BytesIO instead of a file to allow
to have a one-liner and avoid that the file remains opened"""
bytes_io = StringIO()
bytes_io = BytesIO()
with open(path, "rb") as f:
bytes_io.write(f.read())
bytes_io.seek(0)
return bytes_io
class FileTest(MongoDBTestCase):
class TestFileField(MongoDBTestCase):
def tearDown(self):
self.db.drop_collection("fs.files")
self.db.drop_collection("fs.chunks")
@@ -56,7 +57,7 @@ class FileTest(MongoDBTestCase):
PutFile.drop_collection()
text = six.b("Hello, World!")
text = "Hello, World!".encode("latin-1")
content_type = "text/plain"
putfile = PutFile()
@@ -64,13 +65,13 @@ class FileTest(MongoDBTestCase):
putfile.save()
result = PutFile.objects.first()
self.assertEqual(putfile, result)
self.assertEqual(
"%s" % result.the_file,
"<GridFSProxy: hello (%s)>" % result.the_file.grid_id,
assert putfile == result
assert (
"%s" % result.the_file
== "<GridFSProxy: hello (%s)>" % result.the_file.grid_id
)
self.assertEqual(result.the_file.read(), text)
self.assertEqual(result.the_file.content_type, content_type)
assert result.the_file.read() == text
assert result.the_file.content_type == content_type
result.the_file.delete() # Remove file from GridFS
PutFile.objects.delete()
@@ -78,16 +79,16 @@ class FileTest(MongoDBTestCase):
PutFile.drop_collection()
putfile = PutFile()
putstring = StringIO()
putstring = BytesIO()
putstring.write(text)
putstring.seek(0)
putfile.the_file.put(putstring, content_type=content_type)
putfile.save()
result = PutFile.objects.first()
self.assertEqual(putfile, result)
self.assertEqual(result.the_file.read(), text)
self.assertEqual(result.the_file.content_type, content_type)
assert putfile == result
assert result.the_file.read() == text
assert result.the_file.content_type == content_type
result.the_file.delete()
def test_file_fields_stream(self):
@@ -99,8 +100,8 @@ class FileTest(MongoDBTestCase):
StreamFile.drop_collection()
text = six.b("Hello, World!")
more_text = six.b("Foo Bar")
text = "Hello, World!".encode("latin-1")
more_text = "Foo Bar".encode("latin-1")
content_type = "text/plain"
streamfile = StreamFile()
@@ -111,19 +112,19 @@ class FileTest(MongoDBTestCase):
streamfile.save()
result = StreamFile.objects.first()
self.assertEqual(streamfile, result)
self.assertEqual(result.the_file.read(), text + more_text)
self.assertEqual(result.the_file.content_type, content_type)
assert streamfile == result
assert result.the_file.read() == text + more_text
assert result.the_file.content_type == content_type
result.the_file.seek(0)
self.assertEqual(result.the_file.tell(), 0)
self.assertEqual(result.the_file.read(len(text)), text)
self.assertEqual(result.the_file.tell(), len(text))
self.assertEqual(result.the_file.read(len(more_text)), more_text)
self.assertEqual(result.the_file.tell(), len(text + more_text))
assert result.the_file.tell() == 0
assert result.the_file.read(len(text)) == text
assert result.the_file.tell() == len(text)
assert result.the_file.read(len(more_text)) == more_text
assert result.the_file.tell() == len(text + more_text)
result.the_file.delete()
# Ensure deleted file returns None
self.assertTrue(result.the_file.read() is None)
assert result.the_file.read() is None
def test_file_fields_stream_after_none(self):
"""Ensure that a file field can be written to after it has been saved as
@@ -135,9 +136,8 @@ class FileTest(MongoDBTestCase):
StreamFile.drop_collection()
text = six.b("Hello, World!")
more_text = six.b("Foo Bar")
content_type = "text/plain"
text = "Hello, World!".encode("latin-1")
more_text = "Foo Bar".encode("latin-1")
streamfile = StreamFile()
streamfile.save()
@@ -148,26 +148,26 @@ class FileTest(MongoDBTestCase):
streamfile.save()
result = StreamFile.objects.first()
self.assertEqual(streamfile, result)
self.assertEqual(result.the_file.read(), text + more_text)
# self.assertEqual(result.the_file.content_type, content_type)
assert streamfile == result
assert result.the_file.read() == text + more_text
# assert result.the_file.content_type == content_type
result.the_file.seek(0)
self.assertEqual(result.the_file.tell(), 0)
self.assertEqual(result.the_file.read(len(text)), text)
self.assertEqual(result.the_file.tell(), len(text))
self.assertEqual(result.the_file.read(len(more_text)), more_text)
self.assertEqual(result.the_file.tell(), len(text + more_text))
assert result.the_file.tell() == 0
assert result.the_file.read(len(text)) == text
assert result.the_file.tell() == len(text)
assert result.the_file.read(len(more_text)) == more_text
assert result.the_file.tell() == len(text + more_text)
result.the_file.delete()
# Ensure deleted file returns None
self.assertTrue(result.the_file.read() is None)
assert result.the_file.read() is None
def test_file_fields_set(self):
class SetFile(Document):
the_file = FileField()
text = six.b("Hello, World!")
more_text = six.b("Foo Bar")
text = "Hello, World!".encode("latin-1")
more_text = "Foo Bar".encode("latin-1")
SetFile.drop_collection()
@@ -176,16 +176,16 @@ class FileTest(MongoDBTestCase):
setfile.save()
result = SetFile.objects.first()
self.assertEqual(setfile, result)
self.assertEqual(result.the_file.read(), text)
assert setfile == result
assert result.the_file.read() == text
# Try replacing file with new one
result.the_file.replace(more_text)
result.save()
result = SetFile.objects.first()
self.assertEqual(setfile, result)
self.assertEqual(result.the_file.read(), more_text)
assert setfile == result
assert result.the_file.read() == more_text
result.the_file.delete()
def test_file_field_no_default(self):
@@ -195,7 +195,7 @@ class FileTest(MongoDBTestCase):
GridDocument.drop_collection()
with tempfile.TemporaryFile() as f:
f.write(six.b("Hello World!"))
f.write("Hello World!".encode("latin-1"))
f.flush()
# Test without default
@@ -205,28 +205,28 @@ class FileTest(MongoDBTestCase):
doc_b = GridDocument.objects.with_id(doc_a.id)
doc_b.the_file.replace(f, filename="doc_b")
doc_b.save()
self.assertNotEqual(doc_b.the_file.grid_id, None)
assert doc_b.the_file.grid_id is not None
# Test it matches
doc_c = GridDocument.objects.with_id(doc_b.id)
self.assertEqual(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
doc_d = GridDocument(the_file=six.b(""))
doc_d = GridDocument(the_file="".encode("latin-1"))
doc_d.save()
doc_e = GridDocument.objects.with_id(doc_d.id)
self.assertEqual(doc_d.the_file.grid_id, doc_e.the_file.grid_id)
assert doc_d.the_file.grid_id == doc_e.the_file.grid_id
doc_e.the_file.replace(f, filename="doc_e")
doc_e.save()
doc_f = GridDocument.objects.with_id(doc_e.id)
self.assertEqual(doc_e.the_file.grid_id, doc_f.the_file.grid_id)
assert doc_e.the_file.grid_id == doc_f.the_file.grid_id
db = GridDocument._get_db()
grid_fs = gridfs.GridFS(db)
self.assertEqual(["doc_b", "doc_e"], grid_fs.list())
assert ["doc_b", "doc_e"] == grid_fs.list()
def test_file_uniqueness(self):
"""Ensure that each instance of a FileField is unique
@@ -239,15 +239,15 @@ class FileTest(MongoDBTestCase):
# First instance
test_file = TestFile()
test_file.name = "Hello, World!"
test_file.the_file.put(six.b("Hello, World!"))
test_file.the_file.put("Hello, World!".encode("latin-1"))
test_file.save()
# Second instance
test_file_dupe = TestFile()
data = test_file_dupe.the_file.read() # Should be None
self.assertNotEqual(test_file.name, test_file_dupe.name)
self.assertNotEqual(test_file.the_file.read(), data)
assert test_file.name != test_file_dupe.name
assert test_file.the_file.read() != data
TestFile.drop_collection()
@@ -268,8 +268,8 @@ class FileTest(MongoDBTestCase):
marmot.save()
marmot = Animal.objects.get()
self.assertEqual(marmot.photo.content_type, "image/jpeg")
self.assertEqual(marmot.photo.foo, "bar")
assert marmot.photo.content_type == "image/jpeg"
assert marmot.photo.foo == "bar"
def test_file_reassigning(self):
class TestFile(Document):
@@ -278,12 +278,12 @@ class FileTest(MongoDBTestCase):
TestFile.drop_collection()
test_file = TestFile(the_file=get_file(TEST_IMAGE_PATH)).save()
self.assertEqual(test_file.the_file.get().length, 8313)
assert test_file.the_file.get().length == 8313
test_file = TestFile.objects.first()
test_file.the_file = get_file(TEST_IMAGE2_PATH)
test_file.save()
self.assertEqual(test_file.the_file.get().length, 4971)
assert test_file.the_file.get().length == 4971
def test_file_boolean(self):
"""Ensure that a boolean test of a FileField indicates its presence
@@ -295,13 +295,15 @@ class FileTest(MongoDBTestCase):
TestFile.drop_collection()
test_file = TestFile()
self.assertFalse(bool(test_file.the_file))
test_file.the_file.put(six.b("Hello, World!"), content_type="text/plain")
assert not bool(test_file.the_file)
test_file.the_file.put(
"Hello, World!".encode("latin-1"), content_type="text/plain"
)
test_file.save()
self.assertTrue(bool(test_file.the_file))
assert bool(test_file.the_file)
test_file = TestFile.objects.first()
self.assertEqual(test_file.the_file.content_type, "text/plain")
assert test_file.the_file.content_type == "text/plain"
def test_file_cmp(self):
"""Test comparing against other types"""
@@ -310,7 +312,7 @@ class FileTest(MongoDBTestCase):
the_file = FileField()
test_file = TestFile()
self.assertNotIn(test_file.the_file, [{"test": 1}])
assert test_file.the_file not in [{"test": 1}]
def test_file_disk_space(self):
""" Test disk space usage when we delete/replace a file """
@@ -318,7 +320,7 @@ class FileTest(MongoDBTestCase):
class TestFile(Document):
the_file = FileField()
text = six.b("Hello, World!")
text = "Hello, World!".encode("latin-1")
content_type = "text/plain"
testfile = TestFile()
@@ -330,16 +332,16 @@ class FileTest(MongoDBTestCase):
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEqual(len(list(files)), 1)
self.assertEqual(len(list(chunks)), 1)
assert len(list(files)) == 1
assert len(list(chunks)) == 1
# Deleting the docoument should delete the files
testfile.delete()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEqual(len(list(files)), 0)
self.assertEqual(len(list(chunks)), 0)
assert len(list(files)) == 0
assert len(list(chunks)) == 0
# Test case where we don't store a file in the first place
testfile = TestFile()
@@ -347,48 +349,46 @@ class FileTest(MongoDBTestCase):
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEqual(len(list(files)), 0)
self.assertEqual(len(list(chunks)), 0)
assert len(list(files)) == 0
assert len(list(chunks)) == 0
testfile.delete()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEqual(len(list(files)), 0)
self.assertEqual(len(list(chunks)), 0)
assert len(list(files)) == 0
assert len(list(chunks)) == 0
# Test case where we overwrite the file
testfile = TestFile()
testfile.the_file.put(text, content_type=content_type, filename="hello")
testfile.save()
text = six.b("Bonjour, World!")
text = "Bonjour, World!".encode("latin-1")
testfile.the_file.replace(text, content_type=content_type, filename="hello")
testfile.save()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEqual(len(list(files)), 1)
self.assertEqual(len(list(chunks)), 1)
assert len(list(files)) == 1
assert len(list(chunks)) == 1
testfile.delete()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEqual(len(list(files)), 0)
self.assertEqual(len(list(chunks)), 0)
assert len(list(files)) == 0
assert len(list(chunks)) == 0
@require_pil
def test_image_field(self):
if not HAS_PIL:
raise SkipTest("PIL not installed")
class TestImage(Document):
image = ImageField()
TestImage.drop_collection()
with tempfile.TemporaryFile() as f:
f.write(six.b("Hello World!"))
f.write("Hello World!".encode("latin-1"))
f.flush()
t = TestImage()
@@ -396,9 +396,7 @@ class FileTest(MongoDBTestCase):
t.image.put(f)
self.fail("Should have raised an invalidation error")
except ValidationError as e:
self.assertEqual(
"%s" % e, "Invalid image: cannot identify image file %s" % f
)
assert "%s" % e == "Invalid image: cannot identify image file %s" % f
t = TestImage()
t.image.put(get_file(TEST_IMAGE_PATH))
@@ -406,35 +404,31 @@ class FileTest(MongoDBTestCase):
t = TestImage.objects.first()
self.assertEqual(t.image.format, "PNG")
assert t.image.format == "PNG"
w, h = t.image.size
self.assertEqual(w, 371)
self.assertEqual(h, 76)
assert w == 371
assert h == 76
t.image.delete()
@require_pil
def test_image_field_reassigning(self):
if not HAS_PIL:
raise SkipTest("PIL not installed")
class TestFile(Document):
the_file = ImageField()
TestFile.drop_collection()
test_file = TestFile(the_file=get_file(TEST_IMAGE_PATH)).save()
self.assertEqual(test_file.the_file.size, (371, 76))
assert test_file.the_file.size == (371, 76)
test_file = TestFile.objects.first()
test_file.the_file = get_file(TEST_IMAGE2_PATH)
test_file.save()
self.assertEqual(test_file.the_file.size, (45, 101))
assert test_file.the_file.size == (45, 101)
@require_pil
def test_image_field_resize(self):
if not HAS_PIL:
raise SkipTest("PIL not installed")
class TestImage(Document):
image = ImageField(size=(185, 37))
@@ -446,18 +440,16 @@ class FileTest(MongoDBTestCase):
t = TestImage.objects.first()
self.assertEqual(t.image.format, "PNG")
assert t.image.format == "PNG"
w, h = t.image.size
self.assertEqual(w, 185)
self.assertEqual(h, 37)
assert w == 185
assert h == 37
t.image.delete()
@require_pil
def test_image_field_resize_force(self):
if not HAS_PIL:
raise SkipTest("PIL not installed")
class TestImage(Document):
image = ImageField(size=(185, 37, True))
@@ -469,18 +461,16 @@ class FileTest(MongoDBTestCase):
t = TestImage.objects.first()
self.assertEqual(t.image.format, "PNG")
assert t.image.format == "PNG"
w, h = t.image.size
self.assertEqual(w, 185)
self.assertEqual(h, 37)
assert w == 185
assert h == 37
t.image.delete()
@require_pil
def test_image_field_thumbnail(self):
if not HAS_PIL:
raise SkipTest("PIL not installed")
class TestImage(Document):
image = ImageField(thumbnail_size=(92, 18))
@@ -492,9 +482,9 @@ class FileTest(MongoDBTestCase):
t = TestImage.objects.first()
self.assertEqual(t.image.thumbnail.format, "PNG")
self.assertEqual(t.image.thumbnail.width, 92)
self.assertEqual(t.image.thumbnail.height, 18)
assert t.image.thumbnail.format == "PNG"
assert t.image.thumbnail.width == 92
assert t.image.thumbnail.height == 18
t.image.delete()
@@ -514,21 +504,21 @@ class FileTest(MongoDBTestCase):
# First instance
test_file = TestFile()
test_file.name = "Hello, World!"
test_file.the_file.put(six.b("Hello, World!"), name="hello.txt")
test_file.the_file.put("Hello, World!".encode("latin-1"), name="hello.txt")
test_file.save()
data = get_db("test_files").macumba.files.find_one()
self.assertEqual(data.get("name"), "hello.txt")
assert data.get("name") == "hello.txt"
test_file = TestFile.objects.first()
self.assertEqual(test_file.the_file.read(), six.b("Hello, World!"))
assert test_file.the_file.read() == "Hello, World!".encode("latin-1")
test_file = TestFile.objects.first()
test_file.the_file = six.b("HELLO, WORLD!")
test_file.the_file = "Hello, World!".encode("latin-1")
test_file.save()
test_file = TestFile.objects.first()
self.assertEqual(test_file.the_file.read(), six.b("HELLO, WORLD!"))
assert test_file.the_file.read() == "Hello, World!".encode("latin-1")
def test_copyable(self):
class PutFile(Document):
@@ -536,7 +526,7 @@ class FileTest(MongoDBTestCase):
PutFile.drop_collection()
text = six.b("Hello, World!")
text = "Hello, World!".encode("latin-1")
content_type = "text/plain"
putfile = PutFile()
@@ -546,14 +536,11 @@ class FileTest(MongoDBTestCase):
class TestFile(Document):
name = StringField()
self.assertEqual(putfile, copy.copy(putfile))
self.assertEqual(putfile, copy.deepcopy(putfile))
assert putfile == copy.copy(putfile)
assert putfile == copy.deepcopy(putfile)
@require_pil
def test_get_image_by_grid_id(self):
if not HAS_PIL:
raise SkipTest("PIL not installed")
class TestImage(Document):
image1 = ImageField()
@@ -569,9 +556,7 @@ class FileTest(MongoDBTestCase):
test = TestImage.objects.first()
grid_id = test.image1.grid_id
self.assertEqual(
1, TestImage.objects(Q(image1=grid_id) or Q(image2=grid_id)).count()
)
assert 1 == TestImage.objects(Q(image1=grid_id) or Q(image2=grid_id)).count()
def test_complex_field_filefield(self):
"""Ensure you can add meta data to file"""
@@ -593,9 +578,9 @@ class FileTest(MongoDBTestCase):
marmot.save()
marmot = Animal.objects.get()
self.assertEqual(marmot.photos[0].content_type, "image/jpeg")
self.assertEqual(marmot.photos[0].foo, "bar")
self.assertEqual(marmot.photos[0].get().length, 8313)
assert marmot.photos[0].content_type == "image/jpeg"
assert marmot.photos[0].foo == "bar"
assert marmot.photos[0].get().length == 8313
if __name__ == "__main__":

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
import six
import pytest
from mongoengine import *
@@ -16,8 +16,8 @@ class TestFloatField(MongoDBTestCase):
TestDocument(float_fld=None).save()
TestDocument(float_fld=1).save()
self.assertEqual(1, TestDocument.objects(float_fld__ne=None).count())
self.assertEqual(1, TestDocument.objects(float_fld__ne=1).count())
assert 1 == TestDocument.objects(float_fld__ne=None).count()
assert 1 == TestDocument.objects(float_fld__ne=1).count()
def test_validation(self):
"""Ensure that invalid values cannot be assigned to float fields.
@@ -34,25 +34,29 @@ class TestFloatField(MongoDBTestCase):
person.validate()
person.height = "2.0"
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
person.height = 0.01
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
person.height = 4.0
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
person_2 = Person(height="something invalid")
self.assertRaises(ValidationError, person_2.validate)
with pytest.raises(ValidationError):
person_2.validate()
big_person = BigPerson()
for value, value_type in enumerate(six.integer_types):
big_person.height = value_type(value)
big_person.validate()
big_person.height = int(0)
big_person.validate()
big_person.height = 2 ** 500
big_person.validate()
big_person.height = 2 ** 100000 # Too big for a float value
self.assertRaises(ValidationError, big_person.validate)
with pytest.raises(ValidationError):
big_person.validate()

View File

@@ -2,22 +2,16 @@
import unittest
from mongoengine import *
from mongoengine.connection import get_db
__all__ = ("GeoFieldTest",)
from tests.utils import MongoDBTestCase
class GeoFieldTest(unittest.TestCase):
def setUp(self):
connect(db="mongoenginetest")
self.db = get_db()
class TestGeoField(MongoDBTestCase):
def _test_for_expected_error(self, Cls, loc, expected):
try:
Cls(loc=loc).validate()
self.fail("Should not validate the location {0}".format(loc))
except ValidationError as e:
self.assertEqual(expected, e.to_dict()["loc"])
assert expected == e.to_dict()["loc"]
def test_geopoint_validation(self):
class Location(Document):
@@ -305,7 +299,7 @@ class GeoFieldTest(unittest.TestCase):
location = GeoPointField()
geo_indicies = Event._geo_indices()
self.assertEqual(geo_indicies, [{"fields": [("location", "2d")]}])
assert geo_indicies == [{"fields": [("location", "2d")]}]
def test_geopoint_embedded_indexes(self):
"""Ensure that indexes are created automatically for GeoPointFields on
@@ -321,7 +315,7 @@ class GeoFieldTest(unittest.TestCase):
venue = EmbeddedDocumentField(Venue)
geo_indicies = Event._geo_indices()
self.assertEqual(geo_indicies, [{"fields": [("venue.location", "2d")]}])
assert geo_indicies == [{"fields": [("venue.location", "2d")]}]
def test_indexes_2dsphere(self):
"""Ensure that indexes are created automatically for GeoPointFields.
@@ -334,9 +328,9 @@ class GeoFieldTest(unittest.TestCase):
polygon = PolygonField()
geo_indicies = Event._geo_indices()
self.assertIn({"fields": [("line", "2dsphere")]}, geo_indicies)
self.assertIn({"fields": [("polygon", "2dsphere")]}, geo_indicies)
self.assertIn({"fields": [("point", "2dsphere")]}, geo_indicies)
assert {"fields": [("line", "2dsphere")]} in geo_indicies
assert {"fields": [("polygon", "2dsphere")]} in geo_indicies
assert {"fields": [("point", "2dsphere")]} in geo_indicies
def test_indexes_2dsphere_embedded(self):
"""Ensure that indexes are created automatically for GeoPointFields.
@@ -353,9 +347,9 @@ class GeoFieldTest(unittest.TestCase):
venue = EmbeddedDocumentField(Venue)
geo_indicies = Event._geo_indices()
self.assertIn({"fields": [("venue.line", "2dsphere")]}, geo_indicies)
self.assertIn({"fields": [("venue.polygon", "2dsphere")]}, geo_indicies)
self.assertIn({"fields": [("venue.point", "2dsphere")]}, geo_indicies)
assert {"fields": [("venue.line", "2dsphere")]} in geo_indicies
assert {"fields": [("venue.polygon", "2dsphere")]} in geo_indicies
assert {"fields": [("venue.point", "2dsphere")]} in geo_indicies
def test_geo_indexes_recursion(self):
class Location(Document):
@@ -371,12 +365,12 @@ class GeoFieldTest(unittest.TestCase):
Parent(name="Berlin").save()
info = Parent._get_collection().index_information()
self.assertNotIn("location_2d", info)
assert "location_2d" not in info
info = Location._get_collection().index_information()
self.assertIn("location_2d", info)
assert "location_2d" in info
self.assertEqual(len(Parent._geo_indices()), 0)
self.assertEqual(len(Location._geo_indices()), 1)
assert len(Parent._geo_indices()) == 0
assert len(Location._geo_indices()) == 1
def test_geo_indexes_auto_index(self):
@@ -387,16 +381,16 @@ class GeoFieldTest(unittest.TestCase):
meta = {"indexes": [[("location", "2dsphere"), ("datetime", 1)]]}
self.assertEqual([], Log._geo_indices())
assert [] == Log._geo_indices()
Log.drop_collection()
Log.ensure_indexes()
info = Log._get_collection().index_information()
self.assertEqual(
info["location_2dsphere_datetime_1"]["key"],
[("location", "2dsphere"), ("datetime", 1)],
)
assert info["location_2dsphere_datetime_1"]["key"] == [
("location", "2dsphere"),
("datetime", 1),
]
# Test listing explicitly
class Log(Document):
@@ -407,16 +401,16 @@ class GeoFieldTest(unittest.TestCase):
"indexes": [{"fields": [("location", "2dsphere"), ("datetime", 1)]}]
}
self.assertEqual([], Log._geo_indices())
assert [] == Log._geo_indices()
Log.drop_collection()
Log.ensure_indexes()
info = Log._get_collection().index_information()
self.assertEqual(
info["location_2dsphere_datetime_1"]["key"],
[("location", "2dsphere"), ("datetime", 1)],
)
assert info["location_2dsphere_datetime_1"]["key"] == [
("location", "2dsphere"),
("datetime", 1),
]
if __name__ == "__main__":

View File

@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase
@@ -23,11 +25,14 @@ class TestIntField(MongoDBTestCase):
person.validate()
person.age = -1
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
person.age = 120
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
person.age = "ten"
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
def test_ne_operator(self):
class TestDocument(Document):
@@ -38,5 +43,5 @@ class TestIntField(MongoDBTestCase):
TestDocument(int_fld=None).save()
TestDocument(int_fld=1).save()
self.assertEqual(1, TestDocument.objects(int_fld__ne=None).count())
self.assertEqual(1, TestDocument.objects(int_fld__ne=1).count())
assert 1 == TestDocument.objects(int_fld__ne=None).count()
assert 1 == TestDocument.objects(int_fld__ne=1).count()

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from bson import DBRef, ObjectId
import pytest
from mongoengine import *
from mongoengine.base import LazyReference
@@ -11,7 +12,8 @@ class TestLazyReferenceField(MongoDBTestCase):
def test_lazy_reference_config(self):
# Make sure ReferenceField only accepts a document class or a string
# with a document class name.
self.assertRaises(ValidationError, LazyReferenceField, EmbeddedDocument)
with pytest.raises(ValidationError):
LazyReferenceField(EmbeddedDocument)
def test___repr__(self):
class Animal(Document):
@@ -25,7 +27,7 @@ class TestLazyReferenceField(MongoDBTestCase):
animal = Animal()
oc = Ocurrence(animal=animal)
self.assertIn("LazyReference", repr(oc.animal))
assert "LazyReference" in repr(oc.animal)
def test___getattr___unknown_attr_raises_attribute_error(self):
class Animal(Document):
@@ -39,7 +41,7 @@ class TestLazyReferenceField(MongoDBTestCase):
animal = Animal().save()
oc = Ocurrence(animal=animal)
with self.assertRaises(AttributeError):
with pytest.raises(AttributeError):
oc.animal.not_exist
def test_lazy_reference_simple(self):
@@ -57,19 +59,19 @@ class TestLazyReferenceField(MongoDBTestCase):
animal = Animal(name="Leopard", tag="heavy").save()
Ocurrence(person="test", animal=animal).save()
p = Ocurrence.objects.get()
self.assertIsInstance(p.animal, LazyReference)
assert isinstance(p.animal, LazyReference)
fetched_animal = p.animal.fetch()
self.assertEqual(fetched_animal, animal)
assert fetched_animal == animal
# `fetch` keep cache on referenced document by default...
animal.tag = "not so heavy"
animal.save()
double_fetch = p.animal.fetch()
self.assertIs(fetched_animal, double_fetch)
self.assertEqual(double_fetch.tag, "heavy")
assert fetched_animal is double_fetch
assert double_fetch.tag == "heavy"
# ...unless specified otherwise
fetch_force = p.animal.fetch(force=True)
self.assertIsNot(fetch_force, fetched_animal)
self.assertEqual(fetch_force.tag, "not so heavy")
assert fetch_force is not fetched_animal
assert fetch_force.tag == "not so heavy"
def test_lazy_reference_fetch_invalid_ref(self):
class Animal(Document):
@@ -87,8 +89,8 @@ class TestLazyReferenceField(MongoDBTestCase):
Ocurrence(person="test", animal=animal).save()
animal.delete()
p = Ocurrence.objects.get()
self.assertIsInstance(p.animal, LazyReference)
with self.assertRaises(DoesNotExist):
assert isinstance(p.animal, LazyReference)
with pytest.raises(DoesNotExist):
p.animal.fetch()
def test_lazy_reference_set(self):
@@ -122,7 +124,7 @@ class TestLazyReferenceField(MongoDBTestCase):
):
p = Ocurrence(person="test", animal=ref).save()
p.reload()
self.assertIsInstance(p.animal, LazyReference)
assert isinstance(p.animal, LazyReference)
p.animal.fetch()
def test_lazy_reference_bad_set(self):
@@ -149,8 +151,8 @@ class TestLazyReferenceField(MongoDBTestCase):
DBRef(baddoc._get_collection_name(), animal.pk),
LazyReference(BadDoc, animal.pk),
):
with self.assertRaises(ValidationError):
p = Ocurrence(person="test", animal=bad).save()
with pytest.raises(ValidationError):
Ocurrence(person="test", animal=bad).save()
def test_lazy_reference_query_conversion(self):
"""Ensure that LazyReferenceFields can be queried using objects and values
@@ -179,14 +181,14 @@ class TestLazyReferenceField(MongoDBTestCase):
post2.save()
post = BlogPost.objects(author=m1).first()
self.assertEqual(post.id, post1.id)
assert post.id == post1.id
post = BlogPost.objects(author=m2).first()
self.assertEqual(post.id, post2.id)
assert post.id == post2.id
# Same thing by passing a LazyReference instance
post = BlogPost.objects(author=LazyReference(Member, m2.pk)).first()
self.assertEqual(post.id, post2.id)
assert post.id == post2.id
def test_lazy_reference_query_conversion_dbref(self):
"""Ensure that LazyReferenceFields can be queried using objects and values
@@ -215,14 +217,14 @@ class TestLazyReferenceField(MongoDBTestCase):
post2.save()
post = BlogPost.objects(author=m1).first()
self.assertEqual(post.id, post1.id)
assert post.id == post1.id
post = BlogPost.objects(author=m2).first()
self.assertEqual(post.id, post2.id)
assert post.id == post2.id
# Same thing by passing a LazyReference instance
post = BlogPost.objects(author=LazyReference(Member, m2.pk)).first()
self.assertEqual(post.id, post2.id)
assert post.id == post2.id
def test_lazy_reference_passthrough(self):
class Animal(Document):
@@ -239,20 +241,20 @@ class TestLazyReferenceField(MongoDBTestCase):
animal = Animal(name="Leopard", tag="heavy").save()
Ocurrence(animal=animal, animal_passthrough=animal).save()
p = Ocurrence.objects.get()
self.assertIsInstance(p.animal, LazyReference)
with self.assertRaises(KeyError):
assert isinstance(p.animal, LazyReference)
with pytest.raises(KeyError):
p.animal["name"]
with self.assertRaises(AttributeError):
with pytest.raises(AttributeError):
p.animal.name
self.assertEqual(p.animal.pk, animal.pk)
assert p.animal.pk == animal.pk
self.assertEqual(p.animal_passthrough.name, "Leopard")
self.assertEqual(p.animal_passthrough["name"], "Leopard")
assert p.animal_passthrough.name == "Leopard"
assert p.animal_passthrough["name"] == "Leopard"
# Should not be able to access referenced document's methods
with self.assertRaises(AttributeError):
with pytest.raises(AttributeError):
p.animal.save
with self.assertRaises(KeyError):
with pytest.raises(KeyError):
p.animal["save"]
def test_lazy_reference_not_set(self):
@@ -269,7 +271,7 @@ class TestLazyReferenceField(MongoDBTestCase):
Ocurrence(person="foo").save()
p = Ocurrence.objects.get()
self.assertIs(p.animal, None)
assert p.animal is None
def test_lazy_reference_equality(self):
class Animal(Document):
@@ -280,12 +282,12 @@ class TestLazyReferenceField(MongoDBTestCase):
animal = Animal(name="Leopard", tag="heavy").save()
animalref = LazyReference(Animal, animal.pk)
self.assertEqual(animal, animalref)
self.assertEqual(animalref, animal)
assert animal == animalref
assert animalref == animal
other_animalref = LazyReference(Animal, ObjectId("54495ad94c934721ede76f90"))
self.assertNotEqual(animal, other_animalref)
self.assertNotEqual(other_animalref, animal)
assert animal != other_animalref
assert other_animalref != animal
def test_lazy_reference_embedded(self):
class Animal(Document):
@@ -308,12 +310,12 @@ class TestLazyReferenceField(MongoDBTestCase):
animal2 = Animal(name="cheeta").save()
def check_fields_type(occ):
self.assertIsInstance(occ.direct, LazyReference)
assert isinstance(occ.direct, LazyReference)
for elem in occ.in_list:
self.assertIsInstance(elem, LazyReference)
self.assertIsInstance(occ.in_embedded.direct, LazyReference)
assert isinstance(elem, LazyReference)
assert isinstance(occ.in_embedded.direct, LazyReference)
for elem in occ.in_embedded.in_list:
self.assertIsInstance(elem, LazyReference)
assert isinstance(elem, LazyReference)
occ = Ocurrence(
in_list=[animal1, animal2],
@@ -346,19 +348,19 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
animal = Animal(name="Leopard", tag="heavy").save()
Ocurrence(person="test", animal=animal).save()
p = Ocurrence.objects.get()
self.assertIsInstance(p.animal, LazyReference)
assert isinstance(p.animal, LazyReference)
fetched_animal = p.animal.fetch()
self.assertEqual(fetched_animal, animal)
assert fetched_animal == animal
# `fetch` keep cache on referenced document by default...
animal.tag = "not so heavy"
animal.save()
double_fetch = p.animal.fetch()
self.assertIs(fetched_animal, double_fetch)
self.assertEqual(double_fetch.tag, "heavy")
assert fetched_animal is double_fetch
assert double_fetch.tag == "heavy"
# ...unless specified otherwise
fetch_force = p.animal.fetch(force=True)
self.assertIsNot(fetch_force, fetched_animal)
self.assertEqual(fetch_force.tag, "not so heavy")
assert fetch_force is not fetched_animal
assert fetch_force.tag == "not so heavy"
def test_generic_lazy_reference_choices(self):
class Animal(Document):
@@ -384,14 +386,14 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
mineral = Mineral(name="Granite").save()
occ_animal = Ocurrence(living_thing=animal, thing=animal).save()
occ_vegetal = Ocurrence(living_thing=vegetal, thing=vegetal).save()
with self.assertRaises(ValidationError):
_ = Ocurrence(living_thing=vegetal, thing=vegetal).save()
with pytest.raises(ValidationError):
Ocurrence(living_thing=mineral).save()
occ = Ocurrence.objects.get(living_thing=animal)
self.assertEqual(occ, occ_animal)
self.assertIsInstance(occ.thing, LazyReference)
self.assertIsInstance(occ.living_thing, LazyReference)
assert occ == occ_animal
assert isinstance(occ.thing, LazyReference)
assert isinstance(occ.living_thing, LazyReference)
occ.thing = vegetal
occ.living_thing = vegetal
@@ -399,7 +401,7 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
occ.thing = mineral
occ.living_thing = mineral
with self.assertRaises(ValidationError):
with pytest.raises(ValidationError):
occ.save()
def test_generic_lazy_reference_set(self):
@@ -434,7 +436,7 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
):
p = Ocurrence(person="test", animal=ref).save()
p.reload()
self.assertIsInstance(p.animal, (LazyReference, Document))
assert isinstance(p.animal, (LazyReference, Document))
p.animal.fetch()
def test_generic_lazy_reference_bad_set(self):
@@ -455,8 +457,8 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
animal = Animal(name="Leopard", tag="heavy").save()
baddoc = BadDoc().save()
for bad in (42, "foo", baddoc, LazyReference(BadDoc, animal.pk)):
with self.assertRaises(ValidationError):
p = Ocurrence(person="test", animal=bad).save()
with pytest.raises(ValidationError):
Ocurrence(person="test", animal=bad).save()
def test_generic_lazy_reference_query_conversion(self):
class Member(Document):
@@ -481,14 +483,14 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
post2.save()
post = BlogPost.objects(author=m1).first()
self.assertEqual(post.id, post1.id)
assert post.id == post1.id
post = BlogPost.objects(author=m2).first()
self.assertEqual(post.id, post2.id)
assert post.id == post2.id
# Same thing by passing a LazyReference instance
post = BlogPost.objects(author=LazyReference(Member, m2.pk)).first()
self.assertEqual(post.id, post2.id)
assert post.id == post2.id
def test_generic_lazy_reference_not_set(self):
class Animal(Document):
@@ -504,7 +506,7 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
Ocurrence(person="foo").save()
p = Ocurrence.objects.get()
self.assertIs(p.animal, None)
assert p.animal is None
def test_generic_lazy_reference_accepts_string_instead_of_class(self):
class Animal(Document):
@@ -521,7 +523,7 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
animal = Animal().save()
Ocurrence(animal=animal).save()
p = Ocurrence.objects.get()
self.assertEqual(p.animal, animal)
assert p.animal == animal
def test_generic_lazy_reference_embedded(self):
class Animal(Document):
@@ -544,12 +546,12 @@ class TestGenericLazyReferenceField(MongoDBTestCase):
animal2 = Animal(name="cheeta").save()
def check_fields_type(occ):
self.assertIsInstance(occ.direct, LazyReference)
assert isinstance(occ.direct, LazyReference)
for elem in occ.in_list:
self.assertIsInstance(elem, LazyReference)
self.assertIsInstance(occ.in_embedded.direct, LazyReference)
assert isinstance(elem, LazyReference)
assert isinstance(occ.in_embedded.direct, LazyReference)
for elem in occ.in_embedded.in_list:
self.assertIsInstance(elem, LazyReference)
assert isinstance(elem, LazyReference)
occ = Ocurrence(
in_list=[animal1, animal2],

View File

@@ -1,10 +1,5 @@
# -*- coding: utf-8 -*-
import six
try:
from bson.int64 import Int64
except ImportError:
Int64 = long
from bson.int64 import Int64
import pytest
from mongoengine import *
from mongoengine.connection import get_db
@@ -24,10 +19,10 @@ class TestLongField(MongoDBTestCase):
doc = TestLongFieldConsideredAsInt64(some_long=42).save()
db = get_db()
self.assertIsInstance(
assert isinstance(
db.test_long_field_considered_as_int64.find()[0]["some_long"], Int64
)
self.assertIsInstance(doc.some_long, six.integer_types)
assert isinstance(doc.some_long, int)
def test_long_validation(self):
"""Ensure that invalid values cannot be assigned to long fields.
@@ -41,11 +36,14 @@ class TestLongField(MongoDBTestCase):
doc.validate()
doc.value = -1
self.assertRaises(ValidationError, doc.validate)
with pytest.raises(ValidationError):
doc.validate()
doc.value = 120
self.assertRaises(ValidationError, doc.validate)
with pytest.raises(ValidationError):
doc.validate()
doc.value = "ten"
self.assertRaises(ValidationError, doc.validate)
with pytest.raises(ValidationError):
doc.validate()
def test_long_ne_operator(self):
class TestDocument(Document):
@@ -56,4 +54,4 @@ class TestLongField(MongoDBTestCase):
TestDocument(long_fld=None).save()
TestDocument(long_fld=1).save()
self.assertEqual(1, TestDocument.objects(long_fld__ne=None).count())
assert 1 == TestDocument.objects(long_fld__ne=None).count()

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
import datetime
from mongoengine import *
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase
@@ -19,11 +20,11 @@ class TestMapField(MongoDBTestCase):
e.mapping["someint"] = 1
e.save()
with self.assertRaises(ValidationError):
with pytest.raises(ValidationError):
e.mapping["somestring"] = "abc"
e.save()
with self.assertRaises(ValidationError):
with pytest.raises(ValidationError):
class NoDeclaredType(Document):
mapping = MapField()
@@ -51,10 +52,10 @@ class TestMapField(MongoDBTestCase):
e.save()
e2 = Extensible.objects.get(id=e.id)
self.assertIsInstance(e2.mapping["somestring"], StringSetting)
self.assertIsInstance(e2.mapping["someint"], IntegerSetting)
assert isinstance(e2.mapping["somestring"], StringSetting)
assert isinstance(e2.mapping["someint"], IntegerSetting)
with self.assertRaises(ValidationError):
with pytest.raises(ValidationError):
e.mapping["someint"] = 123
e.save()
@@ -74,9 +75,9 @@ class TestMapField(MongoDBTestCase):
Test.objects.update_one(inc__my_map__DICTIONARY_KEY__number=1)
test = Test.objects.get()
self.assertEqual(test.my_map["DICTIONARY_KEY"].number, 2)
assert test.my_map["DICTIONARY_KEY"].number == 2
doc = self.db.test.find_one()
self.assertEqual(doc["x"]["DICTIONARY_KEY"]["i"], 2)
assert doc["x"]["DICTIONARY_KEY"]["i"] == 2
def test_mapfield_numerical_index(self):
"""Ensure that MapField accept numeric strings as indexes."""
@@ -116,13 +117,13 @@ class TestMapField(MongoDBTestCase):
actions={"friends": Action(operation="drink", object="beer")},
).save()
self.assertEqual(1, Log.objects(visited__friends__exists=True).count())
assert 1 == Log.objects(visited__friends__exists=True).count()
self.assertEqual(
1,
Log.objects(
assert (
1
== Log.objects(
actions__friends__operation="drink", actions__friends__object="beer"
).count(),
).count()
)
def test_map_field_unicode(self):
@@ -139,7 +140,7 @@ class TestMapField(MongoDBTestCase):
tree.save()
self.assertEqual(
BlogPost.objects.get(id=tree.id).info_dict[u"éééé"].description,
u"VALUE: éééé",
assert (
BlogPost.objects.get(id=tree.id).info_dict[u"éééé"].description
== u"VALUE: éééé"
)

View File

@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
from bson import SON, DBRef
from bson import DBRef, SON
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase
@@ -24,19 +24,22 @@ class TestReferenceField(MongoDBTestCase):
# Make sure ReferenceField only accepts a document class or a string
# with a document class name.
self.assertRaises(ValidationError, ReferenceField, EmbeddedDocument)
with pytest.raises(ValidationError):
ReferenceField(EmbeddedDocument)
user = User(name="Test User")
# Ensure that the referenced object must have been saved
post1 = BlogPost(content="Chips and gravy taste good.")
post1.author = user
self.assertRaises(ValidationError, post1.save)
with pytest.raises(ValidationError):
post1.save()
# Check that an invalid object type cannot be used
post2 = BlogPost(content="Chips and chilli taste good.")
post1.author = post2
self.assertRaises(ValidationError, post1.validate)
with pytest.raises(ValidationError):
post1.validate()
# Ensure ObjectID's are accepted as references
user_object_id = user.pk
@@ -52,22 +55,8 @@ class TestReferenceField(MongoDBTestCase):
# Make sure referencing a saved document of the *wrong* type fails
post2.save()
post1.author = post2
self.assertRaises(ValidationError, post1.validate)
def test_objectid_reference_fields(self):
"""Make sure storing Object ID references works."""
class Person(Document):
name = StringField()
parent = ReferenceField("self")
Person.drop_collection()
p1 = Person(name="John").save()
Person(name="Ross", parent=p1.pk).save()
p = Person.objects.get(name="Ross")
self.assertEqual(p.parent, p1)
with pytest.raises(ValidationError):
post1.validate()
def test_dbref_reference_fields(self):
"""Make sure storing references as bson.dbref.DBRef works."""
@@ -81,13 +70,12 @@ class TestReferenceField(MongoDBTestCase):
p1 = Person(name="John").save()
Person(name="Ross", parent=p1).save()
self.assertEqual(
Person._get_collection().find_one({"name": "Ross"})["parent"],
DBRef("person", p1.pk),
assert Person._get_collection().find_one({"name": "Ross"})["parent"] == DBRef(
"person", p1.pk
)
p = Person.objects.get(name="Ross")
self.assertEqual(p.parent, p1)
assert p.parent == p1
def test_dbref_to_mongo(self):
"""Make sure that calling to_mongo on a ReferenceField which
@@ -100,9 +88,7 @@ class TestReferenceField(MongoDBTestCase):
parent = ReferenceField("self", dbref=False)
p = Person(name="Steve", parent=DBRef("person", "abcdefghijklmnop"))
self.assertEqual(
p.to_mongo(), SON([("name", u"Steve"), ("parent", "abcdefghijklmnop")])
)
assert p.to_mongo() == SON([("name", u"Steve"), ("parent", "abcdefghijklmnop")])
def test_objectid_reference_fields(self):
class Person(Document):
@@ -116,10 +102,10 @@ class TestReferenceField(MongoDBTestCase):
col = Person._get_collection()
data = col.find_one({"name": "Ross"})
self.assertEqual(data["parent"], p1.pk)
assert data["parent"] == p1.pk
p = Person.objects.get(name="Ross")
self.assertEqual(p.parent, p1)
assert p.parent == p1
def test_undefined_reference(self):
"""Ensure that ReferenceFields may reference undefined Documents.
@@ -144,14 +130,14 @@ class TestReferenceField(MongoDBTestCase):
me.save()
obj = Product.objects(company=ten_gen).first()
self.assertEqual(obj, mongodb)
self.assertEqual(obj.company, ten_gen)
assert obj == mongodb
assert obj.company == ten_gen
obj = Product.objects(company=None).first()
self.assertEqual(obj, me)
assert obj == me
obj = Product.objects.get(company=None)
self.assertEqual(obj, me)
assert obj == me
def test_reference_query_conversion(self):
"""Ensure that ReferenceFields can be queried using objects and values
@@ -180,10 +166,10 @@ class TestReferenceField(MongoDBTestCase):
post2.save()
post = BlogPost.objects(author=m1).first()
self.assertEqual(post.id, post1.id)
assert post.id == post1.id
post = BlogPost.objects(author=m2).first()
self.assertEqual(post.id, post2.id)
assert post.id == post2.id
def test_reference_query_conversion_dbref(self):
"""Ensure that ReferenceFields can be queried using objects and values
@@ -212,7 +198,7 @@ class TestReferenceField(MongoDBTestCase):
post2.save()
post = BlogPost.objects(author=m1).first()
self.assertEqual(post.id, post1.id)
assert post.id == post1.id
post = BlogPost.objects(author=m2).first()
self.assertEqual(post.id, post2.id)
assert post.id == post2.id

View File

@@ -18,17 +18,17 @@ class TestSequenceField(MongoDBTestCase):
Person(name="Person %s" % x).save()
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
self.assertEqual(c["next"], 10)
assert c["next"] == 10
ids = [i.id for i in Person.objects]
self.assertEqual(ids, range(1, 11))
assert ids == list(range(1, 11))
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
self.assertEqual(c["next"], 10)
assert c["next"] == 10
Person.id.set_next_value(1000)
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
self.assertEqual(c["next"], 1000)
assert c["next"] == 1000
def test_sequence_field_get_next_value(self):
class Person(Document):
@@ -41,10 +41,10 @@ class TestSequenceField(MongoDBTestCase):
for x in range(10):
Person(name="Person %s" % x).save()
self.assertEqual(Person.id.get_next_value(), 11)
assert Person.id.get_next_value() == 11
self.db["mongoengine.counters"].drop()
self.assertEqual(Person.id.get_next_value(), 1)
assert Person.id.get_next_value() == 1
class Person(Document):
id = SequenceField(primary_key=True, value_decorator=str)
@@ -56,10 +56,10 @@ class TestSequenceField(MongoDBTestCase):
for x in range(10):
Person(name="Person %s" % x).save()
self.assertEqual(Person.id.get_next_value(), "11")
assert Person.id.get_next_value() == "11"
self.db["mongoengine.counters"].drop()
self.assertEqual(Person.id.get_next_value(), "1")
assert Person.id.get_next_value() == "1"
def test_sequence_field_sequence_name(self):
class Person(Document):
@@ -73,17 +73,17 @@ class TestSequenceField(MongoDBTestCase):
Person(name="Person %s" % x).save()
c = self.db["mongoengine.counters"].find_one({"_id": "jelly.id"})
self.assertEqual(c["next"], 10)
assert c["next"] == 10
ids = [i.id for i in Person.objects]
self.assertEqual(ids, range(1, 11))
assert ids == list(range(1, 11))
c = self.db["mongoengine.counters"].find_one({"_id": "jelly.id"})
self.assertEqual(c["next"], 10)
assert c["next"] == 10
Person.id.set_next_value(1000)
c = self.db["mongoengine.counters"].find_one({"_id": "jelly.id"})
self.assertEqual(c["next"], 1000)
assert c["next"] == 1000
def test_multiple_sequence_fields(self):
class Person(Document):
@@ -98,24 +98,24 @@ class TestSequenceField(MongoDBTestCase):
Person(name="Person %s" % x).save()
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
self.assertEqual(c["next"], 10)
assert c["next"] == 10
ids = [i.id for i in Person.objects]
self.assertEqual(ids, range(1, 11))
assert ids == list(range(1, 11))
counters = [i.counter for i in Person.objects]
self.assertEqual(counters, range(1, 11))
assert counters == list(range(1, 11))
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
self.assertEqual(c["next"], 10)
assert c["next"] == 10
Person.id.set_next_value(1000)
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
self.assertEqual(c["next"], 1000)
assert c["next"] == 1000
Person.counter.set_next_value(999)
c = self.db["mongoengine.counters"].find_one({"_id": "person.counter"})
self.assertEqual(c["next"], 999)
assert c["next"] == 999
def test_sequence_fields_reload(self):
class Animal(Document):
@@ -127,20 +127,20 @@ class TestSequenceField(MongoDBTestCase):
a = Animal(name="Boi").save()
self.assertEqual(a.counter, 1)
assert a.counter == 1
a.reload()
self.assertEqual(a.counter, 1)
assert a.counter == 1
a.counter = None
self.assertEqual(a.counter, 2)
assert a.counter == 2
a.save()
self.assertEqual(a.counter, 2)
assert a.counter == 2
a = Animal.objects.first()
self.assertEqual(a.counter, 2)
assert a.counter == 2
a.reload()
self.assertEqual(a.counter, 2)
assert a.counter == 2
def test_multiple_sequence_fields_on_docs(self):
class Animal(Document):
@@ -160,22 +160,22 @@ class TestSequenceField(MongoDBTestCase):
Person(name="Person %s" % x).save()
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
self.assertEqual(c["next"], 10)
assert c["next"] == 10
c = self.db["mongoengine.counters"].find_one({"_id": "animal.id"})
self.assertEqual(c["next"], 10)
assert c["next"] == 10
ids = [i.id for i in Person.objects]
self.assertEqual(ids, range(1, 11))
assert ids == list(range(1, 11))
id = [i.id for i in Animal.objects]
self.assertEqual(id, range(1, 11))
assert id == list(range(1, 11))
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
self.assertEqual(c["next"], 10)
assert c["next"] == 10
c = self.db["mongoengine.counters"].find_one({"_id": "animal.id"})
self.assertEqual(c["next"], 10)
assert c["next"] == 10
def test_sequence_field_value_decorator(self):
class Person(Document):
@@ -190,13 +190,13 @@ class TestSequenceField(MongoDBTestCase):
p.save()
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
self.assertEqual(c["next"], 10)
assert c["next"] == 10
ids = [i.id for i in Person.objects]
self.assertEqual(ids, map(str, range(1, 11)))
assert ids == [str(i) for i in range(1, 11)]
c = self.db["mongoengine.counters"].find_one({"_id": "person.id"})
self.assertEqual(c["next"], 10)
assert c["next"] == 10
def test_embedded_sequence_field(self):
class Comment(EmbeddedDocument):
@@ -218,10 +218,10 @@ class TestSequenceField(MongoDBTestCase):
],
).save()
c = self.db["mongoengine.counters"].find_one({"_id": "comment.id"})
self.assertEqual(c["next"], 2)
assert c["next"] == 2
post = Post.objects.first()
self.assertEqual(1, post.comments[0].id)
self.assertEqual(2, post.comments[1].id)
assert 1 == post.comments[0].id
assert 2 == post.comments[1].id
def test_inherited_sequencefield(self):
class Base(Document):
@@ -241,16 +241,14 @@ class TestSequenceField(MongoDBTestCase):
foo = Foo(name="Foo")
foo.save()
self.assertTrue(
"base.counter" in self.db["mongoengine.counters"].find().distinct("_id")
)
self.assertFalse(
assert "base.counter" in self.db["mongoengine.counters"].find().distinct("_id")
assert not (
("foo.counter" or "bar.counter")
in self.db["mongoengine.counters"].find().distinct("_id")
)
self.assertNotEqual(foo.counter, bar.counter)
self.assertEqual(foo._fields["counter"].owner_document, Base)
self.assertEqual(bar._fields["counter"].owner_document, Base)
assert foo.counter != bar.counter
assert foo._fields["counter"].owner_document == Base
assert bar._fields["counter"].owner_document == Base
def test_no_inherited_sequencefield(self):
class Base(Document):
@@ -269,13 +267,12 @@ class TestSequenceField(MongoDBTestCase):
foo = Foo(name="Foo")
foo.save()
self.assertFalse(
"base.counter" in self.db["mongoengine.counters"].find().distinct("_id")
assert "base.counter" not in self.db["mongoengine.counters"].find().distinct(
"_id"
)
self.assertTrue(
("foo.counter" and "bar.counter")
in self.db["mongoengine.counters"].find().distinct("_id")
)
self.assertEqual(foo.counter, bar.counter)
self.assertEqual(foo._fields["counter"].owner_document, Foo)
self.assertEqual(bar._fields["counter"].owner_document, Bar)
existing_counters = self.db["mongoengine.counters"].find().distinct("_id")
assert "foo.counter" in existing_counters
assert "bar.counter" in existing_counters
assert foo.counter == bar.counter
assert foo._fields["counter"].owner_document == Foo
assert bar._fields["counter"].owner_document == Bar

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from mongoengine import *
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase
@@ -13,7 +14,8 @@ class TestURLField(MongoDBTestCase):
link = Link()
link.url = "google"
self.assertRaises(ValidationError, link.validate)
with pytest.raises(ValidationError):
link.validate()
link.url = "http://www.google.com:8080"
link.validate()
@@ -29,11 +31,11 @@ class TestURLField(MongoDBTestCase):
# TODO fix URL validation - this *IS* a valid URL
# For now we just want to make sure that the error message is correct
with self.assertRaises(ValidationError) as ctx_err:
with pytest.raises(ValidationError) as exc_info:
link.validate()
self.assertEqual(
unicode(ctx_err.exception),
u"ValidationError (Link:None) (Invalid URL: http://\u043f\u0440\u0438\u0432\u0435\u0442.com: ['url'])",
assert (
str(exc_info.value)
== u"ValidationError (Link:None) (Invalid URL: http://\u043f\u0440\u0438\u0432\u0435\u0442.com: ['url'])"
)
def test_url_scheme_validation(self):
@@ -48,7 +50,8 @@ class TestURLField(MongoDBTestCase):
link = Link()
link.url = "ws://google.com"
self.assertRaises(ValidationError, link.validate)
with pytest.raises(ValidationError):
link.validate()
scheme_link = SchemeLink()
scheme_link.url = "ws://google.com"

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
import uuid
from mongoengine import *
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase, get_as_pymongo
@@ -14,9 +15,7 @@ class TestUUIDField(MongoDBTestCase):
def test_storage(self):
uid = uuid.uuid4()
person = Person(api_key=uid).save()
self.assertEqual(
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):
"""Test UUID fields storing as String
@@ -25,8 +24,8 @@ class TestUUIDField(MongoDBTestCase):
uu = uuid.uuid4()
Person(api_key=uu).save()
self.assertEqual(1, Person.objects(api_key=uu).count())
self.assertEqual(uu, Person.objects.first().api_key)
assert 1 == Person.objects(api_key=uu).count()
assert uu == Person.objects.first().api_key
person = Person()
valid = (uuid.uuid4(), uuid.uuid1())
@@ -40,7 +39,8 @@ class TestUUIDField(MongoDBTestCase):
)
for api_key in invalid:
person.api_key = api_key
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()
def test_field_binary(self):
"""Test UUID fields storing as Binary object."""
@@ -48,8 +48,8 @@ class TestUUIDField(MongoDBTestCase):
uu = uuid.uuid4()
Person(api_key=uu).save()
self.assertEqual(1, Person.objects(api_key=uu).count())
self.assertEqual(uu, Person.objects.first().api_key)
assert 1 == Person.objects(api_key=uu).count()
assert uu == Person.objects.first().api_key
person = Person()
valid = (uuid.uuid4(), uuid.uuid1())
@@ -63,4 +63,5 @@ class TestUUIDField(MongoDBTestCase):
)
for api_key in invalid:
person.api_key = api_key
self.assertRaises(ValidationError, person.validate)
with pytest.raises(ValidationError):
person.validate()

View File

@@ -42,11 +42,11 @@ class PickleSignalsTest(Document):
@classmethod
def post_save(self, sender, document, created, **kwargs):
pickled = pickle.dumps(document)
pickle.dumps(document)
@classmethod
def post_delete(self, sender, document, **kwargs):
pickled = pickle.dumps(document)
pickle.dumps(document)
signals.post_save.connect(PickleSignalsTest.post_save, sender=PickleSignalsTest)

View File

@@ -1,6 +0,0 @@
from .transform import *
from .field_list import *
from .queryset import *
from .visitor import *
from .geo import *
from .modify import *

View File

@@ -1,72 +1,72 @@
import unittest
import pytest
from mongoengine import *
from mongoengine.queryset import QueryFieldList
__all__ = ("QueryFieldListTest", "OnlyExcludeAllTest")
class QueryFieldListTest(unittest.TestCase):
class TestQueryFieldList:
def test_empty(self):
q = QueryFieldList()
self.assertFalse(q)
assert not q
q = QueryFieldList(always_include=["_cls"])
self.assertFalse(q)
assert not q
def test_include_include(self):
q = QueryFieldList()
q += QueryFieldList(
fields=["a", "b"], value=QueryFieldList.ONLY, _only_called=True
)
self.assertEqual(q.as_dict(), {"a": 1, "b": 1})
assert q.as_dict() == {"a": 1, "b": 1}
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {"a": 1, "b": 1, "c": 1})
assert q.as_dict() == {"a": 1, "b": 1, "c": 1}
def test_include_exclude(self):
q = QueryFieldList()
q += QueryFieldList(fields=["a", "b"], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {"a": 1, "b": 1})
assert q.as_dict() == {"a": 1, "b": 1}
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {"a": 1})
assert q.as_dict() == {"a": 1}
def test_exclude_exclude(self):
q = QueryFieldList()
q += QueryFieldList(fields=["a", "b"], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {"a": 0, "b": 0})
assert q.as_dict() == {"a": 0, "b": 0}
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {"a": 0, "b": 0, "c": 0})
assert q.as_dict() == {"a": 0, "b": 0, "c": 0}
def test_exclude_include(self):
q = QueryFieldList()
q += QueryFieldList(fields=["a", "b"], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {"a": 0, "b": 0})
assert q.as_dict() == {"a": 0, "b": 0}
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {"c": 1})
assert q.as_dict() == {"c": 1}
def test_always_include(self):
q = QueryFieldList(always_include=["x", "y"])
q += QueryFieldList(fields=["a", "b", "x"], value=QueryFieldList.EXCLUDE)
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {"x": 1, "y": 1, "c": 1})
assert q.as_dict() == {"x": 1, "y": 1, "c": 1}
def test_reset(self):
q = QueryFieldList(always_include=["x", "y"])
q += QueryFieldList(fields=["a", "b", "x"], value=QueryFieldList.EXCLUDE)
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {"x": 1, "y": 1, "c": 1})
assert q.as_dict() == {"x": 1, "y": 1, "c": 1}
q.reset()
self.assertFalse(q)
assert not q
q += QueryFieldList(fields=["b", "c"], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {"x": 1, "y": 1, "b": 1, "c": 1})
assert q.as_dict() == {"x": 1, "y": 1, "b": 1, "c": 1}
def test_using_a_slice(self):
q = QueryFieldList()
q += QueryFieldList(fields=["a"], value={"$slice": 5})
self.assertEqual(q.as_dict(), {"a": {"$slice": 5}})
assert q.as_dict() == {"a": {"$slice": 5}}
class OnlyExcludeAllTest(unittest.TestCase):
class TestOnlyExcludeAll(unittest.TestCase):
def setUp(self):
connect(db="mongoenginetest")
@@ -92,25 +92,23 @@ class OnlyExcludeAllTest(unittest.TestCase):
only = ["b", "c"]
qs = MyDoc.objects.fields(**{i: 1 for i in include})
self.assertEqual(
qs._loaded_fields.as_dict(), {"a": 1, "b": 1, "c": 1, "d": 1, "e": 1}
)
assert qs._loaded_fields.as_dict() == {"a": 1, "b": 1, "c": 1, "d": 1, "e": 1}
qs = qs.only(*only)
self.assertEqual(qs._loaded_fields.as_dict(), {"b": 1, "c": 1})
assert qs._loaded_fields.as_dict() == {"b": 1, "c": 1}
qs = qs.exclude(*exclude)
self.assertEqual(qs._loaded_fields.as_dict(), {"b": 1, "c": 1})
assert qs._loaded_fields.as_dict() == {"b": 1, "c": 1}
qs = MyDoc.objects.fields(**{i: 1 for i in include})
qs = qs.exclude(*exclude)
self.assertEqual(qs._loaded_fields.as_dict(), {"a": 1, "b": 1, "c": 1})
assert qs._loaded_fields.as_dict() == {"a": 1, "b": 1, "c": 1}
qs = qs.only(*only)
self.assertEqual(qs._loaded_fields.as_dict(), {"b": 1, "c": 1})
assert qs._loaded_fields.as_dict() == {"b": 1, "c": 1}
qs = MyDoc.objects.exclude(*exclude)
qs = qs.fields(**{i: 1 for i in include})
self.assertEqual(qs._loaded_fields.as_dict(), {"a": 1, "b": 1, "c": 1})
assert qs._loaded_fields.as_dict() == {"a": 1, "b": 1, "c": 1}
qs = qs.only(*only)
self.assertEqual(qs._loaded_fields.as_dict(), {"b": 1, "c": 1})
assert qs._loaded_fields.as_dict() == {"b": 1, "c": 1}
def test_slicing(self):
class MyDoc(Document):
@@ -129,15 +127,16 @@ class OnlyExcludeAllTest(unittest.TestCase):
qs = qs.exclude(*exclude)
qs = qs.only(*only)
qs = qs.fields(slice__b=5)
self.assertEqual(qs._loaded_fields.as_dict(), {"b": {"$slice": 5}, "c": 1})
assert qs._loaded_fields.as_dict() == {"b": {"$slice": 5}, "c": 1}
qs = qs.fields(slice__c=[5, 1])
self.assertEqual(
qs._loaded_fields.as_dict(), {"b": {"$slice": 5}, "c": {"$slice": [5, 1]}}
)
assert qs._loaded_fields.as_dict() == {
"b": {"$slice": 5},
"c": {"$slice": [5, 1]},
}
qs = qs.exclude("c")
self.assertEqual(qs._loaded_fields.as_dict(), {"b": {"$slice": 5}})
assert qs._loaded_fields.as_dict() == {"b": {"$slice": 5}}
def test_mix_slice_with_other_fields(self):
class MyDoc(Document):
@@ -146,7 +145,7 @@ class OnlyExcludeAllTest(unittest.TestCase):
c = ListField()
qs = MyDoc.objects.fields(a=1, b=0, slice__c=2)
self.assertEqual(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):
"""Ensure that QuerySet.only only returns the requested fields.
@@ -155,20 +154,20 @@ class OnlyExcludeAllTest(unittest.TestCase):
person.save()
obj = self.Person.objects.only("name").get()
self.assertEqual(obj.name, person.name)
self.assertEqual(obj.age, None)
assert obj.name == person.name
assert obj.age is None
obj = self.Person.objects.only("age").get()
self.assertEqual(obj.name, None)
self.assertEqual(obj.age, person.age)
assert obj.name is None
assert obj.age == person.age
obj = self.Person.objects.only("name", "age").get()
self.assertEqual(obj.name, person.name)
self.assertEqual(obj.age, person.age)
assert obj.name == person.name
assert obj.age == person.age
obj = self.Person.objects.only(*("id", "name")).get()
self.assertEqual(obj.name, person.name)
self.assertEqual(obj.age, None)
assert obj.name == person.name
assert obj.age is None
# Check polymorphism still works
class Employee(self.Person):
@@ -178,12 +177,12 @@ class OnlyExcludeAllTest(unittest.TestCase):
employee.save()
obj = self.Person.objects(id=employee.id).only("age").get()
self.assertIsInstance(obj, Employee)
assert isinstance(obj, Employee)
# Check field names are looked up properly
obj = Employee.objects(id=employee.id).only("salary").get()
self.assertEqual(obj.salary, employee.salary)
self.assertEqual(obj.name, None)
assert obj.salary == employee.salary
assert obj.name is None
def test_only_with_subfields(self):
class User(EmbeddedDocument):
@@ -217,29 +216,29 @@ class OnlyExcludeAllTest(unittest.TestCase):
post.save()
obj = BlogPost.objects.only("author.name").get()
self.assertEqual(obj.content, None)
self.assertEqual(obj.author.email, None)
self.assertEqual(obj.author.name, "Test User")
self.assertEqual(obj.comments, [])
assert obj.content is None
assert obj.author.email is None
assert obj.author.name == "Test User"
assert obj.comments == []
obj = BlogPost.objects.only("various.test_dynamic.some").get()
self.assertEqual(obj.various["test_dynamic"].some, True)
assert obj.various["test_dynamic"].some is True
obj = BlogPost.objects.only("content", "comments.title").get()
self.assertEqual(obj.content, "Had a good coffee today...")
self.assertEqual(obj.author, None)
self.assertEqual(obj.comments[0].title, "I aggree")
self.assertEqual(obj.comments[1].title, "Coffee")
self.assertEqual(obj.comments[0].text, None)
self.assertEqual(obj.comments[1].text, None)
assert obj.content == "Had a good coffee today..."
assert obj.author is None
assert obj.comments[0].title == "I aggree"
assert obj.comments[1].title == "Coffee"
assert obj.comments[0].text is None
assert obj.comments[1].text is None
obj = BlogPost.objects.only("comments").get()
self.assertEqual(obj.content, None)
self.assertEqual(obj.author, None)
self.assertEqual(obj.comments[0].title, "I aggree")
self.assertEqual(obj.comments[1].title, "Coffee")
self.assertEqual(obj.comments[0].text, "Great post!")
self.assertEqual(obj.comments[1].text, "I hate coffee")
assert obj.content is None
assert obj.author is None
assert obj.comments[0].title == "I aggree"
assert obj.comments[1].title == "Coffee"
assert obj.comments[0].text == "Great post!"
assert obj.comments[1].text == "I hate coffee"
BlogPost.drop_collection()
@@ -268,10 +267,10 @@ class OnlyExcludeAllTest(unittest.TestCase):
post.save()
obj = BlogPost.objects.exclude("author", "comments.text").get()
self.assertEqual(obj.author, None)
self.assertEqual(obj.content, "Had a good coffee today...")
self.assertEqual(obj.comments[0].title, "I aggree")
self.assertEqual(obj.comments[0].text, None)
assert obj.author is None
assert obj.content == "Had a good coffee today..."
assert obj.comments[0].title == "I aggree"
assert obj.comments[0].text is None
BlogPost.drop_collection()
@@ -303,18 +302,18 @@ class OnlyExcludeAllTest(unittest.TestCase):
email.save()
obj = Email.objects.exclude("content_type").exclude("body").get()
self.assertEqual(obj.sender, "me")
self.assertEqual(obj.to, "you")
self.assertEqual(obj.subject, "From Russia with Love")
self.assertEqual(obj.body, None)
self.assertEqual(obj.content_type, None)
assert obj.sender == "me"
assert obj.to == "you"
assert obj.subject == "From Russia with Love"
assert obj.body is None
assert obj.content_type is None
obj = Email.objects.only("sender", "to").exclude("body", "sender").get()
self.assertEqual(obj.sender, None)
self.assertEqual(obj.to, "you")
self.assertEqual(obj.subject, None)
self.assertEqual(obj.body, None)
self.assertEqual(obj.content_type, None)
assert obj.sender is None
assert obj.to == "you"
assert obj.subject is None
assert obj.body is None
assert obj.content_type is None
obj = (
Email.objects.exclude("attachments.content")
@@ -322,13 +321,13 @@ class OnlyExcludeAllTest(unittest.TestCase):
.only("to", "attachments.name")
.get()
)
self.assertEqual(obj.attachments[0].name, "file1.doc")
self.assertEqual(obj.attachments[0].content, None)
self.assertEqual(obj.sender, None)
self.assertEqual(obj.to, "you")
self.assertEqual(obj.subject, None)
self.assertEqual(obj.body, None)
self.assertEqual(obj.content_type, None)
assert obj.attachments[0].name == "file1.doc"
assert obj.attachments[0].content is None
assert obj.sender is None
assert obj.to == "you"
assert obj.subject is None
assert obj.body is None
assert obj.content_type is None
Email.drop_collection()
@@ -357,11 +356,11 @@ class OnlyExcludeAllTest(unittest.TestCase):
.all_fields()
.get()
)
self.assertEqual(obj.sender, "me")
self.assertEqual(obj.to, "you")
self.assertEqual(obj.subject, "From Russia with Love")
self.assertEqual(obj.body, "Hello!")
self.assertEqual(obj.content_type, "text/plain")
assert obj.sender == "me"
assert obj.to == "you"
assert obj.subject == "From Russia with Love"
assert obj.body == "Hello!"
assert obj.content_type == "text/plain"
Email.drop_collection()
@@ -379,27 +378,27 @@ class OnlyExcludeAllTest(unittest.TestCase):
# first three
numbers = Numbers.objects.fields(slice__n=3).get()
self.assertEqual(numbers.n, [0, 1, 2])
assert numbers.n == [0, 1, 2]
# last three
numbers = Numbers.objects.fields(slice__n=-3).get()
self.assertEqual(numbers.n, [-3, -2, -1])
assert numbers.n == [-3, -2, -1]
# skip 2, limit 3
numbers = Numbers.objects.fields(slice__n=[2, 3]).get()
self.assertEqual(numbers.n, [2, 3, 4])
assert numbers.n == [2, 3, 4]
# skip to fifth from last, limit 4
numbers = Numbers.objects.fields(slice__n=[-5, 4]).get()
self.assertEqual(numbers.n, [-5, -4, -3, -2])
assert numbers.n == [-5, -4, -3, -2]
# skip to fifth from last, limit 10
numbers = Numbers.objects.fields(slice__n=[-5, 10]).get()
self.assertEqual(numbers.n, [-5, -4, -3, -2, -1])
assert numbers.n == [-5, -4, -3, -2, -1]
# skip to fifth from last, limit 10 dict method
numbers = Numbers.objects.fields(n={"$slice": [-5, 10]}).get()
self.assertEqual(numbers.n, [-5, -4, -3, -2, -1])
assert numbers.n == [-5, -4, -3, -2, -1]
def test_slicing_nested_fields(self):
"""Ensure that query slicing an embedded array works.
@@ -419,27 +418,27 @@ class OnlyExcludeAllTest(unittest.TestCase):
# first three
numbers = Numbers.objects.fields(slice__embedded__n=3).get()
self.assertEqual(numbers.embedded.n, [0, 1, 2])
assert numbers.embedded.n == [0, 1, 2]
# last three
numbers = Numbers.objects.fields(slice__embedded__n=-3).get()
self.assertEqual(numbers.embedded.n, [-3, -2, -1])
assert numbers.embedded.n == [-3, -2, -1]
# skip 2, limit 3
numbers = Numbers.objects.fields(slice__embedded__n=[2, 3]).get()
self.assertEqual(numbers.embedded.n, [2, 3, 4])
assert numbers.embedded.n == [2, 3, 4]
# skip to fifth from last, limit 4
numbers = Numbers.objects.fields(slice__embedded__n=[-5, 4]).get()
self.assertEqual(numbers.embedded.n, [-5, -4, -3, -2])
assert numbers.embedded.n == [-5, -4, -3, -2]
# skip to fifth from last, limit 10
numbers = Numbers.objects.fields(slice__embedded__n=[-5, 10]).get()
self.assertEqual(numbers.embedded.n, [-5, -4, -3, -2, -1])
assert numbers.embedded.n == [-5, -4, -3, -2, -1]
# skip to fifth from last, limit 10 dict method
numbers = Numbers.objects.fields(embedded__n={"$slice": [-5, 10]}).get()
self.assertEqual(numbers.embedded.n, [-5, -4, -3, -2, -1])
assert numbers.embedded.n == [-5, -4, -3, -2, -1]
def test_exclude_from_subclasses_docs(self):
class Base(Document):
@@ -458,9 +457,10 @@ class OnlyExcludeAllTest(unittest.TestCase):
User(username="mongodb", password="secret").save()
user = Base.objects().exclude("password", "wibble").first()
self.assertEqual(user.password, None)
assert user.password is None
self.assertRaises(LookUpError, Base.objects.exclude, "made_up")
with pytest.raises(LookUpError):
Base.objects.exclude("made_up")
if __name__ == "__main__":

View File

@@ -6,10 +6,7 @@ from mongoengine import *
from tests.utils import MongoDBTestCase
__all__ = ("GeoQueriesTest",)
class GeoQueriesTest(MongoDBTestCase):
class TestGeoQueries(MongoDBTestCase):
def _create_event_data(self, point_field_class=GeoPointField):
"""Create some sample data re-used in many of the tests below."""
@@ -51,14 +48,14 @@ class GeoQueriesTest(MongoDBTestCase):
# note that "near" will show the san francisco event, too,
# although it sorts to last.
events = self.Event.objects(location__near=[-87.67892, 41.9120459])
self.assertEqual(events.count(), 3)
self.assertEqual(list(events), [event1, event3, event2])
assert events.count() == 3
assert list(events) == [event1, event3, event2]
# ensure ordering is respected by "near"
events = self.Event.objects(location__near=[-87.67892, 41.9120459])
events = events.order_by("-date")
self.assertEqual(events.count(), 3)
self.assertEqual(list(events), [event3, event1, event2])
assert events.count() == 3
assert list(events) == [event3, event1, event2]
def test_near_and_max_distance(self):
"""Ensure the "max_distance" operator works alongside the "near"
@@ -69,8 +66,8 @@ class GeoQueriesTest(MongoDBTestCase):
# find events within 10 degrees of san francisco
point = [-122.415579, 37.7566023]
events = self.Event.objects(location__near=point, location__max_distance=10)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
assert events.count() == 1
assert events[0] == event2
def test_near_and_min_distance(self):
"""Ensure the "min_distance" operator works alongside the "near"
@@ -81,7 +78,7 @@ class GeoQueriesTest(MongoDBTestCase):
# find events at least 10 degrees away of san francisco
point = [-122.415579, 37.7566023]
events = self.Event.objects(location__near=point, location__min_distance=10)
self.assertEqual(events.count(), 2)
assert events.count() == 2
def test_within_distance(self):
"""Make sure the "within_distance" operator works."""
@@ -90,29 +87,29 @@ class GeoQueriesTest(MongoDBTestCase):
# find events within 5 degrees of pitchfork office, chicago
point_and_distance = [[-87.67892, 41.9120459], 5]
events = self.Event.objects(location__within_distance=point_and_distance)
self.assertEqual(events.count(), 2)
assert events.count() == 2
events = list(events)
self.assertNotIn(event2, events)
self.assertIn(event1, events)
self.assertIn(event3, events)
assert event2 not in events
assert event1 in events
assert event3 in events
# find events within 10 degrees of san francisco
point_and_distance = [[-122.415579, 37.7566023], 10]
events = self.Event.objects(location__within_distance=point_and_distance)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
assert events.count() == 1
assert events[0] == event2
# find events within 1 degree of greenpoint, broolyn, nyc, ny
point_and_distance = [[-73.9509714, 40.7237134], 1]
events = self.Event.objects(location__within_distance=point_and_distance)
self.assertEqual(events.count(), 0)
assert events.count() == 0
# ensure ordering is respected by "within_distance"
point_and_distance = [[-87.67892, 41.9120459], 10]
events = self.Event.objects(location__within_distance=point_and_distance)
events = events.order_by("-date")
self.assertEqual(events.count(), 2)
self.assertEqual(events[0], event3)
assert events.count() == 2
assert events[0] == event3
def test_within_box(self):
"""Ensure the "within_box" operator works."""
@@ -121,8 +118,8 @@ class GeoQueriesTest(MongoDBTestCase):
# check that within_box works
box = [(-125.0, 35.0), (-100.0, 40.0)]
events = self.Event.objects(location__within_box=box)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0].id, event2.id)
assert events.count() == 1
assert events[0].id == event2.id
def test_within_polygon(self):
"""Ensure the "within_polygon" operator works."""
@@ -136,8 +133,8 @@ class GeoQueriesTest(MongoDBTestCase):
(-87.656164, 41.898061),
]
events = self.Event.objects(location__within_polygon=polygon)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0].id, event1.id)
assert events.count() == 1
assert events[0].id == event1.id
polygon2 = [
(-1.742249, 54.033586),
@@ -145,7 +142,7 @@ class GeoQueriesTest(MongoDBTestCase):
(-4.40094, 53.389881),
]
events = self.Event.objects(location__within_polygon=polygon2)
self.assertEqual(events.count(), 0)
assert events.count() == 0
def test_2dsphere_near(self):
"""Make sure the "near" operator works with a PointField, which
@@ -157,14 +154,14 @@ class GeoQueriesTest(MongoDBTestCase):
# note that "near" will show the san francisco event, too,
# although it sorts to last.
events = self.Event.objects(location__near=[-87.67892, 41.9120459])
self.assertEqual(events.count(), 3)
self.assertEqual(list(events), [event1, event3, event2])
assert events.count() == 3
assert list(events) == [event1, event3, event2]
# ensure ordering is respected by "near"
events = self.Event.objects(location__near=[-87.67892, 41.9120459])
events = events.order_by("-date")
self.assertEqual(events.count(), 3)
self.assertEqual(list(events), [event3, event1, event2])
assert events.count() == 3
assert list(events) == [event3, event1, event2]
def test_2dsphere_near_and_max_distance(self):
"""Ensure the "max_distance" operator works alongside the "near"
@@ -175,21 +172,21 @@ class GeoQueriesTest(MongoDBTestCase):
# find events within 10km of san francisco
point = [-122.415579, 37.7566023]
events = self.Event.objects(location__near=point, location__max_distance=10000)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
assert events.count() == 1
assert events[0] == event2
# find events within 1km of greenpoint, broolyn, nyc, ny
events = self.Event.objects(
location__near=[-73.9509714, 40.7237134], location__max_distance=1000
)
self.assertEqual(events.count(), 0)
assert events.count() == 0
# ensure ordering is respected by "near"
events = self.Event.objects(
location__near=[-87.67892, 41.9120459], location__max_distance=10000
).order_by("-date")
self.assertEqual(events.count(), 2)
self.assertEqual(events[0], event3)
assert events.count() == 2
assert events[0] == event3
def test_2dsphere_geo_within_box(self):
"""Ensure the "geo_within_box" operator works with a 2dsphere
@@ -200,8 +197,8 @@ class GeoQueriesTest(MongoDBTestCase):
# check that within_box works
box = [(-125.0, 35.0), (-100.0, 40.0)]
events = self.Event.objects(location__geo_within_box=box)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0].id, event2.id)
assert events.count() == 1
assert events[0].id == event2.id
def test_2dsphere_geo_within_polygon(self):
"""Ensure the "geo_within_polygon" operator works with a
@@ -217,8 +214,8 @@ class GeoQueriesTest(MongoDBTestCase):
(-87.656164, 41.898061),
]
events = self.Event.objects(location__geo_within_polygon=polygon)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0].id, event1.id)
assert events.count() == 1
assert events[0].id == event1.id
polygon2 = [
(-1.742249, 54.033586),
@@ -226,7 +223,7 @@ class GeoQueriesTest(MongoDBTestCase):
(-4.40094, 53.389881),
]
events = self.Event.objects(location__geo_within_polygon=polygon2)
self.assertEqual(events.count(), 0)
assert events.count() == 0
def test_2dsphere_near_and_min_max_distance(self):
"""Ensure "min_distace" and "max_distance" operators work well
@@ -240,15 +237,15 @@ class GeoQueriesTest(MongoDBTestCase):
location__min_distance=1000,
location__max_distance=10000,
).order_by("-date")
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event3)
assert events.count() == 1
assert events[0] == event3
# ensure ordering is respected by "near" with "min_distance"
events = self.Event.objects(
location__near=[-87.67892, 41.9120459], location__min_distance=10000
).order_by("-date")
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
assert events.count() == 1
assert events[0] == event2
def test_2dsphere_geo_within_center(self):
"""Make sure the "geo_within_center" operator works with a
@@ -259,11 +256,11 @@ class GeoQueriesTest(MongoDBTestCase):
# find events within 5 degrees of pitchfork office, chicago
point_and_distance = [[-87.67892, 41.9120459], 2]
events = self.Event.objects(location__geo_within_center=point_and_distance)
self.assertEqual(events.count(), 2)
assert events.count() == 2
events = list(events)
self.assertNotIn(event2, events)
self.assertIn(event1, events)
self.assertIn(event3, events)
assert event2 not in events
assert event1 in events
assert event3 in events
def _test_embedded(self, point_field_class):
"""Helper test method ensuring given point field class works
@@ -293,8 +290,8 @@ class GeoQueriesTest(MongoDBTestCase):
# note that "near" will show the san francisco event, too,
# although it sorts to last.
events = Event.objects(venue__location__near=[-87.67892, 41.9120459])
self.assertEqual(events.count(), 3)
self.assertEqual(list(events), [event1, event3, event2])
assert events.count() == 3
assert list(events) == [event1, event3, event2]
def test_geo_spatial_embedded(self):
"""Make sure GeoPointField works properly in an embedded document."""
@@ -322,55 +319,55 @@ class GeoQueriesTest(MongoDBTestCase):
# Finds both points because they are within 60 km of the reference
# point equidistant between them.
points = Point.objects(location__near_sphere=[-122, 37.5])
self.assertEqual(points.count(), 2)
assert points.count() == 2
# Same behavior for _within_spherical_distance
points = Point.objects(
location__within_spherical_distance=[[-122, 37.5], 60 / earth_radius]
)
self.assertEqual(points.count(), 2)
assert points.count() == 2
points = Point.objects(
location__near_sphere=[-122, 37.5], location__max_distance=60 / earth_radius
)
self.assertEqual(points.count(), 2)
assert points.count() == 2
# Test query works with max_distance, being farer from one point
points = Point.objects(
location__near_sphere=[-122, 37.8], location__max_distance=60 / earth_radius
)
close_point = points.first()
self.assertEqual(points.count(), 1)
assert points.count() == 1
# Test query works with min_distance, being farer from one point
points = Point.objects(
location__near_sphere=[-122, 37.8], location__min_distance=60 / earth_radius
)
self.assertEqual(points.count(), 1)
assert points.count() == 1
far_point = points.first()
self.assertNotEqual(close_point, far_point)
assert close_point != far_point
# Finds both points, but orders the north point first because it's
# closer to the reference point to the north.
points = Point.objects(location__near_sphere=[-122, 38.5])
self.assertEqual(points.count(), 2)
self.assertEqual(points[0].id, north_point.id)
self.assertEqual(points[1].id, south_point.id)
assert points.count() == 2
assert points[0].id == north_point.id
assert points[1].id == south_point.id
# Finds both points, but orders the south point first because it's
# closer to the reference point to the south.
points = Point.objects(location__near_sphere=[-122, 36.5])
self.assertEqual(points.count(), 2)
self.assertEqual(points[0].id, south_point.id)
self.assertEqual(points[1].id, north_point.id)
assert points.count() == 2
assert points[0].id == south_point.id
assert points[1].id == north_point.id
# Finds only one point because only the first point is within 60km of
# the reference point to the south.
points = Point.objects(
location__within_spherical_distance=[[-122, 36.5], 60 / earth_radius]
)
self.assertEqual(points.count(), 1)
self.assertEqual(points[0].id, south_point.id)
assert points.count() == 1
assert points[0].id == south_point.id
def test_linestring(self):
class Road(Document):
@@ -384,13 +381,13 @@ class GeoQueriesTest(MongoDBTestCase):
# near
point = {"type": "Point", "coordinates": [40, 5]}
roads = Road.objects.filter(line__near=point["coordinates"]).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(line__near=point).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(line__near={"$geometry": point}).count()
self.assertEqual(1, roads)
assert 1 == roads
# Within
polygon = {
@@ -398,37 +395,37 @@ class GeoQueriesTest(MongoDBTestCase):
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]],
}
roads = Road.objects.filter(line__geo_within=polygon["coordinates"]).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(line__geo_within=polygon).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(line__geo_within={"$geometry": polygon}).count()
self.assertEqual(1, roads)
assert 1 == roads
# Intersects
line = {"type": "LineString", "coordinates": [[40, 5], [40, 6]]}
roads = Road.objects.filter(line__geo_intersects=line["coordinates"]).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(line__geo_intersects=line).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(line__geo_intersects={"$geometry": line}).count()
self.assertEqual(1, roads)
assert 1 == roads
polygon = {
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]],
}
roads = Road.objects.filter(line__geo_intersects=polygon["coordinates"]).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(line__geo_intersects=polygon).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(line__geo_intersects={"$geometry": polygon}).count()
self.assertEqual(1, roads)
assert 1 == roads
def test_polygon(self):
class Road(Document):
@@ -442,13 +439,13 @@ class GeoQueriesTest(MongoDBTestCase):
# near
point = {"type": "Point", "coordinates": [40, 5]}
roads = Road.objects.filter(poly__near=point["coordinates"]).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(poly__near=point).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(poly__near={"$geometry": point}).count()
self.assertEqual(1, roads)
assert 1 == roads
# Within
polygon = {
@@ -456,37 +453,37 @@ class GeoQueriesTest(MongoDBTestCase):
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]],
}
roads = Road.objects.filter(poly__geo_within=polygon["coordinates"]).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(poly__geo_within=polygon).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(poly__geo_within={"$geometry": polygon}).count()
self.assertEqual(1, roads)
assert 1 == roads
# Intersects
line = {"type": "LineString", "coordinates": [[40, 5], [41, 6]]}
roads = Road.objects.filter(poly__geo_intersects=line["coordinates"]).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(poly__geo_intersects=line).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(poly__geo_intersects={"$geometry": line}).count()
self.assertEqual(1, roads)
assert 1 == roads
polygon = {
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]],
}
roads = Road.objects.filter(poly__geo_intersects=polygon["coordinates"]).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(poly__geo_intersects=polygon).count()
self.assertEqual(1, roads)
assert 1 == roads
roads = Road.objects.filter(poly__geo_intersects={"$geometry": polygon}).count()
self.assertEqual(1, roads)
assert 1 == roads
def test_aspymongo_with_only(self):
"""Ensure as_pymongo works with only"""
@@ -498,13 +495,10 @@ class GeoQueriesTest(MongoDBTestCase):
p = Place(location=[24.946861267089844, 60.16311983618494])
p.save()
qs = Place.objects().only("location")
self.assertDictEqual(
qs.as_pymongo()[0]["location"],
{
u"type": u"Point",
u"coordinates": [24.946861267089844, 60.16311983618494],
},
)
assert qs.as_pymongo()[0]["location"] == {
u"type": u"Point",
u"coordinates": [24.946861267089844, 60.16311983618494],
}
def test_2dsphere_point_sets_correctly(self):
class Location(Document):
@@ -514,11 +508,11 @@ class GeoQueriesTest(MongoDBTestCase):
Location(loc=[1, 2]).save()
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["loc"], {"type": "Point", "coordinates": [1, 2]})
assert loc["loc"] == {"type": "Point", "coordinates": [1, 2]}
Location.objects.update(set__loc=[2, 1])
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["loc"], {"type": "Point", "coordinates": [2, 1]})
assert loc["loc"] == {"type": "Point", "coordinates": [2, 1]}
def test_2dsphere_linestring_sets_correctly(self):
class Location(Document):
@@ -528,15 +522,11 @@ class GeoQueriesTest(MongoDBTestCase):
Location(line=[[1, 2], [2, 2]]).save()
loc = Location.objects.as_pymongo()[0]
self.assertEqual(
loc["line"], {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}
)
assert loc["line"] == {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}
Location.objects.update(set__line=[[2, 1], [1, 2]])
loc = Location.objects.as_pymongo()[0]
self.assertEqual(
loc["line"], {"type": "LineString", "coordinates": [[2, 1], [1, 2]]}
)
assert loc["line"] == {"type": "LineString", "coordinates": [[2, 1], [1, 2]]}
def test_geojson_PolygonField(self):
class Location(Document):
@@ -546,17 +536,17 @@ class GeoQueriesTest(MongoDBTestCase):
Location(poly=[[[40, 5], [40, 6], [41, 6], [40, 5]]]).save()
loc = Location.objects.as_pymongo()[0]
self.assertEqual(
loc["poly"],
{"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]},
)
assert loc["poly"] == {
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]],
}
Location.objects.update(set__poly=[[[40, 4], [40, 6], [41, 6], [40, 4]]])
loc = Location.objects.as_pymongo()[0]
self.assertEqual(
loc["poly"],
{"type": "Polygon", "coordinates": [[[40, 4], [40, 6], [41, 6], [40, 4]]]},
)
assert loc["poly"] == {
"type": "Polygon",
"coordinates": [[[40, 4], [40, 6], [41, 6], [40, 4]]],
}
if __name__ == "__main__":

View File

@@ -1,8 +1,6 @@
import unittest
from mongoengine import connect, Document, IntField, StringField, ListField
__all__ = ("FindAndModifyTest",)
from mongoengine import Document, IntField, ListField, StringField, connect
class Doc(Document):
@@ -10,20 +8,20 @@ class Doc(Document):
value = IntField()
class FindAndModifyTest(unittest.TestCase):
class TestFindAndModify(unittest.TestCase):
def setUp(self):
connect(db="mongoenginetest")
Doc.drop_collection()
def assertDbEqual(self, docs):
self.assertEqual(list(Doc._collection.find().sort("id")), docs)
assert list(Doc._collection.find().sort("id")) == docs
def test_modify(self):
Doc(id=0, value=0).save()
doc = Doc(id=1, value=1).save()
old_doc = Doc.objects(id=1).modify(set__value=-1)
self.assertEqual(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}])
def test_modify_with_new(self):
@@ -32,18 +30,18 @@ class FindAndModifyTest(unittest.TestCase):
new_doc = Doc.objects(id=1).modify(set__value=-1, new=True)
doc.value = -1
self.assertEqual(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}])
def test_modify_not_existing(self):
Doc(id=0, value=0).save()
self.assertEqual(Doc.objects(id=1).modify(set__value=-1), None)
assert Doc.objects(id=1).modify(set__value=-1) is None
self.assertDbEqual([{"_id": 0, "value": 0}])
def test_modify_with_upsert(self):
Doc(id=0, value=0).save()
old_doc = Doc.objects(id=1).modify(set__value=1, upsert=True)
self.assertEqual(old_doc, None)
assert old_doc is None
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
def test_modify_with_upsert_existing(self):
@@ -51,13 +49,13 @@ class FindAndModifyTest(unittest.TestCase):
doc = Doc(id=1, value=1).save()
old_doc = Doc.objects(id=1).modify(set__value=-1, upsert=True)
self.assertEqual(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}])
def test_modify_with_upsert_with_new(self):
Doc(id=0, value=0).save()
new_doc = Doc.objects(id=1).modify(upsert=True, new=True, set__value=1)
self.assertEqual(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}])
def test_modify_with_remove(self):
@@ -65,12 +63,12 @@ class FindAndModifyTest(unittest.TestCase):
doc = Doc(id=1, value=1).save()
old_doc = Doc.objects(id=1).modify(remove=True)
self.assertEqual(old_doc.to_json(), doc.to_json())
assert old_doc.to_json() == doc.to_json()
self.assertDbEqual([{"_id": 0, "value": 0}])
def test_find_and_modify_with_remove_not_existing(self):
Doc(id=0, value=0).save()
self.assertEqual(Doc.objects(id=1).modify(remove=True), None)
assert Doc.objects(id=1).modify(remove=True) is None
self.assertDbEqual([{"_id": 0, "value": 0}])
def test_modify_with_order_by(self):
@@ -80,7 +78,7 @@ class FindAndModifyTest(unittest.TestCase):
doc = Doc(id=3, value=0).save()
old_doc = Doc.objects().order_by("-id").modify(set__value=-1)
self.assertEqual(old_doc.to_json(), doc.to_json())
assert old_doc.to_json() == doc.to_json()
self.assertDbEqual(
[
{"_id": 0, "value": 3},
@@ -95,7 +93,7 @@ class FindAndModifyTest(unittest.TestCase):
Doc(id=1, value=1).save()
old_doc = Doc.objects(id=1).only("id").modify(set__value=-1)
self.assertEqual(old_doc.to_mongo(), {"_id": 1})
assert old_doc.to_mongo() == {"_id": 1}
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
def test_modify_with_push(self):
@@ -108,23 +106,23 @@ class FindAndModifyTest(unittest.TestCase):
# Push a new tag via modify with new=False (default).
BlogPost(id=blog.id).modify(push__tags="code")
self.assertEqual(blog.tags, [])
assert blog.tags == []
blog.reload()
self.assertEqual(blog.tags, ["code"])
assert blog.tags == ["code"]
# Push a new tag via modify with new=True.
blog = BlogPost.objects(id=blog.id).modify(push__tags="java", new=True)
self.assertEqual(blog.tags, ["code", "java"])
assert blog.tags == ["code", "java"]
# Push a new tag with a positional argument.
blog = BlogPost.objects(id=blog.id).modify(push__tags__0="python", new=True)
self.assertEqual(blog.tags, ["python", "code", "java"])
assert blog.tags == ["python", "code", "java"]
# Push multiple new tags with a positional argument.
blog = BlogPost.objects(id=blog.id).modify(
push__tags__1=["go", "rust"], new=True
)
self.assertEqual(blog.tags, ["python", "go", "rust", "code", "java"])
assert blog.tags == ["python", "go", "rust", "code", "java"]
if __name__ == "__main__":

View File

@@ -1,10 +1,9 @@
import pickle
import unittest
from pymongo.mongo_client import MongoClient
from mongoengine import Document, StringField, IntField
from mongoengine.connection import connect
__author__ = "stas"
from mongoengine import Document, IntField, StringField
from mongoengine.connection import connect
from tests.utils import MongoDBTestCase
class Person(Document):
@@ -12,7 +11,7 @@ class Person(Document):
age = IntField()
class TestQuerysetPickable(unittest.TestCase):
class TestQuerysetPickable(MongoDBTestCase):
"""
Test for adding pickling support for QuerySet instances
See issue https://github.com/MongoEngine/mongoengine/issues/442
@@ -20,11 +19,6 @@ class TestQuerysetPickable(unittest.TestCase):
def setUp(self):
super(TestQuerysetPickable, self).setUp()
connection = connect(db="test") # type: pymongo.mongo_client.MongoClient
connection.drop_database("test")
self.john = Person.objects.create(name="John", age=21)
def test_picke_simple_qs(self):
@@ -43,13 +37,13 @@ class TestQuerysetPickable(unittest.TestCase):
loadedQs = self._get_loaded(qs)
self.assertEqual(qs.count(), loadedQs.count())
assert qs.count() == loadedQs.count()
# can update loadedQs
loadedQs.update(age=23)
# check
self.assertEqual(Person.objects.first().age, 23)
assert Person.objects.first().age == 23
def test_pickle_support_filtration(self):
Person.objects.create(name="Alice", age=22)
@@ -57,9 +51,9 @@ class TestQuerysetPickable(unittest.TestCase):
Person.objects.create(name="Bob", age=23)
qs = Person.objects.filter(age__gte=22)
self.assertEqual(qs.count(), 2)
assert qs.count() == 2
loaded = self._get_loaded(qs)
self.assertEqual(loaded.count(), 2)
self.assertEqual(loaded.filter(name="Bob").first().age, 23)
assert loaded.count() == 2
assert loaded.filter(name="Bob").first().age == 23

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,255 @@
# -*- coding: utf-8 -*-
import unittest
import warnings
from pymongo.read_preferences import ReadPreference
from mongoengine import *
from tests.utils import MongoDBTestCase
class TestQuerysetAggregate(MongoDBTestCase):
def test_read_preference_aggregation_framework(self):
class Bar(Document):
txt = StringField()
meta = {"indexes": ["txt"]}
# Aggregates with read_preference
pipeline = []
bars = Bar.objects.read_preference(
ReadPreference.SECONDARY_PREFERRED
).aggregate(pipeline)
assert (
bars._CommandCursor__collection.read_preference
== ReadPreference.SECONDARY_PREFERRED
)
def test_queryset_aggregation_framework(self):
class Person(Document):
name = StringField()
age = IntField()
Person.drop_collection()
p1 = Person(name="Isabella Luanna", age=16)
p2 = Person(name="Wilson Junior", age=21)
p3 = Person(name="Sandra Mara", age=37)
Person.objects.insert([p1, p2, p3])
pipeline = [{"$project": {"name": {"$toUpper": "$name"}}}]
data = Person.objects(age__lte=22).aggregate(pipeline)
assert list(data) == [
{"_id": p1.pk, "name": "ISABELLA LUANNA"},
{"_id": p2.pk, "name": "WILSON JUNIOR"},
]
pipeline = [{"$project": {"name": {"$toUpper": "$name"}}}]
data = Person.objects(age__lte=22).order_by("-name").aggregate(pipeline)
assert list(data) == [
{"_id": p2.pk, "name": "WILSON JUNIOR"},
{"_id": p1.pk, "name": "ISABELLA LUANNA"},
]
pipeline = [
{"$group": {"_id": None, "total": {"$sum": 1}, "avg": {"$avg": "$age"}}}
]
data = (
Person.objects(age__gte=17, age__lte=40)
.order_by("-age")
.aggregate(pipeline)
)
assert list(data) == [{"_id": None, "avg": 29, "total": 2}]
pipeline = [{"$match": {"name": "Isabella Luanna"}}]
data = Person.objects().aggregate(pipeline)
assert list(data) == [{u"_id": p1.pk, u"age": 16, u"name": u"Isabella Luanna"}]
def test_queryset_aggregation_with_skip(self):
class Person(Document):
name = StringField()
age = IntField()
Person.drop_collection()
p1 = Person(name="Isabella Luanna", age=16)
p2 = Person(name="Wilson Junior", age=21)
p3 = Person(name="Sandra Mara", age=37)
Person.objects.insert([p1, p2, p3])
pipeline = [{"$project": {"name": {"$toUpper": "$name"}}}]
data = Person.objects.skip(1).aggregate(pipeline)
assert list(data) == [
{"_id": p2.pk, "name": "WILSON JUNIOR"},
{"_id": p3.pk, "name": "SANDRA MARA"},
]
def test_queryset_aggregation_with_limit(self):
class Person(Document):
name = StringField()
age = IntField()
Person.drop_collection()
p1 = Person(name="Isabella Luanna", age=16)
p2 = Person(name="Wilson Junior", age=21)
p3 = Person(name="Sandra Mara", age=37)
Person.objects.insert([p1, p2, p3])
pipeline = [{"$project": {"name": {"$toUpper": "$name"}}}]
data = Person.objects.limit(1).aggregate(pipeline)
assert list(data) == [{"_id": p1.pk, "name": "ISABELLA LUANNA"}]
def test_queryset_aggregation_with_sort(self):
class Person(Document):
name = StringField()
age = IntField()
Person.drop_collection()
p1 = Person(name="Isabella Luanna", age=16)
p2 = Person(name="Wilson Junior", age=21)
p3 = Person(name="Sandra Mara", age=37)
Person.objects.insert([p1, p2, p3])
pipeline = [{"$project": {"name": {"$toUpper": "$name"}}}]
data = Person.objects.order_by("name").aggregate(pipeline)
assert list(data) == [
{"_id": p1.pk, "name": "ISABELLA LUANNA"},
{"_id": p3.pk, "name": "SANDRA MARA"},
{"_id": p2.pk, "name": "WILSON JUNIOR"},
]
def test_queryset_aggregation_with_skip_with_limit(self):
class Person(Document):
name = StringField()
age = IntField()
Person.drop_collection()
p1 = Person(name="Isabella Luanna", age=16)
p2 = Person(name="Wilson Junior", age=21)
p3 = Person(name="Sandra Mara", age=37)
Person.objects.insert([p1, p2, p3])
pipeline = [{"$project": {"name": {"$toUpper": "$name"}}}]
data = list(Person.objects.skip(1).limit(1).aggregate(pipeline))
assert list(data) == [{"_id": p2.pk, "name": "WILSON JUNIOR"}]
# Make sure limit/skip chaining order has no impact
data2 = Person.objects.limit(1).skip(1).aggregate(pipeline)
assert data == list(data2)
def test_queryset_aggregation_with_sort_with_limit(self):
class Person(Document):
name = StringField()
age = IntField()
Person.drop_collection()
p1 = Person(name="Isabella Luanna", age=16)
p2 = Person(name="Wilson Junior", age=21)
p3 = Person(name="Sandra Mara", age=37)
Person.objects.insert([p1, p2, p3])
pipeline = [{"$project": {"name": {"$toUpper": "$name"}}}]
data = Person.objects.order_by("name").limit(2).aggregate(pipeline)
assert list(data) == [
{"_id": p1.pk, "name": "ISABELLA LUANNA"},
{"_id": p3.pk, "name": "SANDRA MARA"},
]
# Verify adding limit/skip steps works as expected
pipeline = [{"$project": {"name": {"$toUpper": "$name"}}}, {"$limit": 1}]
data = Person.objects.order_by("name").limit(2).aggregate(pipeline)
assert list(data) == [{"_id": p1.pk, "name": "ISABELLA LUANNA"}]
pipeline = [
{"$project": {"name": {"$toUpper": "$name"}}},
{"$skip": 1},
{"$limit": 1},
]
data = Person.objects.order_by("name").limit(2).aggregate(pipeline)
assert list(data) == [{"_id": p3.pk, "name": "SANDRA MARA"}]
def test_queryset_aggregation_with_sort_with_skip(self):
class Person(Document):
name = StringField()
age = IntField()
Person.drop_collection()
p1 = Person(name="Isabella Luanna", age=16)
p2 = Person(name="Wilson Junior", age=21)
p3 = Person(name="Sandra Mara", age=37)
Person.objects.insert([p1, p2, p3])
pipeline = [{"$project": {"name": {"$toUpper": "$name"}}}]
data = Person.objects.order_by("name").skip(2).aggregate(pipeline)
assert list(data) == [{"_id": p2.pk, "name": "WILSON JUNIOR"}]
def test_queryset_aggregation_with_sort_with_skip_with_limit(self):
class Person(Document):
name = StringField()
age = IntField()
Person.drop_collection()
p1 = Person(name="Isabella Luanna", age=16)
p2 = Person(name="Wilson Junior", age=21)
p3 = Person(name="Sandra Mara", age=37)
Person.objects.insert([p1, p2, p3])
pipeline = [{"$project": {"name": {"$toUpper": "$name"}}}]
data = Person.objects.order_by("name").skip(1).limit(1).aggregate(pipeline)
assert list(data) == [{"_id": p3.pk, "name": "SANDRA MARA"}]
def test_queryset_aggregation_deprecated_interface(self):
class Person(Document):
name = StringField()
Person.drop_collection()
p1 = Person(name="Isabella Luanna")
p2 = Person(name="Wilson Junior")
p3 = Person(name="Sandra Mara")
Person.objects.insert([p1, p2, p3])
pipeline = [{"$project": {"name": {"$toUpper": "$name"}}}]
# Make sure a warning is emitted
with warnings.catch_warnings():
warnings.simplefilter("error", DeprecationWarning)
with self.assertRaises(DeprecationWarning):
Person.objects.order_by("name").limit(2).aggregate(*pipeline)
# Make sure old interface works as expected with a 1-step pipeline
data = Person.objects.order_by("name").limit(2).aggregate(*pipeline)
assert list(data) == [
{"_id": p1.pk, "name": "ISABELLA LUANNA"},
{"_id": p3.pk, "name": "SANDRA MARA"},
]
# Make sure old interface works as expected with a 2-steps pipeline
pipeline = [{"$project": {"name": {"$toUpper": "$name"}}}, {"$limit": 1}]
data = Person.objects.order_by("name").limit(2).aggregate(*pipeline)
assert list(data) == [{"_id": p1.pk, "name": "ISABELLA LUANNA"}]
if __name__ == "__main__":
unittest.main()

View File

@@ -1,37 +1,35 @@
import unittest
from bson.son import SON
import pytest
from mongoengine import *
from mongoengine.queryset import Q, transform
__all__ = ("TransformTest",)
class TransformTest(unittest.TestCase):
class TestTransform(unittest.TestCase):
def setUp(self):
connect(db="mongoenginetest")
def test_transform_query(self):
"""Ensure that the _transform_query function operates correctly.
"""
self.assertEqual(
transform.query(name="test", age=30), {"name": "test", "age": 30}
)
self.assertEqual(transform.query(age__lt=30), {"age": {"$lt": 30}})
self.assertEqual(
transform.query(age__gt=20, age__lt=50), {"age": {"$gt": 20, "$lt": 50}}
)
self.assertEqual(
transform.query(age=20, age__gt=50),
{"$and": [{"age": {"$gt": 50}}, {"age": 20}]},
)
self.assertEqual(
transform.query(friend__age__gte=30), {"friend.age": {"$gte": 30}}
)
self.assertEqual(
transform.query(name__exists=True), {"name": {"$exists": True}}
)
assert transform.query(name="test", age=30) == {"name": "test", "age": 30}
assert transform.query(age__lt=30) == {"age": {"$lt": 30}}
assert transform.query(age__gt=20, age__lt=50) == {
"age": {"$gt": 20, "$lt": 50}
}
assert transform.query(age=20, age__gt=50) == {
"$and": [{"age": {"$gt": 50}}, {"age": 20}]
}
assert transform.query(friend__age__gte=30) == {"friend.age": {"$gte": 30}}
assert transform.query(name__exists=True) == {"name": {"$exists": True}}
assert transform.query(name=["Mark"], __raw__={"name": {"$in": "Tom"}}) == {
"$and": [{"name": ["Mark"]}, {"name": {"$in": "Tom"}}]
}
assert transform.query(name__in=["Tom"], __raw__={"name": "Mark"}) == {
"$and": [{"name": {"$in": ["Tom"]}}, {"name": "Mark"}]
}
def test_transform_update(self):
class LisDoc(Document):
@@ -56,17 +54,17 @@ class TransformTest(unittest.TestCase):
("push", "$push"),
):
update = transform.update(DicDoc, **{"%s__dictField__test" % k: doc})
self.assertIsInstance(update[v]["dictField.test"], dict)
assert isinstance(update[v]["dictField.test"], dict)
# Update special cases
update = transform.update(DicDoc, unset__dictField__test=doc)
self.assertEqual(update["$unset"]["dictField.test"], 1)
assert update["$unset"]["dictField.test"] == 1
update = transform.update(DicDoc, pull__dictField__test=doc)
self.assertIsInstance(update["$pull"]["dictField"]["test"], dict)
assert isinstance(update["$pull"]["dictField"]["test"], dict)
update = transform.update(LisDoc, pull__foo__in=["a"])
self.assertEqual(update, {"$pull": {"foo": {"$in": ["a"]}}})
assert update == {"$pull": {"foo": {"$in": ["a"]}}}
def test_transform_update_push(self):
"""Ensure the differences in behvaior between 'push' and 'push_all'"""
@@ -75,10 +73,10 @@ class TransformTest(unittest.TestCase):
tags = ListField(StringField())
update = transform.update(BlogPost, push__tags=["mongo", "db"])
self.assertEqual(update, {"$push": {"tags": ["mongo", "db"]}})
assert update == {"$push": {"tags": ["mongo", "db"]}}
update = transform.update(BlogPost, push_all__tags=["mongo", "db"])
self.assertEqual(update, {"$push": {"tags": {"$each": ["mongo", "db"]}}})
assert update == {"$push": {"tags": {"$each": ["mongo", "db"]}}}
def test_transform_update_no_operator_default_to_set(self):
"""Ensure the differences in behvaior between 'push' and 'push_all'"""
@@ -87,7 +85,7 @@ class TransformTest(unittest.TestCase):
tags = ListField(StringField())
update = transform.update(BlogPost, tags=["mongo", "db"])
self.assertEqual(update, {"$set": {"tags": ["mongo", "db"]}})
assert update == {"$set": {"tags": ["mongo", "db"]}}
def test_query_field_name(self):
"""Ensure that the correct field name is used when querying.
@@ -108,18 +106,18 @@ class TransformTest(unittest.TestCase):
post = BlogPost(**data)
post.save()
self.assertIn("postTitle", BlogPost.objects(title=data["title"])._query)
self.assertFalse("title" in BlogPost.objects(title=data["title"])._query)
self.assertEqual(BlogPost.objects(title=data["title"]).count(), 1)
assert "postTitle" in BlogPost.objects(title=data["title"])._query
assert not ("title" in BlogPost.objects(title=data["title"])._query)
assert BlogPost.objects(title=data["title"]).count() == 1
self.assertIn("_id", BlogPost.objects(pk=post.id)._query)
self.assertEqual(BlogPost.objects(pk=post.id).count(), 1)
assert "_id" in BlogPost.objects(pk=post.id)._query
assert BlogPost.objects(pk=post.id).count() == 1
self.assertIn(
"postComments.commentContent",
BlogPost.objects(comments__content="test")._query,
assert (
"postComments.commentContent"
in BlogPost.objects(comments__content="test")._query
)
self.assertEqual(BlogPost.objects(comments__content="test").count(), 1)
assert BlogPost.objects(comments__content="test").count() == 1
BlogPost.drop_collection()
@@ -137,9 +135,9 @@ class TransformTest(unittest.TestCase):
post = BlogPost(**data)
post.save()
self.assertIn("_id", BlogPost.objects(pk=data["title"])._query)
self.assertIn("_id", BlogPost.objects(title=data["title"])._query)
self.assertEqual(BlogPost.objects(pk=data["title"]).count(), 1)
assert "_id" in BlogPost.objects(pk=data["title"])._query
assert "_id" in BlogPost.objects(title=data["title"])._query
assert BlogPost.objects(pk=data["title"]).count() == 1
BlogPost.drop_collection()
@@ -165,7 +163,7 @@ class TransformTest(unittest.TestCase):
q2 = B.objects.filter(a__in=[a1, a2])
q2 = q2.filter(a=a1)._query
self.assertEqual(q1, q2)
assert q1 == q2
def test_raw_query_and_Q_objects(self):
"""
@@ -181,11 +179,11 @@ class TransformTest(unittest.TestCase):
meta = {"allow_inheritance": False}
query = Foo.objects(__raw__={"$nor": [{"name": "bar"}]})._query
self.assertEqual(query, {"$nor": [{"name": "bar"}]})
assert query == {"$nor": [{"name": "bar"}]}
q1 = {"$or": [{"a": 1}, {"b": 1}]}
query = Foo.objects(Q(__raw__=q1) & Q(c=1))._query
self.assertEqual(query, {"$or": [{"a": 1}, {"b": 1}], "c": 1})
assert query == {"$or": [{"a": 1}, {"b": 1}], "c": 1}
def test_raw_and_merging(self):
class Doc(Document):
@@ -202,51 +200,39 @@ class TransformTest(unittest.TestCase):
}
)._query
self.assertEqual(
raw_query,
{
"deleted": False,
"scraped": "yes",
"$nor": [
{"views.extracted": "no"},
{"attachments.views.extracted": "no"},
],
},
)
assert raw_query == {
"deleted": False,
"scraped": "yes",
"$nor": [{"views.extracted": "no"}, {"attachments.views.extracted": "no"}],
}
def test_geojson_PointField(self):
class Location(Document):
loc = PointField()
update = transform.update(Location, set__loc=[1, 2])
self.assertEqual(
update, {"$set": {"loc": {"type": "Point", "coordinates": [1, 2]}}}
)
assert update == {"$set": {"loc": {"type": "Point", "coordinates": [1, 2]}}}
update = transform.update(
Location, set__loc={"type": "Point", "coordinates": [1, 2]}
)
self.assertEqual(
update, {"$set": {"loc": {"type": "Point", "coordinates": [1, 2]}}}
)
assert update == {"$set": {"loc": {"type": "Point", "coordinates": [1, 2]}}}
def test_geojson_LineStringField(self):
class Location(Document):
line = LineStringField()
update = transform.update(Location, set__line=[[1, 2], [2, 2]])
self.assertEqual(
update,
{"$set": {"line": {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}},
)
assert update == {
"$set": {"line": {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}
}
update = transform.update(
Location, set__line={"type": "LineString", "coordinates": [[1, 2], [2, 2]]}
)
self.assertEqual(
update,
{"$set": {"line": {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}},
)
assert update == {
"$set": {"line": {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}
}
def test_geojson_PolygonField(self):
class Location(Document):
@@ -255,17 +241,14 @@ class TransformTest(unittest.TestCase):
update = transform.update(
Location, set__poly=[[[40, 5], [40, 6], [41, 6], [40, 5]]]
)
self.assertEqual(
update,
{
"$set": {
"poly": {
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]],
}
assert update == {
"$set": {
"poly": {
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]],
}
},
)
}
}
update = transform.update(
Location,
@@ -274,17 +257,14 @@ class TransformTest(unittest.TestCase):
"coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]],
},
)
self.assertEqual(
update,
{
"$set": {
"poly": {
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]],
}
assert update == {
"$set": {
"poly": {
"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]],
}
},
)
}
}
def test_type(self):
class Doc(Document):
@@ -293,10 +273,10 @@ class TransformTest(unittest.TestCase):
Doc(df=True).save()
Doc(df=7).save()
Doc(df="df").save()
self.assertEqual(Doc.objects(df__type=1).count(), 0) # double
self.assertEqual(Doc.objects(df__type=8).count(), 1) # bool
self.assertEqual(Doc.objects(df__type=2).count(), 1) # str
self.assertEqual(Doc.objects(df__type=16).count(), 1) # int
assert Doc.objects(df__type=1).count() == 0 # double
assert Doc.objects(df__type=8).count() == 1 # bool
assert Doc.objects(df__type=2).count() == 1 # str
assert Doc.objects(df__type=16).count() == 1 # int
def test_last_field_name_like_operator(self):
class EmbeddedItem(EmbeddedDocument):
@@ -311,12 +291,12 @@ class TransformTest(unittest.TestCase):
doc = Doc(item=EmbeddedItem(type="axe", name="Heroic axe"))
doc.save()
self.assertEqual(1, Doc.objects(item__type__="axe").count())
self.assertEqual(1, Doc.objects(item__name__="Heroic axe").count())
assert 1 == Doc.objects(item__type__="axe").count()
assert 1 == Doc.objects(item__name__="Heroic axe").count()
Doc.objects(id=doc.id).update(set__item__type__="sword")
self.assertEqual(1, Doc.objects(item__type__="sword").count())
self.assertEqual(0, Doc.objects(item__type__="axe").count())
assert 1 == Doc.objects(item__type__="sword").count()
assert 0 == Doc.objects(item__type__="axe").count()
def test_understandable_error_raised(self):
class Event(Document):
@@ -326,7 +306,7 @@ class TransformTest(unittest.TestCase):
box = [(35.0, -125.0), (40.0, -100.0)]
# I *meant* to execute location__within_box=box
events = Event.objects(location__within=box)
with self.assertRaises(InvalidQueryError):
with pytest.raises(InvalidQueryError):
events.count()
def test_update_pull_for_list_fields(self):
@@ -349,24 +329,20 @@ class TransformTest(unittest.TestCase):
word = Word(word="abc", index=1)
update = transform.update(MainDoc, pull__content__text=word)
self.assertEqual(
update, {"$pull": {"content.text": SON([("word", u"abc"), ("index", 1)])}}
)
assert update == {
"$pull": {"content.text": SON([("word", u"abc"), ("index", 1)])}
}
update = transform.update(MainDoc, pull__content__heading="xyz")
self.assertEqual(update, {"$pull": {"content.heading": "xyz"}})
assert update == {"$pull": {"content.heading": "xyz"}}
update = transform.update(MainDoc, pull__content__text__word__in=["foo", "bar"])
self.assertEqual(
update, {"$pull": {"content.text": {"word": {"$in": ["foo", "bar"]}}}}
)
assert update == {"$pull": {"content.text": {"word": {"$in": ["foo", "bar"]}}}}
update = transform.update(
MainDoc, pull__content__text__word__nin=["foo", "bar"]
)
self.assertEqual(
update, {"$pull": {"content.text": {"word": {"$nin": ["foo", "bar"]}}}}
)
assert update == {"$pull": {"content.text": {"word": {"$nin": ["foo", "bar"]}}}}
if __name__ == "__main__":

View File

@@ -3,15 +3,14 @@ import re
import unittest
from bson import ObjectId
import pytest
from mongoengine import *
from mongoengine.errors import InvalidQueryError
from mongoengine.queryset import Q
__all__ = ("QTest",)
class QTest(unittest.TestCase):
class TestQ(unittest.TestCase):
def setUp(self):
connect(db="mongoenginetest")
@@ -37,10 +36,10 @@ class QTest(unittest.TestCase):
age = IntField()
query = {"$or": [{"age": {"$gte": 18}}, {"name": "test"}]}
self.assertEqual((q1 | q2 | q3 | q4 | q5).to_query(Person), query)
assert (q1 | q2 | q3 | q4 | q5).to_query(Person) == query
query = {"age": {"$gte": 18}, "name": "test"}
self.assertEqual((q1 & q2 & q3 & q4 & q5).to_query(Person), query)
assert (q1 & q2 & q3 & q4 & q5).to_query(Person) == query
def test_q_with_dbref(self):
"""Ensure Q objects handle DBRefs correctly"""
@@ -55,8 +54,8 @@ class QTest(unittest.TestCase):
user = User.objects.create()
Post.objects.create(created_user=user)
self.assertEqual(Post.objects.filter(created_user=user).count(), 1)
self.assertEqual(Post.objects.filter(Q(created_user=user)).count(), 1)
assert Post.objects.filter(created_user=user).count() == 1
assert Post.objects.filter(Q(created_user=user)).count() == 1
def test_and_combination(self):
"""Ensure that Q-objects correctly AND together.
@@ -67,12 +66,10 @@ class QTest(unittest.TestCase):
y = StringField()
query = (Q(x__lt=7) & Q(x__lt=3)).to_query(TestDoc)
self.assertEqual(query, {"$and": [{"x": {"$lt": 7}}, {"x": {"$lt": 3}}]})
assert query == {"$and": [{"x": {"$lt": 7}}, {"x": {"$lt": 3}}]}
query = (Q(y="a") & Q(x__lt=7) & Q(x__lt=3)).to_query(TestDoc)
self.assertEqual(
query, {"$and": [{"y": "a"}, {"x": {"$lt": 7}}, {"x": {"$lt": 3}}]}
)
assert query == {"$and": [{"y": "a"}, {"x": {"$lt": 7}}, {"x": {"$lt": 3}}]}
# Check normal cases work without an error
query = Q(x__lt=7) & Q(x__gt=3)
@@ -80,7 +77,7 @@ class QTest(unittest.TestCase):
q1 = Q(x__lt=7)
q2 = Q(x__gt=3)
query = (q1 & q2).to_query(TestDoc)
self.assertEqual(query, {"x": {"$lt": 7, "$gt": 3}})
assert query == {"x": {"$lt": 7, "$gt": 3}}
# More complex nested example
query = Q(x__lt=100) & Q(y__ne="NotMyString")
@@ -89,7 +86,7 @@ class QTest(unittest.TestCase):
"x": {"$lt": 100, "$gt": -100},
"y": {"$ne": "NotMyString", "$in": ["a", "b", "c"]},
}
self.assertEqual(query.to_query(TestDoc), mongo_query)
assert query.to_query(TestDoc) == mongo_query
def test_or_combination(self):
"""Ensure that Q-objects correctly OR together.
@@ -101,7 +98,7 @@ class QTest(unittest.TestCase):
q1 = Q(x__lt=3)
q2 = Q(x__gt=7)
query = (q1 | q2).to_query(TestDoc)
self.assertEqual(query, {"$or": [{"x": {"$lt": 3}}, {"x": {"$gt": 7}}]})
assert query == {"$or": [{"x": {"$lt": 3}}, {"x": {"$gt": 7}}]}
def test_and_or_combination(self):
"""Ensure that Q-objects handle ANDing ORed components.
@@ -115,15 +112,12 @@ class QTest(unittest.TestCase):
query = Q(x__gt=0) | Q(x__exists=False)
query &= Q(x__lt=100)
self.assertEqual(
query.to_query(TestDoc),
{
"$and": [
{"$or": [{"x": {"$gt": 0}}, {"x": {"$exists": False}}]},
{"x": {"$lt": 100}},
]
},
)
assert query.to_query(TestDoc) == {
"$and": [
{"$or": [{"x": {"$gt": 0}}, {"x": {"$exists": False}}]},
{"x": {"$lt": 100}},
]
}
q1 = Q(x__gt=0) | Q(x__exists=False)
q2 = Q(x__lt=100) | Q(y=True)
@@ -133,16 +127,13 @@ class QTest(unittest.TestCase):
TestDoc(x=10).save()
TestDoc(y=True).save()
self.assertEqual(
query,
{
"$and": [
{"$or": [{"x": {"$gt": 0}}, {"x": {"$exists": False}}]},
{"$or": [{"x": {"$lt": 100}}, {"y": True}]},
]
},
)
self.assertEqual(2, TestDoc.objects(q1 & q2).count())
assert query == {
"$and": [
{"$or": [{"x": {"$gt": 0}}, {"x": {"$exists": False}}]},
{"$or": [{"x": {"$lt": 100}}, {"y": True}]},
]
}
assert 2 == TestDoc.objects(q1 & q2).count()
def test_or_and_or_combination(self):
"""Ensure that Q-objects handle ORing ANDed ORed components. :)
@@ -162,26 +153,23 @@ class QTest(unittest.TestCase):
q2 = Q(x__lt=100) & (Q(y=False) | Q(y__exists=False))
query = (q1 | q2).to_query(TestDoc)
self.assertEqual(
query,
{
"$or": [
{
"$and": [
{"x": {"$gt": 0}},
{"$or": [{"y": True}, {"y": {"$exists": False}}]},
]
},
{
"$and": [
{"x": {"$lt": 100}},
{"$or": [{"y": False}, {"y": {"$exists": False}}]},
]
},
]
},
)
self.assertEqual(2, TestDoc.objects(q1 | q2).count())
assert query == {
"$or": [
{
"$and": [
{"x": {"$gt": 0}},
{"$or": [{"y": True}, {"y": {"$exists": False}}]},
]
},
{
"$and": [
{"x": {"$lt": 100}},
{"$or": [{"y": False}, {"y": {"$exists": False}}]},
]
},
]
}
assert 2 == TestDoc.objects(q1 | q2).count()
def test_multiple_occurence_in_field(self):
class Test(Document):
@@ -194,8 +182,8 @@ class QTest(unittest.TestCase):
q3 = q1 & q2
query = q3.to_query(Test)
self.assertEqual(query["$and"][0], q1.to_query(Test))
self.assertEqual(query["$and"][1], q2.to_query(Test))
assert query["$and"][0] == q1.to_query(Test)
assert query["$and"][1] == q2.to_query(Test)
def test_q_clone(self):
class TestDoc(Document):
@@ -209,15 +197,15 @@ class QTest(unittest.TestCase):
# Check normal cases work without an error
test = TestDoc.objects(Q(x__lt=7) & Q(x__gt=3))
self.assertEqual(test.count(), 3)
assert test.count() == 3
test2 = test.clone()
self.assertEqual(test2.count(), 3)
self.assertNotEqual(test2, test)
assert test2.count() == 3
assert test2 != test
test3 = test2.filter(x=6)
self.assertEqual(test3.count(), 1)
self.assertEqual(test.count(), 3)
assert test3.count() == 1
assert test.count() == 3
def test_q(self):
"""Ensure that Q objects may be used to query for documents.
@@ -254,19 +242,19 @@ class QTest(unittest.TestCase):
# Check ObjectId lookup works
obj = BlogPost.objects(id=post1.id).first()
self.assertEqual(obj, post1)
assert obj == post1
# Check Q object combination with one does not exist
q = BlogPost.objects(Q(title="Test 5") | Q(published=True))
posts = [post.id for post in q]
published_posts = (post2, post3)
self.assertTrue(all(obj.id in posts for obj in published_posts))
assert all(obj.id in posts for obj in published_posts)
q = BlogPost.objects(Q(title="Test 1") | Q(published=True))
posts = [post.id for post in q]
published_posts = (post1, post2, post3, post5, post6)
self.assertTrue(all(obj.id in posts for obj in published_posts))
assert all(obj.id in posts for obj in published_posts)
# Check Q object combination
date = datetime.datetime(2010, 1, 10)
@@ -274,9 +262,9 @@ class QTest(unittest.TestCase):
posts = [post.id for post in q]
published_posts = (post1, post2, post3, post4)
self.assertTrue(all(obj.id in posts for obj in published_posts))
assert all(obj.id in posts for obj in published_posts)
self.assertFalse(any(obj.id in posts for obj in [post5, post6]))
assert not any(obj.id in posts for obj in [post5, post6])
BlogPost.drop_collection()
@@ -286,15 +274,15 @@ class QTest(unittest.TestCase):
self.Person(name="user3", age=30).save()
self.Person(name="user4", age=40).save()
self.assertEqual(self.Person.objects(Q(age__in=[20])).count(), 2)
self.assertEqual(self.Person.objects(Q(age__in=[20, 30])).count(), 3)
assert self.Person.objects(Q(age__in=[20])).count() == 2
assert self.Person.objects(Q(age__in=[20, 30])).count() == 3
# Test invalid query objs
with self.assertRaises(InvalidQueryError):
with pytest.raises(InvalidQueryError):
self.Person.objects("user1")
# filter should fail, too
with self.assertRaises(InvalidQueryError):
with pytest.raises(InvalidQueryError):
self.Person.objects.filter("user1")
def test_q_regex(self):
@@ -304,31 +292,31 @@ class QTest(unittest.TestCase):
person.save()
obj = self.Person.objects(Q(name=re.compile("^Gui"))).first()
self.assertEqual(obj, person)
assert obj == person
obj = self.Person.objects(Q(name=re.compile("^gui"))).first()
self.assertEqual(obj, None)
assert obj is None
obj = self.Person.objects(Q(name=re.compile("^gui", re.I))).first()
self.assertEqual(obj, person)
assert obj == person
obj = self.Person.objects(Q(name__not=re.compile("^bob"))).first()
self.assertEqual(obj, person)
assert obj == person
obj = self.Person.objects(Q(name__not=re.compile("^Gui"))).first()
self.assertEqual(obj, None)
assert obj is None
def test_q_repr(self):
self.assertEqual(repr(Q()), "Q(**{})")
self.assertEqual(repr(Q(name="test")), "Q(**{'name': 'test'})")
assert repr(Q()) == "Q(**{})"
assert repr(Q(name="test")) == "Q(**{'name': 'test'})"
self.assertEqual(
repr(Q(name="test") & Q(age__gte=18)),
"(Q(**{'name': 'test'}) & Q(**{'age__gte': 18}))",
assert (
repr(Q(name="test") & Q(age__gte=18))
== "(Q(**{'name': 'test'}) & Q(**{'age__gte': 18}))"
)
self.assertEqual(
repr(Q(name="test") | Q(age__gte=18)),
"(Q(**{'name': 'test'}) | Q(**{'age__gte': 18}))",
assert (
repr(Q(name="test") | Q(age__gte=18))
== "(Q(**{'name': 'test'}) | Q(**{'age__gte': 18}))"
)
def test_q_lists(self):
@@ -343,8 +331,8 @@ class QTest(unittest.TestCase):
BlogPost(tags=["python", "mongo"]).save()
BlogPost(tags=["python"]).save()
self.assertEqual(BlogPost.objects(Q(tags="mongo")).count(), 1)
self.assertEqual(BlogPost.objects(Q(tags="python")).count(), 2)
assert BlogPost.objects(Q(tags="mongo")).count() == 1
assert BlogPost.objects(Q(tags="python")).count() == 2
BlogPost.drop_collection()
@@ -357,12 +345,12 @@ class QTest(unittest.TestCase):
pk = ObjectId()
User(email="example@example.com", pk=pk).save()
self.assertEqual(
1,
User.objects.filter(Q(email="example@example.com") | Q(name="John Doe"))
assert (
1
== User.objects.filter(Q(email="example@example.com") | Q(name="John Doe"))
.limit(2)
.filter(pk=pk)
.count(),
.count()
)
def test_chained_q_or_filtering(self):
@@ -378,16 +366,58 @@ class QTest(unittest.TestCase):
Item(postables=[Post(name="a"), Post(name="c")]).save()
Item(postables=[Post(name="a"), Post(name="b"), Post(name="c")]).save()
self.assertEqual(
Item.objects(Q(postables__name="a") & Q(postables__name="b")).count(), 2
assert (
Item.objects(Q(postables__name="a") & Q(postables__name="b")).count() == 2
)
self.assertEqual(
Item.objects.filter(postables__name="a")
.filter(postables__name="b")
.count(),
2,
assert (
Item.objects.filter(postables__name="a").filter(postables__name="b").count()
== 2
)
def test_equality(self):
assert Q(name="John") == Q(name="John")
assert Q() == Q()
def test_inequality(self):
assert Q(name="John") != Q(name="Ralph")
def test_operation_equality(self):
q1 = Q(name="John") | Q(title="Sir") & Q(surname="Paul")
q2 = Q(name="John") | Q(title="Sir") & Q(surname="Paul")
assert q1 == q2
def test_operation_inequality(self):
q1 = Q(name="John") | Q(title="Sir")
q2 = Q(title="Sir") | Q(name="John")
assert q1 != q2
def test_combine_and_empty(self):
q = Q(x=1)
assert q & Q() == q
assert Q() & q == q
def test_combine_and_both_empty(self):
assert Q() & Q() == Q()
def test_combine_or_empty(self):
q = Q(x=1)
assert q | Q() == q
assert Q() | q == q
def test_combine_or_both_empty(self):
assert Q() | Q() == Q()
def test_q_bool(self):
assert Q(name="John")
assert not Q()
def test_combine_bool(self):
assert not Q() & Q()
assert Q() & Q(name="John")
assert Q(name="John") & Q()
assert Q() | Q(name="John")
assert Q(name="John") | Q()
if __name__ == "__main__":
unittest.main()

9
tests/test_ci.py Normal file
View File

@@ -0,0 +1,9 @@
def test_ci_placeholder():
# This empty test is used within the CI to
# setup the tox venv without running the test suite
# if we simply skip all test with pytest -k=wrong_pattern
# pytest command would return with exit_code=5 (i.e "no tests run")
# making travis fail
# this empty test is the recommended way to handle this
# as described in https://github.com/pytest-dev/pytest/issues/2393
pass

View File

@@ -1,14 +1,16 @@
import unittest
from mongoengine.common import _import_class
import pytest
from mongoengine import Document
from mongoengine.common import _import_class
class TestCommon(unittest.TestCase):
class TestCommon:
def test__import_class(self):
doc_cls = _import_class("Document")
self.assertIs(doc_cls, Document)
assert doc_cls is Document
def test__import_class_raise_if_not_known(self):
with self.assertRaises(ValueError):
with pytest.raises(ValueError):
_import_class("UnknownClass")

View File

@@ -1,35 +1,32 @@
import datetime
from pymongo import MongoClient
from pymongo.errors import OperationFailure, InvalidName
from pymongo import ReadPreference
from bson.tz_util import utc
import pymongo
from mongoengine import Document
from pymongo import MongoClient, ReadPreference
from pymongo.errors import InvalidName, OperationFailure
import pytest
try:
import unittest2 as unittest
except ImportError:
import unittest
from nose.plugins.skip import SkipTest
import pymongo
from bson.tz_util import utc
from mongoengine import (
connect,
register_connection,
Document,
DateTimeField,
disconnect_all,
StringField,
)
import mongoengine.connection
from mongoengine import (
DateTimeField,
Document,
StringField,
connect,
disconnect_all,
register_connection,
)
from mongoengine.connection import (
ConnectionFailure,
get_db,
get_connection,
disconnect,
DEFAULT_DATABASE_NAME,
disconnect,
get_connection,
get_db,
)
@@ -37,6 +34,18 @@ def get_tz_awareness(connection):
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):
@classmethod
def setUpClass(cls):
@@ -56,15 +65,15 @@ class ConnectionTest(unittest.TestCase):
connect("mongoenginetest")
conn = get_connection()
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
assert isinstance(conn, pymongo.mongo_client.MongoClient)
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, "mongoenginetest")
assert isinstance(db, pymongo.database.Database)
assert db.name == "mongoenginetest"
connect("mongoenginetest2", alias="testdb")
conn = get_connection("testdb")
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
assert isinstance(conn, pymongo.mongo_client.MongoClient)
def test_connect_disconnect_works_properly(self):
class History1(Document):
@@ -84,31 +93,27 @@ class ConnectionTest(unittest.TestCase):
h = History1(name="default").save()
h1 = History2(name="db1").save()
self.assertEqual(
list(History1.objects().as_pymongo()), [{"_id": h.id, "name": "default"}]
)
self.assertEqual(
list(History2.objects().as_pymongo()), [{"_id": h1.id, "name": "db1"}]
)
assert list(History1.objects().as_pymongo()) == [
{"_id": h.id, "name": "default"}
]
assert list(History2.objects().as_pymongo()) == [{"_id": h1.id, "name": "db1"}]
disconnect("db1")
disconnect("db2")
with self.assertRaises(ConnectionFailure):
with pytest.raises(ConnectionFailure):
list(History1.objects().as_pymongo())
with self.assertRaises(ConnectionFailure):
with pytest.raises(ConnectionFailure):
list(History2.objects().as_pymongo())
connect("db1", alias="db1")
connect("db2", alias="db2")
self.assertEqual(
list(History1.objects().as_pymongo()), [{"_id": h.id, "name": "default"}]
)
self.assertEqual(
list(History2.objects().as_pymongo()), [{"_id": h1.id, "name": "db1"}]
)
assert list(History1.objects().as_pymongo()) == [
{"_id": h.id, "name": "default"}
]
assert list(History2.objects().as_pymongo()) == [{"_id": h1.id, "name": "db1"}]
def test_connect_different_documents_to_different_database(self):
class History(Document):
@@ -134,68 +139,64 @@ class ConnectionTest(unittest.TestCase):
h1 = History1(name="db1").save()
h2 = History2(name="db2").save()
self.assertEqual(History._collection.database.name, DEFAULT_DATABASE_NAME)
self.assertEqual(History1._collection.database.name, "db1")
self.assertEqual(History2._collection.database.name, "db2")
assert History._collection.database.name == DEFAULT_DATABASE_NAME
assert History1._collection.database.name == "db1"
assert History2._collection.database.name == "db2"
self.assertEqual(
list(History.objects().as_pymongo()), [{"_id": h.id, "name": "default"}]
)
self.assertEqual(
list(History1.objects().as_pymongo()), [{"_id": h1.id, "name": "db1"}]
)
self.assertEqual(
list(History2.objects().as_pymongo()), [{"_id": h2.id, "name": "db2"}]
)
assert list(History.objects().as_pymongo()) == [
{"_id": h.id, "name": "default"}
]
assert list(History1.objects().as_pymongo()) == [{"_id": h1.id, "name": "db1"}]
assert list(History2.objects().as_pymongo()) == [{"_id": h2.id, "name": "db2"}]
def test_connect_fails_if_connect_2_times_with_default_alias(self):
connect("mongoenginetest")
with self.assertRaises(ConnectionFailure) as ctx_err:
with pytest.raises(ConnectionFailure) as exc_info:
connect("mongoenginetest2")
self.assertEqual(
"A different connection with alias `default` was already registered. Use disconnect() first",
str(ctx_err.exception),
assert (
"A different connection with alias `default` was already registered. Use disconnect() first"
== str(exc_info.value)
)
def test_connect_fails_if_connect_2_times_with_custom_alias(self):
connect("mongoenginetest", alias="alias1")
with self.assertRaises(ConnectionFailure) as ctx_err:
with pytest.raises(ConnectionFailure) as exc_info:
connect("mongoenginetest2", alias="alias1")
self.assertEqual(
"A different connection with alias `alias1` was already registered. Use disconnect() first",
str(ctx_err.exception),
assert (
"A different connection with alias `alias1` was already registered. Use disconnect() first"
== str(exc_info.value)
)
def test_connect_fails_if_similar_connection_settings_arent_defined_the_same_way(
self
self,
):
"""Intended to keep the detecton function simple but robust"""
db_name = "mongoenginetest"
db_alias = "alias1"
connect(db=db_name, alias=db_alias, host="localhost", port=27017)
with self.assertRaises(ConnectionFailure):
with pytest.raises(ConnectionFailure):
connect(host="mongodb://localhost:27017/%s" % db_name, alias=db_alias)
def test_connect_passes_silently_connect_multiple_times_with_same_config(self):
# test default connection to `test`
connect()
connect()
self.assertEqual(len(mongoengine.connection._connections), 1)
assert len(mongoengine.connection._connections) == 1
connect("test01", alias="test01")
connect("test01", alias="test01")
self.assertEqual(len(mongoengine.connection._connections), 2)
assert len(mongoengine.connection._connections) == 2
connect(host="mongodb://localhost:27017/mongoenginetest02", alias="test02")
connect(host="mongodb://localhost:27017/mongoenginetest02", alias="test02")
self.assertEqual(len(mongoengine.connection._connections), 3)
assert len(mongoengine.connection._connections) == 3
def test_connect_with_invalid_db_name(self):
"""Ensure that connect() method fails fast if db name is invalid
"""
with self.assertRaises(InvalidName):
with pytest.raises(InvalidName):
connect("mongomock://localhost")
def test_connect_with_db_name_external(self):
@@ -205,38 +206,34 @@ class ConnectionTest(unittest.TestCase):
connect("$external")
conn = get_connection()
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
assert isinstance(conn, pymongo.mongo_client.MongoClient)
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, "$external")
assert isinstance(db, pymongo.database.Database)
assert db.name == "$external"
connect("$external", alias="testdb")
conn = get_connection("testdb")
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
assert isinstance(conn, pymongo.mongo_client.MongoClient)
def test_connect_with_invalid_db_name_type(self):
"""Ensure that connect() method fails fast if db name has invalid type
"""
with self.assertRaises(TypeError):
with pytest.raises(TypeError):
non_string_db_name = ["e. g. list instead of a string"]
connect(non_string_db_name)
@require_mongomock
def test_connect_in_mocking(self):
"""Ensure that the connect() method works properly in mocking.
"""
try:
import mongomock
except ImportError:
raise SkipTest("you need mongomock installed to run this testcase")
connect("mongoenginetest", host="mongomock://localhost")
conn = get_connection()
self.assertIsInstance(conn, mongomock.MongoClient)
assert isinstance(conn, mongomock.MongoClient)
connect("mongoenginetest2", host="mongomock://localhost", alias="testdb2")
conn = get_connection("testdb2")
self.assertIsInstance(conn, mongomock.MongoClient)
assert isinstance(conn, mongomock.MongoClient)
connect(
"mongoenginetest3",
@@ -245,11 +242,11 @@ class ConnectionTest(unittest.TestCase):
alias="testdb3",
)
conn = get_connection("testdb3")
self.assertIsInstance(conn, mongomock.MongoClient)
assert isinstance(conn, mongomock.MongoClient)
connect("mongoenginetest4", is_mock=True, alias="testdb4")
conn = get_connection("testdb4")
self.assertIsInstance(conn, mongomock.MongoClient)
assert isinstance(conn, mongomock.MongoClient)
connect(
host="mongodb://localhost:27017/mongoenginetest5",
@@ -257,11 +254,11 @@ class ConnectionTest(unittest.TestCase):
alias="testdb5",
)
conn = get_connection("testdb5")
self.assertIsInstance(conn, mongomock.MongoClient)
assert isinstance(conn, mongomock.MongoClient)
connect(host="mongomock://localhost:27017/mongoenginetest6", alias="testdb6")
conn = get_connection("testdb6")
self.assertIsInstance(conn, mongomock.MongoClient)
assert isinstance(conn, mongomock.MongoClient)
connect(
host="mongomock://localhost:27017/mongoenginetest7",
@@ -269,16 +266,12 @@ class ConnectionTest(unittest.TestCase):
alias="testdb7",
)
conn = get_connection("testdb7")
self.assertIsInstance(conn, mongomock.MongoClient)
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.
"""
try:
import mongomock
except ImportError:
raise SkipTest("you need mongomock installed to run this testcase")
disconnect_all()
class SomeDocument(Document):
@@ -288,37 +281,33 @@ class ConnectionTest(unittest.TestCase):
some_document = SomeDocument()
# database won't exist until we save a document
some_document.save()
self.assertEqual(conn.get_default_database().name, "mongoenginetest")
self.assertEqual(conn.database_names()[0], "mongoenginetest")
assert conn.get_default_database().name == "mongoenginetest"
assert conn.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
"""
try:
import mongomock
except ImportError:
raise SkipTest("you need mongomock installed to run this testcase")
connect(host=["mongomock://localhost"])
conn = get_connection()
self.assertIsInstance(conn, mongomock.MongoClient)
assert isinstance(conn, mongomock.MongoClient)
connect(host=["mongodb://localhost"], is_mock=True, alias="testdb2")
conn = get_connection("testdb2")
self.assertIsInstance(conn, mongomock.MongoClient)
assert isinstance(conn, mongomock.MongoClient)
connect(host=["localhost"], is_mock=True, alias="testdb3")
conn = get_connection("testdb3")
self.assertIsInstance(conn, mongomock.MongoClient)
assert isinstance(conn, mongomock.MongoClient)
connect(
host=["mongomock://localhost:27017", "mongomock://localhost:27018"],
alias="testdb4",
)
conn = get_connection("testdb4")
self.assertIsInstance(conn, mongomock.MongoClient)
assert isinstance(conn, mongomock.MongoClient)
connect(
host=["mongodb://localhost:27017", "mongodb://localhost:27018"],
@@ -326,13 +315,13 @@ class ConnectionTest(unittest.TestCase):
alias="testdb5",
)
conn = get_connection("testdb5")
self.assertIsInstance(conn, mongomock.MongoClient)
assert isinstance(conn, mongomock.MongoClient)
connect(
host=["localhost:27017", "localhost:27018"], is_mock=True, alias="testdb6"
)
conn = get_connection("testdb6")
self.assertIsInstance(conn, mongomock.MongoClient)
assert isinstance(conn, mongomock.MongoClient)
def test_disconnect_cleans_globals(self):
"""Ensure that the disconnect() method cleans the globals objects"""
@@ -342,44 +331,42 @@ class ConnectionTest(unittest.TestCase):
connect("mongoenginetest")
self.assertEqual(len(connections), 1)
self.assertEqual(len(dbs), 0)
self.assertEqual(len(connection_settings), 1)
assert len(connections) == 1
assert len(dbs) == 0
assert len(connection_settings) == 1
class TestDoc(Document):
pass
TestDoc.drop_collection() # triggers the db
self.assertEqual(len(dbs), 1)
assert len(dbs) == 1
disconnect()
self.assertEqual(len(connections), 0)
self.assertEqual(len(dbs), 0)
self.assertEqual(len(connection_settings), 0)
assert len(connections) == 0
assert len(dbs) == 0
assert len(connection_settings) == 0
def test_disconnect_cleans_cached_collection_attribute_in_document(self):
"""Ensure that the disconnect() method works properly"""
conn1 = connect("mongoenginetest")
connect("mongoenginetest")
class History(Document):
pass
self.assertIsNone(History._collection)
assert History._collection is None
History.drop_collection()
History.objects.first() # will trigger the caching of _collection attribute
self.assertIsNotNone(History._collection)
assert History._collection is not None
disconnect()
self.assertIsNone(History._collection)
assert History._collection is None
with self.assertRaises(ConnectionFailure) as ctx_err:
with pytest.raises(ConnectionFailure) as exc_info:
History.objects.first()
self.assertEqual(
"You have not defined a default connection", str(ctx_err.exception)
)
assert "You have not defined a default connection" == str(exc_info.value)
def test_connect_disconnect_works_on_same_document(self):
"""Ensure that the connect/disconnect works properly with a single Document"""
@@ -401,7 +388,7 @@ class ConnectionTest(unittest.TestCase):
disconnect()
# Make sure save doesnt work at this stage
with self.assertRaises(ConnectionFailure):
with pytest.raises(ConnectionFailure):
User(name="Wont work").save()
# Save in db2
@@ -410,13 +397,13 @@ class ConnectionTest(unittest.TestCase):
disconnect()
db1_users = list(client[db1].user.find())
self.assertEqual(db1_users, [{"_id": user1.id, "name": "John is in db1"}])
assert db1_users == [{"_id": user1.id, "name": "John is in db1"}]
db2_users = list(client[db2].user.find())
self.assertEqual(db2_users, [{"_id": user2.id, "name": "Bob is in db2"}])
assert db2_users == [{"_id": user2.id, "name": "Bob is in db2"}]
def test_disconnect_silently_pass_if_alias_does_not_exist(self):
connections = mongoengine.connection._connections
self.assertEqual(len(connections), 0)
assert len(connections) == 0
disconnect(alias="not_exist")
def test_disconnect_all(self):
@@ -439,26 +426,26 @@ class ConnectionTest(unittest.TestCase):
History1.drop_collection()
History1.objects.first()
self.assertIsNotNone(History._collection)
self.assertIsNotNone(History1._collection)
assert History._collection is not None
assert History1._collection is not None
self.assertEqual(len(connections), 2)
self.assertEqual(len(dbs), 2)
self.assertEqual(len(connection_settings), 2)
assert len(connections) == 2
assert len(dbs) == 2
assert len(connection_settings) == 2
disconnect_all()
self.assertIsNone(History._collection)
self.assertIsNone(History1._collection)
assert History._collection is None
assert History1._collection is None
self.assertEqual(len(connections), 0)
self.assertEqual(len(dbs), 0)
self.assertEqual(len(connection_settings), 0)
assert len(connections) == 0
assert len(dbs) == 0
assert len(connection_settings) == 0
with self.assertRaises(ConnectionFailure):
with pytest.raises(ConnectionFailure):
History.objects.first()
with self.assertRaises(ConnectionFailure):
with pytest.raises(ConnectionFailure):
History1.objects.first()
def test_disconnect_all_silently_pass_if_no_connection_exist(self):
@@ -475,7 +462,7 @@ class ConnectionTest(unittest.TestCase):
expected_connection.server_info()
self.assertEqual(expected_connection, actual_connection)
assert expected_connection == actual_connection
def test_connect_uri(self):
"""Ensure that the connect() method works properly with URIs."""
@@ -492,11 +479,11 @@ class ConnectionTest(unittest.TestCase):
)
conn = get_connection()
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
assert isinstance(conn, pymongo.mongo_client.MongoClient)
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, "mongoenginetest")
assert isinstance(db, pymongo.database.Database)
assert db.name == "mongoenginetest"
c.admin.system.users.delete_many({})
c.mongoenginetest.system.users.delete_many({})
@@ -508,11 +495,11 @@ class ConnectionTest(unittest.TestCase):
connect("mongoenginetest", host="mongodb://localhost/")
conn = get_connection()
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
assert isinstance(conn, pymongo.mongo_client.MongoClient)
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, "mongoenginetest")
assert isinstance(db, pymongo.database.Database)
assert db.name == "mongoenginetest"
def test_connect_uri_default_db(self):
"""Ensure connect() defaults to the right database name if
@@ -521,24 +508,25 @@ class ConnectionTest(unittest.TestCase):
connect(host="mongodb://localhost/")
conn = get_connection()
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
assert isinstance(conn, pymongo.mongo_client.MongoClient)
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, "test")
assert isinstance(db, pymongo.database.Database)
assert db.name == "test"
def test_uri_without_credentials_doesnt_override_conn_settings(self):
"""Ensure connect() uses the username & password params if the URI
doesn't explicitly specify them.
"""
c = connect(
connect(
host="mongodb://localhost/mongoenginetest", username="user", password="pass"
)
# OperationFailure means that mongoengine attempted authentication
# w/ the provided username/password and failed - that's the desired
# behavior. If the MongoDB URI would override the credentials
self.assertRaises(OperationFailure, get_db)
with pytest.raises(OperationFailure):
get_db()
def test_connect_uri_with_authsource(self):
"""Ensure that the connect() method works well with `authSource`
@@ -556,7 +544,8 @@ class ConnectionTest(unittest.TestCase):
alias="test1",
host="mongodb://username2:password@localhost/mongoenginetest",
)
self.assertRaises(OperationFailure, test_conn.server_info)
with pytest.raises(OperationFailure):
test_conn.server_info()
# Authentication succeeds with "authSource"
authd_conn = connect(
@@ -568,8 +557,8 @@ class ConnectionTest(unittest.TestCase):
),
)
db = get_db("test2")
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, "mongoenginetest")
assert isinstance(db, pymongo.database.Database)
assert db.name == "mongoenginetest"
# Clear all users
authd_conn.admin.system.users.delete_many({})
@@ -579,13 +568,14 @@ class ConnectionTest(unittest.TestCase):
"""
register_connection("testdb", "mongoenginetest2")
self.assertRaises(ConnectionFailure, get_connection)
with pytest.raises(ConnectionFailure):
get_connection()
conn = get_connection("testdb")
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
assert isinstance(conn, pymongo.mongo_client.MongoClient)
db = get_db("testdb")
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, "mongoenginetest2")
assert isinstance(db, pymongo.database.Database)
assert db.name == "mongoenginetest2"
def test_register_connection_defaults(self):
"""Ensure that defaults are used when the host and port are None.
@@ -593,18 +583,18 @@ class ConnectionTest(unittest.TestCase):
register_connection("testdb", "mongoenginetest", host=None, port=None)
conn = get_connection("testdb")
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
assert isinstance(conn, pymongo.mongo_client.MongoClient)
def test_connection_kwargs(self):
"""Ensure that connection kwargs get passed to pymongo."""
connect("mongoenginetest", alias="t1", tz_aware=True)
conn = get_connection("t1")
self.assertTrue(get_tz_awareness(conn))
assert get_tz_awareness(conn)
connect("mongoenginetest2", alias="t2")
conn = get_connection("t2")
self.assertFalse(get_tz_awareness(conn))
assert not get_tz_awareness(conn)
def test_connection_pool_via_kwarg(self):
"""Ensure we can specify a max connection pool size using
@@ -615,7 +605,7 @@ class ConnectionTest(unittest.TestCase):
conn = connect(
"mongoenginetest", alias="max_pool_size_via_kwarg", **pool_size_kwargs
)
self.assertEqual(conn.max_pool_size, 100)
assert conn.max_pool_size == 100
def test_connection_pool_via_uri(self):
"""Ensure we can specify a max connection pool size using
@@ -625,35 +615,37 @@ class ConnectionTest(unittest.TestCase):
host="mongodb://localhost/test?maxpoolsize=100",
alias="max_pool_size_via_uri",
)
self.assertEqual(conn.max_pool_size, 100)
assert conn.max_pool_size == 100
def test_write_concern(self):
"""Ensure write concern can be specified in connect() via
a kwarg or as part of the connection URI.
"""
conn1 = connect(alias="conn1", host="mongodb://localhost/testing?w=1&j=true")
conn2 = connect("testing", alias="conn2", w=1, j=True)
self.assertEqual(conn1.write_concern.document, {"w": 1, "j": True})
self.assertEqual(conn2.write_concern.document, {"w": 1, "j": True})
conn1 = connect(
alias="conn1", host="mongodb://localhost/testing?w=1&journal=true"
)
conn2 = connect("testing", alias="conn2", w=1, journal=True)
assert conn1.write_concern.document == {"w": 1, "j": True}
assert conn2.write_concern.document == {"w": 1, "j": True}
def test_connect_with_replicaset_via_uri(self):
"""Ensure connect() works when specifying a replicaSet via the
MongoDB URI.
"""
c = connect(host="mongodb://localhost/test?replicaSet=local-rs")
connect(host="mongodb://localhost/test?replicaSet=local-rs")
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, "test")
assert isinstance(db, pymongo.database.Database)
assert db.name == "test"
def test_connect_with_replicaset_via_kwargs(self):
"""Ensure connect() works when specifying a replicaSet via the
connection kwargs
"""
c = connect(replicaset="local-rs")
self.assertEqual(c._MongoClient__options.replica_set_name, "local-rs")
assert c._MongoClient__options.replica_set_name == "local-rs"
db = get_db()
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, "test")
assert isinstance(db, pymongo.database.Database)
assert db.name == "test"
def test_connect_tz_aware(self):
connect("mongoenginetest", tz_aware=True)
@@ -666,13 +658,13 @@ class ConnectionTest(unittest.TestCase):
DateDoc(the_date=d).save()
date_doc = DateDoc.objects.first()
self.assertEqual(d, date_doc.the_date)
assert d == date_doc.the_date
def test_read_preference_from_parse(self):
conn = connect(
host="mongodb://a1.vpc,a2.vpc,a3.vpc/prod?readPreference=secondaryPreferred"
)
self.assertEqual(conn.read_preference, ReadPreference.SECONDARY_PREFERRED)
assert conn.read_preference == ReadPreference.SECONDARY_PREFERRED
def test_multiple_connection_settings(self):
connect("mongoenginetest", alias="t1", host="localhost")
@@ -680,27 +672,27 @@ class ConnectionTest(unittest.TestCase):
connect("mongoenginetest2", alias="t2", host="127.0.0.1")
mongo_connections = mongoengine.connection._connections
self.assertEqual(len(mongo_connections.items()), 2)
self.assertIn("t1", mongo_connections.keys())
self.assertIn("t2", mongo_connections.keys())
assert len(mongo_connections.items()) == 2
assert "t1" in mongo_connections.keys()
assert "t2" in mongo_connections.keys()
# Handle PyMongo 3+ Async Connection
# Ensure we are connected, throws ServerSelectionTimeoutError otherwise.
# Purposely not catching exception to fail test if thrown.
mongo_connections["t1"].server_info()
mongo_connections["t2"].server_info()
self.assertEqual(mongo_connections["t1"].address[0], "localhost")
self.assertEqual(mongo_connections["t2"].address[0], "127.0.0.1")
assert mongo_connections["t1"].address[0] == "localhost"
assert mongo_connections["t2"].address[0] == "127.0.0.1"
def test_connect_2_databases_uses_same_client_if_only_dbname_differs(self):
c1 = connect(alias="testdb1", db="testdb1")
c2 = connect(alias="testdb2", db="testdb2")
self.assertIs(c1, c2)
assert c1 is c2
def test_connect_2_databases_uses_different_client_if_different_parameters(self):
c1 = connect(alias="testdb1", db="testdb1", username="u1")
c2 = connect(alias="testdb2", db="testdb2", username="u2")
self.assertIsNot(c1, c2)
assert c1 is not c2
if __name__ == "__main__":

Some files were not shown because too many files have changed in this diff Show More