Merge branch 'master' of github.com:MongoEngine/mongoengine into negative_indexes_in_list
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user