Merge branch 'master' of github.com:MongoEngine/mongoengine into bump_development_status_classifier
This commit is contained in:
		
							
								
								
									
										1
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								AUTHORS
									
									
									
									
									
								
							| @@ -255,3 +255,4 @@ that much better: | |||||||
|  * Filip Kucharczyk (https://github.com/Pacu2) |  * Filip Kucharczyk (https://github.com/Pacu2) | ||||||
|  * Eric Timmons (https://github.com/daewok) |  * Eric Timmons (https://github.com/daewok) | ||||||
|  * Matthew Simpson (https://github.com/mcsimps2) |  * Matthew Simpson (https://github.com/mcsimps2) | ||||||
|  |  * Leonardo Domingues (https://github.com/leodmgs) | ||||||
|   | |||||||
| @@ -8,6 +8,15 @@ Development | |||||||
| - (Fill this out as you fix issues and develop your features). | - (Fill this out as you fix issues and develop your features). | ||||||
| - Add Mongo 4.0 to Travis | - Add Mongo 4.0 to Travis | ||||||
| - Bump development Status classifier to Production/Stable #2232 | - 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 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`` | ||||||
|  |  | ||||||
| Changes in 0.19.1 | Changes in 0.19.1 | ||||||
| ================= | ================= | ||||||
|   | |||||||
| @@ -555,7 +555,6 @@ There are a few top level defaults for all indexes that can be set:: | |||||||
|             'index_background': True, |             'index_background': True, | ||||||
|             'index_cls': False, |             'index_cls': False, | ||||||
|             'auto_create_index': True, |             '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 |     in systems where indexes are managed separately. Disabling this will improve | ||||||
|     performance. |     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 | Compound Indexes and Indexing sub documents | ||||||
| ------------------------------------------- | ------------------------------------------- | ||||||
|   | |||||||
| @@ -36,7 +36,6 @@ class BaseField(object): | |||||||
|     def __init__( |     def __init__( | ||||||
|         self, |         self, | ||||||
|         db_field=None, |         db_field=None, | ||||||
|         name=None, |  | ||||||
|         required=False, |         required=False, | ||||||
|         default=None, |         default=None, | ||||||
|         unique=False, |         unique=False, | ||||||
| @@ -51,7 +50,6 @@ class BaseField(object): | |||||||
|         """ |         """ | ||||||
|         :param db_field: The database field to store this field in |         :param db_field: The database field to store this field in | ||||||
|             (defaults to the name of the field) |             (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 |         :param required: If the field is required. Whether it has to have a | ||||||
|             value or not. Defaults to False. |             value or not. Defaults to False. | ||||||
|         :param default: (optional) The default value for this field if no value |         :param default: (optional) The default value for this field if no value | ||||||
| @@ -75,11 +73,8 @@ class BaseField(object): | |||||||
|             existing attributes. Common metadata includes `verbose_name` and |             existing attributes. Common metadata includes `verbose_name` and | ||||||
|             `help_text`. |             `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.required = required or primary_key | ||||||
|         self.default = default |         self.default = default | ||||||
|         self.unique = bool(unique or unique_with) |         self.unique = bool(unique or unique_with) | ||||||
|   | |||||||
| @@ -284,7 +284,6 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): | |||||||
|                 "indexes": [],  # indexes to be ensured at runtime |                 "indexes": [],  # indexes to be ensured at runtime | ||||||
|                 "id_field": None, |                 "id_field": None, | ||||||
|                 "index_background": False, |                 "index_background": False, | ||||||
|                 "index_drop_dups": False, |  | ||||||
|                 "index_opts": None, |                 "index_opts": None, | ||||||
|                 "delete_rules": None, |                 "delete_rules": None, | ||||||
|                 # allow_inheritance can be True, False, and None. True means |                 # allow_inheritance can be True, False, and None. True means | ||||||
|   | |||||||
| @@ -56,7 +56,7 @@ class InvalidCollectionError(Exception): | |||||||
|  |  | ||||||
|  |  | ||||||
| class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)): | class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)): | ||||||
|     """A :class:`~mongoengine.Document` that isn't stored in its own |     r"""A :class:`~mongoengine.Document` that isn't stored in its own | ||||||
|     collection.  :class:`~mongoengine.EmbeddedDocument`\ s should be used as |     collection.  :class:`~mongoengine.EmbeddedDocument`\ s should be used as | ||||||
|     fields on :class:`~mongoengine.Document`\ s through the |     fields on :class:`~mongoengine.Document`\ s through the | ||||||
|     :class:`~mongoengine.EmbeddedDocumentField` field type. |     :class:`~mongoengine.EmbeddedDocumentField` field type. | ||||||
| @@ -332,7 +332,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): | |||||||
|     ): |     ): | ||||||
|         """Save the :class:`~mongoengine.Document` to the database. If the |         """Save the :class:`~mongoengine.Document` to the database. If the | ||||||
|         document already exists, it will be updated, otherwise it will be |         document already exists, it will be updated, otherwise it will be | ||||||
|         created. |         created. Returns the saved object instance. | ||||||
|  |  | ||||||
|         :param force_insert: only try to create a new document, don't allow |         :param force_insert: only try to create a new document, don't allow | ||||||
|             updates of existing documents. |             updates of existing documents. | ||||||
| @@ -851,17 +851,13 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): | |||||||
|         index_spec = cls._build_index_spec(keys) |         index_spec = cls._build_index_spec(keys) | ||||||
|         index_spec = index_spec.copy() |         index_spec = index_spec.copy() | ||||||
|         fields = index_spec.pop("fields") |         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["background"] = background | ||||||
|         index_spec.update(kwargs) |         index_spec.update(kwargs) | ||||||
|  |  | ||||||
|         return cls._get_collection().create_index(fields, **index_spec) |         return cls._get_collection().create_index(fields, **index_spec) | ||||||
|  |  | ||||||
|     @classmethod |     @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 |         """Ensure that the given indexes are in place. Deprecated in favour | ||||||
|         of create_index. |         of create_index. | ||||||
|  |  | ||||||
| @@ -869,12 +865,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): | |||||||
|             construct a multi-field index); keys may be prefixed with a **+** |             construct a multi-field index); keys may be prefixed with a **+** | ||||||
|             or a **-** to determine the index ordering |             or a **-** to determine the index ordering | ||||||
|         :param background: Allows index creation in the background |         :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) |         return cls.create_index(key_or_list, background=background, **kwargs) | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
| @@ -887,12 +878,8 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): | |||||||
|                   `auto_create_index` to False in the documents meta data |                   `auto_create_index` to False in the documents meta data | ||||||
|         """ |         """ | ||||||
|         background = cls._meta.get("index_background", False) |         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_opts = cls._meta.get("index_opts") or {} | ||||||
|         index_cls = cls._meta.get("index_cls", True) |         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() |         collection = cls._get_collection() | ||||||
|         # 746: when connection is via mongos, the read preference is not necessarily an indication that |         # 746: when connection is via mongos, the read preference is not necessarily an indication that | ||||||
|   | |||||||
| @@ -1088,14 +1088,12 @@ class DictField(ComplexBaseField): | |||||||
|             msg = "Invalid dictionary key - documents must have only string keys" |             msg = "Invalid dictionary key - documents must have only string keys" | ||||||
|             self.error(msg) |             self.error(msg) | ||||||
|  |  | ||||||
|         curr_mongo_ver = get_mongodb_version() |         # Following condition applies to MongoDB >= 3.6 | ||||||
|  |         # older Mongo has stricter constraints but | ||||||
|         if curr_mongo_ver < MONGODB_36 and key_has_dot_or_dollar(value): |         # it will be rejected upon insertion anyway | ||||||
|             self.error( |         # Having a validation that depends on the MongoDB version | ||||||
|                 'Invalid dictionary key name - keys may not contain "."' |         # is not straightforward as the field isn't aware of the connected Mongo | ||||||
|                 ' or startswith "$" characters' |         if key_starts_with_dollar(value): | ||||||
|             ) |  | ||||||
|         elif curr_mongo_ver >= MONGODB_36 and key_starts_with_dollar(value): |  | ||||||
|             self.error( |             self.error( | ||||||
|                 'Invalid dictionary key name - keys may not startswith "$" characters' |                 'Invalid dictionary key name - keys may not startswith "$" characters' | ||||||
|             ) |             ) | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ MONGODB_36 = (3, 6) | |||||||
|  |  | ||||||
|  |  | ||||||
| def get_mongodb_version(): | 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) |     :return: tuple(int, int) | ||||||
|     """ |     """ | ||||||
|   | |||||||
| @@ -60,7 +60,6 @@ class BaseQuerySet(object): | |||||||
|         self._ordering = None |         self._ordering = None | ||||||
|         self._snapshot = False |         self._snapshot = False | ||||||
|         self._timeout = True |         self._timeout = True | ||||||
|         self._slave_okay = False |  | ||||||
|         self._read_preference = None |         self._read_preference = None | ||||||
|         self._iter = False |         self._iter = False | ||||||
|         self._scalar = [] |         self._scalar = [] | ||||||
| @@ -260,16 +259,18 @@ class BaseQuerySet(object): | |||||||
|         except StopIteration: |         except StopIteration: | ||||||
|             msg = "%s matching query does not exist." % queryset._document._class_name |             msg = "%s matching query does not exist." % queryset._document._class_name | ||||||
|             raise queryset._document.DoesNotExist(msg) |             raise queryset._document.DoesNotExist(msg) | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|  |             # Check if there is another match | ||||||
|             six.next(queryset) |             six.next(queryset) | ||||||
|         except StopIteration: |         except StopIteration: | ||||||
|             return result |             return result | ||||||
|  |  | ||||||
|         # If we were able to retrieve the 2nd doc, rewind the cursor and |         # If we were able to retrieve the 2nd doc, rewind the cursor and | ||||||
|         # raise the MultipleObjectsReturned exception. |         # raise the MultipleObjectsReturned exception. | ||||||
|         queryset.rewind() |         raise queryset._document.MultipleObjectsReturned( | ||||||
|         message = u"%d items returned, instead of 1" % queryset.count() |             u"2 or more items returned, instead of 1" | ||||||
|         raise queryset._document.MultipleObjectsReturned(message) |         ) | ||||||
|  |  | ||||||
|     def create(self, **kwargs): |     def create(self, **kwargs): | ||||||
|         """Create new object. Returns the saved object instance. |         """Create new object. Returns the saved object instance. | ||||||
| @@ -694,8 +695,8 @@ class BaseQuerySet(object): | |||||||
|     def in_bulk(self, object_ids): |     def in_bulk(self, object_ids): | ||||||
|         """Retrieve a set of documents by their ids. |         """Retrieve a set of documents by their ids. | ||||||
|  |  | ||||||
|         :param object_ids: a list or tuple of ``ObjectId``\ s |         :param object_ids: a list or tuple of ObjectId's | ||||||
|         :rtype: dict of ObjectIds as keys and collection-specific |         :rtype: dict of ObjectId's as keys and collection-specific | ||||||
|                 Document subclasses as values. |                 Document subclasses as values. | ||||||
|  |  | ||||||
|         .. versionadded:: 0.3 |         .. versionadded:: 0.3 | ||||||
| @@ -775,7 +776,6 @@ class BaseQuerySet(object): | |||||||
|             "_ordering", |             "_ordering", | ||||||
|             "_snapshot", |             "_snapshot", | ||||||
|             "_timeout", |             "_timeout", | ||||||
|             "_slave_okay", |  | ||||||
|             "_read_preference", |             "_read_preference", | ||||||
|             "_iter", |             "_iter", | ||||||
|             "_scalar", |             "_scalar", | ||||||
| @@ -1026,9 +1026,11 @@ class BaseQuerySet(object): | |||||||
|  |  | ||||||
|             posts = BlogPost.objects(...).fields(comments=0) |             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(slice__comments=5) | ||||||
|  |             posts = BlogPost.objects(...).fields(elemMatch__comments="test") | ||||||
|  |  | ||||||
|         :param kwargs: A set of keyword arguments identifying what to |         :param kwargs: A set of keyword arguments identifying what to | ||||||
|             include, exclude, or slice. |             include, exclude, or slice. | ||||||
| @@ -1037,7 +1039,7 @@ class BaseQuerySet(object): | |||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         # Check for an operator and transform to mongo-style if there is |         # Check for an operator and transform to mongo-style if there is | ||||||
|         operators = ["slice"] |         operators = ["slice", "elemMatch"] | ||||||
|         cleaned_fields = [] |         cleaned_fields = [] | ||||||
|         for key, value in kwargs.items(): |         for key, value in kwargs.items(): | ||||||
|             parts = key.split("__") |             parts = key.split("__") | ||||||
| @@ -1140,7 +1142,7 @@ class BaseQuerySet(object): | |||||||
|  |  | ||||||
|     def explain(self): |     def explain(self): | ||||||
|         """Return an explain plan record for the |         """Return an explain plan record for the | ||||||
|         :class:`~mongoengine.queryset.QuerySet`\ 's cursor. |         :class:`~mongoengine.queryset.QuerySet` cursor. | ||||||
|         """ |         """ | ||||||
|         return self._cursor.explain() |         return self._cursor.explain() | ||||||
|  |  | ||||||
| @@ -1170,20 +1172,6 @@ class BaseQuerySet(object): | |||||||
|         queryset._timeout = enabled |         queryset._timeout = enabled | ||||||
|         return queryset |         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): |     def read_preference(self, read_preference): | ||||||
|         """Change the read_preference when querying. |         """Change the read_preference when querying. | ||||||
|  |  | ||||||
| @@ -1958,23 +1946,3 @@ class BaseQuerySet(object): | |||||||
|         setattr(queryset, "_" + method_name, val) |         setattr(queryset, "_" + method_name, val) | ||||||
|  |  | ||||||
|         return queryset |         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() |  | ||||||
|   | |||||||
| @@ -169,9 +169,9 @@ def query(_doc_cls=None, **kwargs): | |||||||
|  |  | ||||||
|         key = ".".join(parts) |         key = ".".join(parts) | ||||||
|  |  | ||||||
|         if op is None or key not in mongo_query: |         if key not in mongo_query: | ||||||
|             mongo_query[key] = value |             mongo_query[key] = value | ||||||
|         elif key in mongo_query: |         else: | ||||||
|             if isinstance(mongo_query[key], dict) and isinstance(value, dict): |             if isinstance(mongo_query[key], dict) and isinstance(value, dict): | ||||||
|                 mongo_query[key].update(value) |                 mongo_query[key].update(value) | ||||||
|                 # $max/minDistance needs to come last - convert to SON |                 # $max/minDistance needs to come last - convert to SON | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								setup.py
									
									
									
									
									
								
							| @@ -108,6 +108,10 @@ CLASSIFIERS = [ | |||||||
|     "Topic :: Software Development :: Libraries :: Python Modules", |     "Topic :: Software Development :: Libraries :: Python Modules", | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | PYTHON_VERSION = sys.version_info[0] | ||||||
|  | PY3 = PYTHON_VERSION == 3 | ||||||
|  | PY2 = PYTHON_VERSION == 2 | ||||||
|  |  | ||||||
| extra_opts = { | extra_opts = { | ||||||
|     "packages": find_packages(exclude=["tests", "tests.*"]), |     "packages": find_packages(exclude=["tests", "tests.*"]), | ||||||
|     "tests_require": [ |     "tests_require": [ | ||||||
| @@ -116,9 +120,12 @@ extra_opts = { | |||||||
|         "coverage<5.0",  # recent coverage switched to sqlite format for the .coverage file which isn't handled properly by coveralls |         "coverage<5.0",  # recent coverage switched to sqlite format for the .coverage file which isn't handled properly by coveralls | ||||||
|         "blinker", |         "blinker", | ||||||
|         "Pillow>=2.0.0, <7.0.0",  # 7.0.0 dropped Python2 support |         "Pillow>=2.0.0, <7.0.0",  # 7.0.0 dropped Python2 support | ||||||
|  |         "zipp<2.0.0",  # (dependency of pytest) dropped python2 support | ||||||
|  |         "pyparsing<3",  # sub-dependency that dropped py2 support | ||||||
|  |         "configparser<5",  # sub-dependency that dropped py2 support | ||||||
|     ], |     ], | ||||||
| } | } | ||||||
| if sys.version_info[0] == 3: | if PY3: | ||||||
|     extra_opts["use_2to3"] = True |     extra_opts["use_2to3"] = True | ||||||
|     if "test" in sys.argv: |     if "test" in sys.argv: | ||||||
|         extra_opts["packages"] = find_packages() |         extra_opts["packages"] = find_packages() | ||||||
|   | |||||||
| @@ -806,18 +806,6 @@ class TestIndexes(unittest.TestCase): | |||||||
|         info = Log.objects._collection.index_information() |         info = Log.objects._collection.index_information() | ||||||
|         assert 3600 == info["created_1"]["expireAfterSeconds"] |         assert 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() |  | ||||||
|  |  | ||||||
|     def test_unique_and_indexes(self): |     def test_unique_and_indexes(self): | ||||||
|         """Ensure that 'unique' constraints aren't overridden by |         """Ensure that 'unique' constraints aren't overridden by | ||||||
|         meta.indexes. |         meta.indexes. | ||||||
| @@ -1058,10 +1046,6 @@ class TestIndexes(unittest.TestCase): | |||||||
|                 del index_info[key][ |                 del index_info[key][ | ||||||
|                     "ns" |                     "ns" | ||||||
|                 ]  # drop the index namespace - we don't care about that here, MongoDB 3+ |                 ]  # 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+ |  | ||||||
|  |  | ||||||
|         assert index_info == { |         assert index_info == { | ||||||
|             "txt_1": {"key": [("txt", 1)], "background": False}, |             "txt_1": {"key": [("txt", 1)], "background": False}, | ||||||
|   | |||||||
| @@ -523,7 +523,6 @@ class TestInheritance(MongoDBTestCase): | |||||||
|  |  | ||||||
|         defaults = { |         defaults = { | ||||||
|             "index_background": True, |             "index_background": True, | ||||||
|             "index_drop_dups": True, |  | ||||||
|             "index_opts": {"hello": "world"}, |             "index_opts": {"hello": "world"}, | ||||||
|             "allow_inheritance": True, |             "allow_inheritance": True, | ||||||
|             "queryset_class": "QuerySet", |             "queryset_class": "QuerySet", | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ class ComplexDateTimeFieldTest(MongoDBTestCase): | |||||||
|         for values in itertools.product([2014], mm, dd, hh, ii, ss, microsecond): |         for values in itertools.product([2014], mm, dd, hh, ii, ss, microsecond): | ||||||
|             stored = LogEntry(date=datetime.datetime(*values)).to_mongo()["date"] |             stored = LogEntry(date=datetime.datetime(*values)).to_mongo()["date"] | ||||||
|             assert ( |             assert ( | ||||||
|                 re.match("^\d{4},\d{2},\d{2},\d{2},\d{2},\d{2},\d{6}$", stored) |                 re.match(r"^\d{4},\d{2},\d{2},\d{2},\d{2},\d{2},\d{6}$", stored) | ||||||
|                 is not None |                 is not None | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
| @@ -74,7 +74,7 @@ class ComplexDateTimeFieldTest(MongoDBTestCase): | |||||||
|             "date_with_dots" |             "date_with_dots" | ||||||
|         ] |         ] | ||||||
|         assert ( |         assert ( | ||||||
|             re.match("^\d{4}.\d{2}.\d{2}.\d{2}.\d{2}.\d{2}.\d{6}$", stored) is not None |             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): |     def test_complexdatetime_usage(self): | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| import pytest | import pytest | ||||||
|  | from bson import InvalidDocument | ||||||
|  |  | ||||||
| from mongoengine import * | from mongoengine import * | ||||||
| from mongoengine.base import BaseDict | from mongoengine.base import BaseDict | ||||||
| @@ -19,22 +20,24 @@ class TestDictField(MongoDBTestCase): | |||||||
|         post = BlogPost(info=info).save() |         post = BlogPost(info=info).save() | ||||||
|         assert get_as_pymongo(post) == {"_id": post.id, "info": info} |         assert get_as_pymongo(post) == {"_id": post.id, "info": info} | ||||||
|  |  | ||||||
|     def test_general_things(self): |     def test_validate_invalid_type(self): | ||||||
|         """Ensure that dict types work as expected.""" |         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): |         class BlogPost(Document): | ||||||
|             info = DictField() |             info = DictField() | ||||||
|  |  | ||||||
|         BlogPost.drop_collection() |         BlogPost.drop_collection() | ||||||
|  |  | ||||||
|         post = BlogPost() |         post = BlogPost() | ||||||
|         post.info = "my post" |  | ||||||
|         with pytest.raises(ValidationError): |  | ||||||
|             post.validate() |  | ||||||
|  |  | ||||||
|         post.info = ["test", "test"] |  | ||||||
|         with pytest.raises(ValidationError): |  | ||||||
|             post.validate() |  | ||||||
|  |  | ||||||
|         post.info = {"$title": "test"} |         post.info = {"$title": "test"} | ||||||
|         with pytest.raises(ValidationError): |         with pytest.raises(ValidationError): | ||||||
| @@ -48,25 +51,34 @@ class TestDictField(MongoDBTestCase): | |||||||
|         with pytest.raises(ValidationError): |         with pytest.raises(ValidationError): | ||||||
|             post.validate() |             post.validate() | ||||||
|  |  | ||||||
|         post.info = {1: "test"} |  | ||||||
|         with pytest.raises(ValidationError): |  | ||||||
|             post.validate() |  | ||||||
|  |  | ||||||
|         post.info = {"nested": {"the.title": "test"}} |         post.info = {"nested": {"the.title": "test"}} | ||||||
|         if get_mongodb_version() < MONGODB_36: |         if get_mongodb_version() < MONGODB_36: | ||||||
|             with pytest.raises(ValidationError): |             # 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() |             post.validate() | ||||||
|  |             with pytest.raises(InvalidDocument): | ||||||
|  |                 post.save() | ||||||
|         else: |         else: | ||||||
|             post.validate() |             post.validate() | ||||||
|  |  | ||||||
|         post.info = {"dollar_and_dot": {"te$st.test": "test"}} |         post.info = {"dollar_and_dot": {"te$st.test": "test"}} | ||||||
|         if get_mongodb_version() < MONGODB_36: |         if get_mongodb_version() < MONGODB_36: | ||||||
|             with pytest.raises(ValidationError): |  | ||||||
|             post.validate() |             post.validate() | ||||||
|  |             with pytest.raises(InvalidDocument): | ||||||
|  |                 post.save() | ||||||
|         else: |         else: | ||||||
|             post.validate() |             post.validate() | ||||||
|  |  | ||||||
|         post.info = {"title": "test"} |     def test_general_things(self): | ||||||
|  |         """Ensure that dict types work as expected.""" | ||||||
|  |  | ||||||
|  |         class BlogPost(Document): | ||||||
|  |             info = DictField() | ||||||
|  |  | ||||||
|  |         BlogPost.drop_collection() | ||||||
|  |  | ||||||
|  |         post = BlogPost(info={"title": "test"}) | ||||||
|         post.save() |         post.save() | ||||||
|  |  | ||||||
|         post = BlogPost() |         post = BlogPost() | ||||||
|   | |||||||
| @@ -274,32 +274,47 @@ class TestQueryset(unittest.TestCase): | |||||||
|         with pytest.raises(InvalidQueryError): |         with pytest.raises(InvalidQueryError): | ||||||
|             self.Person.objects(name="User A").with_id(person1.id) |             self.Person.objects(name="User A").with_id(person1.id) | ||||||
|  |  | ||||||
|     def test_find_only_one(self): |     def test_get_no_document_exists_raises_doesnotexist(self): | ||||||
|         """Ensure that a query using ``get`` returns at most one result. |         assert self.Person.objects.count() == 0 | ||||||
|         """ |  | ||||||
|         # Try retrieving when no objects exists |         # Try retrieving when no objects exists | ||||||
|         with pytest.raises(DoesNotExist): |         with pytest.raises(DoesNotExist): | ||||||
|             self.Person.objects.get() |             self.Person.objects.get() | ||||||
|         with pytest.raises(self.Person.DoesNotExist): |         with pytest.raises(self.Person.DoesNotExist): | ||||||
|             self.Person.objects.get() |             self.Person.objects.get() | ||||||
|  |  | ||||||
|  |     def test_get_multiple_match_raises_multipleobjectsreturned(self): | ||||||
|  |         """Ensure that a query using ``get`` returns at most one result. | ||||||
|  |         """ | ||||||
|  |         assert self.Person.objects().count() == 0 | ||||||
|  |  | ||||||
|         person1 = self.Person(name="User A", age=20) |         person1 = self.Person(name="User A", age=20) | ||||||
|         person1.save() |         person1.save() | ||||||
|         person2 = self.Person(name="User B", age=30) |  | ||||||
|  |         p = self.Person.objects.get() | ||||||
|  |         assert p == person1 | ||||||
|  |  | ||||||
|  |         person2 = self.Person(name="User B", age=20) | ||||||
|         person2.save() |         person2.save() | ||||||
|  |  | ||||||
|         # Retrieve the first person from the database |         person3 = self.Person(name="User C", age=30) | ||||||
|  |         person3.save() | ||||||
|  |  | ||||||
|  |         # .get called without argument | ||||||
|         with pytest.raises(MultipleObjectsReturned): |         with pytest.raises(MultipleObjectsReturned): | ||||||
|             self.Person.objects.get() |             self.Person.objects.get() | ||||||
|         with pytest.raises(self.Person.MultipleObjectsReturned): |         with pytest.raises(self.Person.MultipleObjectsReturned): | ||||||
|             self.Person.objects.get() |             self.Person.objects.get() | ||||||
|  |  | ||||||
|  |         # check filtering | ||||||
|  |         with pytest.raises(MultipleObjectsReturned): | ||||||
|  |             self.Person.objects.get(age__lt=30) | ||||||
|  |         with pytest.raises(MultipleObjectsReturned) as exc_info: | ||||||
|  |             self.Person.objects(age__lt=30).get() | ||||||
|  |         assert "2 or more items returned, instead of 1" == str(exc_info.value) | ||||||
|  |  | ||||||
|         # Use a query to filter the people found to just person2 |         # Use a query to filter the people found to just person2 | ||||||
|         person = self.Person.objects.get(age=30) |         person = self.Person.objects.get(age=30) | ||||||
|         assert person.name == "User B" |         assert person == person3 | ||||||
|  |  | ||||||
|         person = self.Person.objects.get(age__lt=30) |  | ||||||
|         assert person.name == "User A" |  | ||||||
|  |  | ||||||
|     def test_find_array_position(self): |     def test_find_array_position(self): | ||||||
|         """Ensure that query by array position works. |         """Ensure that query by array position works. | ||||||
| @@ -4476,6 +4491,74 @@ class TestQueryset(unittest.TestCase): | |||||||
|             expected = "[u'A1', u'A2']" |             expected = "[u'A1', u'A2']" | ||||||
|         assert expected == "%s" % sorted(names) |         assert expected == "%s" % sorted(names) | ||||||
|  |  | ||||||
|  |     def test_fields(self): | ||||||
|  |         class Bar(EmbeddedDocument): | ||||||
|  |             v = StringField() | ||||||
|  |             z = StringField() | ||||||
|  |  | ||||||
|  |         class Foo(Document): | ||||||
|  |             x = StringField() | ||||||
|  |             y = IntField() | ||||||
|  |             items = EmbeddedDocumentListField(Bar) | ||||||
|  |  | ||||||
|  |         Foo.drop_collection() | ||||||
|  |  | ||||||
|  |         Foo(x="foo1", y=1).save() | ||||||
|  |         Foo(x="foo2", y=2, items=[]).save() | ||||||
|  |         Foo(x="foo3", y=3, items=[Bar(z="a", v="V")]).save() | ||||||
|  |         Foo( | ||||||
|  |             x="foo4", | ||||||
|  |             y=4, | ||||||
|  |             items=[ | ||||||
|  |                 Bar(z="a", v="V"), | ||||||
|  |                 Bar(z="b", v="W"), | ||||||
|  |                 Bar(z="b", v="X"), | ||||||
|  |                 Bar(z="c", v="V"), | ||||||
|  |             ], | ||||||
|  |         ).save() | ||||||
|  |         Foo( | ||||||
|  |             x="foo5", | ||||||
|  |             y=5, | ||||||
|  |             items=[ | ||||||
|  |                 Bar(z="b", v="X"), | ||||||
|  |                 Bar(z="c", v="V"), | ||||||
|  |                 Bar(z="d", v="V"), | ||||||
|  |                 Bar(z="e", v="V"), | ||||||
|  |             ], | ||||||
|  |         ).save() | ||||||
|  |  | ||||||
|  |         foos_with_x = list(Foo.objects.order_by("y").fields(x=1)) | ||||||
|  |  | ||||||
|  |         assert all(o.x is not None for o in foos_with_x) | ||||||
|  |  | ||||||
|  |         foos_without_y = list(Foo.objects.order_by("y").fields(y=0)) | ||||||
|  |  | ||||||
|  |         assert all(o.y is None for o in foos_with_x) | ||||||
|  |  | ||||||
|  |         foos_with_sliced_items = list(Foo.objects.order_by("y").fields(slice__items=1)) | ||||||
|  |  | ||||||
|  |         assert foos_with_sliced_items[0].items == [] | ||||||
|  |         assert foos_with_sliced_items[1].items == [] | ||||||
|  |         assert len(foos_with_sliced_items[2].items) == 1 | ||||||
|  |         assert foos_with_sliced_items[2].items[0].z == "a" | ||||||
|  |         assert len(foos_with_sliced_items[3].items) == 1 | ||||||
|  |         assert foos_with_sliced_items[3].items[0].z == "a" | ||||||
|  |         assert len(foos_with_sliced_items[4].items) == 1 | ||||||
|  |         assert foos_with_sliced_items[4].items[0].z == "b" | ||||||
|  |  | ||||||
|  |         foos_with_elem_match_items = list( | ||||||
|  |             Foo.objects.order_by("y").fields(elemMatch__items={"z": "b"}) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         assert foos_with_elem_match_items[0].items == [] | ||||||
|  |         assert foos_with_elem_match_items[1].items == [] | ||||||
|  |         assert foos_with_elem_match_items[2].items == [] | ||||||
|  |         assert len(foos_with_elem_match_items[3].items) == 1 | ||||||
|  |         assert foos_with_elem_match_items[3].items[0].z == "b" | ||||||
|  |         assert foos_with_elem_match_items[3].items[0].v == "W" | ||||||
|  |         assert len(foos_with_elem_match_items[4].items) == 1 | ||||||
|  |         assert foos_with_elem_match_items[4].items[0].z == "b" | ||||||
|  |  | ||||||
|     def test_elem_match(self): |     def test_elem_match(self): | ||||||
|         class Foo(EmbeddedDocument): |         class Foo(EmbeddedDocument): | ||||||
|             shape = StringField() |             shape = StringField() | ||||||
|   | |||||||
| @@ -24,6 +24,12 @@ class TestTransform(unittest.TestCase): | |||||||
|         } |         } | ||||||
|         assert transform.query(friend__age__gte=30) == {"friend.age": {"$gte": 30}} |         assert transform.query(friend__age__gte=30) == {"friend.age": {"$gte": 30}} | ||||||
|         assert transform.query(name__exists=True) == {"name": {"$exists": True}} |         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): |     def test_transform_update(self): | ||||||
|         class LisDoc(Document): |         class LisDoc(Document): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user