Skip to content

Commit 5136bb7

Browse files
author
Luke Lovett
committed
PYTHON-707 Add a BSONInt64 type.
1 parent 55f18f0 commit 5136bb7

File tree

5 files changed

+45
-14
lines changed

5 files changed

+45
-14
lines changed

bson/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from bson.binary import (Binary, OLD_UUID_SUBTYPE,
2626
JAVA_LEGACY, CSHARP_LEGACY)
27+
from bson.bsonint64 import BSONInt64
2728
from bson.code import Code
2829
from bson.dbref import DBRef
2930
from bson.errors import (InvalidBSON,
@@ -280,7 +281,7 @@ def _get_timestamp(
280281
def _get_long(data, position, as_class, tz_aware, uuid_subtype, compile_re):
281282
# Have to cast to long (for python 2.x); on 32-bit unpack may return
282283
# an int.
283-
value = long(struct.unpack("<q", data[position:position + 8])[0])
284+
value = BSONInt64(struct.unpack("<q", data[position:position + 8])[0])
284285
position += 8
285286
return value, position
286287

@@ -416,6 +417,8 @@ def _element_to_bson(key, value, check_keys, uuid_subtype):
416417
return BSONBOO + name + ONE
417418
if value is False:
418419
return BSONBOO + name + ZERO
420+
if isinstance(value, BSONInt64):
421+
return BSONLON + name + struct.pack("<q", value)
419422
if isinstance(value, int):
420423
# TODO this is an ugly way to check for this...
421424
if value > MAX_INT64 or value < MIN_INT64:

bson/_cbsonmodule.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ struct module_state {
5050
PyObject* MaxKey;
5151
PyObject* UTC;
5252
PyTypeObject* REType;
53+
PyObject* BSONInt64;
5354
};
5455

5556
/* The Py_TYPE macro was introduced in CPython 2.6 */
@@ -330,6 +331,7 @@ static int _load_python_objects(PyObject* module) {
330331
_load_object(&state->UTC, "bson.tz_util", "utc") ||
331332
_load_object(&state->RECompile, "re", "compile") ||
332333
_load_object(&state->Regex, "bson.regex", "Regex") ||
334+
_load_object(&state->BSONInt64, "bson.bsonint64", "BSONInt64") ||
333335
_load_object(&state->UUID, "uuid", "UUID")) {
334336
return 1;
335337
}
@@ -2028,16 +2030,17 @@ static PyObject* get_value(PyObject* self, const char* buffer, unsigned* positio
20282030
}
20292031
case 18:
20302032
{
2033+
PyObject* bsonint64_type = _get_object(state->BSONInt64, "bson.bsonint64", "BSONInt64");
2034+
if (!bsonint64_type)
2035+
goto invalid;
20312036
long long ll;
20322037
if (max < 8) {
20332038
goto invalid;
20342039
}
20352040
memcpy(&ll, buffer + *position, 8);
2036-
value = PyLong_FromLongLong(ll);
2037-
if (!value) {
2038-
goto invalid;
2039-
}
2041+
value = PyObject_CallFunction(bsonint64_type, "L", ll);
20402042
*position += 8;
2043+
Py_DECREF(bsonint64_type);
20412044
break;
20422045
}
20432046
case 255:

bson/bsonint64.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from bson.py3compat import PY3
2+
3+
if PY3:
4+
long = int
5+
6+
7+
class BSONInt64(long):
8+
"""Representation of the BSON int64 type.
9+
10+
This is necessary because every integral number is an :class:`int` in
11+
Python 3. Small integral numbers are encoded to BSON int32 by default,
12+
but BSONInt64 numbers will always be encoded to BSON int64.
13+
14+
:Parameters:
15+
- `value`: the numeric value to represent
16+
"""
17+
18+
_type_marker = 18

test/test_bson.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
is_valid,
3131
Regex)
3232
from bson.binary import Binary, UUIDLegacy
33+
from bson.bsonint64 import BSONInt64
3334
from bson.code import Code
3435
from bson.objectid import ObjectId
3536
from bson.dbref import DBRef
@@ -234,10 +235,10 @@ def helper(dict):
234235
helper({})
235236
helper({"test": u("hello")})
236237
self.assertTrue(isinstance(BSON.encode({"hello": "world"})
237-
.decode()["hello"],
238+
.decode()["hello"],
238239
text_type))
239240
helper({"mike": -10120})
240-
helper({"long": long(10)})
241+
helper({"long": BSONInt64(10)})
241242
helper({"really big long": 2147483648})
242243
helper({u("hello"): 0.0013109})
243244
helper({"something": True})
@@ -372,18 +373,16 @@ def test_overflow(self):
372373
{"x": long(-9223372036854775809)})
373374

374375
def test_small_long_encode_decode(self):
375-
if PY3:
376-
raise SkipTest("No long type in Python 3.")
377-
378376
encoded1 = BSON.encode({'x': 256})
379377
decoded1 = BSON.decode(encoded1)['x']
380378
self.assertEqual(256, decoded1)
381379
self.assertEqual(type(256), type(decoded1))
382380

383-
encoded2 = BSON.encode({'x': long(256)})
381+
encoded2 = BSON.encode({'x': BSONInt64(256)})
384382
decoded2 = BSON.decode(encoded2)['x']
385-
self.assertEqual(long(256), decoded2)
386-
self.assertEqual(type(long(256)), type(decoded2))
383+
expected = BSONInt64(256)
384+
self.assertEqual(expected, decoded2)
385+
self.assertEqual(type(expected), type(decoded2))
387386

388387
self.assertNotEqual(type(decoded1), type(decoded2))
389388

test/test_database.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
sys.path[0:0] = [""]
2323

24+
from bson.bsonint64 import BSONInt64
2425
from bson.code import Code
2526
from bson.regex import Regex
2627
from bson.dbref import DBRef
@@ -742,7 +743,14 @@ def test_long(self):
742743
db = self.client.pymongo_test
743744
db.test.remove({})
744745
db.test.save({"x": long(9223372036854775807)})
745-
self.assertEqual(long(9223372036854775807), db.test.find_one()["x"])
746+
retrieved = db.test.find_one()['x']
747+
self.assertEqual(BSONInt64(9223372036854775807), retrieved)
748+
self.assertIsInstance(retrieved, BSONInt64)
749+
db.test.remove({})
750+
db.test.insert({"x": BSONInt64(1)})
751+
retrieved = db.test.find_one()['x']
752+
self.assertEqual(BSONInt64(1), retrieved)
753+
self.assertIsInstance(retrieved, BSONInt64)
746754

747755
def test_remove(self):
748756
db = self.client.pymongo_test

0 commit comments

Comments
 (0)