From 6f486030e70b54bae90414b95b7660f4fcd656b9 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Thu, 24 Oct 2019 16:59:52 +1100 Subject: [PATCH 01/25] List all needed dependencies --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f822246..b8eb901 100644 --- a/setup.py +++ b/setup.py @@ -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'], + test_requires=['pytest'], ) From fd1db8a907cd4b5ca4f8d762d047ad8c3a07d804 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Thu, 24 Oct 2019 17:00:12 +1100 Subject: [PATCH 02/25] Don't crash if no pymongo wrapper is supplied --- mongorm/queryset/QuerySetManager.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mongorm/queryset/QuerySetManager.py b/mongorm/queryset/QuerySetManager.py index 6bf086f..664473d 100644 --- a/mongorm/queryset/QuerySetManager.py +++ b/mongorm/queryset/QuerySetManager.py @@ -15,4 +15,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) From 9a82719b7d27123af2f482cff7023b6c9c446ffb Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Thu, 24 Oct 2019 17:01:39 +1100 Subject: [PATCH 03/25] Remove reference to removed pushall mongo modifier --- mongorm/queryset/QuerySet.py | 2 +- tests/test_queries.py | 6 ------ tests/test_update.py | 21 +++++++-------------- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/mongorm/queryset/QuerySet.py b/mongorm/queryset/QuerySet.py index 03450c6..7aa4c6f 100644 --- a/mongorm/queryset/QuerySet.py +++ b/mongorm/queryset/QuerySet.py @@ -116,7 +116,7 @@ def _prepareActions( self, **actions ): for action, value in actions.iteritems( ): 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] = {} diff --git a/tests/test_queries.py b/tests/test_queries.py index e329c6d..f255f35 100644 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -252,12 +252,6 @@ class TestPush(Document): ) == { '$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""" diff --git a/tests/test_update.py b/tests/test_update.py index d531c83..e2a2735 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -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'] From 981ab9a5fe8bda202cabaf356e0b98f283e39288 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Thu, 24 Oct 2019 17:02:40 +1100 Subject: [PATCH 04/25] Correctly default to array on upsert For mongo bug SERVER-3946 --- mongorm/queryset/QuerySet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongorm/queryset/QuerySet.py b/mongorm/queryset/QuerySet.py index 7aa4c6f..6447d60 100644 --- a/mongorm/queryset/QuerySet.py +++ b/mongorm/queryset/QuerySet.py @@ -269,7 +269,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 From 64e16d2148b5bd97f527aa0982a1ac5507a4a312 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Thu, 24 Oct 2019 17:03:09 +1100 Subject: [PATCH 05/25] Remove extra spaces --- tests/test_inheritance.py | 38 +++++++++++++++++++------------------- tests/test_queries.py | 38 +++++++++++++++++++------------------- tests/test_update.py | 14 +++++++------- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py index 64eed80..7c30f54 100644 --- a/tests/test_inheritance.py +++ b/tests/test_inheritance.py @@ -21,32 +21,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 +55,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 +65,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 +96,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 +116,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_queries.py b/tests/test_queries.py index f255f35..67d54e1 100644 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -12,7 +12,7 @@ def test_basic_equality( ): class Test(Document): data = DictField( ) name = StringField( ) - + # equality assert Q( name='c' ).toMongo( Test ) \ == {'name': 'c'} @@ -24,7 +24,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 +36,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 +53,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 +68,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 +88,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 +102,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 +133,7 @@ class TestAndOr(Document): {'index': '123'}, {'index': '456'}, ]} - + # multiple ORs with embedded ANDs assert TestAndOr.objects.filter( Q(name__icontains='abc') | @@ -226,16 +226,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,7 +246,7 @@ def test_push( ): class TestPush(Document): names = ListField( StringField( ) ) - + assert TestPush.objects._prepareActions( push__names='123' ) == { diff --git a/tests/test_update.py b/tests/test_update.py index e2a2735..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)}}} From 2a44caa923df889f14dc1ed09b9372f249140fc3 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Mon, 28 Oct 2019 10:00:25 +1100 Subject: [PATCH 06/25] Stage 1 of futurize --- mongorm/Document.py | 2 +- mongorm/fields/DateField.py | 4 ++-- mongorm/fields/DecimalField.py | 4 ++-- mongorm/queryset/Q.py | 37 +++++++++++++++++----------------- mongorm/queryset/QuerySet.py | 2 +- 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/mongorm/Document.py b/mongorm/Document.py index 2bad2df..9e3de09 100644 --- a/mongorm/Document.py +++ b/mongorm/Document.py @@ -43,7 +43,7 @@ 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): message = u'Tried to save duplicate unique keys (%s)' diff --git a/mongorm/fields/DateField.py b/mongorm/fields/DateField.py index 8ad6ef7..55fa07c 100644 --- a/mongorm/fields/DateField.py +++ b/mongorm/fields/DateField.py @@ -14,11 +14,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/DecimalField.py b/mongorm/fields/DecimalField.py index 8f81cac..fc7b501 100644 --- a/mongorm/fields/DecimalField.py +++ b/mongorm/fields/DecimalField.py @@ -7,8 +7,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/queryset/Q.py b/mongorm/queryset/Q.py index 62cea2e..994fe4c 100644 --- a/mongorm/queryset/Q.py +++ b/mongorm/queryset/Q.py @@ -1,15 +1,14 @@ - 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( ): @@ -21,9 +20,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,10 +32,10 @@ def toMongo( self, document, forUpdate=False, modifier=None ): 'startswith': ( '^%s', '' ), 'istartswith': ( '^%s', 'i' ), - + 'endswith': ( '%s$', '' ), 'iendswith': ( '%s$', 'i' ), - + 'matches': ( None, '' ), 'imatches': ( None, 'i' ), } @@ -57,10 +56,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 +89,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] @@ -125,12 +124,12 @@ 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 the 2 queries have overlapping keys, we need to use a $and to join them. @@ -141,17 +140,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 6447d60..b9a2cf8 100644 --- a/mongorm/queryset/QuerySet.py +++ b/mongorm/queryset/QuerySet.py @@ -29,7 +29,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 ): From d972b7969ad2edf1fe8a7df9a9804b663296b6ce Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 29 Oct 2019 16:05:15 +1100 Subject: [PATCH 07/25] Use builtins object --- mongorm/BaseDocument.py | 1 + mongorm/DocumentRegistry.py | 1 + mongorm/fields/BaseField.py | 1 + mongorm/queryset/Q.py | 1 + mongorm/queryset/QuerySet.py | 1 + mongorm/queryset/QuerySetManager.py | 1 + 6 files changed, 6 insertions(+) diff --git a/mongorm/BaseDocument.py b/mongorm/BaseDocument.py index 1b2e772..e669364 100644 --- a/mongorm/BaseDocument.py +++ b/mongorm/BaseDocument.py @@ -1,3 +1,4 @@ +from builtins import object from mongorm import connection from mongorm.DocumentMetaclass import DocumentMetaclass 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/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/queryset/Q.py b/mongorm/queryset/Q.py index 994fe4c..978a21f 100644 --- a/mongorm/queryset/Q.py +++ b/mongorm/queryset/Q.py @@ -1,3 +1,4 @@ +from builtins import object class Q(object): def __init__( self, _query=None, **search ): if _query is None: diff --git a/mongorm/queryset/QuerySet.py b/mongorm/queryset/QuerySet.py index b9a2cf8..5f80290 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 diff --git a/mongorm/queryset/QuerySetManager.py b/mongorm/queryset/QuerySetManager.py index 664473d..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 From 538b93802b90ce8a5b921e62413fc792b086462f Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 29 Oct 2019 16:06:00 +1100 Subject: [PATCH 08/25] Use with_metaclass method --- mongorm/BaseDocument.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mongorm/BaseDocument.py b/mongorm/BaseDocument.py index e669364..b9c7762 100644 --- a/mongorm/BaseDocument.py +++ b/mongorm/BaseDocument.py @@ -3,9 +3,9 @@ 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): @@ -18,13 +18,13 @@ def __init__( self, **kwargs ): 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] ) @@ -97,7 +97,7 @@ def __getattr__( self, name ): 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 From 9af04ec0e61e6dc7d68e3ea3d2aa32cbd2f8fc4a Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 29 Oct 2019 16:07:17 +1100 Subject: [PATCH 09/25] Convert iterX methods to use py3 syntax --- mongorm/DocumentMetaclass.py | 2 +- mongorm/queryset/Q.py | 4 ++-- mongorm/queryset/QuerySet.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mongorm/DocumentMetaclass.py b/mongorm/DocumentMetaclass.py index d77c5ec..233a336 100644 --- a/mongorm/DocumentMetaclass.py +++ b/mongorm/DocumentMetaclass.py @@ -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 diff --git a/mongorm/queryset/Q.py b/mongorm/queryset/Q.py index 978a21f..382aee0 100644 --- a/mongorm/queryset/Q.py +++ b/mongorm/queryset/Q.py @@ -12,7 +12,7 @@ def __init__( self, _query=None, **search ): 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 ] @@ -115,7 +115,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: diff --git a/mongorm/queryset/QuerySet.py b/mongorm/queryset/QuerySet.py index 5f80290..bb24639 100644 --- a/mongorm/queryset/QuerySet.py +++ b/mongorm/queryset/QuerySet.py @@ -114,7 +114,7 @@ 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', 'pull', 'pullAll'], 'Unknown modifier "%s"' % modifier @@ -215,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: @@ -246,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 @@ -342,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 ): From 92aecb3f640a693113f4f0bc91fe68b17752ce5d Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 29 Oct 2019 16:08:06 +1100 Subject: [PATCH 10/25] Make iterators behave like python 2 expects --- mongorm/DocumentMetaclass.py | 4 ++-- mongorm/fields/SafeDictField.py | 2 +- mongorm/queryset/Q.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mongorm/DocumentMetaclass.py b/mongorm/DocumentMetaclass.py index 233a336..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 @@ -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/fields/SafeDictField.py b/mongorm/fields/SafeDictField.py index a9e3185..152687e 100644 --- a/mongorm/fields/SafeDictField.py +++ b/mongorm/fields/SafeDictField.py @@ -9,7 +9,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] diff --git a/mongorm/queryset/Q.py b/mongorm/queryset/Q.py index 382aee0..3319862 100644 --- a/mongorm/queryset/Q.py +++ b/mongorm/queryset/Q.py @@ -40,7 +40,7 @@ def toMongo( self, document, forUpdate=False, modifier=None ): '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 @@ -132,7 +132,7 @@ 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( list(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: From b6bd4cd395e1f7e4c9d8390786c5cc0952de0fb1 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 29 Oct 2019 16:08:32 +1100 Subject: [PATCH 11/25] Guard against futures newobject type that wraps object --- mongorm/blackMagic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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'] ] From 3a1f6ba715972a766d9aaaee301a9468cbb45f58 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 29 Oct 2019 16:09:29 +1100 Subject: [PATCH 12/25] Import basestring for use in the codebase --- mongorm/connection.py | 1 + mongorm/fields/DateField.py | 1 + mongorm/fields/DateTimeField.py | 1 + mongorm/fields/DecimalField.py | 1 + mongorm/fields/ReferenceField.py | 1 + mongorm/fields/SafeDictField.py | 1 + mongorm/queryset/Q.py | 1 + 7 files changed, 7 insertions(+) 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/DateField.py b/mongorm/fields/DateField.py index 55fa07c..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 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 fc7b501..7f56205 100644 --- a/mongorm/fields/DecimalField.py +++ b/mongorm/fields/DecimalField.py @@ -1,3 +1,4 @@ +from past.builtins import basestring from mongorm.fields.BaseField import BaseField from decimal import Decimal diff --git a/mongorm/fields/ReferenceField.py b/mongorm/fields/ReferenceField.py index 7bc27ae..69b22a0 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 diff --git a/mongorm/fields/SafeDictField.py b/mongorm/fields/SafeDictField.py index 152687e..c9f07cc 100644 --- a/mongorm/fields/SafeDictField.py +++ b/mongorm/fields/SafeDictField.py @@ -1,3 +1,4 @@ +from past.builtins import basestring from mongorm.fields.DictField import DictField from collections import deque diff --git a/mongorm/queryset/Q.py b/mongorm/queryset/Q.py index 3319862..5514ec7 100644 --- a/mongorm/queryset/Q.py +++ b/mongorm/queryset/Q.py @@ -1,3 +1,4 @@ +from past.builtins import basestring from builtins import object class Q(object): def __init__( self, _query=None, **search ): From b6d4ffec3a25a721ec6e4feb018a1f774388a4c0 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 29 Oct 2019 16:10:13 +1100 Subject: [PATCH 13/25] Use binascii for generic py2/3 interface in converting to hex --- mongorm/fields/SafeDictField.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mongorm/fields/SafeDictField.py b/mongorm/fields/SafeDictField.py index c9f07cc..be38f75 100644 --- a/mongorm/fields/SafeDictField.py +++ b/mongorm/fields/SafeDictField.py @@ -1,3 +1,6 @@ +import binascii + +from builtins import str, bytes from past.builtins import basestring from mongorm.fields.DictField import DictField @@ -20,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 ): From 86e9cc6b15167ab7746e2897f836b10a39356ead Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 29 Oct 2019 16:11:14 +1100 Subject: [PATCH 14/25] Use new definition of range --- tests/test_inheritance.py | 1 + tests/test_queries.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py index 7c30f54..2db5bfe 100644 --- a/tests/test_inheritance.py +++ b/tests/test_inheritance.py @@ -1,3 +1,4 @@ +from builtins import range from mongorm import * try: diff --git a/tests/test_queries.py b/tests/test_queries.py index 67d54e1..8005be0 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 From b735f5b9058026ed7050553d6d0985ac7e43c3d5 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 29 Oct 2019 16:11:37 +1100 Subject: [PATCH 15/25] Make pickle use compatible syntax --- tests/test_pickle.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_pickle.py b/tests/test_pickle.py index c8c0310..be53eaf 100644 --- a/tests/test_pickle.py +++ b/tests/test_pickle.py @@ -1,7 +1,9 @@ +from future import standard_library +standard_library.install_aliases() from mongorm import Document, DocumentRegistry, StringField, connect try: - import cPickle as pickle + import pickle as pickle except ImportError: import pickle From 2db729f7463b8d4704bd320b16a072fb678ee029 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 29 Oct 2019 16:12:03 +1100 Subject: [PATCH 16/25] Update range value to use new import --- tests/test_queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_queries.py b/tests/test_queries.py index 8005be0..ae9da5c 100644 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -372,7 +372,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( ): From c6c0d77e6cf700d29b33f033cf7d921faecb66ca Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 29 Oct 2019 16:12:25 +1100 Subject: [PATCH 17/25] Ensure order of sets in test --- tests/test_queries.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/test_queries.py b/tests/test_queries.py index ae9da5c..c5d0350 100644 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -289,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( ) @@ -417,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( ) @@ -441,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( ) From 94029b32b8f007157c08fc091b5ab0f9a9b4c2e6 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 29 Oct 2019 16:13:07 +1100 Subject: [PATCH 18/25] Use new py3 compatible str --- mongorm/Document.py | 5 +++-- mongorm/fields/DecimalField.py | 1 + mongorm/fields/ObjectIdField.py | 4 ++-- mongorm/fields/ReferenceField.py | 1 + mongorm/fields/StringField.py | 9 ++++++--- tests/test_equality.py | 1 + 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/mongorm/Document.py b/mongorm/Document.py index 9e3de09..14318e8 100644 --- a/mongorm/Document.py +++ b/mongorm/Document.py @@ -1,3 +1,4 @@ +from builtins import str import pymongo import warnings @@ -45,9 +46,9 @@ def save( self, forceInsert=False, **kwargs ): newId = collection.save( self._data, **kwargs ) 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/fields/DecimalField.py b/mongorm/fields/DecimalField.py index 7f56205..d8f7400 100644 --- a/mongorm/fields/DecimalField.py +++ b/mongorm/fields/DecimalField.py @@ -1,3 +1,4 @@ +from builtins import str from past.builtins import basestring from mongorm.fields.BaseField import BaseField 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 69b22a0..421dfcb 100644 --- a/mongorm/fields/ReferenceField.py +++ b/mongorm/fields/ReferenceField.py @@ -1,3 +1,4 @@ +from builtins import str from past.builtins import basestring from bson import objectid, dbref import bson.errors 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/tests/test_equality.py b/tests/test_equality.py index 2bb1498..8f8bdbc 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 From b6f3dbe5929ceebf1244f50ce01fca77429a5b29 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 29 Oct 2019 16:13:49 +1100 Subject: [PATCH 19/25] Change tests to be better aligned with what's expected from py3/py2 --- tests/test_equality.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_equality.py b/tests/test_equality.py index 8f8bdbc..f2836d6 100644 --- a/tests/test_equality.py +++ b/tests/test_equality.py @@ -77,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.""" From 38864611a47afb3f8107e4366d8b1798a3a04493 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Wed, 30 Oct 2019 10:57:54 +1100 Subject: [PATCH 20/25] Bump up version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b8eb901..dcecbed 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', From 92f3f819131cc728f4526b27b53488427ff72d08 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Wed, 30 Oct 2019 11:34:18 +1100 Subject: [PATCH 21/25] Explicitly require future as a requirement --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index dcecbed..fda1734 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,6 @@ description='Mongorm', long_description='Mongorm', platforms=['any'], - install_requires=['pymongo >= 2.4.2', 'pysignals', 'iso8601'], + install_requires=['pymongo >= 2.4.2', 'pysignals', 'iso8601', 'future'], test_requires=['pytest'], ) From b9ad57410306223ea7664763e7eb211b885ece40 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Wed, 30 Oct 2019 13:48:19 +1100 Subject: [PATCH 22/25] Ensure documents are truthy --- mongorm/BaseDocument.py | 34 +++++++++++++++++++--------------- tests/test_truthiness.py | 4 ++++ 2 files changed, 23 insertions(+), 15 deletions(-) create mode 100644 tests/test_truthiness.py diff --git a/mongorm/BaseDocument.py b/mongorm/BaseDocument.py index b9c7762..bcc20a1 100644 --- a/mongorm/BaseDocument.py +++ b/mongorm/BaseDocument.py @@ -7,31 +7,35 @@ 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.items( ): setattr(self, name, value) - + def _fromMongo( self, data, overwrite=True ): self._is_lazy = True - + 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), @@ -50,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 ) @@ -62,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: @@ -79,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: @@ -90,11 +94,11 @@ 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.items( ): @@ -104,4 +108,4 @@ def _resyncFromPython( self ): pythonValue = getattr(self, name) self._data[dbField] = field.fromPython( pythonValue ) #print 'resyncing', dbField, 'to', self._data[dbField] - + 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 From 666a0aa8634ba0a3b1744b1ac7cb07b1911f68d9 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Wed, 30 Oct 2019 14:13:52 +1100 Subject: [PATCH 23/25] Default to use python version of str when passing to ObjectId --- mongorm/fields/ReferenceField.py | 35 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/mongorm/fields/ReferenceField.py b/mongorm/fields/ReferenceField.py index 421dfcb..4837d4b 100644 --- a/mongorm/fields/ReferenceField.py +++ b/mongorm/fields/ReferenceField.py @@ -1,4 +1,3 @@ -from builtins import str from past.builtins import basestring from bson import objectid, dbref import bson.errors @@ -13,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__ @@ -29,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 @@ -50,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 @@ -74,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): @@ -97,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' From 1eb0c34da90fbb4e91f7026197d598fa8cbf6712 Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Fri, 1 Nov 2019 08:17:01 +1100 Subject: [PATCH 24/25] Use a set for intersection --- mongorm/queryset/Q.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongorm/queryset/Q.py b/mongorm/queryset/Q.py index 5514ec7..b8e4554 100644 --- a/mongorm/queryset/Q.py +++ b/mongorm/queryset/Q.py @@ -133,7 +133,7 @@ def __or__( self, other ): return self.do_merge( other, '$or' ) def __and__( self, other ): - if len( set( self.query.keys() ).intersection( list(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: From 1db43875f643c4ab411f7c07e25fba988a4aea7e Mon Sep 17 00:00:00 2001 From: Paul Jukic Date: Tue, 5 Nov 2019 16:56:51 +1100 Subject: [PATCH 25/25] Default to use cPickle --- tests/test_pickle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pickle.py b/tests/test_pickle.py index be53eaf..d9eaff9 100644 --- a/tests/test_pickle.py +++ b/tests/test_pickle.py @@ -3,7 +3,7 @@ from mongorm import Document, DocumentRegistry, StringField, connect try: - import pickle as pickle + import cPickle as pickle except ImportError: import pickle