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

This commit is contained in:
Bastien Gérard
2019-12-20 23:27:51 +01:00
77 changed files with 5029 additions and 4821 deletions

View File

@@ -62,13 +62,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
@@ -732,7 +732,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
@@ -770,7 +773,9 @@ 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()])
errors = "\n".join(
["Field '%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,

View File

@@ -171,7 +171,7 @@ class no_sub_classes(object):
class query_counter(object):
"""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 +182,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
@@ -247,8 +247,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

View File

@@ -12,6 +12,7 @@ __all__ = (
"InvalidQueryError",
"OperationError",
"NotUniqueError",
"BulkWriteError",
"FieldDoesNotExist",
"ValidationError",
"SaveConditionError",
@@ -51,6 +52,10 @@ class NotUniqueError(OperationError):
pass
class BulkWriteError(OperationError):
pass
class SaveConditionError(OperationError):
pass

View File

@@ -2291,7 +2291,7 @@ class LineStringField(GeoJsonBaseField):
.. code-block:: js
{'type' : 'LineString' ,
'coordinates' : [[x1, y1], [x1, y1] ... [xn, yn]]}
'coordinates' : [[x1, y1], [x2, y2] ... [xn, yn]]}
You can either pass a dict with the full information or a list of points.
@@ -2502,6 +2502,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

@@ -20,6 +20,7 @@ from mongoengine.common import _import_class
from mongoengine.connection import get_db
from mongoengine.context_managers import set_write_concern, switch_db
from mongoengine.errors import (
BulkWriteError,
InvalidQueryError,
LookUpError,
NotUniqueError,
@@ -80,6 +81,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
@@ -355,8 +357,8 @@ class BaseQuerySet(object):
except pymongo.errors.BulkWriteError as err:
# inserting documents that already have an _id field will
# give huge performance debt or raise
message = u"Document must not have _id value before bulk write (%s)"
raise NotUniqueError(message % six.text_type(err))
message = u"Bulk write error: (%s)"
raise BulkWriteError(message % six.text_type(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)):
@@ -781,6 +783,7 @@ class BaseQuerySet(object):
"_limit",
"_skip",
"_hint",
"_collation",
"_auto_dereference",
"_search_text",
"only_fields",
@@ -863,6 +866,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).
@@ -1164,9 +1193,7 @@ 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 scalar(self, *fields):
@@ -1576,7 +1603,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()
@@ -1607,6 +1637,7 @@ class BaseQuerySet(object):
).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)
@@ -1636,6 +1667,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)

View File

@@ -1,4 +1,5 @@
import copy
import warnings
from mongoengine.errors import InvalidQueryError
from mongoengine.queryset import transform
@@ -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,11 @@ 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)
__nonzero__ = __bool__ # For Py2 support
def accept(self, visitor):
for i in range(len(self.children)):
if isinstance(self.children[i], QNode):
@@ -144,8 +154,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 +177,14 @@ class Q(QNode):
def __repr__(self):
return "Q(**%s)" % repr(self.query)
def __bool__(self):
return bool(self.query)
__nonzero__ = __bool__ # For Py2 support
def __eq__(self, other):
return self.__class__ == other.__class__ and self.query == other.query
def accept(self, visitor):
return visitor.visit_query(self)