Compare commits

...

9 Commits

Author SHA1 Message Date
Stefan Wojcik
af292b0ec2 Bump version to v0.18.2 2019-06-25 16:52:31 +02:00
Stefan Wojcik
1ead7f9b2b Add changelog entries for v0.18.2 2019-06-25 16:51:56 +02:00
Stefan Wojcik
5c91877b69 Fix the Travis deployment condition
See https://github.com/MongoEngine/mongoengine/issues/2104 for details.

For now I'm hardcoding `$MONGODB = 3.4.17` just to get a release out there,
but we should probably use the globals going forward. Will do that in
a follow-up commit once I get the `travis-conditions` gem up and running and
hence can test `.travis.yml` changes without deploying.
2019-06-25 16:48:51 +02:00
Bastien Gérard
b1002dd4f9 Merge pull request #2097 from bagerard/remove_deprecated_pymongo_methods
remove pymongo deprecated methods: find_and_modify & remove
2019-06-24 22:03:58 +02:00
Stefan Wojcik
a4fe091a51 Cleaner code & comments in BaseField.__set__ 2019-06-21 13:51:53 +02:00
Stefan Wojcik
216217e2c6 Datastructures comments: fix typos and tweak formatting [ci skip] 2019-06-21 13:48:24 +02:00
Stefan Wojcik
799775b3a7 Slightly cleaner docstring of BaseQuerySet.no_sub_classes [ci skip] 2019-06-20 12:18:58 +02:00
Stefan Wójcik
ae0384df29 Improve Document.meta.shard_key docs (#2099)
This closes #2096. Previous documentation of the shard_key meta attribute was
missing the crucial point that it really only matters if your collection is
sharded over a compound index.
2019-06-20 11:25:51 +02:00
Bastien Gérard
8f57279dc7 remove pymongo deprecated methods: find_and_modify & remove 2019-06-19 23:04:23 +02:00
10 changed files with 65 additions and 39 deletions

View File

@@ -102,5 +102,5 @@ deploy:
on: on:
tags: true tags: true
repo: MongoEngine/mongoengine repo: MongoEngine/mongoengine
condition: ($PYMONGO = 3.x) && ($MONGODB = 3.4) condition: ($PYMONGO = 3.x) && ($MONGODB = 3.4.17)
python: 2.7 python: 2.7

View File

@@ -7,6 +7,11 @@ Development
=========== ===========
- (Fill this out as you fix issues and develop your features). - (Fill this out as you fix issues and develop your features).
Changes in 0.18.2
=================
- Replace some of the deprecated PyMongo v2.x methods with their v3.x equivalents #2097
- Various code clarity and documentation improvements
Changes in 0.18.1 Changes in 0.18.1
================= =================
- Fix a bug introduced in 0.18.0 which was causing `.save()` to update all the fields - Fix a bug introduced in 0.18.0 which was causing `.save()` to update all the fields

View File

@@ -714,11 +714,16 @@ subsequent calls to :meth:`~mongoengine.queryset.QuerySet.order_by`. ::
Shard keys Shard keys
========== ==========
If your collection is sharded, then you need to specify the shard key as a tuple, If your collection is sharded by multiple keys, then you can improve shard
using the :attr:`shard_key` attribute of :attr:`~mongoengine.Document.meta`. routing (and thus the performance of your application) by specifying the shard
This ensures that the shard key is sent with the query when calling the key, using the :attr:`shard_key` attribute of
:meth:`~mongoengine.document.Document.save` or :attr:`~mongoengine.Document.meta`. The shard key should be defined as a tuple.
:meth:`~mongoengine.document.Document.update` method on an existing
This ensures that the full shard key is sent with the query when calling
methods such as :meth:`~mongoengine.document.Document.save`,
:meth:`~mongoengine.document.Document.update`,
:meth:`~mongoengine.document.Document.modify`, or
:meth:`~mongoengine.document.Document.delete` on an existing
:class:`~mongoengine.Document` instance:: :class:`~mongoengine.Document` instance::
class LogEntry(Document): class LogEntry(Document):
@@ -728,7 +733,8 @@ This ensures that the shard key is sent with the query when calling the
data = StringField() data = StringField()
meta = { meta = {
'shard_key': ('machine', 'timestamp',) 'shard_key': ('machine', 'timestamp'),
'indexes': ('machine', 'timestamp'),
} }
.. _document-inheritance: .. _document-inheritance:

View File

@@ -23,7 +23,7 @@ __all__ = (list(document.__all__) + list(fields.__all__) +
list(signals.__all__) + list(errors.__all__)) list(signals.__all__) + list(errors.__all__))
VERSION = (0, 18, 1) VERSION = (0, 18, 2)
def get_version(): def get_version():

View File

@@ -11,18 +11,20 @@ __all__ = ('BaseDict', 'StrictDict', 'BaseList', 'EmbeddedDocumentList', 'LazyRe
def mark_as_changed_wrapper(parent_method): def mark_as_changed_wrapper(parent_method):
"""Decorators that ensures _mark_as_changed method gets called""" """Decorator that ensures _mark_as_changed method gets called."""
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
result = parent_method(self, *args, **kwargs) # Can't use super() in the decorator # Can't use super() in the decorator.
result = parent_method(self, *args, **kwargs)
self._mark_as_changed() self._mark_as_changed()
return result return result
return wrapper return wrapper
def mark_key_as_changed_wrapper(parent_method): def mark_key_as_changed_wrapper(parent_method):
"""Decorators that ensures _mark_as_changed method gets called with the key argument""" """Decorator that ensures _mark_as_changed method gets called with the key argument"""
def wrapper(self, key, *args, **kwargs): def wrapper(self, key, *args, **kwargs):
result = parent_method(self, key, *args, **kwargs) # Can't use super() in the decorator # Can't use super() in the decorator.
result = parent_method(self, key, *args, **kwargs)
self._mark_as_changed(key) self._mark_as_changed(key)
return result return result
return wrapper return wrapper

View File

@@ -128,10 +128,9 @@ class BaseField(object):
return instance._data.get(self.name) return instance._data.get(self.name)
def __set__(self, instance, value): def __set__(self, instance, value):
"""Descriptor for assigning a value to a field in a document. """Descriptor for assigning a value to a field in a document."""
""" # If setting to None and there is a default value provided for this
# If setting to None and there is a default # field, then set the value to the default value.
# Then set the value to the default value
if value is None: if value is None:
if self.null: if self.null:
value = None value = None
@@ -142,12 +141,16 @@ class BaseField(object):
if instance._initialised: if instance._initialised:
try: try:
if (self.name not in instance._data or value_has_changed = (
instance._data[self.name] != value): self.name not in instance._data or
instance._data[self.name] != value
)
if value_has_changed:
instance._mark_as_changed(self.name) instance._mark_as_changed(self.name)
except Exception: except Exception:
# Values cant be compared eg: naive and tz datetimes # Some values can't be compared and throw an error when we
# So mark it as changed # attempt to do so (e.g. tz-naive and tz-aware datetimes).
# Mark the field as changed in such cases.
instance._mark_as_changed(self.name) instance._mark_as_changed(self.name)
EmbeddedDocument = _import_class('EmbeddedDocument') EmbeddedDocument = _import_class('EmbeddedDocument')
@@ -157,6 +160,7 @@ class BaseField(object):
for v in value: for v in value:
if isinstance(v, EmbeddedDocument): if isinstance(v, EmbeddedDocument):
v._instance = weakref.proxy(instance) v._instance = weakref.proxy(instance)
instance._data[self.name] = value instance._data[self.name] = value
def error(self, message='', errors=None, field_name=None): def error(self, message='', errors=None, field_name=None):

View File

@@ -544,7 +544,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
@property @property
def _qs(self): def _qs(self):
"""Return the queryset to use for updating / reloading / deletions.""" """Return the default queryset corresponding to this document."""
if not hasattr(self, '__objects'): if not hasattr(self, '__objects'):
self.__objects = QuerySet(self, self._get_collection()) self.__objects = QuerySet(self, self._get_collection())
return self.__objects return self.__objects
@@ -552,9 +552,11 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
@property @property
def _object_key(self): def _object_key(self):
"""Get the query dict that can be used to fetch this object from """Get the query dict that can be used to fetch this object from
the database. Most of the time it's a simple PK lookup, but in the database.
case of a sharded collection with a compound shard key, it can
contain a more complex query. Most of the time the dict is a simple PK lookup, but in case of
a sharded collection with a compound shard key, it can contain a more
complex query.
""" """
select_dict = {'pk': self.pk} select_dict = {'pk': self.pk}
shard_key = self.__class__._meta.get('shard_key', tuple()) shard_key = self.__class__._meta.get('shard_key', tuple())

View File

@@ -10,6 +10,7 @@ from operator import itemgetter
from bson import Binary, DBRef, ObjectId, SON from bson import Binary, DBRef, ObjectId, SON
import gridfs import gridfs
import pymongo import pymongo
from pymongo import ReturnDocument
import six import six
from six import iteritems from six import iteritems
@@ -1964,9 +1965,11 @@ class SequenceField(BaseField):
sequence_name = self.get_sequence_name() sequence_name = self.get_sequence_name()
sequence_id = '%s.%s' % (sequence_name, self.name) sequence_id = '%s.%s' % (sequence_name, self.name)
collection = get_db(alias=self.db_alias)[self.collection_name] collection = get_db(alias=self.db_alias)[self.collection_name]
counter = collection.find_and_modify(query={'_id': sequence_id},
counter = collection.find_one_and_update(
filter={'_id': sequence_id},
update={'$inc': {'next': 1}}, update={'$inc': {'next': 1}},
new=True, return_document=ReturnDocument.AFTER,
upsert=True) upsert=True)
return self.value_decorator(counter['next']) return self.value_decorator(counter['next'])
@@ -1975,9 +1978,10 @@ class SequenceField(BaseField):
sequence_name = self.get_sequence_name() sequence_name = self.get_sequence_name()
sequence_id = "%s.%s" % (sequence_name, self.name) sequence_id = "%s.%s" % (sequence_name, self.name)
collection = get_db(alias=self.db_alias)[self.collection_name] collection = get_db(alias=self.db_alias)[self.collection_name]
counter = collection.find_and_modify(query={"_id": sequence_id}, counter = collection.find_one_and_update(
filter={"_id": sequence_id},
update={"$set": {"next": value}}, update={"$set": {"next": value}},
new=True, return_document=ReturnDocument.AFTER,
upsert=True) upsert=True)
return self.value_decorator(counter['next']) return self.value_decorator(counter['next'])

View File

@@ -73,6 +73,7 @@ class BaseQuerySet(object):
self._initial_query = { self._initial_query = {
'_cls': {'$in': self._document._subclasses}} '_cls': {'$in': self._document._subclasses}}
self._loaded_fields = QueryFieldList(always_include=['_cls']) self._loaded_fields = QueryFieldList(always_include=['_cls'])
self._cursor_obj = None self._cursor_obj = None
self._limit = None self._limit = None
self._skip = None self._skip = None
@@ -480,9 +481,10 @@ class BaseQuerySet(object):
write_concern=write_concern, write_concern=write_concern,
**{'pull_all__%s' % field_name: self}) **{'pull_all__%s' % field_name: self})
result = queryset._collection.remove(queryset._query, **write_concern) with set_write_concern(queryset._collection, write_concern) as collection:
if result: result = collection.delete_many(queryset._query)
return result.get('n') if result.acknowledged:
return result.deleted_count
def update(self, upsert=False, multi=True, write_concern=None, def update(self, upsert=False, multi=True, write_concern=None,
full_result=False, **update): full_result=False, **update):
@@ -707,8 +709,9 @@ class BaseQuerySet(object):
return queryset return queryset
def no_sub_classes(self): def no_sub_classes(self):
""" """Filter for only the instances of this specific document.
Only return instances of this document and not any inherited documents
Do NOT return any inherited documents.
""" """
if self._document._meta.get('allow_inheritance') is True: if self._document._meta.get('allow_inheritance') is True:
self._initial_query = {'_cls': self._document._class_name} self._initial_query = {'_cls': self._document._class_name}

View File

@@ -1857,8 +1857,8 @@ class QuerySetTest(unittest.TestCase):
self.Person.objects()[:1].delete() self.Person.objects()[:1].delete()
self.assertEqual(1, BlogPost.objects.count()) self.assertEqual(1, BlogPost.objects.count())
def test_limit_with_write_concern_0(self): def test_delete_edge_case_with_write_concern_0_return_None(self):
"""Return None when write is unacknowledged"""
p1 = self.Person(name="User Z", age=20).save() p1 = self.Person(name="User Z", age=20).save()
del_result = p1.delete(w=0) del_result = p1.delete(w=0)
self.assertEqual(None, del_result) self.assertEqual(None, del_result)