19
19
from typing import Union
20
20
21
21
from bson .son import SON
22
+ from collections import abc
23
+
22
24
from pymongo .collection import Collection
25
+ from pymongo .collation import validate_collation_or_none
23
26
from .utils import convert_to_camelcase
24
27
25
28
26
29
Document = Union [dict , SON ]
27
30
28
31
32
+ def _index_document (index_list ):
33
+ """Helper to generate an index specifying document.
34
+
35
+ Takes a list of (key, direction) pairs.
36
+ """
37
+ if isinstance (index_list , abc .Mapping ):
38
+ raise TypeError ("passing a dict to sort/create_index/hint is not "
39
+ "allowed - use a list of tuples instead. did you "
40
+ "mean %r?" % list (index_list .items ()))
41
+ elif not isinstance (index_list , (list , tuple )):
42
+ raise TypeError ("must use a list of (key, direction) pairs, "
43
+ "not: " + repr (index_list ))
44
+ if not len (index_list ):
45
+ raise ValueError ("key_or_list must not be the empty list" )
46
+
47
+ index = SON ()
48
+ for (key , value ) in index_list :
49
+ if not isinstance (key , str ):
50
+ raise TypeError ("first item in each key pair must be a string" )
51
+ if not isinstance (value , (str , int , abc .Mapping )):
52
+ raise TypeError ("second item in each key pair must be 1, -1, "
53
+ "'2d', 'geoHaystack', or another valid MongoDB "
54
+ "index specifier." )
55
+ index [key ] = value
56
+ return index
57
+
58
+
59
+ def _fields_list_to_dict (fields , option_name ):
60
+ """Takes a sequence of field names and returns a matching dictionary.
61
+
62
+ ["a", "b"] becomes {"a": 1, "b": 1}
63
+
64
+ and
65
+
66
+ ["a.b.c", "d", "a.c"] becomes {"a.b.c": 1, "d": 1, "a.c": 1}
67
+ """
68
+ if isinstance (fields , abc .Mapping ):
69
+ return fields
70
+
71
+ if isinstance (fields , (abc .Sequence , abc .Set )):
72
+ if not all (isinstance (field , str ) for field in fields ):
73
+ raise TypeError ("%s must be a list of key names, each an "
74
+ "instance of %s" % (option_name ,
75
+ str .__name__ ))
76
+ return dict .fromkeys (fields , 1 )
77
+
78
+ raise TypeError ("%s must be a mapping or "
79
+ "list of key names" % (option_name ,))
80
+
81
+
29
82
class BaseCommand ():
30
- def __init__ (self , collection ):
83
+ def __init__ (self , collection , collation ):
31
84
self .command_document = {}
85
+ collation = validate_collation_or_none (collation )
86
+ if collation is not None :
87
+ self .command_document ["collation" ] = collation
32
88
self .collection = collection
33
89
34
90
@property
@@ -44,29 +100,51 @@ def get_SON(self):
44
100
45
101
class UpdateCommand (BaseCommand ):
46
102
def __init__ (self , collection : Collection , filter , update ,
47
- kwargs ):
48
- super ().__init__ (collection .name )
49
- return_document = {"updates" :[{"q" : filter , "u" : update }]}
50
- for key in kwargs :
51
- value = kwargs [key ]
52
- if key == "bypass_document_validation" :
53
- return_document [key ] = value
54
- else :
55
- return_document ["updates" ][0 ][key ] = value
56
- self .command_document = convert_to_camelcase (return_document )
103
+ upsert = None , multi = None , collation = None , array_filters = None ,
104
+ hint = None , ordered = None , write_concern = None ,
105
+ bypass_document_validation = None , comment = None ):
106
+ super ().__init__ (collection .name , collation )
107
+ update_doc = {"q" : filter , "u" : update }
108
+ if upsert is not None :
109
+ update_doc ["upsert" ] = upsert
110
+
111
+ if multi is not None :
112
+ update_doc ["multi" ] = multi
113
+
114
+ if array_filters is not None :
115
+ update_doc ["array_filters" ] = array_filters
116
+
117
+ if hint is not None :
118
+ update_doc ["hint" ] = hint if \
119
+ isinstance (hint , str ) else _index_document (hint )
120
+ self .command_document ["updates" ] = [update_doc ]
121
+
122
+ if ordered is not None :
123
+ self .command_document ["ordered" ] = ordered
124
+
125
+ if write_concern is not None :
126
+ self .command_document ["write_concern" ] = write_concern
127
+
128
+ if bypass_document_validation is not None and \
129
+ bypass_document_validation is not False :
130
+ self .command_document ["bypass_document_validation" ] = bypass_document_validation
131
+
132
+ if comment is not None :
133
+ self .command_document ["comment" ] = comment
134
+
135
+ self .command_document = convert_to_camelcase (self .command_document )
57
136
58
137
@property
59
138
def command_name (self ):
60
139
return "update"
61
140
62
141
63
142
class DistinctCommand (BaseCommand ):
64
- def __init__ (self , collection : Collection , key , filter , session ,
143
+ def __init__ (self , collection : Collection , key , filter ,
65
144
kwargs ):
66
- super ().__init__ (collection .name )
67
- self .command_document = {"key" : key , "query" : filter }
68
- for key , value in kwargs .items ():
69
- self .command_document [key ] = value
145
+ super ().__init__ (collection .name , kwargs .pop ("collation" , None ))
146
+ self .command_document .update ({"key" : key , "query" : filter })
147
+
70
148
self .command_document = convert_to_camelcase (self .command_document )
71
149
72
150
@property
@@ -75,26 +153,34 @@ def command_name(self):
75
153
76
154
77
155
class AggregateCommand (BaseCommand ):
78
- def __init__ (self , collection : Collection , pipeline , session ,
156
+ def __init__ (self , collection : Collection , pipeline ,
79
157
cursor_options ,
80
- kwargs , exclude_keys = []):
81
- super ().__init__ (collection .name )
82
- self .command_document = {"pipeline" : pipeline , "cursor" : cursor_options }
158
+ kwargs ):
159
+
160
+ super ().__init__ (collection .name , kwargs .pop ("collation" , None ))
161
+ self .command_document .update ({"pipeline" : pipeline , "cursor" :
162
+ cursor_options })
163
+
83
164
for key , value in kwargs .items ():
84
- self .command_document [key ] = value
165
+ if key == "batchSize" :
166
+ if value == 0 :
167
+ continue
168
+ self .command_document ["cursor" ]["batchSize" ] = value
169
+ else :
170
+ self .command_document [key ] = value
85
171
86
172
self .command_document = convert_to_camelcase (
87
- self .command_document , exclude_keys = exclude_keys )
173
+ self .command_document )
88
174
89
175
@property
90
176
def command_name (self ):
91
177
return "aggregate"
92
178
179
+
93
180
class CountCommand (BaseCommand ):
94
- def __init__ (self , collection : Collection , filter ,
95
- kwargs ):
96
- super ().__init__ (collection .name )
97
- self .command_document = {"query" : filter }
181
+ def __init__ (self , collection : Collection , filter , kwargs ):
182
+ super ().__init__ (collection .name , kwargs .pop ("collation" , None ))
183
+ self .command_document .update ({"query" : filter })
98
184
for key , value in kwargs .items ():
99
185
self .command_document [key ] = value
100
186
self .command_document = convert_to_camelcase (self .command_document )
@@ -107,21 +193,37 @@ def command_name(self):
107
193
class FindCommand (BaseCommand ):
108
194
def __init__ (self , collection : Collection ,
109
195
kwargs ):
110
- super ().__init__ (collection .name )
196
+ super ().__init__ (collection .name , kwargs . pop ( "collation" , None ) )
111
197
for key , value in kwargs .items ():
112
- self .command_document [key ] = value
198
+ if key == "projection" and value is not None :
199
+ self .command_document ["projection" ] = _fields_list_to_dict (
200
+ value , "projection" )
201
+ elif key == "sort" :
202
+ self .command_document ["sort" ] = _index_document (
203
+ value )
204
+ else :
205
+ self .command_document [key ] = value
206
+
113
207
self .command_document = convert_to_camelcase (self .command_document )
114
208
115
209
@property
116
210
def command_name (self ):
117
211
return "find"
118
212
213
+
119
214
class FindAndModifyCommand (BaseCommand ):
120
215
def __init__ (self , collection : Collection ,
121
216
kwargs ):
122
- super ().__init__ (collection .name )
217
+ super ().__init__ (collection .name , kwargs . pop ( "collation" , None ) )
123
218
for key , value in kwargs .items ():
124
- self .command_document [key ] = value
219
+ if key == "hint" :
220
+ self .command_document ["hint" ] = value if \
221
+ isinstance (value , str ) else _index_document (value )
222
+ elif key == "sort" and value is not None :
223
+ self .command_document ["sort" ] = _index_document (
224
+ value )
225
+ else :
226
+ self .command_document [key ] = value
125
227
self .command_document = convert_to_camelcase (self .command_document )
126
228
127
229
@property
@@ -132,10 +234,16 @@ def command_name(self):
132
234
class DeleteCommand (BaseCommand ):
133
235
def __init__ (self , collection : Collection , filter ,
134
236
limit , collation , kwargs ):
135
- super ().__init__ (collection .name )
136
- self .command_document = {"deletes" : [SON ({"q" : filter , "limit" : limit })]}
237
+ super ().__init__ (collection .name , kwargs .pop ("collation" , None ))
238
+ self .command_document ["deletes" ] = [{"q" : filter , "limit" :
239
+ limit }]
137
240
for key , value in kwargs .items ():
138
- self .command_document [key ] = value
241
+ if key == "hint" :
242
+ self .command_document ["deletes" ][0 ]["hint" ] = value if \
243
+ isinstance (value , str ) else _index_document (value )
244
+ else :
245
+ self .command_document [key ] = value
246
+
139
247
self .command_document = convert_to_camelcase (self .command_document )
140
248
141
249
@property
0 commit comments