Skip to content

Commit b9b7b5e

Browse files
author
Mike Dirolf
committed
bson for UUID <=> Binary w/ subtype 3, only on Python >= 2.5
1 parent 02a03fd commit b9b7b5e

File tree

3 files changed

+109
-1
lines changed

3 files changed

+109
-1
lines changed

pymongo/_cbsonmodule.c

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@
2020
* BSON encoding and decoding.
2121
*/
2222

23+
24+
#define _GNU_SOURCE
25+
#include <stdio.h>
26+
#include <time.h>
27+
#undef _GNU_SOURCE // avoid multiple define from Python.h
28+
2329
#include <Python.h>
2430
#include <datetime.h>
25-
#include <time.h>
2631

2732
static PyObject* CBSONError;
2833
static PyObject* InvalidName;
@@ -34,6 +39,7 @@ static PyObject* Code;
3439
static PyObject* ObjectId;
3540
static PyObject* DBRef;
3641
static PyObject* RECompile;
42+
static PyObject* UUID;
3743

3844
#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
3945
typedef int Py_ssize_t;
@@ -473,6 +479,33 @@ static int write_element_to_buffer(bson_buffer* buffer, int type_byte, PyObject*
473479
}
474480
}
475481
return 1;
482+
} else if (UUID && PyObject_IsInstance(value, UUID)) {
483+
// Just a special case of Binary above, but simpler to do as a separate case
484+
485+
// UUID is always 16 bytes, subtype 3
486+
int length = 16;
487+
const char subtype = 3;
488+
489+
PyObject* bytes;
490+
491+
*(buffer->buffer + type_byte) = 0x05;
492+
if (!buffer_write_bytes(buffer, (const char*)&length, 4)) {
493+
return 0;
494+
}
495+
if (!buffer_write_bytes(buffer, &subtype, 1)) {
496+
return 0;
497+
}
498+
499+
bytes = PyObject_GetAttrString(value, "bytes");
500+
if (!bytes) {
501+
return 0;
502+
}
503+
if (!buffer_write_bytes(buffer, PyString_AsString(bytes), length)) {
504+
Py_DECREF(bytes);
505+
return 0;
506+
}
507+
Py_DECREF(bytes);
508+
return 1;
476509
} else if (PyObject_IsInstance(value, Code)) {
477510
int start_position,
478511
length_location,
@@ -1072,6 +1105,7 @@ static PyObject* get_value(const char* buffer, int* position, int type) {
10721105

10731106
memcpy(&length, buffer + *position, 4);
10741107
subtype = (unsigned char)buffer[*position + 4];
1108+
10751109
if (subtype == 2) {
10761110
data = PyString_FromStringAndSize(buffer + *position + 9, length - 4);
10771111
} else {
@@ -1080,6 +1114,35 @@ static PyObject* get_value(const char* buffer, int* position, int type) {
10801114
if (!data) {
10811115
return NULL;
10821116
}
1117+
1118+
if (subtype == 3 && UUID) { // Encode as UUID, not Binary
1119+
PyObject* kwargs;
1120+
PyObject* args = PyTuple_New(0);
1121+
if (!args) {
1122+
return NULL;
1123+
}
1124+
kwargs = PyDict_New();
1125+
if (!kwargs) {
1126+
Py_DECREF(args);
1127+
return NULL;
1128+
}
1129+
1130+
assert(length == 16); // UUID should always be 16 bytes
1131+
1132+
PyDict_SetItemString(kwargs, "bytes", data);
1133+
value = PyObject_Call(UUID, args, kwargs);
1134+
1135+
Py_DECREF(args);
1136+
Py_DECREF(kwargs);
1137+
Py_DECREF(data);
1138+
if (!value) {
1139+
return NULL;
1140+
}
1141+
1142+
*position += length + 5;
1143+
break;
1144+
}
1145+
10831146
st = PyInt_FromLong(subtype);
10841147
if (!st) {
10851148
Py_DECREF(data);
@@ -1433,4 +1496,13 @@ PyMODINIT_FUNC init_cbson(void) {
14331496
}
14341497
RECompile = PyObject_GetAttrString(module, "compile");
14351498
Py_DECREF(module);
1499+
1500+
module = PyImport_ImportModule("uuid");
1501+
if (!module) {
1502+
UUID = NULL;
1503+
PyErr_Clear();
1504+
} else {
1505+
UUID = PyObject_GetAttrString(module, "UUID");
1506+
Py_DECREF(module);
1507+
}
14361508
}

pymongo/bson.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,12 @@ def _get_binary(data):
256256
if length2 != length - 4:
257257
raise InvalidBSON("invalid binary (st 2) - lengths don't match!")
258258
length = length2
259+
if subtype == 3:
260+
try:
261+
import uuid
262+
return (uuid.UUID(bytes=data[:length]), data[length:])
263+
except ImportError:
264+
pass
259265
return (Binary(data[:length], subtype), data[length:])
260266

261267

@@ -374,6 +380,16 @@ def _element_to_bson(key, value, check_keys):
374380
name = _make_c_string(key)
375381
if isinstance(value, float):
376382
return "\x01" + name + struct.pack("<d", value)
383+
384+
# Use Binary w/ subtype 3 for UUID instances
385+
try:
386+
import uuid
387+
388+
if isinstance(value, uuid.UUID):
389+
value = Binary(value.bytes, subtype=3)
390+
except ImportError:
391+
pass
392+
377393
if isinstance(value, Binary):
378394
subtype = value.subtype
379395
if subtype == 2:

test/test_bson.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@
2121
import sys
2222
import types
2323

24+
try:
25+
import uuid
26+
should_test_uuid = True
27+
except ImportError:
28+
should_test_uuid = False
29+
30+
from nose.plugins.skip import SkipTest
31+
2432
sys.path[0:0] = [""]
2533

2634
import qcheck
@@ -213,5 +221,17 @@ def test_tuple(self):
213221
self.assertEqual({"tuple": [1, 2]},
214222
BSON.from_dict({"tuple": (1, 2)}).to_dict())
215223

224+
def test_uuid(self):
225+
if not should_test_uuid:
226+
raise SkipTest()
227+
228+
id = uuid.uuid4()
229+
transformed_id = (BSON.from_dict({"id": id})).to_dict()["id"]
230+
231+
self.assert_(isinstance(transformed_id, uuid.UUID))
232+
self.assertEqual(id, transformed_id)
233+
self.assertNotEqual(uuid.uuid4(), transformed_id)
234+
235+
216236
if __name__ == "__main__":
217237
unittest.main()

0 commit comments

Comments
 (0)