Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions mongorm/BaseDocument.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
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):
Expand All @@ -17,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] )
Expand Down Expand Up @@ -96,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
Expand Down
2 changes: 1 addition & 1 deletion mongorm/Document.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)'
Expand Down
6 changes: 3 additions & 3 deletions mongorm/DocumentMetaclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 )

Expand Down
1 change: 1 addition & 0 deletions mongorm/DocumentRegistry.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from builtins import object
class DocumentRegistry(object):
documentTypes = {}

Expand Down
1 change: 1 addition & 0 deletions mongorm/fields/BaseField.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from builtins import object
class BaseField(object):
_resyncAtSave = False

Expand Down
4 changes: 2 additions & 2 deletions mongorm/fields/DateField.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions mongorm/fields/DecimalField.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion mongorm/fields/SafeDictField.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
46 changes: 23 additions & 23 deletions mongorm/queryset/Q.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@

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( ):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will incur a performance hit on Python 2.x. You might want to consider using six.iteritems instead if you want to avoid it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the performance impact will be small - most of the larger arrays that were in our mongo documents have been factored out into their own collections (for reasons of mongo/memcached performance).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm surprised it makes that one of the default changes as part of the auto upgrade tool actually... I would have expected it to have gone with option 3 from their site: https://python-future.org/compatible_idioms.html#iterating-through-dict-keys-values-items

# Python 2 and 3: option 3
from future.utils import iteritems
# or
from six import iteritems

for (key, value) in iteritems(heights):
    ...

I guess they try default into making it as python3 compatible as possible from the get go, to hint that you should really be moving to py3 sooner rather than later :P

There's a few places that have this change (aka, the Convert iterX methods to use py3 syntax commit). Do you think it's worth updating them to use that import?

if name in ['$or', '$and']:
# mongodb logic operator - value is a list of Qs
newSearch[name] = [ value.toMongo( document ) for value in value ]
Expand All @@ -21,9 +21,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', '' ),
Expand All @@ -33,14 +33,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
Expand All @@ -57,10 +57,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:
Expand Down Expand Up @@ -90,10 +90,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]
Expand All @@ -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:
Expand All @@ -125,14 +125,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( 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:
Expand All @@ -141,17 +141,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 )
15 changes: 8 additions & 7 deletions mongorm/queryset/QuerySet.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from builtins import object
from mongorm.queryset.Q import Q
from mongorm.util import sortListToPyMongo
import pymongo
Expand Down Expand Up @@ -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 ):
Expand Down Expand Up @@ -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] = {}
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 ):
Expand Down
6 changes: 5 additions & 1 deletion mongorm/queryset/QuerySetManager.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from builtins import object
from mongorm.connection import getDatabase
from mongorm.queryset.QuerySet import QuerySet

Expand All @@ -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)
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if actually pymongo 2.9+ is required (since we updated to support mongo 3.0)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a few comments about using pymongo 3.0+ specific things already it seems (as in breaking changes that mean a version < 3.0 would break). I just think everyones been blissfully unaware because this setup.py pretty much just installs the latest version of pymongo (3.9.0 at the current time). So yeah, I think I'll update this to be a hard >= 3.0.0 requirement

test_requires=['pytest'],
)
Loading