Skip to content

Commit c44083f

Browse files
author
Reed O'Brien
committed
2 parents 0917bd8 + f67ee73 commit c44083f

File tree

6 files changed

+92
-21
lines changed

6 files changed

+92
-21
lines changed

bson/_cbsonmodule.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,12 @@ int write_dict(buffer_t buffer, PyObject* dict, unsigned char check_keys, unsign
778778
}
779779
while ((key = PyIter_Next(iter)) != NULL) {
780780
PyObject* value = PyDict_GetItem(dict, key);
781+
if (!value) {
782+
PyErr_SetObject(PyExc_KeyError, key);
783+
Py_DECREF(key);
784+
Py_DECREF(iter);
785+
return 0;
786+
}
781787
if (!decode_and_write_pair(buffer, key, value, check_keys, top_level)) {
782788
Py_DECREF(key);
783789
Py_DECREF(iter);

bson/son.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
of keys is important. A SON object can be used just like a normal Python
1919
dictionary."""
2020

21+
import copy
2122

2223
class SON(dict):
2324
"""SON data.
@@ -202,3 +203,9 @@ def transform_value(value):
202203
return value
203204

204205
return transform_value(dict(self))
206+
207+
def __deepcopy__(self, memo):
208+
out = SON()
209+
for k, v in self.iteritems():
210+
out[k] = copy.deepcopy(v, memo)
211+
return out

pymongo/collection.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -634,11 +634,13 @@ def create_index(self, key_or_list, deprecated_unique=None,
634634

635635
index.update(kwargs)
636636

637+
self.__database.system.indexes.insert(index, manipulate=False,
638+
check_keys=False,
639+
safe=True)
640+
637641
self.__database.connection._cache_index(self.__database.name,
638642
self.__name, name, ttl)
639643

640-
self.__database.system.indexes.insert(index, manipulate=False,
641-
check_keys=False)
642644
return name
643645

644646
def ensure_index(self, key_or_list, deprecated_unique=None,

pymongo/connection.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,28 @@ def _str_to_node(string, default_port=27017):
8181
return (host, port)
8282

8383

84-
def _parse_uri(uri, default_port):
84+
def _parse_uri(uri, default_port=27017):
8585
"""MongoDB URI parser.
8686
"""
87-
info = {}
8887

8988
if uri.startswith("mongodb://"):
9089
uri = uri[len("mongodb://"):]
9190
elif "://" in uri:
9291
raise InvalidURI("Invalid uri scheme: %s" % _partition(uri, "://")[0])
9392

94-
(hosts, database) = _partition(uri, "/")
93+
(hosts, namespace) = _partition(uri, "/")
9594

96-
if not database:
97-
database = None
95+
raw_options = None
96+
if namespace:
97+
(namespace, raw_options) = _partition(namespace, "?")
98+
if namespace.find(".") < 0:
99+
db = namespace
100+
collection = None
101+
else:
102+
(db, collection) = namespace.split(".", 1)
103+
else:
104+
db = None
105+
collection = None
98106

99107
username = None
100108
password = None
@@ -112,7 +120,21 @@ def _parse_uri(uri, default_port):
112120
raise InvalidURI("empty host (or extra comma in host list)")
113121
host_list.append(_str_to_node(host, default_port))
114122

115-
return (host_list, database, username, password)
123+
options = {}
124+
if raw_options:
125+
and_idx = raw_options.find("&")
126+
semi_idx = raw_options.find(";")
127+
if and_idx >= 0 and semi_idx >= 0:
128+
raise InvalidURI("Cannot mix & and ; for option separators.")
129+
elif and_idx >= 0:
130+
options = dict([kv.split("=") for kv in raw_options.split("&")])
131+
elif semi_idx >= 0:
132+
options = dict([kv.split("=") for kv in raw_options.split(";")])
133+
elif raw_options.find("="):
134+
options = dict([raw_options.split("=")])
135+
136+
137+
return (host_list, db, username, password, collection, options)
116138

117139

118140
def _closed(sock):
@@ -266,12 +288,16 @@ def __init__(self, host=None, port=None, pool_size=None,
266288
database = None
267289
username = None
268290
password = None
291+
collection = None
292+
options = {}
269293
for uri in host:
270-
(n, db, u, p) = _parse_uri(uri, port)
294+
(n, db, u, p, coll, opts) = _parse_uri(uri, port)
271295
nodes.update(n)
272296
database = db or database
273297
username = u or username
274298
password = p or password
299+
collection = coll or collection
300+
options = opts or options
275301
if not nodes:
276302
raise ConfigurationError("need to specify at least one host")
277303
self.__nodes = nodes
@@ -292,11 +318,20 @@ def __init__(self, host=None, port=None, pool_size=None,
292318
self.__host = None
293319
self.__port = None
294320

295-
self.__slave_okay = slave_okay
321+
if options.has_key("slaveok"):
322+
self.__slave_okay = options['slaveok'][0].upper()=='T'
323+
else:
324+
self.__slave_okay = slave_okay
325+
296326
if slave_okay and len(self.__nodes) > 1:
297327
raise ConfigurationError("cannot specify slave_okay for a paired "
298328
"or replica set connection")
299329

330+
# TODO - Support using other options like w and fsync from URI
331+
self.__options = options
332+
# TODO - Support setting the collection from URI as the Java driver does
333+
self.__collection = collection
334+
300335
self.__cursor_manager = CursorManager(self)
301336

302337
self.__pool = _Pool(self.__connect)
@@ -615,7 +650,7 @@ def __check_response_to_last_error(self, response):
615650
raise AutoReconnect("not master")
616651

617652
if "code" in error:
618-
if error["code"] in [11000, 11001]:
653+
if error["code"] in [11000, 11001, 12582]:
619654
raise DuplicateKeyError(error["err"])
620655
else:
621656
raise OperationFailure(error["err"], error["code"])

test/test_collection.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ def test_create_index(self):
114114
[a["name"] for a in db.system.indexes
115115
.find({"ns": u"pymongo_test.test"})])
116116

117+
db.test.drop()
118+
db.test.insert({'a':1})
119+
db.test.insert({'a':1})
120+
self.assertRaises(DuplicateKeyError, db.test.create_index,
121+
'a', unique=True)
122+
117123
def test_ensure_index(self):
118124
db = self.db
119125

test/test_connection.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -236,40 +236,53 @@ def test_disconnect(self):
236236
coll.count()
237237

238238
def test_parse_uri(self):
239-
self.assertEqual(([("localhost", 27017)], None, None, None),
239+
self.assertEqual(([("localhost", 27017)], None, None, None, None, {}),
240240
_parse_uri("localhost", 27017))
241-
self.assertEqual(([("localhost", 27018)], None, None, None),
241+
self.assertEqual(([("localhost", 27018)], None, None, None, None, {}),
242242
_parse_uri("localhost", 27018))
243243
self.assertRaises(InvalidURI, _parse_uri,
244244
"http://foobar.com", 27017)
245245
self.assertRaises(InvalidURI, _parse_uri,
246246
"http://[email protected]", 27017)
247247

248-
self.assertEqual(([("localhost", 27017)], None, None, None),
248+
self.assertEqual(([("localhost", 27017)], None, None, None, None, {}),
249249
_parse_uri("mongodb://localhost", 27017))
250-
self.assertEqual(([("localhost", 27017)], None, "fred", "foobar"),
250+
self.assertEqual(([("localhost", 27017)], None, "fred", "foobar", None, {}),
251251
_parse_uri("mongodb://fred:foobar@localhost",
252252
27017))
253-
self.assertEqual(([("localhost", 27017)], "baz", "fred", "foobar"),
253+
self.assertEqual(([("localhost", 27017)], "baz", "fred", "foobar", None, {}),
254254
_parse_uri("mongodb://fred:foobar@localhost/baz",
255255
27017))
256256
self.assertEqual(([("example1.com", 27017), ("example2.com", 27017)],
257-
None, None, None),
257+
None, None, None, None, {}),
258258
_parse_uri("mongodb://example1.com:27017,example2.com:27017",
259259
27018))
260260
self.assertEqual(([("localhost", 27017),
261261
("localhost", 27018),
262-
("localhost", 27019)], None, None, None),
262+
("localhost", 27019)], None, None, None, None, {}),
263263
_parse_uri("mongodb://localhost,localhost:27018,localhost:27019",
264264
27017))
265265

266-
self.assertEqual(([("localhost", 27018)], None, None, None),
266+
self.assertEqual(([("localhost", 27018)], None, None, None, None, {}),
267267
_parse_uri("localhost:27018", 27017))
268-
self.assertEqual(([("localhost", 27017)], "foo", None, None),
268+
self.assertEqual(([("localhost", 27017)], "foo", None, None, None, {}),
269269
_parse_uri("localhost/foo", 27017))
270-
self.assertEqual(([("localhost", 27017)], None, None, None),
270+
self.assertEqual(([("localhost", 27017)], None, None, None, None, {}),
271271
_parse_uri("localhost/", 27017))
272272

273+
self.assertEqual(([("localhost", 27017)], "test", None, None, "yield_historical.in", {}),
274+
_parse_uri("mongodb://localhost/test.yield_historical.in", 27017))
275+
self.assertEqual(([("localhost", 27017)], "test", "fred", "foobar", "yield_historical.in", {}),
276+
_parse_uri("mongodb://fred:foobar@localhost/test.yield_historical.in",
277+
27017))
278+
self.assertEqual(([("example1.com", 27017), ("example2.com", 27017)],
279+
"test", None, None, "yield_historical.in", {}),
280+
_parse_uri("mongodb://example1.com:27017,example2.com:27017/test.yield_historical.in",
281+
27017))
282+
self.assertEqual(([("localhost", 27017)], "test", "fred", "foobar", "yield_historical.in", {'slaveok': 'true'}),
283+
_parse_uri("mongodb://fred:foobar@localhost/test.yield_historical.in?slaveok=true",
284+
27017))
285+
273286
def test_from_uri(self):
274287
c = Connection(self.host, self.port)
275288

@@ -304,6 +317,8 @@ def test_from_uri(self):
304317
self.assert_(Connection("mongodb://%s:%s" %
305318
(self.host, self.port),
306319
slave_okay=True).slave_okay)
320+
self.assert_(Connection("mongodb://%s:%s/?slaveok=true;w=2" %
321+
(self.host, self.port)).slave_okay)
307322

308323
def test_fork(self):
309324
"""Test using a connection before and after a fork.

0 commit comments

Comments
 (0)