To achive storing object data in order with minimum implementation, I

changed followings.

- added optional parameter `container_class` which enables to choose
  intermediate class at encoding Python data, instead of additional
  field class.
- removed OrderedDocument class because the equivalent feature could
  be implemented by the outside of Mongoengine.
This commit is contained in:
Hiroyasu OHYAMA 2017-03-01 09:20:57 +00:00
parent e32a9777d7
commit 5957dc72eb
4 changed files with 29 additions and 70 deletions

View File

@ -1,7 +1,6 @@
import re import re
import warnings import warnings
from collections import OrderedDict
from bson.dbref import DBRef from bson.dbref import DBRef
import pymongo import pymongo
from pymongo.read_preferences import ReadPreference from pymongo.read_preferences import ReadPreference
@ -22,8 +21,7 @@ from mongoengine.queryset import (NotUniqueError, OperationError,
__all__ = ('Document', 'EmbeddedDocument', 'DynamicDocument', __all__ = ('Document', 'EmbeddedDocument', 'DynamicDocument',
'DynamicEmbeddedDocument', 'OperationError', 'DynamicEmbeddedDocument', 'OperationError',
'InvalidCollectionError', 'NotUniqueError', 'MapReduceDocument', 'InvalidCollectionError', 'NotUniqueError', 'MapReduceDocument')
'OrderedDocument')
def includes_cls(fields): def includes_cls(fields):
@ -1038,27 +1036,3 @@ class MapReduceDocument(object):
self._key_object = self._document.objects.with_id(self.key) self._key_object = self._document.objects.with_id(self.key)
return self._key_object return self._key_object
return self._key_object return self._key_object
class OrderedDocument(Document):
"""A document that is almost same with Document except for returning
results in OrderedDict instead of dict.
"""
# The __metaclass__ attribute is removed by 2to3 when running with Python3
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
my_metaclass = TopLevelDocumentMetaclass
__metaclass__ = TopLevelDocumentMetaclass
@classmethod
def _get_collection(cls):
collection = super(OrderedDocument, cls)._get_collection()
if IS_PYMONGO_3:
# returns collection object which is set OrderedDict class to be decoded from BSON document
from bson import CodecOptions
return collection.with_options(codec_options=CodecOptions(document_class=OrderedDict))
else:
# set attribute to specify the class to be decoeded
cls.decoded_class = OrderedDict
return collection

View File

@ -5,7 +5,6 @@ import re
import time import time
import uuid import uuid
import warnings import warnings
from collections import OrderedDict
from operator import itemgetter from operator import itemgetter
from bson import Binary, DBRef, ObjectId, SON from bson import Binary, DBRef, ObjectId, SON
@ -50,7 +49,7 @@ __all__ = (
'FileField', 'ImageGridFsProxy', 'ImproperlyConfigured', 'ImageField', 'FileField', 'ImageGridFsProxy', 'ImproperlyConfigured', 'ImageField',
'GeoPointField', 'PointField', 'LineStringField', 'PolygonField', 'GeoPointField', 'PointField', 'LineStringField', 'PolygonField',
'SequenceField', 'UUIDField', 'MultiPointField', 'MultiLineStringField', 'SequenceField', 'UUIDField', 'MultiPointField', 'MultiLineStringField',
'MultiPolygonField', 'GeoJsonBaseField', 'OrderedDynamicField' 'MultiPolygonField', 'GeoJsonBaseField'
) )
RECURSIVE_REFERENCE_CONSTANT = 'self' RECURSIVE_REFERENCE_CONSTANT = 'self'
@ -620,6 +619,14 @@ class DynamicField(BaseField):
Used by :class:`~mongoengine.DynamicDocument` to handle dynamic data""" Used by :class:`~mongoengine.DynamicDocument` to handle dynamic data"""
def __init__(self, container_class=dict, *args, **kwargs):
self._container_cls = container_class
if not issubclass(self._container_cls, dict):
self.error('The class that is specified in `container_class` parameter '
'must be a subclass of `dict`.')
super(DynamicField, self).__init__(*args, **kwargs)
def to_mongo(self, value, use_db_field=True, fields=None): def to_mongo(self, value, use_db_field=True, fields=None):
"""Convert a Python type to a MongoDB compatible type. """Convert a Python type to a MongoDB compatible type.
""" """
@ -645,7 +652,7 @@ class DynamicField(BaseField):
is_list = True is_list = True
value = {k: v for k, v in enumerate(value)} value = {k: v for k, v in enumerate(value)}
data = self._container_type() if hasattr(self, '_container_type') else {} data = self._container_cls()
for k, v in value.iteritems(): for k, v in value.iteritems():
data[k] = self.to_mongo(v, use_db_field, fields) data[k] = self.to_mongo(v, use_db_field, fields)
@ -676,16 +683,6 @@ class DynamicField(BaseField):
value.validate(clean=clean) value.validate(clean=clean)
class OrderedDynamicField(DynamicField):
"""A field that wraps DynamicField. This uses OrderedDict class
to guarantee to store data in the defined order instead of dict.
"""
def __init__(self, *args, **kwargs):
super(OrderedDynamicField, self).__init__(*args, **kwargs)
self._container_type = OrderedDict
class ListField(ComplexBaseField): class ListField(ComplexBaseField):
"""A list field that wraps a standard field, allowing multiple instances """A list field that wraps a standard field, allowing multiple instances
of the field to be used as a list in the database. of the field to be used as a list in the database.

View File

@ -1501,10 +1501,6 @@ class BaseQuerySet(object):
cursor_args['read_preference'] = self._read_preference cursor_args['read_preference'] = self._read_preference
else: else:
cursor_args['slave_okay'] = self._slave_okay cursor_args['slave_okay'] = self._slave_okay
# set decode format if needed
if hasattr(self._document, 'decoded_class'):
cursor_args['as_class'] = self._document.decoded_class
else: else:
fields_name = 'projection' fields_name = 'projection'
# snapshot is not handled at all by PyMongo 3+ # snapshot is not handled at all by PyMongo 3+

View File

@ -4500,39 +4500,31 @@ class EmbeddedDocumentListFieldTestCase(MongoDBTestCase):
self.assertTrue(hasattr(CustomData.c_field, 'custom_data')) self.assertTrue(hasattr(CustomData.c_field, 'custom_data'))
self.assertEqual(custom_data['a'], CustomData.c_field.custom_data['a']) self.assertEqual(custom_data['a'], CustomData.c_field.custom_data['a'])
def test_ordered_dynamic_fields_class(self): def test_dynamicfield_with_container_class(self):
""" """
Tests that OrderedDynamicFields interits features of the DynamicFields Tests that object can be stored in order by DynamicField class
and saves/retrieves data in order. with container_class parameter.
""" """
class Member(Document): raw_data = [('d', 1), ('c', 2), ('b', 3), ('a', 4)]
name = StringField()
age = IntField()
class Team(OrderedDocument): class Doc(Document):
members = OrderedDynamicField() ordered_data = DynamicField(container_class=OrderedDict)
unordered_data = DynamicField()
Member.drop_collection() Doc.drop_collection()
Team.drop_collection()
member_info = [ doc = Doc(ordered_data=OrderedDict(raw_data),
('Martin McFly', 17), unordered_data=dict(raw_data)).save()
('Emmett Brown', 65),
('George McFly', 47)
]
members = OrderedDict()
for name, age in member_info:
members[name] = Member(name=name, age=age)
members[name].save()
Team(members=members).save() self.assertEqual(type(doc.ordered_data), OrderedDict)
self.assertEqual(type(doc.unordered_data), dict)
self.assertEqual([k for k,_ in doc.ordered_data.items()], ['d', 'c', 'b', 'a'])
self.assertNotEqual([k for k,_ in doc.unordered_data.items()], ['d', 'c', 'b', 'a'])
index = 0 def test_dynamicfield_with_wrong_container_class(self):
team = Team.objects.get() with self.assertRaises(ValidationError):
for member in team.members: class DocWithInvalidField:
print("%s == %s" % (member, member_info[index][0])) data = DynamicField(container_class=list)
self.assertEqual(member, member_info[index][0])
index += 1
if __name__ == '__main__': if __name__ == '__main__':