From b6b9d24da30caa37367c7e21b61560b590497637 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Wed, 20 Aug 2025 11:32:35 -0700 Subject: [PATCH 1/4] PYTHON-5508 - Add built-in DecimalEncoder and DecimalDecoder --- bson/decimal128.py | 35 ++++++++++++++++++++++++++ doc/changelog.rst | 10 ++++++++ test/asynchronous/test_custom_types.py | 25 ++---------------- test/test_custom_types.py | 25 ++---------------- 4 files changed, 49 insertions(+), 46 deletions(-) diff --git a/bson/decimal128.py b/bson/decimal128.py index 92c054d878..c783645bc3 100644 --- a/bson/decimal128.py +++ b/bson/decimal128.py @@ -20,8 +20,11 @@ import decimal import struct +from decimal import Decimal from typing import Any, Sequence, Tuple, Type, Union +from bson.codec_options import TypeDecoder, TypeEncoder + _PACK_64 = struct.Struct(" decimal.Context: """Returns an instance of :class:`decimal.Context` appropriate for working with IEEE-754 128-bit decimal floating point values. diff --git a/doc/changelog.rst b/doc/changelog.rst index e41ecc7e1b..517a6cb6b7 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,5 +1,15 @@ Changelog ========= +Changes in Version 4.15.0 (XXXX/XX/XX) +-------------------------------------- +.. warning:: When converting BSON data types to and from built-in data types, the possibility of data loss is always present + due to mismatches in underlying implementations. + +PyMongo 4.15 brings a number of changes including: + +- Added :class:`bson.decimal128.DecimalEncoder` and :class:`bson.decimal128.DecimalDecoder` + to support encoding and decoding of BSON Decimal128 values to decimal.Decimal values using the TypeRegistry API. + Changes in Version 4.14.1 (2025/08/19) -------------------------------------- diff --git a/test/asynchronous/test_custom_types.py b/test/asynchronous/test_custom_types.py index 385f755a1d..82c54512cc 100644 --- a/test/asynchronous/test_custom_types.py +++ b/test/asynchronous/test_custom_types.py @@ -23,6 +23,7 @@ from random import random from typing import Any, Tuple, Type, no_type_check +from bson.decimal128 import DecimalDecoder, DecimalEncoder from gridfs.asynchronous.grid_file import AsyncGridIn, AsyncGridOut sys.path[0:0] = [""] @@ -59,29 +60,7 @@ _IS_SYNC = False -class DecimalEncoder(TypeEncoder): - @property - def python_type(self): - return Decimal - - def transform_python(self, value): - return Decimal128(value) - - -class DecimalDecoder(TypeDecoder): - @property - def bson_type(self): - return Decimal128 - - def transform_bson(self, value): - return value.to_decimal() - - -class DecimalCodec(DecimalDecoder, DecimalEncoder): - pass - - -DECIMAL_CODECOPTS = CodecOptions(type_registry=TypeRegistry([DecimalCodec()])) +DECIMAL_CODECOPTS = CodecOptions(type_registry=TypeRegistry([DecimalEncoder(), DecimalDecoder()])) class UndecipherableInt64Type: diff --git a/test/test_custom_types.py b/test/test_custom_types.py index 7360f2b18b..aba6b55119 100644 --- a/test/test_custom_types.py +++ b/test/test_custom_types.py @@ -23,6 +23,7 @@ from random import random from typing import Any, Tuple, Type, no_type_check +from bson.decimal128 import DecimalDecoder, DecimalEncoder from gridfs.synchronous.grid_file import GridIn, GridOut sys.path[0:0] = [""] @@ -59,29 +60,7 @@ _IS_SYNC = True -class DecimalEncoder(TypeEncoder): - @property - def python_type(self): - return Decimal - - def transform_python(self, value): - return Decimal128(value) - - -class DecimalDecoder(TypeDecoder): - @property - def bson_type(self): - return Decimal128 - - def transform_bson(self, value): - return value.to_decimal() - - -class DecimalCodec(DecimalDecoder, DecimalEncoder): - pass - - -DECIMAL_CODECOPTS = CodecOptions(type_registry=TypeRegistry([DecimalCodec()])) +DECIMAL_CODECOPTS = CodecOptions(type_registry=TypeRegistry([DecimalEncoder(), DecimalDecoder()])) class UndecipherableInt64Type: From 7b28e3128ea0673250acfc11ecd97a1925f96d4c Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Wed, 20 Aug 2025 11:41:35 -0700 Subject: [PATCH 2/4] Fix typing --- bson/decimal128.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bson/decimal128.py b/bson/decimal128.py index c783645bc3..19c82b6ad8 100644 --- a/bson/decimal128.py +++ b/bson/decimal128.py @@ -70,10 +70,10 @@ class DecimalEncoder(TypeEncoder): .. versionadded:: 4.15""" @property - def python_type(self): + def python_type(self) -> Type[Decimal]: return Decimal - def transform_python(self, value): + def transform_python(self, value: Any) -> Decimal128: return Decimal128(value) @@ -86,10 +86,10 @@ class DecimalDecoder(TypeDecoder): .. versionadded:: 4.15""" @property - def bson_type(self): + def bson_type(self) -> Type[Decimal128]: return Decimal128 - def transform_bson(self, value): + def transform_bson(self, value: Any) -> decimal.Decimal: return value.to_decimal() From fd9b9e134218ebcc952f73188d358bb21bd6b196 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Wed, 20 Aug 2025 11:59:52 -0700 Subject: [PATCH 3/4] Address review --- bson/decimal128.py | 10 ++++++---- doc/changelog.rst | 3 --- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/bson/decimal128.py b/bson/decimal128.py index 19c82b6ad8..b52f3c2999 100644 --- a/bson/decimal128.py +++ b/bson/decimal128.py @@ -64,8 +64,9 @@ class DecimalEncoder(TypeEncoder): """Converts Python :class:`decimal.Decimal` to BSON :class:`Decimal128`. - .. warning:: When converting BSON data types to and from built-in data types, - the possibility of data loss is always present due to mismatches in underlying implementations. + For example:: + opts = CodecOptions(type_registry=TypeRegistry([DecimalEncoder()])) + bson.encode({"d": decimal.Decimal('1.0')}, codec_options=opts) .. versionadded:: 4.15""" @@ -80,8 +81,9 @@ def transform_python(self, value: Any) -> Decimal128: class DecimalDecoder(TypeDecoder): """Converts BSON :class:`Decimal128` to Python :class:`decimal.Decimal`. - .. warning:: When converting BSON data types to and from built-in data types, - the possibility of data loss is always present due to mismatches in underlying implementations. + For example:: + opts = CodecOptions(type_registry=TypeRegistry([DecimalDecoder()])) + bson.decode(b'\x18\x00\x00\x00\x13d\x00\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>0\x00', codec_options=opts) .. versionadded:: 4.15""" diff --git a/doc/changelog.rst b/doc/changelog.rst index 517a6cb6b7..305c989106 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -2,9 +2,6 @@ Changelog ========= Changes in Version 4.15.0 (XXXX/XX/XX) -------------------------------------- -.. warning:: When converting BSON data types to and from built-in data types, the possibility of data loss is always present - due to mismatches in underlying implementations. - PyMongo 4.15 brings a number of changes including: - Added :class:`bson.decimal128.DecimalEncoder` and :class:`bson.decimal128.DecimalDecoder` From a66b82edf69ecd6048d24c70b9e38d7fe283d785 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Wed, 20 Aug 2025 12:16:16 -0700 Subject: [PATCH 4/4] Fix docstrings --- bson/decimal128.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bson/decimal128.py b/bson/decimal128.py index b52f3c2999..7480f94d0a 100644 --- a/bson/decimal128.py +++ b/bson/decimal128.py @@ -68,7 +68,8 @@ class DecimalEncoder(TypeEncoder): opts = CodecOptions(type_registry=TypeRegistry([DecimalEncoder()])) bson.encode({"d": decimal.Decimal('1.0')}, codec_options=opts) - .. versionadded:: 4.15""" + .. versionadded:: 4.15 + """ @property def python_type(self) -> Type[Decimal]: @@ -83,9 +84,10 @@ class DecimalDecoder(TypeDecoder): For example:: opts = CodecOptions(type_registry=TypeRegistry([DecimalDecoder()])) - bson.decode(b'\x18\x00\x00\x00\x13d\x00\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>0\x00', codec_options=opts) + bson.decode(data, codec_options=opts) - .. versionadded:: 4.15""" + .. versionadded:: 4.15 + """ @property def bson_type(self) -> Type[Decimal128]: