Skip to content

Commit 08e878b

Browse files
authored
DGS-20014 Add utilities to convert decimals from/to Protobuf (#1946)
* DGS-20014 Add utilities to convert decimals from/to Protobuf * Add comment
1 parent 5f94067 commit 08e878b

File tree

3 files changed

+88
-2
lines changed

3 files changed

+88
-2
lines changed

src/confluent_kafka/schema_registry/confluent/types/decimal.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ message Decimal {
99
// The two's-complement representation of the unscaled integer value in big-endian byte order
1010
bytes value = 1;
1111

12-
// The precision
12+
// The precision (zero indicates unlimited precision)
1313
uint32 precision = 2;
1414

1515
// The scale

src/confluent_kafka/schema_registry/protobuf.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import struct
2222
import warnings
2323
from collections import deque
24+
from decimal import Context, Decimal, MAX_PREC
2425
from typing import Set, List, Union, Optional, Any, Tuple
2526

2627
from google.protobuf import descriptor_pb2, any_pb2, api_pb2, empty_pb2, \
@@ -1076,3 +1077,65 @@ def _is_builtin(name: str) -> bool:
10761077
return name.startswith('confluent/') or \
10771078
name.startswith('google/protobuf/') or \
10781079
name.startswith('google/type/')
1080+
1081+
1082+
def decimalToProtobuf(value: Decimal, scale: int) -> decimal_pb2.Decimal:
1083+
"""
1084+
Converts a Decimal to a Protobuf value.
1085+
1086+
Args:
1087+
value (Decimal): The Decimal value to convert.
1088+
1089+
Returns:
1090+
The Protobuf value.
1091+
"""
1092+
sign, digits, exp = value.as_tuple()
1093+
1094+
delta = exp + scale
1095+
1096+
if delta < 0:
1097+
raise ValueError(
1098+
"Scale provided does not match the decimal")
1099+
1100+
unscaled_datum = 0
1101+
for digit in digits:
1102+
unscaled_datum = (unscaled_datum * 10) + digit
1103+
1104+
unscaled_datum = 10**delta * unscaled_datum
1105+
1106+
bytes_req = (unscaled_datum.bit_length() + 8) // 8
1107+
1108+
if sign:
1109+
unscaled_datum = -unscaled_datum
1110+
1111+
bytes = unscaled_datum.to_bytes(bytes_req, byteorder="big", signed=True)
1112+
1113+
result = decimal_pb2.Decimal()
1114+
result.value = bytes
1115+
result.precision = 0
1116+
result.scale = scale
1117+
return result
1118+
1119+
1120+
decimal_context = Context()
1121+
1122+
1123+
def protobufToDecimal(value: decimal_pb2.Decimal) -> Decimal:
1124+
"""
1125+
Converts a Protobuf value to Decimal.
1126+
1127+
Args:
1128+
value (decimal_pb2.Decimal): The Protobuf value to convert.
1129+
1130+
Returns:
1131+
The Decimal value.
1132+
"""
1133+
unscaled_datum = int.from_bytes(value.value, byteorder="big", signed=True)
1134+
1135+
if value.precision > 0:
1136+
decimal_context.prec = value.precision
1137+
else:
1138+
decimal_context.prec = MAX_PREC
1139+
return decimal_context.create_decimal(unscaled_datum).scaleb(
1140+
-value.scale, decimal_context
1141+
)

tests/schema_registry/test_proto.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616
# limitations under the License.
1717
#
1818
import binascii
19+
from decimal import Decimal
1920
from io import BytesIO
2021

2122
import pytest
2223

2324
from confluent_kafka.schema_registry.protobuf import (ProtobufSerializer,
2425
ProtobufDeserializer,
25-
_create_index_array)
26+
_create_index_array,
27+
decimalToProtobuf,
28+
protobufToDecimal)
2629
from tests.integration.schema_registry.data.proto import (DependencyTestProto_pb2,
2730
metadata_proto_pb2)
2831

@@ -83,3 +86,23 @@ def test_index_encoder(msg_idx, zigzag, expected_hex):
8386
buf.seek(0)
8487
decoded_msg_idx = ProtobufDeserializer._read_index_array(buf, zigzag=zigzag)
8588
assert decoded_msg_idx == msg_idx
89+
90+
91+
@pytest.mark.parametrize("decimal, scale", [
92+
("0", 0),
93+
("1.01", 2),
94+
("123456789123456789.56", 2),
95+
("1234", 0),
96+
("1234.5", 1),
97+
("-0", 0),
98+
("-1.01", 2),
99+
("-123456789123456789.56", 2),
100+
("-1234", 0),
101+
("-1234.5", 1),
102+
("-1234.56", 2)
103+
])
104+
def test_proto_decimal(decimal, scale):
105+
input = Decimal(decimal)
106+
converted = decimalToProtobuf(input, scale)
107+
result = protobufToDecimal(converted)
108+
assert result == input

0 commit comments

Comments
 (0)