diff --git a/mongorm/BaseDocument.py b/mongorm/BaseDocument.py index 1b2e772..bcc20a1 100644 --- a/mongorm/BaseDocument.py +++ b/mongorm/BaseDocument.py @@ -1,36 +1,41 @@ +from builtins import object from mongorm import connection from mongorm.DocumentMetaclass import DocumentMetaclass from mongorm.blackMagic import serialiseTypesForDocumentType +from future.utils import with_metaclass -class BaseDocument(object): - __metaclass__ = DocumentMetaclass +class BaseDocument(with_metaclass(DocumentMetaclass, object)): __internal__ = True - + class DoesNotExist(Exception): pass - + def __init__( self, **kwargs ): self._is_lazy = False self._data = {} self._values = {} - + self._data['_types'] = serialiseTypesForDocumentType( self.__class__ ) - - for name,value in kwargs.iteritems( ): + + for name,value in kwargs.items( ): setattr(self, name, value) - + def _fromMongo( self, data, overwrite=True ): self._is_lazy = True - - for (name,field) in self._fields.iteritems( ): + + for (name,field) in self._fields.items( ): dbField = field.dbField if dbField in data and ( overwrite or not name in self._values ): pythonValue = field.toPython( data[dbField] ) setattr(self, name, pythonValue) - + return self + # For python 2/3 compatibility + def __bool__( self ): + return True + # The following three methods are used for pickling/unpickling. # If it weren't for the fact that __getattr__ returns None # for non-existing attributes (rather than raising an AttributeError), @@ -49,7 +54,7 @@ def __setattr__( self, name, value ): assert name[0] == '_' or (name in self._fields), \ "Field '%s' does not exist in document '%s'" \ % (name, self.__class__.__name__) - + if name in self._fields: field = self._fields[name] mongoValue = field.fromPython( value ) @@ -61,7 +66,7 @@ def __setattr__( self, name, value ): else: assert name.startswith( '_' ), 'Only internal variables should ever be set as an attribute' super(BaseDocument, self).__setattr__( name, value ) - + def __getattr__( self, name ): if name not in self._values and self._is_lazy and \ '_id' in self._data and self._data['_id'] is not None: @@ -78,9 +83,9 @@ def __getattr__( self, name ): if result is None: raise self.DoesNotExist self._fromMongo( result, overwrite=False ) - + self._is_lazy = False - + field = self._fields.get( name, None ) if not name in self._values: @@ -89,18 +94,18 @@ def __getattr__( self, name ): default = field.getDefault( ) self._values[name] = default - + value = self._values.get( name ) - + return value - + def _resyncFromPython( self ): # before we go any further, re-sync from python values where needed - for (name,field) in self._fields.iteritems( ): + for (name,field) in self._fields.items( ): requiresDefaultCall = (name not in self._values and callable(field.default)) if field._resyncAtSave or requiresDefaultCall: dbField = field.dbField pythonValue = getattr(self, name) self._data[dbField] = field.fromPython( pythonValue ) #print 'resyncing', dbField, 'to', self._data[dbField] - + diff --git a/mongorm/Document.py b/mongorm/Document.py index 2bad2df..14318e8 100644 --- a/mongorm/Document.py +++ b/mongorm/Document.py @@ -1,3 +1,4 @@ +from builtins import str import pymongo import warnings @@ -43,11 +44,11 @@ def save( self, forceInsert=False, **kwargs ): newId = collection.insert( self._data, **kwargs ) else: newId = collection.save( self._data, **kwargs ) - except pymongo.errors.OperationFailure, err: + except pymongo.errors.OperationFailure as err: message = 'Could not save document (%s)' - if u'duplicate key' in unicode(err): + if u'duplicate key' in str(err): message = u'Tried to save duplicate unique keys (%s)' - raise OperationError( message % unicode(err) ) + raise OperationError( message % str(err) ) if newId is not None: setattr(self, self._primaryKeyField, newId) diff --git a/mongorm/DocumentMetaclass.py b/mongorm/DocumentMetaclass.py index d77c5ec..cf49f65 100644 --- a/mongorm/DocumentMetaclass.py +++ b/mongorm/DocumentMetaclass.py @@ -50,7 +50,7 @@ def __new__( cls, name, bases, attrs ): attrs['_collection'] = collection # find all fields and add them to our field list - for attrName, attrValue in attrs.items( ): + for attrName, attrValue in list(attrs.items( )): if hasattr(attrValue, '__class__') and \ issubclass(attrValue.__class__, BaseField): field = attrValue @@ -65,7 +65,7 @@ def indexConverter( fieldName ): return fields[fieldName].optimalIndex( ) return fieldName - for field,value in fields.iteritems( ): + for field,value in fields.items( ): if value.primaryKey: assert primaryKey is None, "Can only have one primary key per document" primaryKey = field @@ -109,7 +109,7 @@ def indexConverter( fieldName ): newClass = superNew( cls, name, bases, attrs ) # record the document in the fields - for field in newClass._fields.values( ): + for field in list(newClass._fields.values( )): #field.ownerDocument = newClass field.setOwnerDocument( newClass ) diff --git a/mongorm/DocumentRegistry.py b/mongorm/DocumentRegistry.py index 7931147..8e86cf9 100644 --- a/mongorm/DocumentRegistry.py +++ b/mongorm/DocumentRegistry.py @@ -1,3 +1,4 @@ +from builtins import object class DocumentRegistry(object): documentTypes = {} diff --git a/mongorm/blackMagic.py b/mongorm/blackMagic.py index b7eed88..24d6dd2 100644 --- a/mongorm/blackMagic.py +++ b/mongorm/blackMagic.py @@ -1,4 +1,5 @@ +from future.types.newobject import newobject def serialiseTypesForDocumentType( documentType ): - return [ cls.__name__ for cls in documentType.mro() if cls != object \ + return [ cls.__name__ for cls in documentType.mro() if cls not in [object, newobject] \ and cls.__name__ not in ['Document', 'BaseDocument', 'EmbeddedDocument'] ] diff --git a/mongorm/connection.py b/mongorm/connection.py index 368a986..969e20f 100644 --- a/mongorm/connection.py +++ b/mongorm/connection.py @@ -1,3 +1,4 @@ +from past.builtins import basestring from pymongo import MongoClient, MongoReplicaSetClient from pymongo.collection import Collection connection = None diff --git a/mongorm/fields/BaseField.py b/mongorm/fields/BaseField.py index 24c4c19..780fb7d 100644 --- a/mongorm/fields/BaseField.py +++ b/mongorm/fields/BaseField.py @@ -1,3 +1,4 @@ +from builtins import object class BaseField(object): _resyncAtSave = False diff --git a/mongorm/fields/DateField.py b/mongorm/fields/DateField.py index 8ad6ef7..f96b3eb 100644 --- a/mongorm/fields/DateField.py +++ b/mongorm/fields/DateField.py @@ -1,3 +1,4 @@ +from past.builtins import basestring from mongorm.fields.BaseField import BaseField import time @@ -14,11 +15,11 @@ def fromPython( self, pythonValue, dereferences=[], modifier=None ): try: pythonValue = date.fromtimestamp( time.mktime( time.strptime( pythonValue, '%Y-%m-%d' ) ) ) except: - raise ValueError, "String format of date must be YYYY-MM-DD" + raise ValueError("String format of date must be YYYY-MM-DD") # make sure we ended up with a date() object if not isinstance(pythonValue, date): - raise ValueError, "Value must be a date object" + raise ValueError("Value must be a date object") # convert it to a string since mongo doesn't have a date-only type and datetime # searches would be wrong. this format should still allow sorting, etc. diff --git a/mongorm/fields/DateTimeField.py b/mongorm/fields/DateTimeField.py index b014d07..207b2c8 100644 --- a/mongorm/fields/DateTimeField.py +++ b/mongorm/fields/DateTimeField.py @@ -1,3 +1,4 @@ +from past.builtins import basestring from mongorm.fields.BaseField import BaseField from datetime import datetime diff --git a/mongorm/fields/DecimalField.py b/mongorm/fields/DecimalField.py index 8f81cac..d8f7400 100644 --- a/mongorm/fields/DecimalField.py +++ b/mongorm/fields/DecimalField.py @@ -1,3 +1,5 @@ +from builtins import str +from past.builtins import basestring from mongorm.fields.BaseField import BaseField from decimal import Decimal @@ -7,8 +9,8 @@ def fromPython( self, pythonValue, dereferences=[], modifier=None ): if isinstance(pythonValue, (basestring, int, float)): pythonValue = Decimal(pythonValue) if not isinstance(pythonValue, Decimal): - raise ValueError, "Value (%s: %s) must be a Decimal object, or must be able to be passed to the Decimal constructor" % (type(pythonValue), pythonValue,) + raise ValueError("Value (%s: %s) must be a Decimal object, or must be able to be passed to the Decimal constructor" % (type(pythonValue), pythonValue)) return str(pythonValue) - + def toPython( self, bsonValue ): return Decimal(bsonValue) diff --git a/mongorm/fields/ObjectIdField.py b/mongorm/fields/ObjectIdField.py index e8b65f6..e726f30 100644 --- a/mongorm/fields/ObjectIdField.py +++ b/mongorm/fields/ObjectIdField.py @@ -8,9 +8,9 @@ class ObjectIdField(BaseField): def fromPython( self, pythonValue, dereferences=[], modifier=None ): if pythonValue is not None: - return objectid.ObjectId( unicode(pythonValue) ) + return objectid.ObjectId( str(pythonValue) ) else: return None - + def toPython( self, bsonValue ): return bsonValue \ No newline at end of file diff --git a/mongorm/fields/ReferenceField.py b/mongorm/fields/ReferenceField.py index 7bc27ae..4837d4b 100644 --- a/mongorm/fields/ReferenceField.py +++ b/mongorm/fields/ReferenceField.py @@ -1,3 +1,4 @@ +from past.builtins import basestring from bson import objectid, dbref import bson.errors @@ -11,12 +12,12 @@ def __init__( self, documentClass, *args, **kwargs ): super(ReferenceField, self).__init__( *args, **kwargs ) self._use_ref_id = kwargs.get('use_ref_id', False) self.inputDocumentClass = documentClass - + def _getClassInfo( self ): if hasattr(self, 'documentName'): return - + documentClass = self.inputDocumentClass - + if isinstance(documentClass, basestring): if documentClass == 'self': self.documentName = self.ownerDocument.__name__ @@ -27,13 +28,13 @@ def _getClassInfo( self ): else: self.documentClass = documentClass self.documentName = documentClass.__name__ - + def fromPython( self, pythonValue, dereferences=[], modifier=None ): self._getClassInfo( ) - + if pythonValue is None: return None - + if isinstance(pythonValue, dbref.DBRef): return { '_ref': pythonValue @@ -48,18 +49,18 @@ def fromPython( self, pythonValue, dereferences=[], modifier=None ): return { '_ref': dbref.DBRef( self.documentClass._collection, objectId ), } - + assert isinstance(pythonValue, self.documentClass), \ "Referenced value must be a document of type %s" % (self.documentName,) assert pythonValue.id is not None, "Referenced Document must be saved before being assigned" - + data = { '_types': serialiseTypesForDocumentType(pythonValue.__class__), '_ref': dbref.DBRef( pythonValue.__class__._collection, pythonValue.id ), } - + return data - + def toQuery( self, pythonValue, dereferences=[] ): if pythonValue is None: return None @@ -72,13 +73,13 @@ def toQuery( self, pythonValue, dereferences=[] ): return { '_ref': self.fromPython( pythonValue )['_ref'] } - + def toPython( self, bsonValue ): self._getClassInfo( ) - + if bsonValue is None: return None - + documentClass = None if isinstance(bsonValue, dbref.DBRef): @@ -95,19 +96,19 @@ def toPython( self, bsonValue ): if '_cls' in bsonValue: # mongoengine GenericReferenceField compatibility documentName = bsonValue['_cls'] - elif '_types' in bsonValue: + elif '_types' in bsonValue: documentName = bsonValue['_types'][0] else: return dbRef documentClass = DocumentRegistry.getDocument( documentName ) - + initialData = { '_id': dbRef.id, } initialData.update( bsonValue.get( '_cache', {} ) ) - + return documentClass( )._fromMongo( initialData ) - + def optimalIndex( self ): return self.dbField + '._ref' diff --git a/mongorm/fields/SafeDictField.py b/mongorm/fields/SafeDictField.py index a9e3185..be38f75 100644 --- a/mongorm/fields/SafeDictField.py +++ b/mongorm/fields/SafeDictField.py @@ -1,3 +1,7 @@ +import binascii + +from builtins import str, bytes +from past.builtins import basestring from mongorm.fields.DictField import DictField from collections import deque @@ -9,7 +13,7 @@ def deepCoded( dictionary, coder ): toCode = deque( [dictionary] ) while toCode: nextDictionary = toCode.popleft( ) - for key, value in nextDictionary.items( ): # can't be iteritems as we're changing the dict + for key, value in list(nextDictionary.items( )): # can't be iteritems as we're changing the dict if isinstance(key, basestring): # Keys have to be strings in mongo so this should always occur del nextDictionary[key] @@ -19,12 +23,12 @@ def deepCoded( dictionary, coder ): return dictionary def encode( string ): - if isinstance(string, unicode): + if isinstance(string, str): string = string.encode( 'utf-8' ) - return string.encode( 'hex' ) + return bytes(binascii.hexlify(string)).decode('utf-8') def decode( string ): - return string.decode( 'hex' ).decode( 'utf-8' ) + return bytes(binascii.unhexlify(string)).decode('utf-8') class SafeDictField(DictField): def fromPython( self, *args, **kwargs ): diff --git a/mongorm/fields/StringField.py b/mongorm/fields/StringField.py index 0c328f8..14cd2da 100644 --- a/mongorm/fields/StringField.py +++ b/mongorm/fields/StringField.py @@ -1,12 +1,15 @@ +from builtins import str + from mongorm.fields.BaseField import BaseField + class StringField(BaseField): def fromPython( self, pythonValue, dereferences=[], modifier=None ): if pythonValue is not None: - pythonValue = unicode(pythonValue) + pythonValue = str(pythonValue) return pythonValue - + def toPython( self, bsonValue ): if bsonValue is not None: - bsonValue = unicode(bsonValue) + bsonValue = str(bsonValue) return bsonValue diff --git a/mongorm/queryset/Q.py b/mongorm/queryset/Q.py index 62cea2e..b8e4554 100644 --- a/mongorm/queryset/Q.py +++ b/mongorm/queryset/Q.py @@ -1,18 +1,19 @@ - +from past.builtins import basestring +from builtins import object class Q(object): def __init__( self, _query=None, **search ): if _query is None: if 'pk' in search: search['id'] = search['pk'] del search['pk'] - + self.query = search else: self.query = _query - + def toMongo( self, document, forUpdate=False, modifier=None ): newSearch = {} - for (name, value) in self.query.iteritems( ): + for (name, value) in self.query.items( ): if name in ['$or', '$and']: # mongodb logic operator - value is a list of Qs newSearch[name] = [ value.toMongo( document ) for value in value ] @@ -21,9 +22,9 @@ def toMongo( self, document, forUpdate=False, modifier=None ): if name.startswith('$') and isinstance(value, basestring): newSearch[name] = value continue - + fieldName = name - + MONGO_COMPARISONS = ['gt', 'lt', 'lte', 'gte', 'exists', 'ne', 'all', 'in', 'nin', 'elemMatch'] REGEX_COMPARISONS = { 'contains': ( '%s', '' ), @@ -33,14 +34,14 @@ def toMongo( self, document, forUpdate=False, modifier=None ): 'startswith': ( '^%s', '' ), 'istartswith': ( '^%s', 'i' ), - + 'endswith': ( '%s$', '' ), 'iendswith': ( '%s$', 'i' ), - + 'matches': ( None, '' ), 'imatches': ( None, 'i' ), } - ALL_COMPARISONS = MONGO_COMPARISONS + REGEX_COMPARISONS.keys() + ALL_COMPARISONS = MONGO_COMPARISONS + list(REGEX_COMPARISONS.keys()) ARRAY_VALUE_COMPARISONS = ['all', 'in', 'nin'] comparison = None @@ -57,10 +58,10 @@ def toMongo( self, document, forUpdate=False, modifier=None ): # not a comparison operator dereferences = chunks[1:] comparison = None - + if fieldName not in document._fields: - raise AttributeError, "%s does not contain the field '%s'" % (document.__name__, fieldName) - + raise AttributeError("%s does not contain the field '%s'" % (document.__name__, fieldName)) + field = document._fields[fieldName] if not forUpdate: if comparison in ARRAY_VALUE_COMPARISONS: @@ -90,10 +91,10 @@ def toMongo( self, document, forUpdate=False, modifier=None ): else: searchValue = field.fromPython( value, dereferences=dereferences, modifier=modifier ) targetSearchKey = '.'.join( [field.dbField] + dereferences) - + valueMapper = lambda value: value - - + + if comparison is not None: if comparison in REGEX_COMPARISONS: regex,options = REGEX_COMPARISONS[comparison] @@ -115,7 +116,7 @@ def toMongo( self, document, forUpdate=False, modifier=None ): if isinstance(searchValue, dict): if not forUpdate: - for name,value in searchValue.iteritems( ): + for name,value in searchValue.items( ): if name: key = targetSearchKey + '.' + name else: @@ -125,14 +126,14 @@ def toMongo( self, document, forUpdate=False, modifier=None ): newSearch[targetSearchKey] = valueMapper(searchValue) else: newSearch[targetSearchKey] = valueMapper(searchValue) - + return newSearch - + def __or__( self, other ): return self.do_merge( other, '$or' ) - + def __and__( self, other ): - if len( set( self.query.keys() ).intersection( other.query.keys() ) ) > 0: + if len( set( self.query.keys() ).intersection( set( other.query.keys() ) ) ) > 0: # if the 2 queries have overlapping keys, we need to use a $and to join them. return self.do_merge( other, '$and' ) else: @@ -141,17 +142,17 @@ def __and__( self, other ): newQuery.update( self.query ) newQuery.update( other.query ) return Q( _query=newQuery ) - + def do_merge( self, other, op ): if len(self.query) == 0: return other if len(other.query) == 0: return self - + if op in self.query and len(self.query) == 1: items = self.query[op] + [other] elif op in other.query and len(self.query) == 1: items = other.query[op] + [self] else: items = [ self, other ] - + newQuery = { op: items } return Q( _query=newQuery ) diff --git a/mongorm/queryset/QuerySet.py b/mongorm/queryset/QuerySet.py index 03450c6..bb24639 100644 --- a/mongorm/queryset/QuerySet.py +++ b/mongorm/queryset/QuerySet.py @@ -1,3 +1,4 @@ +from builtins import object from mongorm.queryset.Q import Q from mongorm.util import sortListToPyMongo import pymongo @@ -29,7 +30,7 @@ def __init__( self, document, collection, query=None, orderBy=None, fields=None, if types: for subclass in types: if not issubclass(subclass, self.document): - raise TypeError, "'%s' is not a subclass of '%s'" % (subclass, self.document) + raise TypeError("'%s' is not a subclass of '%s'" % (subclass, self.document)) self.types.append( subclass ) def _getNewInstance( self, data ): @@ -113,10 +114,10 @@ def delete( self ): def _prepareActions( self, **actions ): updates = {} - for action, value in actions.iteritems( ): + for action, value in actions.items( ): assert '__' in action, 'Action "%s" not legal for update' % (action,) modifier, fieldName = action.split( '__', 1 ) - assert modifier in ['set', 'unset', 'setOnInsert', 'inc', 'dec', 'push', 'pushAll', 'pull', 'pullAll'], 'Unknown modifier "%s"' % modifier + assert modifier in ['set', 'unset', 'setOnInsert', 'inc', 'dec', 'push', 'pull', 'pullAll'], 'Unknown modifier "%s"' % modifier if '$'+modifier not in updates: updates['$'+modifier] = {} @@ -214,7 +215,7 @@ def ignore( self, *fields ): def fields( self, **projections ): kwargs = self._get_kwargs( ) kwargs['fields'] = dict(self._fields or {}) - for field, value in projections.iteritems( ): + for field, value in projections.items( ): if '__' in field: fieldName, sep, projection = field.rpartition( '__' ) if projection in PROJECTIONS: @@ -245,7 +246,7 @@ def _do_find( self, **kwargs ): search = self._get_query( ) - if '_types' in search and 'projection' in kwargs and not kwargs['projection'].get( '_types' ) and all(kwargs['projection'].itervalues( )): + if '_types' in search and 'projection' in kwargs and not kwargs['projection'].get( '_types' ) and all(kwargs['projection'].values( )): kwargs['projection']['_types'] = True # read_preference not supported in pymongo 3.0+, use with_options() instead @@ -269,7 +270,7 @@ def _get_query( self, forUpsert=False ): search['_types'] = {'$in': [subtype.__name__ for subtype in self.types]} elif len(types) > 1: # only filter when looking at a subclass if forUpsert: - search['_types'] = {'$all':[self.document.__name__]} # filter by the type that was used + search['_types'] = { '$elemMatch': { '$eq': self.document.__name__ } } # filter by the type that was used else: search['_types'] = self.document.__name__ # filter by the type that was used return search @@ -341,7 +342,7 @@ def ensure_indexed( self ): return self def _queryToIndex( self, query ): - for key, value in query.iteritems( ): + for key, value in query.items( ): if key in ('$and', '$or'): for subq in value: for index in self._queryToIndex( subq ): diff --git a/mongorm/queryset/QuerySetManager.py b/mongorm/queryset/QuerySetManager.py index 6bf086f..2ea39d1 100644 --- a/mongorm/queryset/QuerySetManager.py +++ b/mongorm/queryset/QuerySetManager.py @@ -1,3 +1,4 @@ +from builtins import object from mongorm.connection import getDatabase from mongorm.queryset.QuerySet import QuerySet @@ -15,4 +16,7 @@ def __get__( self, instance, owner ): self.collection = database[owner._collection] from mongorm.connection import pymongoWrapper - return QuerySet( owner, pymongoWrapper(self.collection) ) + if pymongoWrapper: + return QuerySet( owner, pymongoWrapper(self.collection) ) + else: + return QuerySet(owner, self.collection) diff --git a/setup.py b/setup.py index f822246..fda1734 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages setup(name='mongorm', - version='0.5.9.0', + version='0.5.10.0', packages=find_packages(), author='Theo Julienne, John Garland', author_email='theo@icy.com.au, john@openlearning.com', @@ -11,5 +11,6 @@ description='Mongorm', long_description='Mongorm', platforms=['any'], - install_requires=['pymongo >= 2.4.2', 'pysignals'], + install_requires=['pymongo >= 2.4.2', 'pysignals', 'iso8601', 'future'], + test_requires=['pytest'], ) diff --git a/tests/test_equality.py b/tests/test_equality.py index 2bb1498..f2836d6 100644 --- a/tests/test_equality.py +++ b/tests/test_equality.py @@ -1,5 +1,6 @@ # -*- coding: utf8 -*- +from builtins import str from mongorm import * from bson.dbref import DBRef @@ -76,9 +77,7 @@ class TestDocument(Document): a = TestDocument( s=u"déjà vu" ) a.save( ) - assert a.s == u"déjà vu" - assert a.s != "déjà vu" - assert a.s != "deja vu" + assert a.s == str(u"déjà vu") def test_equality_with_datetime( ): """Tests to make sure comparisons with datetime objects work.""" diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py index 64eed80..2db5bfe 100644 --- a/tests/test_inheritance.py +++ b/tests/test_inheritance.py @@ -1,3 +1,4 @@ +from builtins import range from mongorm import * try: @@ -21,32 +22,32 @@ class ExtendedThing(BaseThing): class AnotherThing(BaseThing): another = StringField( ) - + BaseThing.objects.delete( ) - + b = BaseThing( name='basey' ).save( ) e = ExtendedThing( name='extendy', extended='extension foo' ).save( ) a = AnotherThing( name='another', another='another bar' ).save( ) - + bOut = BaseThing.objects.get( name='basey' ) assert isinstance( bOut, BaseThing ) assert not isinstance( bOut, (ExtendedThing, AnotherThing) ) assert bOut.name == 'basey' - + eOut = BaseThing.objects.get( name='extendy' ) assert isinstance( eOut, BaseThing ) assert isinstance( eOut, ExtendedThing ) assert not isinstance( eOut, AnotherThing ) assert eOut.name == 'extendy' assert eOut.extended == 'extension foo' - + aOut = BaseThing.objects.get( name='another' ) assert isinstance( aOut, BaseThing ) assert isinstance( aOut, AnotherThing ) assert not isinstance( aOut, ExtendedThing ) assert aOut.name == 'another' assert aOut.another == 'another bar' - + aOutSet = AnotherThing.objects.filter( another='another bar' ) assert aOutSet.count( ) == 1 aOut2 = aOutSet[0] @@ -55,7 +56,7 @@ class AnotherThing(BaseThing): assert not isinstance( aOut2, ExtendedThing ) assert aOut2.name == 'another' assert aOut2.another == 'another bar' - + # test backwards compatability assert BaseThing.objects._getNewInstance( { "_id" : ObjectId("4f72c55402ac3601db000000"), @@ -65,30 +66,30 @@ class AnotherThing(BaseThing): def test_inheritance_upsert( ): DocumentRegistry.clear( ) connect( 'test_mongorm' ) - + class BaseThingUpsert(Document): name = StringField( ) value = IntegerField( ) - + class ExtendedThingUpsert(BaseThingUpsert): extended = StringField( ) - + BaseThingUpsert.objects.delete( ) - + assert BaseThingUpsert.objects.count( ) == 0 - + for i in range( 2 ): BaseThingUpsert.objects.filter( name='upsert1' ).update( upsert=True, set__name='upsert1', set__value=42, ) - + item = BaseThingUpsert.objects.get( name='upsert1' ) - + assert BaseThingUpsert.objects.count( ) == 1 assert item.value == 42 - + for i in range( 2 ): ExtendedThingUpsert.objects.filter( name='upsert2' ).update( upsert=True, @@ -96,9 +97,9 @@ class ExtendedThingUpsert(BaseThingUpsert): set__extended='ext', set__value=42, ) - + item = ExtendedThingUpsert.objects.get( name='upsert2' ) - + assert len( list( ExtendedThingUpsert.objects.all( ) ) ) == 1 assert len( list( BaseThingUpsert.objects.all( ) ) ) == 2 assert ExtendedThingUpsert.objects.count( ) == 1 @@ -116,9 +117,9 @@ class ExtendedThingBackwards(BaseThingBackwards): extended = StringField( ) BaseThingBackwards.objects.delete( ) - + BaseThingBackwards.objects.collection.insert( { 'name': 'test', 'value': 43 } ) - + assert BaseThingBackwards.objects.count( ) == 1 assert len( list( BaseThingBackwards.objects.all( ) ) ) == 1 assert ExtendedThingBackwards.objects.count( ) == 0 diff --git a/tests/test_pickle.py b/tests/test_pickle.py index c8c0310..d9eaff9 100644 --- a/tests/test_pickle.py +++ b/tests/test_pickle.py @@ -1,3 +1,5 @@ +from future import standard_library +standard_library.install_aliases() from mongorm import Document, DocumentRegistry, StringField, connect try: diff --git a/tests/test_queries.py b/tests/test_queries.py index e329c6d..c5d0350 100644 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -1,5 +1,7 @@ # -*- coding: utf8 -*- +from builtins import next +from builtins import range from mongorm import * from pymongo import ReadPreference from pytest import raises @@ -12,7 +14,7 @@ def test_basic_equality( ): class Test(Document): data = DictField( ) name = StringField( ) - + # equality assert Q( name='c' ).toMongo( Test ) \ == {'name': 'c'} @@ -24,7 +26,7 @@ def test_basic_comparisons( ): class Test(Document): data = DictField( ) name = StringField( ) - + # simple comparisons assert Q( name__lte='c' ).toMongo( Test ) \ == {'name': {'$lte': 'c'}} @@ -36,13 +38,13 @@ def test_regex_comparisons( ): class Test(Document): data = DictField( ) name = StringField( ) - + # regex comparisons assert Q( data__attributes__course__name__icontains='c' ).toMongo( Test ) \ == {'data.attributes.course.name': {'$options': 'i', '$regex': u'c'}} assert Q( name__icontains='c' ).toMongo( Test ) \ == {'name': {'$options': 'i', '$regex': u'c'}} - + def test_embedded_basic_comparisons( ): """Tests nested field regex comparisons over an EmbeddedDocument boundary""" class Data(EmbeddedDocument): @@ -53,7 +55,7 @@ class TestPage(Document): # regex comparisons assert Q( data__attributes__course__name__lte='c' ).toMongo( TestPage ) \ == {'data.attributes.course.name': {'$lte': 'c'}} - + def test_embedded_regex_comparisons( ): """Tests nested field regex comparisons over an EmbeddedDocument boundary""" class Data(EmbeddedDocument): @@ -68,14 +70,14 @@ class TestPage(Document): def test_multiple_or( ): class Test(Document): data = DictField( ) - + query = '123' queryFilter = ( Q(data__a__icontains=query) | Q(data__b__icontains=query) | Q(data__c__icontains=query) ) - + assert queryFilter.toMongo( Test ) == { '$or': [ {'data.a': {'$options': 'i', '$regex': '123'}}, @@ -88,7 +90,7 @@ def test_regex_escape( ): """Tests to make sure regex matches work with values containing regex special characters""" class Test(Document): name = StringField( ) - + # equality assert Q( name__icontains='test.test' ).toMongo( Test ) \ == {'name': {'$options': 'i', '$regex': u'test\\.test'}} @@ -102,28 +104,28 @@ class Test(Document): def test_and_or( ): """Tests to make sure 'or's can be embedded in 'and's""" connect( 'test_mongorm' ) - + class TestAndOr(Document): name = StringField( ) path = StringField( ) index = ListField( StringField( ) ) - + # using consecutive .filter calls - assert TestAndOr.objects.filter( + assert TestAndOr.objects.filter( Q( name__icontains='t' ) | Q( name__icontains='e' ) ).filter( name='123' ).query.toMongo( TestAndOr ) \ == {'$or': [{'name': {'$options': 'i', '$regex': 't'}}, {'name': {'$options': 'i', '$regex': 'e'}}], 'name': u'123'} - + # using Q objects - assert TestAndOr.objects.filter( + assert TestAndOr.objects.filter( ( Q( name__icontains='t' ) | Q( name__icontains='e' ) ) & Q( name='123' ) ).query.toMongo( TestAndOr ) \ == {'$or': [{'name': {'$options': 'i', '$regex': 't'}}, {'name': {'$options': 'i', '$regex': 'e'}}], 'name': u'123'} - + # test ANDs assert TestAndOr.objects.filter( Q(index='123') & @@ -133,7 +135,7 @@ class TestAndOr(Document): {'index': '123'}, {'index': '456'}, ]} - + # multiple ORs with embedded ANDs assert TestAndOr.objects.filter( Q(name__icontains='abc') | @@ -226,16 +228,16 @@ class TestRef(Document): class TestHolder(Document): ref = ReferenceField( TestRef ) - + TestHolder.objects.delete( ) TestHolder( ref=None ).save( ) ref = TestRef( name='123' ) ref.save( ) TestHolder( ref=ref ).save( ) - + assert TestHolder.objects.filter( ref=None ).query.toMongo( TestHolder ) \ == {'ref': None} - + assert TestHolder.objects.filter( ref=None ).count( ) == 1 assert TestHolder.objects.filter( ref=ref ).count( ) == 1 assert TestHolder.objects.get( ref=ref ).ref.name == ref.name @@ -246,18 +248,12 @@ def test_push( ): class TestPush(Document): names = ListField( StringField( ) ) - + assert TestPush.objects._prepareActions( push__names='123' ) == { '$push': {'names': '123'} } - - assert TestPush.objects._prepareActions( - pushAll__names=['123', '456'] - ) == { - '$pushAll': {'names': ['123', '456']} - } def test_in_operator( ): """Tests in operator works with lists""" @@ -293,8 +289,9 @@ class Test(Document): assert Q( name__in={} ).toMongo( Test ) \ == {'name': {'$in': []}} - assert Q( name__in=set(['eggs', 'spam']) ).toMongo( Test ) \ - == {'name': {'$in': ['eggs', 'spam']}} + # Python doesn't guarantee order. Should look like + # {'name': {'$in': ['eggs', 'span']}} + assert Q(name__in={'eggs', 'spam'}).toMongo(Test)['name']['$in'].sort() == ['eggs', 'spam'].sort() # Clear objects so that counts will be correct Test.objects.all( ).delete( ) @@ -376,7 +373,7 @@ class Test(Document): it1 = iter(query) it2 = iter(query) - for i in xrange(Test.objects.count( )): + for i in range(Test.objects.count( )): assert next(it1) == next(it2) def test_secondary_read_pref( ): @@ -421,8 +418,9 @@ class Test(Document): assert Q( name__nin=[] ).toMongo( Test ) \ == {'name': {'$nin': []}} - assert Q( name__nin=['eggs', 'spam'] ).toMongo( Test ) \ - == {'name': {'$nin': ['eggs', 'spam']}} + # Python doesn't guarantee order. Should look like + # {'name': {'$nin': ['eggs', 'spam']}} + assert Q( name__nin=['eggs', 'spam'] ).toMongo( Test )['name']['$nin'].sort() == ['eggs', 'spam'].sort() # Clear objects so that counts will be correct Test.objects.all( ).delete( ) @@ -445,8 +443,9 @@ class Test(Document): assert Q( name__nin={} ).toMongo( Test ) \ == {'name': {'$nin': []}} - assert Q( name__nin=set(['eggs', 'spam']) ).toMongo( Test ) \ - == {'name': {'$nin': ['eggs', 'spam']}} + # Python doesn't guarantee order. Should look like + # {'name': {'$nin': ['eggs', 'spam']}} + assert Q(name__nin={'eggs', 'spam'}).toMongo(Test)['name']['$nin'].sort() == ['eggs', 'spam'].sort() # Clear objects so that counts will be correct Test.objects.all( ).delete( ) diff --git a/tests/test_truthiness.py b/tests/test_truthiness.py new file mode 100644 index 0000000..bde0862 --- /dev/null +++ b/tests/test_truthiness.py @@ -0,0 +1,4 @@ +from mongorm import * + +def test_document_truth(): + assert bool(Document()) \ No newline at end of file diff --git a/tests/test_update.py b/tests/test_update.py index d531c83..8f582c4 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -3,7 +3,7 @@ def teardown_module(module): DocumentRegistry.clear( ) - + def test_update_dictfield( ): """Tests to make sure updates are calculated correctly by dictfields""" class TestA(Document): @@ -19,21 +19,21 @@ class TestA(Document): value = ['test'] assert Q( { fieldName: value } ).toMongo( TestA, forUpdate=True )[fieldName.replace('__', '.')] \ == value - + def test_update_types( ): """Tests to make sure updates work with different types""" connect( 'test_mongorm' ) - + class TestB(Document): dictval = DictField( ) boolval = BooleanField( ) stringval = StringField( ) listval = ListField( StringField() ) genericval = GenericReferenceField( ) - + doc = TestB( ) doc.save( ) - + assert TestB.objects._prepareActions( set__boolval=True, set__stringval='test' @@ -42,7 +42,7 @@ class TestB(Document): assert TestB.objects._prepareActions( set__listval=['a','b','c'] ) == {'$set': {'listval': ['a', 'b', 'c']}} - + assert TestB.objects._prepareActions( set__dictval__subkeybool=True, set__dictval__subkeystring='testing', @@ -50,7 +50,7 @@ class TestB(Document): ) == {'$set': { 'dictval.subkeybool': True, 'dictval.subkeydict': {'a': 'b'}, 'dictval.subkeystring': 'testing'}} - + assert TestB.objects._prepareActions( set__genericval=doc ) == {'$set': {'genericval': {'_types': ['TestB'], '_ref': DBRef('testb', doc.id)}}} @@ -70,10 +70,6 @@ class TestPushPull(Document): push__values='spam' ) == {'$push': {'values': 'spam'}} - assert TestPushPull.objects._prepareActions( - pushAll__values=['spam', 'eggs'] - ) == {'$pushAll': {'values': ['spam', 'eggs']}} - assert TestPushPull.objects._prepareActions( pull__values='spam' ) == {'$pull': {'values': 'spam'}} @@ -99,15 +95,6 @@ class TestPushPull(Document): assert TestPushPull.objects.get( pk=a.id ).values == ['spam'] assert TestPushPull.objects.get( pk=b.id ).values == ['eggs', 'spam'] - assert TestPushPull.objects.update( - safeUpdate=True, - updateAllDocuments=True, - pushAll__values=[] - ) == 2 - - assert TestPushPull.objects.get( pk=a.id ).values == ['spam'] - assert TestPushPull.objects.get( pk=b.id ).values == ['eggs', 'spam'] - assert TestPushPull.objects.update( safeUpdate=True, updateAllDocuments=True, @@ -138,7 +125,13 @@ class TestPushPull(Document): assert TestPushPull.objects.update( safeUpdate=True, updateAllDocuments=True, - pushAll__values=['spam', 'eggs'] + push__values='spam' + ) == 2 + + assert TestPushPull.objects.update( + safeUpdate=True, + updateAllDocuments=True, + push__values='eggs' ) == 2 assert TestPushPull.objects.get( pk=a.id ).values == ['spam', 'eggs']