PYTHON-1068 - Support for BSON Decimal128
This commit is contained in:
parent
e43952d2a5
commit
82db71e1e8
@ -34,6 +34,7 @@ from bson.code import Code
|
||||
from bson.codec_options import (
|
||||
CodecOptions, DEFAULT_CODEC_OPTIONS, _raw_document_class)
|
||||
from bson.dbref import DBRef
|
||||
from bson.decimal128 import Decimal128
|
||||
from bson.errors import (InvalidBSON,
|
||||
InvalidDocument,
|
||||
InvalidStringData)
|
||||
@ -82,6 +83,7 @@ BSONCWS = b"\x0F" # Javascript code with scope
|
||||
BSONINT = b"\x10" # 32bit int
|
||||
BSONTIM = b"\x11" # Timestamp
|
||||
BSONLON = b"\x12" # 64bit int
|
||||
BSONDEC = b"\x13" # Decimal128
|
||||
BSONMIN = b"\xFF" # Min key
|
||||
BSONMAX = b"\x7F" # Max key
|
||||
|
||||
@ -289,6 +291,12 @@ def _get_int64(data, position, dummy0, dummy1, dummy2):
|
||||
return Int64(_UNPACK_LONG(data[position:end])[0]), end
|
||||
|
||||
|
||||
def _get_decimal128(data, position, dummy0, dummy1, dummy2):
|
||||
"""Decode a BSON decimal128 to bson.decimal128.Decimal128."""
|
||||
end = position + 16
|
||||
return Decimal128.from_bid(data[position:end]), end
|
||||
|
||||
|
||||
# Each decoder function's signature is:
|
||||
# - data: bytes
|
||||
# - position: int, beginning of object in 'data' to decode
|
||||
@ -313,6 +321,7 @@ _ELEMENT_GETTER = {
|
||||
BSONINT: _get_int,
|
||||
BSONTIM: _get_timestamp,
|
||||
BSONLON: _get_int64,
|
||||
BSONDEC: _get_decimal128,
|
||||
BSONMIN: lambda v, w, x, y, z: (MinKey(), w),
|
||||
BSONMAX: lambda v, w, x, y, z: (MaxKey(), w)}
|
||||
|
||||
@ -619,6 +628,11 @@ def _encode_long(name, value, dummy0, dummy1):
|
||||
raise OverflowError("BSON can only handle up to 8-byte ints")
|
||||
|
||||
|
||||
def _encode_decimal128(name, value, dummy0, dummy1):
|
||||
"""Encode bson.decimal128.Decimal128."""
|
||||
return b"\x13" + name + value.bid
|
||||
|
||||
|
||||
def _encode_minkey(name, dummy0, dummy1, dummy2):
|
||||
"""Encode bson.min_key.MinKey."""
|
||||
return b"\xFF" + name
|
||||
@ -659,6 +673,7 @@ _ENCODERS = {
|
||||
SON: _encode_mapping,
|
||||
Timestamp: _encode_timestamp,
|
||||
UUIDLegacy: _encode_binary,
|
||||
Decimal128: _encode_decimal128,
|
||||
# Special case. This will never be looked up directly.
|
||||
collections.Mapping: _encode_mapping,
|
||||
}
|
||||
|
||||
@ -50,6 +50,7 @@ struct module_state {
|
||||
PyObject* UTC;
|
||||
PyTypeObject* REType;
|
||||
PyObject* BSONInt64;
|
||||
PyObject* Decimal128;
|
||||
PyObject* Mapping;
|
||||
PyObject* CodecOptions;
|
||||
};
|
||||
@ -359,6 +360,7 @@ static int _load_python_objects(PyObject* module) {
|
||||
_load_object(&state->UTC, "bson.tz_util", "utc") ||
|
||||
_load_object(&state->Regex, "bson.regex", "Regex") ||
|
||||
_load_object(&state->BSONInt64, "bson.int64", "Int64") ||
|
||||
_load_object(&state->Decimal128, "bson.decimal128", "Decimal128") ||
|
||||
_load_object(&state->UUID, "uuid", "UUID") ||
|
||||
_load_object(&state->Mapping, "collections", "Mapping") ||
|
||||
_load_object(&state->CodecOptions, "bson.codec_options", "CodecOptions")) {
|
||||
@ -894,6 +896,31 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
|
||||
*(buffer_get_buffer(buffer) + type_byte) = 0x12;
|
||||
return 1;
|
||||
}
|
||||
case 19:
|
||||
{
|
||||
/* Decimal128 */
|
||||
const char* data;
|
||||
PyObject* pystring = PyObject_GetAttrString(value, "bid");
|
||||
if (!pystring) {
|
||||
return 0;
|
||||
}
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
data = PyBytes_AsString(pystring);
|
||||
#else
|
||||
data = PyString_AsString(pystring);
|
||||
#endif
|
||||
if (!data) {
|
||||
Py_DECREF(pystring);
|
||||
return 0;
|
||||
}
|
||||
if (!buffer_write_bytes(buffer, data, 16)) {
|
||||
Py_DECREF(pystring);
|
||||
return 0;
|
||||
}
|
||||
Py_DECREF(pystring);
|
||||
*(buffer_get_buffer(buffer) + type_byte) = 0x13;
|
||||
return 1;
|
||||
}
|
||||
case 100:
|
||||
{
|
||||
/* DBRef */
|
||||
@ -2387,6 +2414,29 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer,
|
||||
Py_DECREF(bson_int64_type);
|
||||
break;
|
||||
}
|
||||
case 19:
|
||||
{
|
||||
PyObject* dec128;
|
||||
if (max < 16) {
|
||||
goto invalid;
|
||||
}
|
||||
if ((dec128 = _get_object(state->Decimal128,
|
||||
"bson.decimal128",
|
||||
"Decimal128"))) {
|
||||
value = PyObject_CallMethod(dec128,
|
||||
"from_bid",
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
"y#",
|
||||
#else
|
||||
"s#",
|
||||
#endif
|
||||
buffer + *position,
|
||||
16);
|
||||
Py_DECREF(dec128);
|
||||
}
|
||||
*position += 16;
|
||||
break;
|
||||
}
|
||||
case 255:
|
||||
{
|
||||
PyObject* minkey_type = _get_object(state->MinKey, "bson.min_key", "MinKey");
|
||||
|
||||
281
bson/decimal128.py
Normal file
281
bson/decimal128.py
Normal file
@ -0,0 +1,281 @@
|
||||
# Copyright 2016 MongoDB, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Tools for working with 128-bit IEEE 754-2008 decimal floating point numbers.
|
||||
"""
|
||||
|
||||
import decimal
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from bson.py3compat import (PY3 as _PY3,
|
||||
string_type as _string_type)
|
||||
|
||||
|
||||
if _PY3:
|
||||
_from_bytes = int.from_bytes # pylint: disable=no-member, invalid-name
|
||||
else:
|
||||
import binascii
|
||||
def _from_bytes(value, dummy, _int=int, _hexlify=binascii.hexlify):
|
||||
"An implementation of int.from_bytes for python 2.x."
|
||||
return _int(_hexlify(value), 16)
|
||||
|
||||
if sys.version_info[:2] == (2, 6):
|
||||
def _bit_length(num):
|
||||
"""bit_length for python 2.6"""
|
||||
if num:
|
||||
# bin() was new in 2.6. Note that this won't work
|
||||
# for values less than 0, which we never have here.
|
||||
return len(bin(num)) - 2
|
||||
# bit_length(0) is 0, but len(bin(0)) - 2 is 1
|
||||
return 0
|
||||
else:
|
||||
def _bit_length(num):
|
||||
"""bit_length for python >= 2.7"""
|
||||
# num could be int or long in python 2.7
|
||||
return num.bit_length()
|
||||
|
||||
|
||||
_PACK_64 = struct.Struct("<Q").pack
|
||||
_UNPACK_64 = struct.Struct("<Q").unpack
|
||||
|
||||
_EXPONENT_MASK = 3 << 61
|
||||
_EXPONENT_BIAS = 6176
|
||||
_EXPONENT_MAX = 6111
|
||||
_EXPONENT_MIN = -6176
|
||||
_MAX_BIT_LENGTH = 113
|
||||
|
||||
_INF = 0x7800000000000000
|
||||
_NAN = 0x7c00000000000000
|
||||
_SNAN = 0x7e00000000000000
|
||||
_SIGN = 0x8000000000000000
|
||||
|
||||
_NINF = (_INF + _SIGN, 0)
|
||||
_PINF = (_INF, 0)
|
||||
_NNAN = (_NAN + _SIGN, 0)
|
||||
_PNAN = (_NAN, 0)
|
||||
_NSNAN = (_SNAN + _SIGN, 0)
|
||||
_PSNAN = (_SNAN, 0)
|
||||
|
||||
|
||||
def _decimal_to_128(value):
|
||||
"""Converts a decimal.Decimal to BID (high bits, low bits).
|
||||
|
||||
:Parameters:
|
||||
- `value`: An instance of decimal.Decimal
|
||||
"""
|
||||
if value.is_infinite():
|
||||
return _NINF if value.is_signed() else _PINF
|
||||
|
||||
sign, digits, exponent = value.as_tuple()
|
||||
|
||||
if value.is_nan():
|
||||
if digits:
|
||||
raise ValueError("NaN with debug payload is not supported")
|
||||
if value.is_snan():
|
||||
return _NSNAN if value.is_signed() else _PSNAN
|
||||
return _NNAN if value.is_signed() else _PNAN
|
||||
|
||||
significand = int("".join([str(digit) for digit in digits]))
|
||||
bit_length = _bit_length(significand)
|
||||
if exponent > _EXPONENT_MAX or exponent < _EXPONENT_MIN:
|
||||
raise ValueError("Exponent is out of range for "
|
||||
"Decimal128 encoding %d" % (exponent,))
|
||||
if bit_length > _MAX_BIT_LENGTH:
|
||||
raise ValueError("Unscaled value is out of range for "
|
||||
"Decimal128 encoding %d" % (significand,))
|
||||
|
||||
high = 0
|
||||
low = 0
|
||||
for i in range(min(64, bit_length)):
|
||||
if significand & (1 << i):
|
||||
low |= 1 << i
|
||||
|
||||
for i in range(64, bit_length):
|
||||
if significand & (1 << i):
|
||||
high |= 1 << (i - 64)
|
||||
|
||||
biased_exponent = exponent + _EXPONENT_BIAS
|
||||
|
||||
if high >> 49 == 1:
|
||||
high = high & 0x7fffffffffff
|
||||
high |= _EXPONENT_MASK
|
||||
high |= (biased_exponent & 0x3fff) << 47
|
||||
else:
|
||||
high |= biased_exponent << 49
|
||||
|
||||
if sign:
|
||||
high |= _SIGN
|
||||
|
||||
return high, low
|
||||
|
||||
|
||||
class Decimal128(object):
|
||||
"""BSON Decimal128 type::
|
||||
|
||||
>>> Decimal128(Decimal("0.0005"))
|
||||
Decimal128('0.0005')
|
||||
>>> Decimal128("0.0005")
|
||||
Decimal128('0.0005')
|
||||
>>> Decimal128((3474527112516337664, 5))
|
||||
Decimal128('0.0005')
|
||||
|
||||
:Parameters:
|
||||
- `value`: An instance of :class:`decimal.Decimal`, string, or tuple of
|
||||
(high bits, low bits) from Binary Integer Decimal (BID) format.
|
||||
|
||||
.. note:: To match the behavior of MongoDB's Decimal128 implementation
|
||||
str(Decimal(value)) may not match str(Decimal128(value)) for NaN values::
|
||||
|
||||
>>> Decimal128(Decimal('NaN'))
|
||||
Decimal128('NaN')
|
||||
>>> Decimal128(Decimal('-NaN'))
|
||||
Decimal128('NaN')
|
||||
>>> Decimal128(Decimal('sNaN'))
|
||||
Decimal128('NaN')
|
||||
>>> Decimal128(Decimal('-sNaN'))
|
||||
Decimal128('NaN')
|
||||
|
||||
However, :meth:`~Decimal128.to_decimal` will return the exact value::
|
||||
|
||||
>>> Decimal128(Decimal('NaN')).to_decimal()
|
||||
Decimal('NaN')
|
||||
>>> Decimal128(Decimal('-NaN')).to_decimal()
|
||||
Decimal('-NaN')
|
||||
>>> Decimal128(Decimal('sNaN')).to_decimal()
|
||||
Decimal('sNaN')
|
||||
>>> Decimal128(Decimal('-sNaN')).to_decimal()
|
||||
Decimal('-sNaN')
|
||||
|
||||
Two instances of :class:`Decimal128` compare equal if their Binary
|
||||
Integer Decimal encodings are equal::
|
||||
|
||||
>>> Decimal128('NaN') == Decimal128('NaN')
|
||||
True
|
||||
>>> Decimal128('NaN').bid == Decimal128('NaN').bid
|
||||
True
|
||||
|
||||
This differs from :class:`decimal.Decimal` comparisons for NaN::
|
||||
|
||||
>>> Decimal('NaN') == Decimal('NaN')
|
||||
False
|
||||
"""
|
||||
__slots__ = ('__high', '__low')
|
||||
|
||||
_type_marker = 19
|
||||
|
||||
def __init__(self, value):
|
||||
if isinstance(value, _string_type):
|
||||
# Really? decimal.Decimal doesn't care...
|
||||
if value.startswith(' ') or value.endswith(' '):
|
||||
raise ValueError("leading or trailing whitespace")
|
||||
try:
|
||||
dec = decimal.Decimal(value)
|
||||
except decimal.InvalidOperation as exc:
|
||||
raise ValueError(str(exc))
|
||||
self.__high, self.__low = _decimal_to_128(dec)
|
||||
elif isinstance(value, decimal.Decimal):
|
||||
self.__high, self.__low = _decimal_to_128(value)
|
||||
elif isinstance(value, (list, tuple)):
|
||||
if len(value) != 2:
|
||||
raise ValueError('Invalid size for creation of Decimal128 '
|
||||
'from list or tuple. Must have exactly 2 '
|
||||
'elements.')
|
||||
self.__high, self.__low = value
|
||||
else:
|
||||
raise TypeError("Cannot convert %r to Decimal128" % (value,))
|
||||
|
||||
def to_decimal(self):
|
||||
"""Returns an instance of :class:`decimal.Decimal` for this
|
||||
:class:`Decimal128`.
|
||||
"""
|
||||
high = self.__high
|
||||
low = self.__low
|
||||
sign = 1 if (high & _SIGN) else 0
|
||||
|
||||
if (high & _SNAN) == _SNAN:
|
||||
return decimal.Decimal((sign, (), 'N'))
|
||||
elif (high & _NAN) == _NAN:
|
||||
return decimal.Decimal((sign, (), 'n'))
|
||||
elif (high & _INF) == _INF:
|
||||
return decimal.Decimal((sign, (0,), 'F'))
|
||||
|
||||
if (high & _EXPONENT_MASK) == _EXPONENT_MASK:
|
||||
exponent = ((high & 0x1fffe00000000000) >> 47) - _EXPONENT_BIAS
|
||||
return decimal.Decimal((sign, (0,), exponent))
|
||||
else:
|
||||
exponent = ((high & 0x7fff800000000000) >> 49) - _EXPONENT_BIAS
|
||||
|
||||
arr = bytearray(15)
|
||||
mask = 0x00000000000000ff
|
||||
for i in range(14, 6, -1):
|
||||
arr[i] = (low & mask) >> ((14 - i) << 3)
|
||||
mask = mask << 8
|
||||
|
||||
mask = 0x00000000000000ff
|
||||
for i in range(6, 0, -1):
|
||||
arr[i] = (high & mask) >> ((6 - i) << 3)
|
||||
mask = mask << 8
|
||||
|
||||
mask = 0x0001000000000000
|
||||
arr[0] = (high & mask) >> 48
|
||||
|
||||
# Have to convert bytearray to bytes for python 2.6.
|
||||
digits = [int(digit) for digit in str(_from_bytes(bytes(arr), 'big'))]
|
||||
|
||||
return decimal.Decimal((sign, digits, exponent))
|
||||
|
||||
@classmethod
|
||||
def from_bid(cls, value):
|
||||
"""Create an instance of :class:`Decimal128` from Binary Integer
|
||||
Decimal string.
|
||||
|
||||
:Parameters:
|
||||
- `value`: 16 byte string (128-bit IEEE 754-2008 decimal floating
|
||||
point in Binary Integer Decimal (BID) format).
|
||||
"""
|
||||
if not isinstance(value, bytes):
|
||||
raise TypeError("value must be an instance of bytes")
|
||||
if len(value) != 16:
|
||||
raise ValueError("value must be exactly 16 bytes")
|
||||
return cls((_UNPACK_64(value[8:])[0], _UNPACK_64(value[:8])[0]))
|
||||
|
||||
@property
|
||||
def bid(self):
|
||||
"""The Binary Integer Decimal (BID) encoding of this instance."""
|
||||
return _PACK_64(self.__low) + _PACK_64(self.__high)
|
||||
|
||||
def __str__(self):
|
||||
dec = self.to_decimal()
|
||||
if dec.is_nan():
|
||||
# Required by the drivers spec to match MongoDB behavior.
|
||||
return "NaN"
|
||||
return str(dec)
|
||||
|
||||
def __repr__(self):
|
||||
return "Decimal128('%s')" % (str(self),)
|
||||
|
||||
def __setstate__(self, value):
|
||||
self.__high, self.__low = value
|
||||
|
||||
def __getstate__(self):
|
||||
return self.__high, self.__low
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Decimal128):
|
||||
return self.bid == other.bid
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
@ -96,6 +96,7 @@ from bson.code import Code
|
||||
from bson.codec_options import CodecOptions
|
||||
from bson.errors import InvalidDatetime
|
||||
from bson.dbref import DBRef
|
||||
from bson.decimal128 import Decimal128
|
||||
from bson.int64 import Int64
|
||||
from bson.max_key import MaxKey
|
||||
from bson.min_key import MinKey
|
||||
@ -356,6 +357,8 @@ def object_hook(dct, json_options=DEFAULT_JSON_OPTIONS):
|
||||
if "$timestamp" in dct:
|
||||
tsp = dct["$timestamp"]
|
||||
return Timestamp(tsp["t"], tsp["i"])
|
||||
if "$numberDecimal" in dct:
|
||||
return Decimal128(dct["$numberDecimal"])
|
||||
return dct
|
||||
|
||||
|
||||
@ -440,4 +443,6 @@ def default(obj, json_options=DEFAULT_JSON_OPTIONS):
|
||||
('$type', "%02x" % subtype)])
|
||||
else:
|
||||
return {"$uuid": obj.hex}
|
||||
if isinstance(obj, Decimal128):
|
||||
return {"$numberDecimal": str(obj)}
|
||||
raise TypeError("%r is not JSON serializable" % obj)
|
||||
|
||||
4
doc/api/bson/decimal128.rst
Normal file
4
doc/api/bson/decimal128.rst
Normal file
@ -0,0 +1,4 @@
|
||||
:mod:`decimal128` -- Support for BSON Decimal128
|
||||
================================================
|
||||
.. automodule:: bson.decimal128
|
||||
:members:
|
||||
@ -14,6 +14,7 @@ Sub-modules:
|
||||
code
|
||||
codec_options
|
||||
dbref
|
||||
decimal128
|
||||
errors
|
||||
int64
|
||||
json_util
|
||||
|
||||
490
test/decimal/decimal128.json
Normal file
490
test/decimal/decimal128.json
Normal file
@ -0,0 +1,490 @@
|
||||
{
|
||||
"description": "Decimal128",
|
||||
"bson_type": "0x13",
|
||||
"test_key": "d",
|
||||
"valid": [
|
||||
{
|
||||
"description": "Special - Canonical NaN",
|
||||
"subject": "180000001364000000000000000000000000000000007C00",
|
||||
"string": "NaN",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Special - Negative NaN",
|
||||
"subject": "18000000136400000000000000000000000000000000FC00",
|
||||
"string": "NaN",
|
||||
"from_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Special - Canonical SNaN",
|
||||
"subject": "180000001364000000000000000000000000000000007E00",
|
||||
"string": "NaN",
|
||||
"from_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Special - Negative SNaN",
|
||||
"subject": "18000000136400000000000000000000000000000000FE00",
|
||||
"string": "NaN",
|
||||
"from_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Special - NaN with a payload",
|
||||
"subject": "180000001364001200000000000000000000000000007E00",
|
||||
"string": "NaN",
|
||||
"from_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Special - Canonical Positive Infinity",
|
||||
"subject": "180000001364000000000000000000000000000000007800",
|
||||
"string": "Infinity",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Special - Canonical Negative Infinity",
|
||||
"subject": "18000000136400000000000000000000000000000000F800",
|
||||
"string": "-Infinity",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Special - Invalid representation treated as 0",
|
||||
"subject": "180000001364000000000000000000000000000000106C00",
|
||||
"string": "0",
|
||||
"from_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Special - Invalid representation treated as -0",
|
||||
"subject": "18000000136400DCBA9876543210DEADBEEF00000010EC00",
|
||||
"string": "-0",
|
||||
"from_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Special - Invalid representation treated as 0E3",
|
||||
"subject": "18000000136400FFFFFFFFFFFFFFFFFFFFFFFFFFFF116C00",
|
||||
"string": "0E+3",
|
||||
"from_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+3\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Regular - Adjusted Exponent Limit",
|
||||
"subject": "18000000136400F2AF967ED05C82DE3297FF6FDE3CF22F00",
|
||||
"string": "0.000001234567890123456789012345678901234",
|
||||
"extjson": "{\"d\": { \"$numberDecimal\": \"0.000001234567890123456789012345678901234\" }}"
|
||||
},
|
||||
{
|
||||
"description": "Regular - Smallest",
|
||||
"subject": "18000000136400D204000000000000000000000000343000",
|
||||
"string": "0.001234",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"0.001234\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Regular - Smallest with Trailing Zeros",
|
||||
"subject": "1800000013640040EF5A07000000000000000000002A3000",
|
||||
"string": "0.00123400000",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00123400000\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Regular - 0.1",
|
||||
"subject": "1800000013640001000000000000000000000000003E3000",
|
||||
"string": "0.1",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Regular - 0.1234567890123456789012345678901234",
|
||||
"subject": "18000000136400F2AF967ED05C82DE3297FF6FDE3CFC2F00",
|
||||
"string": "0.1234567890123456789012345678901234",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1234567890123456789012345678901234\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Regular - 0",
|
||||
"subject": "180000001364000000000000000000000000000000403000",
|
||||
"string": "0",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Regular - -0",
|
||||
"subject": "18000000136400000000000000000000000000000040B000",
|
||||
"string": "-0",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Regular - -0.0",
|
||||
"subject": "1800000013640000000000000000000000000000003EB000",
|
||||
"string": "-0.0",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Regular - 2",
|
||||
"subject": "180000001364000200000000000000000000000000403000",
|
||||
"string": "2",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"2\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Regular - 2.000",
|
||||
"subject": "18000000136400D0070000000000000000000000003A3000",
|
||||
"string": "2.000",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"2.000\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Regular - Largest",
|
||||
"subject": "18000000136400F2AF967ED05C82DE3297FF6FDE3C403000",
|
||||
"string": "1234567890123456789012345678901234",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"1234567890123456789012345678901234\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Scientific - Tiniest",
|
||||
"subject": "18000000136400FFFFFFFF638E8D37C087ADBE09ED010000",
|
||||
"string": "9.999999999999999999999999999999999E-6143",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"9.999999999999999999999999999999999E-6143\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Scientific - Tiny",
|
||||
"subject": "180000001364000100000000000000000000000000000000",
|
||||
"string": "1E-6176",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6176\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Scientific - Negative Tiny",
|
||||
"subject": "180000001364000100000000000000000000000000008000",
|
||||
"string": "-1E-6176",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6176\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Scientific - Adjusted Exponent Limit",
|
||||
"subject": "18000000136400F2AF967ED05C82DE3297FF6FDE3CF02F00",
|
||||
"string": "1.234567890123456789012345678901234E-7",
|
||||
"extjson": "{\"d\": { \"$numberDecimal\": \"1.234567890123456789012345678901234E-7\" }}"
|
||||
},
|
||||
{
|
||||
"description": "Scientific - Fractional",
|
||||
"subject": "1800000013640064000000000000000000000000002CB000",
|
||||
"string": "-1.00E-8",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.00E-8\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Scientific - 0 with Exponent",
|
||||
"subject": "180000001364000000000000000000000000000000205F00",
|
||||
"string": "0E+6000",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6000\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Scientific - 0 with Negative Exponent",
|
||||
"subject": "1800000013640000000000000000000000000000007A2B00",
|
||||
"string": "0E-611",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-611\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Scientific - No Decimal with Signed Exponent",
|
||||
"subject": "180000001364000100000000000000000000000000463000",
|
||||
"string": "1E+3",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+3\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Scientific - Trailing Zero",
|
||||
"subject": "180000001364001A04000000000000000000000000423000",
|
||||
"string": "1.050E+4",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"1.050E+4\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Scientific - With Decimal",
|
||||
"subject": "180000001364006900000000000000000000000000423000",
|
||||
"string": "1.05E+3",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"1.05E+3\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Scientific - Full",
|
||||
"subject": "18000000136400FFFFFFFFFFFFFFFFFFFFFFFFFFFF403000",
|
||||
"string": "5192296858534827628530496329220095",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"5192296858534827628530496329220095\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Scientific - Large",
|
||||
"subject": "18000000136400000000000A5BC138938D44C64D31FE5F00",
|
||||
"string": "1.000000000000000000000000000000000E+6144",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000000E+6144\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Scientific - Largest",
|
||||
"subject": "18000000136400FFFFFFFF638E8D37C087ADBE09EDFF5F00",
|
||||
"string": "9.999999999999999999999999999999999E+6144",
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"9.999999999999999999999999999999999E+6144\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - Exponent Normalization",
|
||||
"subject": "1800000013640064000000000000000000000000002CB000",
|
||||
"string": "-1.00E-8",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"-100E-10\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - Unsigned Positive Exponent",
|
||||
"subject": "180000001364000100000000000000000000000000463000",
|
||||
"string": "1E+3",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"1E3\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - Lowercase Exponent Identifier",
|
||||
"subject": "180000001364000100000000000000000000000000463000",
|
||||
"string": "1E+3",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"1e+3\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - Long Significand with Exponent",
|
||||
"subject": "1800000013640079D9E0F9763ADA429D0200000000583000",
|
||||
"string": "1.2345689012345789012345E+34",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"12345689012345789012345E+12\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - Positive Sign",
|
||||
"subject": "18000000136400F2AF967ED05C82DE3297FF6FDE3C403000",
|
||||
"string": "1234567890123456789012345678901234",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"+1234567890123456789012345678901234\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - Long Decimal String",
|
||||
"subject": "180000001364000100000000000000000000000000722800",
|
||||
"string": "1E-999",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \".000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - nan",
|
||||
"subject": "180000001364000000000000000000000000000000007C00",
|
||||
"string": "NaN",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"nan\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - nAn",
|
||||
"subject": "180000001364000000000000000000000000000000007C00",
|
||||
"string": "NaN",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"nAn\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - +infinity",
|
||||
"subject": "180000001364000000000000000000000000000000007800",
|
||||
"string": "Infinity",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"+infinity\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - infinity",
|
||||
"subject": "180000001364000000000000000000000000000000007800",
|
||||
"string": "Infinity",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"infinity\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - infiniTY",
|
||||
"subject": "180000001364000000000000000000000000000000007800",
|
||||
"string": "Infinity",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"infiniTY\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - infinity",
|
||||
"subject": "180000001364000000000000000000000000000000007800",
|
||||
"string": "Infinity",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"infinity\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - inFinity",
|
||||
"subject": "180000001364000000000000000000000000000000007800",
|
||||
"string": "Infinity",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"inFinity\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - -infinity",
|
||||
"subject": "18000000136400000000000000000000000000000000F800",
|
||||
"string": "-Infinity",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"-infinity\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - -infiniTy",
|
||||
"subject": "18000000136400000000000000000000000000000000F800",
|
||||
"string": "-Infinity",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"-infiniTy\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - -Infinity",
|
||||
"subject": "18000000136400000000000000000000000000000000F800",
|
||||
"string": "-Infinity",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - -infinity",
|
||||
"subject": "18000000136400000000000000000000000000000000F800",
|
||||
"string": "-Infinity",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"-inf\"}}"
|
||||
},
|
||||
{
|
||||
"description": "Non-Canonical Parsing - -inFinity",
|
||||
"subject": "18000000136400000000000000000000000000000000F800",
|
||||
"string": "-Infinity",
|
||||
"to_extjson": false,
|
||||
"extjson": "{\"d\" : {\"$numberDecimal\" : \"-inFinity\"}}"
|
||||
}
|
||||
],
|
||||
"parseErrors": [
|
||||
{
|
||||
"description": "Too many significand digits",
|
||||
"subject": "100000000000000000000000000000000000000000000000000000000001"
|
||||
},
|
||||
{
|
||||
"description": "Too many significand digits",
|
||||
"subject": "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
{
|
||||
"description": "Too many significand digits",
|
||||
"subject": ".100000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
{
|
||||
"description": "Incomplete Exponent",
|
||||
"subject": "1e"
|
||||
},
|
||||
{
|
||||
"description": "Exponent at the beginning",
|
||||
"subject": "E01"
|
||||
},
|
||||
{
|
||||
"description": "Exponent too large",
|
||||
"subject": "1E6112"
|
||||
},
|
||||
{
|
||||
"description": "Exponent too small",
|
||||
"subject": "1E-6177"
|
||||
},
|
||||
{
|
||||
"description": "Just a decimal place",
|
||||
"subject": "."
|
||||
},
|
||||
{
|
||||
"description": "2 decimal places",
|
||||
"subject": "..3"
|
||||
},
|
||||
{
|
||||
"description": "2 decimal places",
|
||||
"subject": ".13.3"
|
||||
},
|
||||
{
|
||||
"description": "2 decimal places",
|
||||
"subject": "1..3"
|
||||
},
|
||||
{
|
||||
"description": "2 decimal places",
|
||||
"subject": "1.3.4"
|
||||
},
|
||||
{
|
||||
"description": "2 decimal places",
|
||||
"subject": "1.34."
|
||||
},
|
||||
{
|
||||
"description": "Decimal with no digits",
|
||||
"subject": ".e"
|
||||
},
|
||||
{
|
||||
"description": "2 signs",
|
||||
"subject": "+-32.4"
|
||||
},
|
||||
{
|
||||
"description": "2 signs",
|
||||
"subject": "-+32.4"
|
||||
},
|
||||
{
|
||||
"description": "2 negative signs",
|
||||
"subject": "--32.4"
|
||||
},
|
||||
{
|
||||
"description": "2 negative signs",
|
||||
"subject": "-32.-4"
|
||||
},
|
||||
{
|
||||
"description": "End in negative sign",
|
||||
"subject": "32.0-"
|
||||
},
|
||||
{
|
||||
"description": "2 negative signs",
|
||||
"subject": "32.4E--21"
|
||||
},
|
||||
{
|
||||
"description": "2 negative signs",
|
||||
"subject": "32.4E-2-1"
|
||||
},
|
||||
{
|
||||
"description": "2 signs",
|
||||
"subject": "32.4E+-21"
|
||||
},
|
||||
{
|
||||
"description": "Empty string",
|
||||
"subject": ""
|
||||
},
|
||||
{
|
||||
"description": "leading white space positive number",
|
||||
"subject": " 1"
|
||||
},
|
||||
{
|
||||
"description": "leading white space negative number",
|
||||
"subject": " -1"
|
||||
},
|
||||
{
|
||||
"description": "trailing white space",
|
||||
"subject": "1 "
|
||||
},
|
||||
{
|
||||
"description": "Invalid",
|
||||
"subject": "E"
|
||||
},
|
||||
{
|
||||
"description": "Invalid",
|
||||
"subject": "invalid"
|
||||
},
|
||||
{
|
||||
"description": "Invalid",
|
||||
"subject": "i"
|
||||
},
|
||||
{
|
||||
"description": "Invalid",
|
||||
"subject": "in"
|
||||
},
|
||||
{
|
||||
"description": "Invalid",
|
||||
"subject": "-in"
|
||||
},
|
||||
{
|
||||
"description": "Invalid",
|
||||
"subject": "Na"
|
||||
},
|
||||
{
|
||||
"description": "Invalid",
|
||||
"subject": "-Na"
|
||||
},
|
||||
{
|
||||
"description": "Invalid",
|
||||
"subject": "1.23abc"
|
||||
},
|
||||
{
|
||||
"description": "Invalid",
|
||||
"subject": "1.23abcE+02"
|
||||
},
|
||||
{
|
||||
"description": "Invalid",
|
||||
"subject": "1.23E+0aabs2"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -408,13 +408,13 @@ class TestBSON(unittest.TestCase):
|
||||
|
||||
def test_unknown_type(self):
|
||||
# Repr value differs with major python version
|
||||
part = "type %r for fieldname 'foo'" % (b'\x13',)
|
||||
part = "type %r for fieldname 'foo'" % (b'\x14',)
|
||||
docs = [
|
||||
b'\x0e\x00\x00\x00\x13foo\x00\x01\x00\x00\x00\x00',
|
||||
(b'\x16\x00\x00\x00\x04foo\x00\x0c\x00\x00\x00\x130'
|
||||
b'\x0e\x00\x00\x00\x14foo\x00\x01\x00\x00\x00\x00',
|
||||
(b'\x16\x00\x00\x00\x04foo\x00\x0c\x00\x00\x00\x140'
|
||||
b'\x00\x01\x00\x00\x00\x00\x00'),
|
||||
(b' \x00\x00\x00\x04bar\x00\x16\x00\x00\x00\x030\x00\x0e\x00\x00'
|
||||
b'\x00\x13foo\x00\x01\x00\x00\x00\x00\x00\x00')]
|
||||
b'\x00\x14foo\x00\x01\x00\x00\x00\x00\x00\x00')]
|
||||
for bs in docs:
|
||||
try:
|
||||
bson.BSON(bs).decode()
|
||||
|
||||
95
test/test_decimal128.py
Normal file
95
test/test_decimal128.py
Normal file
@ -0,0 +1,95 @@
|
||||
# Copyright 2016 MongoDB, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Tests for Decimal128."""
|
||||
|
||||
import codecs
|
||||
import json
|
||||
import os.path
|
||||
import pickle
|
||||
import sys
|
||||
|
||||
from binascii import unhexlify
|
||||
from decimal import Decimal
|
||||
|
||||
sys.path[0:0] = [""]
|
||||
|
||||
from bson import BSON
|
||||
from bson.decimal128 import Decimal128
|
||||
from bson.json_util import dumps, loads
|
||||
from bson.py3compat import b
|
||||
from test import client_context, unittest
|
||||
|
||||
class TestDecimal128(unittest.TestCase):
|
||||
|
||||
def test_round_trip(self):
|
||||
coll = client_context.client.pymongo_test.test
|
||||
coll.drop()
|
||||
|
||||
dec128 = Decimal128.from_bid(
|
||||
b'\x00@cR\xbf\xc6\x01\x00\x00\x00\x00\x00\x00\x00\x1c0')
|
||||
coll.insert_one({'dec128': dec128})
|
||||
doc = coll.find_one({'dec128': dec128})
|
||||
self.assertIsNotNone(doc)
|
||||
self.assertEqual(doc['dec128'], dec128)
|
||||
|
||||
def test_pickle(self):
|
||||
dec128 = Decimal128.from_bid(
|
||||
b'\x00@cR\xbf\xc6\x01\x00\x00\x00\x00\x00\x00\x00\x1c0')
|
||||
for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
|
||||
pkl = pickle.dumps(dec128, protocol=protocol)
|
||||
self.assertEqual(dec128, pickle.loads(pkl))
|
||||
|
||||
def test_special(self):
|
||||
dnan = Decimal('NaN')
|
||||
dnnan = Decimal('-NaN')
|
||||
dsnan = Decimal('sNaN')
|
||||
dnsnan = Decimal('-sNaN')
|
||||
dnan128 = Decimal128(dnan)
|
||||
dnnan128 = Decimal128(dnnan)
|
||||
dsnan128 = Decimal128(dsnan)
|
||||
dnsnan128 = Decimal128(dnsnan)
|
||||
|
||||
# Due to the comparison rules for decimal.Decimal we have to
|
||||
# compare strings.
|
||||
self.assertEqual(str(dnan), str(dnan128.to_decimal()))
|
||||
self.assertEqual(str(dnnan), str(dnnan128.to_decimal()))
|
||||
self.assertEqual(str(dsnan), str(dsnan128.to_decimal()))
|
||||
self.assertEqual(str(dnsnan), str(dnsnan128.to_decimal()))
|
||||
|
||||
def test_spec(self):
|
||||
path = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'decimal',
|
||||
'decimal128.json')
|
||||
with codecs.open(path, 'r', 'utf-8-sig') as fp:
|
||||
suite = json.load(fp)
|
||||
|
||||
for test in suite['valid']:
|
||||
subject = unhexlify(b(test['subject']))
|
||||
doc = BSON(subject).decode()
|
||||
self.assertEqual(BSON.encode(doc), subject)
|
||||
|
||||
self.assertEqual(str(doc['d']), test['string'])
|
||||
|
||||
if test.get('from_extjson', True):
|
||||
self.assertEqual(doc, loads(test['extjson']))
|
||||
|
||||
if test.get('to_extjson', True):
|
||||
extjson = test['extjson'].replace(' ', '')
|
||||
self.assertEqual(extjson, dumps(doc).replace(' ', ''))
|
||||
|
||||
for test in suite['parseErrors']:
|
||||
self.assertRaises(ValueError, Decimal128, test['subject'])
|
||||
|
||||
Loading…
Reference in New Issue
Block a user