Added Python 3 support to MongoEngine
This commit is contained in:
@@ -1,18 +1,19 @@
|
||||
import operator
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
|
||||
from queryset import QuerySet, QuerySetManager
|
||||
from queryset import DoesNotExist, MultipleObjectsReturned
|
||||
from queryset import DO_NOTHING
|
||||
|
||||
from mongoengine import signals
|
||||
from mongoengine.python3_support import PY3, txt_type
|
||||
|
||||
import sys
|
||||
import pymongo
|
||||
from bson import ObjectId
|
||||
import operator
|
||||
|
||||
from functools import partial
|
||||
from bson.dbref import DBRef
|
||||
|
||||
|
||||
@@ -402,7 +403,7 @@ class ComplexBaseField(BaseField):
|
||||
"""
|
||||
errors = {}
|
||||
if self.field:
|
||||
if hasattr(value, 'iteritems'):
|
||||
if hasattr(value, 'iteritems') or hasattr(value, 'items'):
|
||||
sequence = value.iteritems()
|
||||
else:
|
||||
sequence = enumerate(value)
|
||||
@@ -491,7 +492,7 @@ class DocumentMetaclass(type):
|
||||
attrs.update(_get_mixin_fields(p_base))
|
||||
return attrs
|
||||
|
||||
metaclass = attrs.get('__metaclass__')
|
||||
metaclass = attrs.get('my_metaclass')
|
||||
super_new = super(DocumentMetaclass, cls).__new__
|
||||
if metaclass and issubclass(metaclass, DocumentMetaclass):
|
||||
return super_new(cls, name, bases, attrs)
|
||||
@@ -583,7 +584,9 @@ class DocumentMetaclass(type):
|
||||
raise InvalidDocumentError("Reverse delete rules are not supported for EmbeddedDocuments (field: %s)" % field.name)
|
||||
f.document_type.register_delete_rule(new_class, field.name, delete_rule)
|
||||
|
||||
if field.name and hasattr(Document, field.name) and EmbeddedDocument not in new_class.mro():
|
||||
if (field.name and
|
||||
hasattr(Document, field.name) and
|
||||
EmbeddedDocument not in new_class.mro()):
|
||||
raise InvalidDocumentError("%s is a document method and not a valid field name" % field.name)
|
||||
|
||||
module = attrs.get('__module__')
|
||||
@@ -602,6 +605,22 @@ class DocumentMetaclass(type):
|
||||
global _document_registry
|
||||
_document_registry[doc_class_name] = new_class
|
||||
|
||||
# in Python 2, User-defined methods objects have special read-only
|
||||
# attributes 'im_func' and 'im_self' which contain the function obj
|
||||
# and class instance object respectively. With Python 3 these special
|
||||
# attributes have been replaced by __func__ and __self__. The Blinker
|
||||
# module continues to use im_func and im_self, so the code below
|
||||
# copies __func__ into im_func and __self__ into im_self for
|
||||
# classmethod objects in Document derived classes.
|
||||
if PY3:
|
||||
for key, val in new_class.__dict__.items():
|
||||
if isinstance(val, classmethod):
|
||||
f = val.__get__(new_class)
|
||||
if hasattr(f, '__func__') and not hasattr(f, 'im_func'):
|
||||
f.__dict__.update({'im_func':getattr(f, '__func__')})
|
||||
if hasattr(f, '__self__') and not hasattr(f, 'im_self'):
|
||||
f.__dict__.update({'im_self':getattr(f, '__self__')})
|
||||
|
||||
return new_class
|
||||
|
||||
def add_to_class(self, name, value):
|
||||
@@ -623,7 +642,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
|
||||
#
|
||||
# Also assume a class is abstract if it has abstract set to True in
|
||||
# its meta dictionary. This allows custom Document superclasses.
|
||||
if (attrs.get('__metaclass__') == TopLevelDocumentMetaclass or
|
||||
if (attrs.get('my_metaclass') == TopLevelDocumentMetaclass or
|
||||
('meta' in attrs and attrs['meta'].get('abstract', False))):
|
||||
# Make sure no base class was non-abstract
|
||||
non_abstract_bases = [b for b in bases
|
||||
@@ -1189,14 +1208,17 @@ Invalid data to create a `%s` instance.\n%s""".strip() % (cls._class_name, error
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
u = unicode(self).encode('utf-8')
|
||||
u = txt_type(self)
|
||||
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||
u = '[Bad Unicode data]'
|
||||
return '<%s: %s>' % (self.__class__.__name__, u)
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, '__unicode__'):
|
||||
return unicode(self).encode('utf-8')
|
||||
if PY3:
|
||||
return self.__unicode__()
|
||||
else:
|
||||
return unicode(self).encode('utf-8')
|
||||
return '%s object' % self.__class__.__name__
|
||||
|
||||
def __eq__(self, other):
|
||||
@@ -1338,10 +1360,9 @@ class BaseDict(dict):
|
||||
|
||||
if sys.version_info < (2, 5):
|
||||
# Prior to Python 2.5, Exception was an old-style class
|
||||
import types
|
||||
def subclass_exception(name, parents, unused):
|
||||
import types
|
||||
return types.ClassType(name, parents, {})
|
||||
from types import ClassType
|
||||
return ClassType(name, parents, {})
|
||||
else:
|
||||
def subclass_exception(name, parents, module):
|
||||
return type(name, parents, {'__module__': module})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from django.http import Http404
|
||||
from mongoengine.queryset import QuerySet
|
||||
from mongoengine.base import BaseDocument
|
||||
from mongoengine.base import ValidationError
|
||||
@@ -27,6 +26,7 @@ def get_document_or_404(cls, *args, **kwargs):
|
||||
try:
|
||||
return queryset.get(*args, **kwargs)
|
||||
except (queryset._document.DoesNotExist, ValidationError):
|
||||
from django.http import Http404
|
||||
raise Http404('No %s matches the given query.' % queryset._document._class_name)
|
||||
|
||||
def get_list_or_404(cls, *args, **kwargs):
|
||||
@@ -42,5 +42,6 @@ def get_list_or_404(cls, *args, **kwargs):
|
||||
queryset = _get_queryset(cls)
|
||||
obj_list = list(queryset.filter(*args, **kwargs))
|
||||
if not obj_list:
|
||||
from django.http import Http404
|
||||
raise Http404('No %s matches the given query.' % queryset._document._class_name)
|
||||
return obj_list
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
#coding: utf-8
|
||||
from django.test import TestCase
|
||||
from django.conf import settings
|
||||
from nose.plugins.skip import SkipTest
|
||||
|
||||
from mongoengine.python3_support import PY3
|
||||
from mongoengine import connect
|
||||
|
||||
try:
|
||||
from django.test import TestCase
|
||||
from django.conf import settings
|
||||
except Exception as err:
|
||||
if PY3:
|
||||
from unittest import TestCase
|
||||
# Dummy value so no error
|
||||
class settings:
|
||||
MONGO_DATABASE_NAME = 'dummy'
|
||||
else:
|
||||
raise err
|
||||
|
||||
|
||||
class MongoTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
if PY3:
|
||||
raise SkipTest('django does not have Python 3 support')
|
||||
|
||||
"""
|
||||
TestCase class that clear the collection between the tests
|
||||
"""
|
||||
|
||||
@@ -2,7 +2,7 @@ import pymongo
|
||||
|
||||
from bson.dbref import DBRef
|
||||
|
||||
from mongoengine import signals
|
||||
from mongoengine import signals, queryset
|
||||
from base import (DocumentMetaclass, TopLevelDocumentMetaclass, BaseDocument,
|
||||
BaseDict, BaseList)
|
||||
from queryset import OperationError
|
||||
@@ -23,6 +23,9 @@ class EmbeddedDocument(BaseDocument):
|
||||
:class:`~mongoengine.EmbeddedDocumentField` field type.
|
||||
"""
|
||||
|
||||
# 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 = DocumentMetaclass
|
||||
__metaclass__ = DocumentMetaclass
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -91,9 +94,12 @@ class Document(BaseDocument):
|
||||
disabled by either setting types to False on the specific index or
|
||||
by setting index_types to False on the meta dictionary for the document.
|
||||
"""
|
||||
|
||||
# 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
|
||||
|
||||
@apply
|
||||
def pk():
|
||||
"""Primary key alias
|
||||
"""
|
||||
@@ -102,6 +108,7 @@ class Document(BaseDocument):
|
||||
def fset(self, value):
|
||||
return setattr(self, self._meta['id_field'], value)
|
||||
return property(fget, fset)
|
||||
pk = pk()
|
||||
|
||||
@classmethod
|
||||
def _get_db(cls):
|
||||
@@ -244,12 +251,11 @@ class Document(BaseDocument):
|
||||
|
||||
def cascade_save(self, *args, **kwargs):
|
||||
"""Recursively saves any references / generic references on an object"""
|
||||
from fields import ReferenceField, GenericReferenceField
|
||||
import fields
|
||||
_refs = kwargs.get('_refs', []) or []
|
||||
|
||||
for name, cls in self._fields.items():
|
||||
|
||||
if not isinstance(cls, (ReferenceField, GenericReferenceField)):
|
||||
if not isinstance(cls, (fields.ReferenceField, fields.GenericReferenceField)):
|
||||
continue
|
||||
|
||||
ref = getattr(self, name)
|
||||
@@ -304,8 +310,8 @@ class Document(BaseDocument):
|
||||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
from dereference import DeReference
|
||||
self._data = DeReference()(self._data, max_depth)
|
||||
import dereference
|
||||
self._data = dereference.DeReference()(self._data, max_depth)
|
||||
return self
|
||||
|
||||
def reload(self, max_depth=1):
|
||||
@@ -360,10 +366,9 @@ class Document(BaseDocument):
|
||||
"""Drops the entire collection associated with this
|
||||
:class:`~mongoengine.Document` type from the database.
|
||||
"""
|
||||
from mongoengine.queryset import QuerySet
|
||||
db = cls._get_db()
|
||||
db.drop_collection(cls._get_collection_name())
|
||||
QuerySet._reset_already_indexed(cls)
|
||||
queryset.QuerySet._reset_already_indexed(cls)
|
||||
|
||||
|
||||
class DynamicDocument(Document):
|
||||
@@ -379,7 +384,12 @@ class DynamicDocument(Document):
|
||||
|
||||
There is one caveat on Dynamic Documents: fields cannot start with `_`
|
||||
"""
|
||||
|
||||
# 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
|
||||
|
||||
_dynamic = True
|
||||
|
||||
def __delattr__(self, *args, **kwargs):
|
||||
@@ -398,7 +408,11 @@ class DynamicEmbeddedDocument(EmbeddedDocument):
|
||||
information about dynamic documents.
|
||||
"""
|
||||
|
||||
# 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 = DocumentMetaclass
|
||||
__metaclass__ = DocumentMetaclass
|
||||
|
||||
_dynamic = True
|
||||
|
||||
def __delattr__(self, *args, **kwargs):
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
import datetime
|
||||
import time
|
||||
import decimal
|
||||
import gridfs
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
import warnings
|
||||
|
||||
from operator import itemgetter
|
||||
|
||||
import gridfs
|
||||
from bson import Binary, DBRef, SON, ObjectId
|
||||
|
||||
from mongoengine.python3_support import (PY3, b, bin_type,
|
||||
txt_type, str_types, StringIO)
|
||||
from base import (BaseField, ComplexBaseField, ObjectIdField,
|
||||
ValidationError, get_document, BaseDocument)
|
||||
from queryset import DO_NOTHING, QuerySet
|
||||
from document import Document, EmbeddedDocument
|
||||
from connection import get_db, DEFAULT_CONNECTION_NAME
|
||||
from operator import itemgetter
|
||||
|
||||
|
||||
try:
|
||||
@@ -21,12 +26,6 @@ except ImportError:
|
||||
Image = None
|
||||
ImageOps = None
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
|
||||
__all__ = ['StringField', 'IntField', 'FloatField', 'BooleanField',
|
||||
'DateTimeField', 'EmbeddedDocumentField', 'ListField', 'DictField',
|
||||
'ObjectIdField', 'ReferenceField', 'ValidationError', 'MapField',
|
||||
@@ -846,8 +845,9 @@ class BinaryField(BaseField):
|
||||
return Binary(value)
|
||||
|
||||
def validate(self, value):
|
||||
if not isinstance(value, (basestring, Binary)):
|
||||
self.error('BinaryField only accepts string or bson Binary values')
|
||||
if not isinstance(value, (bin_type, txt_type, Binary)):
|
||||
self.error("BinaryField only accepts instances of "
|
||||
"(%s, %s, Binary)" % (bin_type.__name__,txt_type.__name__))
|
||||
|
||||
if self.max_bytes is not None and len(value) > self.max_bytes:
|
||||
self.error('Binary value is too long')
|
||||
@@ -903,12 +903,14 @@ class GridFSProxy(object):
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.grid_id)
|
||||
|
||||
def __cmp__(self, other):
|
||||
if not isinstance(other, GridFSProxy):
|
||||
return -1
|
||||
return cmp((self.grid_id, self.collection_name, self.db_alias),
|
||||
(other.grid_id, other.collection_name, other.db_alias))
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, GridFSProxy):
|
||||
return ((self.grid_id == other.grid_id) and
|
||||
(self.collection_name == other.collection_name) and
|
||||
(self.db_alias == other.db_alias))
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def fs(self):
|
||||
if not self._fs:
|
||||
@@ -1020,7 +1022,8 @@ class FileField(BaseField):
|
||||
|
||||
def __set__(self, instance, value):
|
||||
key = self.name
|
||||
if (hasattr(value, 'read') and not isinstance(value, GridFSProxy)) or isinstance(value, basestring):
|
||||
if ((hasattr(value, 'read') and not
|
||||
isinstance(value, GridFSProxy)) or isinstance(value, str_types)):
|
||||
# using "FileField() = file/string" notation
|
||||
grid_file = instance._data.get(self.name)
|
||||
# If a file already exists, delete it
|
||||
@@ -1211,7 +1214,7 @@ class ImageField(FileField):
|
||||
for att_name, att in extra_args.items():
|
||||
if att and (isinstance(att, tuple) or isinstance(att, list)):
|
||||
setattr(self, att_name, dict(
|
||||
map(None, params_size, att)))
|
||||
zip(params_size, att)))
|
||||
else:
|
||||
setattr(self, att_name, None)
|
||||
|
||||
|
||||
29
mongoengine/python3_support.py
Normal file
29
mongoengine/python3_support.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""Helper functions and types to aid with Python 3 support."""
|
||||
|
||||
import sys
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3:
|
||||
import codecs
|
||||
from io import BytesIO as StringIO
|
||||
# return s converted to binary. b('test') should be equivalent to b'test'
|
||||
def b(s):
|
||||
return codecs.latin_1_encode(s)[0]
|
||||
|
||||
bin_type = bytes
|
||||
txt_type = str
|
||||
else:
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
# Conversion to binary only necessary in Python 3
|
||||
def b(s):
|
||||
return s
|
||||
|
||||
bin_type = str
|
||||
txt_type = unicode
|
||||
|
||||
str_types = (bin_type, txt_type)
|
||||
@@ -3,7 +3,7 @@ import re
|
||||
import copy
|
||||
import itertools
|
||||
import operator
|
||||
|
||||
import functools
|
||||
from functools import partial
|
||||
|
||||
import pymongo
|
||||
@@ -121,12 +121,12 @@ class QueryTreeTransformerVisitor(QNodeVisitor):
|
||||
# that ANDs the necessary part with the $or part.
|
||||
clauses = []
|
||||
for or_group in itertools.product(*or_groups):
|
||||
q_object = reduce(lambda a, b: a & b, and_parts, Q())
|
||||
q_object = reduce(lambda a, b: a & b, or_group, q_object)
|
||||
q_object = functools.reduce(lambda a, b: a & b, and_parts, Q())
|
||||
q_object = functools.reduce(lambda a, b: a & b, or_group, q_object)
|
||||
clauses.append(q_object)
|
||||
# Finally, $or the generated clauses in to one query. Each of the
|
||||
# clauses is sufficient for the query to succeed.
|
||||
return reduce(lambda a, b: a | b, clauses, Q())
|
||||
return functools.reduce(lambda a, b: a | b, clauses, Q())
|
||||
|
||||
if combination.operation == combination.OR:
|
||||
children = []
|
||||
|
||||
Reference in New Issue
Block a user