-
Notifications
You must be signed in to change notification settings - Fork 0
/
collection.py
284 lines (203 loc) · 9.12 KB
/
collection.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
from pymongo.dbref import DBRef
from pymongo.objectid import ObjectId
from pymongo.code import Code
from superdoc import SuperDoc
from doclist import DocList, SuperDocList
from exc import SuperDocError
import types
# patch DBRef to allow lazy retrieval of referenced docs
def get(self, name):
if not hasattr(self,'_db'):
return None
col = Collection(self._db, self.collection)
return getattr(SuperDoc(
col,
self._db[self.collection].find_one(self.id)
), name)
DBRef.__getattr__ = get
class Collection:
"""Represents all methods a collection can have. To create a new
document in a collection, call new().
"""
def __init__(self, _monga_instance, doctype, echo=False):
self._doctype = doctype
self._monga = _monga_instance
DBRef._db = _monga_instance._db
self._echo = echo
def new(self, **datas):
"""Return empty document, with preset collection.
"""
return self._doctype( self._monga, **datas )
def _parse_query(self, kwargs):
"""Parse argument list into mongo query.
Examples:
(user='jack') => {'user': 'jack'}
(comment__user='john') => {'comment.user': 'john'}
(comment__rating__lt=10) => {'comment.rating': {'$lt': 10}}
(user__in=[10, 20]) => {'user': {'$in': [10, 20]}}
"""
q = {}
# iterate over kwargs and build query dict
for k, v in kwargs.items():
# handle query operators
op = k.split('__')[-1]
if op in ('lte', 'gte', 'lt', 'gt', 'ne',
'in', 'nin', 'all', 'size', 'exists'):
k = k[:k.find('__' + op)]
if op == 'in':
v = {'$%s' % op: k == '_id' and map(lambda x: ObjectId(str(x)), v) or v }
else:
v = {'$%s' % op: k == '_id' and ObjectId(str(v)) or v }
if k == '_id':
v = type(v) in (unicode,str) and ObjectId(str(v)) or v
else:
v = type(v) is ObjectId and str(v) or v
#v = k == '_id' and type(v) in (unicode,str) and ObjectId(str(v)) or v
v = type(v) == list and str(v) or v
# XXX dunno if we really need this?
#if type(v) == list:
# v = str(v)
# convert django style notation into dot notation
key = k.replace('__', '.')
# convert mongodbobject SuperDoc type to pymongo DBRef type.
# it's necessary for pymongo search working correctly
if type(v) == SuperDoc:
v = DBRef(v._collection,v._id)
q[key] = v
return q
def _parse_update(self, kwargs):
"""Parse update arguments into mongo update dict.
Examples:
(name='jack') => {'name': 'jack'}
(person__gender='male') => {'person.gender': 'male'}
(set__friends=['mike']) => {'$set': {'friends': ['mike']}}
(push__friends='john') => {'$push': {'friends': 'john'}}
"""
q = {}
op_list = {}
# iterate over kwargs
for k, v in kwargs.items():
# get modification operator
op = k.split('__')[0]
if op in ('inc', 'set', 'push', 'pushall', 'pull', 'pullall'):
# pay attention to case sensitivity
op = op.replace('pushall', 'pushAll')
op = op.replace('pullall', 'pullAll')
# remove operator from key
k = k.replace(op + '__', '')
# append values to operator list (group operators)
if not op_list.has_key(op):
op_list[op] = []
op_list[op].append((k, v))
# simple value assignment
else:
q[k] = v
# append operator dict to mongo update dict
for k, v in op_list.items():
et = {}
for i in v:
et[i[0]] = i[1]
q['$' + k] = et
return q
def _parse_option(self, kwargs):
q = {}
for k, v in kwargs.items():
op = k.split("__")[-1]
if op in ('upsert',):
k = "$%s" % op
q[k] = v
return q
def query(self, **kwargs):
"""This method is used to first say which documents should be
affected and later what to do with these documents. They can be
removed or updated after they have been selected.
c = Collection('test')
c.query(name='jack').remove()
c.query(name='jack').update(set__name='john')
"""
class RemoveUpdateHandler(Collection):
def __init__(self, _monga_instance, doctype, query):
self._monga = _monga_instance
self._doctype = doctype
self.__query = query
def remove(self, safe=False):
try:
self._monga._db[self._doctype._collection_name].remove(self.__query, safe = safe)
except:
return False
return True
def update(self, update, options = {}):
_cond = '_id' in update and ObjectId(str(update['_id'])) or self.__query
_update_param = self._parse_update(update)
if self._echo is True:
print "update:", _update_param
return self._monga._db[self._doctype._collection_name].update(
_cond,
_update_param,
**options
)
if self._monga.config.get('nometaname') == False:
# hanya hapus pada record yg memiliki model yg tepat
# untuk menjaga terjadinya penghapusan data pada beberapa model berbeda
# dalam satu koleksi yg sama
kwargs['_metaname_'] = self._doctype.__name__
# return handler
_query_param = self._parse_query(kwargs)
if self._echo is True:
print "query:", _query_param
rv = RemoveUpdateHandler( self._monga, self._doctype, _query_param)
rv._echo = self._echo
return rv
def insert(self, doc):
if type(doc) == self._doctype:
doc.set_monga( self._monga )
return doc.save()
else:
raise SuperDocError, "Invalid doc type %s inserted to %s collection." % (doc.__class__.__name__ ,self._doctype.__name__)
def find(self, **kwargs):
"""Find documents based on query using the django query syntax.
See _parse_query() for details.
"""
if self._monga.config.get('nometaname') == False:
# hanya untuk record yg memiliki model yg tepat
# untuk menjaga terjadinya pencampuran data pada beberapa model berbeda
# dalam satu koleksi yg sama
kwargs['_metaname_'] = self._doctype.__name__
_cond = self._parse_query(kwargs)
if self._echo is True:
print 'find cond:', _cond
return SuperDocList(
DocList(
self._monga,
self._doctype,
self._monga._db[self._doctype._collection_name].find( _cond )
)
)
# thanks to andrew trusty
def find_one(self, **kwargs):
"""Find one single document. Mainly this is used to retrieve
documents by unique key.
"""
if self._monga.config.get('nometaname') == False:
kwargs['_metaname_'] = self._doctype.__name__
_cond = self._parse_query(kwargs)
docs = self._monga._db[self._doctype._collection_name].find_one( _cond )
if docs is None:
return None
return self._doctype( self._monga, **dict(map(lambda x: (str(x[0]), x[1]), docs.items())) )
def count(self, **kwargs):
'''Nggo ngolehake jumlah record nang njero koleksi secara shortcut
'''
return self.find( **kwargs ).count()
def ensure_index( self, key, ttl=600, unique=False ):
'''nggo ngawe index nek perlu
'''
return self._monga._db[self._doctype._collection_name].ensure_index(key, ttl = ttl, unique = unique)
def map_reduce( self, map_func, reduce_func ):
map_func = Code(map_func.replace('\n',''))
reduce_func = Code(reduce_func.replace('\n',''))
return self._monga._db[self._doctype._collection_name].map_reduce(map_func, reduce_func)
def clear( self ):
'''nggo ngapus kabeh data record neng collection
'''
return self._monga._db[self._doctype._collection_name].remove({})