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