Merge branch 'master' of github.com:MongoEngine/mongoengine into py2py3_improve_compat

This commit is contained in:
Bastien Gérard
2020-03-08 14:44:39 +01:00
23 changed files with 534 additions and 302 deletions

View File

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

View File

@@ -121,6 +121,9 @@ class BaseList(list):
super(BaseList, self).__init__(list_items)
def __getitem__(self, 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(BaseList, self).__getitem__(key)
if isinstance(key, slice):

View File

@@ -330,7 +330,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.

View File

@@ -38,6 +38,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.mongodb_support import MONGODB_36, get_mongodb_version
from mongoengine.python_support import StringIO
from mongoengine.queryset import DO_NOTHING
from mongoengine.queryset.base import BaseQuerySet
@@ -1043,6 +1044,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.
@@ -1069,11 +1079,18 @@ 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):
curr_mongo_ver = get_mongodb_version()
if curr_mongo_ver < MONGODB_36 and key_has_dot_or_dollar(value):
self.error(
'Invalid dictionary key name - keys may not contain "."'
' or startswith "$" characters'
)
elif curr_mongo_ver >= MONGODB_36 and key_starts_with_dollar(value):
self.error(
'Invalid dictionary key name - keys may not startswith "$" characters'
)
super(DictField, self).validate(value)
def lookup_member(self, member_name):
@@ -2494,6 +2511,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():

View File

@@ -302,7 +302,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
@@ -1255,16 +1255,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})
@@ -1281,14 +1292,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(

View File

@@ -169,9 +169,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