Skip to content

Commit 0f16387

Browse files
authored
Add spec test functionality (#15)
1 parent ba1e41b commit 0f16387

File tree

63 files changed

+10204
-86
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+10204
-86
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ language: python
33
services:
44
- mongodb
55

6+
install: python -m pip install https://github.com/mongodb/mongo-python-driver/archive/3.11.0rc0.tar.gz
7+
68
python:
79
- 3.5
810
- 3.6

pymongoexplain/commands.py

Lines changed: 141 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,72 @@
1919
from typing import Union
2020

2121
from bson.son import SON
22+
from collections import abc
23+
2224
from pymongo.collection import Collection
25+
from pymongo.collation import validate_collation_or_none
2326
from .utils import convert_to_camelcase
2427

2528

2629
Document = Union[dict, SON]
2730

2831

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+
2982
class BaseCommand():
30-
def __init__(self, collection):
83+
def __init__(self, collection, collation):
3184
self.command_document = {}
85+
collation = validate_collation_or_none(collation)
86+
if collation is not None:
87+
self.command_document["collation"] = collation
3288
self.collection = collection
3389

3490
@property
@@ -44,29 +100,51 @@ def get_SON(self):
44100

45101
class UpdateCommand(BaseCommand):
46102
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)
57136

58137
@property
59138
def command_name(self):
60139
return "update"
61140

62141

63142
class DistinctCommand(BaseCommand):
64-
def __init__(self, collection: Collection, key, filter, session,
143+
def __init__(self, collection: Collection, key, filter,
65144
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+
70148
self.command_document = convert_to_camelcase(self.command_document)
71149

72150
@property
@@ -75,26 +153,34 @@ def command_name(self):
75153

76154

77155
class AggregateCommand(BaseCommand):
78-
def __init__(self, collection: Collection, pipeline, session,
156+
def __init__(self, collection: Collection, pipeline,
79157
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+
83164
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
85171

86172
self.command_document = convert_to_camelcase(
87-
self.command_document, exclude_keys=exclude_keys)
173+
self.command_document)
88174

89175
@property
90176
def command_name(self):
91177
return "aggregate"
92178

179+
93180
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})
98184
for key, value in kwargs.items():
99185
self.command_document[key] = value
100186
self.command_document = convert_to_camelcase(self.command_document)
@@ -107,21 +193,37 @@ def command_name(self):
107193
class FindCommand(BaseCommand):
108194
def __init__(self, collection: Collection,
109195
kwargs):
110-
super().__init__(collection.name)
196+
super().__init__(collection.name, kwargs.pop("collation", None))
111197
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+
113207
self.command_document = convert_to_camelcase(self.command_document)
114208

115209
@property
116210
def command_name(self):
117211
return "find"
118212

213+
119214
class FindAndModifyCommand(BaseCommand):
120215
def __init__(self, collection: Collection,
121216
kwargs):
122-
super().__init__(collection.name)
217+
super().__init__(collection.name, kwargs.pop("collation", None))
123218
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
125227
self.command_document = convert_to_camelcase(self.command_document)
126228

127229
@property
@@ -132,10 +234,16 @@ def command_name(self):
132234
class DeleteCommand(BaseCommand):
133235
def __init__(self, collection: Collection, filter,
134236
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}]
137240
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+
139247
self.command_document = convert_to_camelcase(self.command_document)
140248

141249
@property

0 commit comments

Comments
 (0)