Skip to content

Commit 1d09a26

Browse files
committed
Improve doc and tests for Decimal128Field
1 parent 51b910f commit 1d09a26

File tree

3 files changed

+41
-20
lines changed

3 files changed

+41
-20
lines changed

docs/changelog.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ Development
1515
- Added meta ``auto_create_index_on_save`` so you can enable index creation
1616
on :meth:`~mongoengine.Document.save()` (as it was < 0.26.0).
1717
- BREAKING CHANGE: remove deprecated method ``ensure_index`` (replaced by ``create_index`` long time ago).
18-
18+
- Addition of Decimal128Field: :class:`~mongoengine.fields.Decimal128Field` for accurate representation of Decimals (much better than the legacy field DecimalField).
19+
Although it could work to switch an existing DecimalField to Decimal128Field without applying a migration script,
20+
it is not recommended to do so (DecimalField uses float/str to store the value, Decimal128Field uses Decimal128).
1921

2022
Changes in 0.25.0
2123
=================

mongoengine/fields.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,10 @@ def prepare_query_value(self, op, value):
440440

441441

442442
class DecimalField(BaseField):
443-
"""Fixed-point decimal number field. Stores the value as a float by default unless `force_string` is used.
443+
"""Disclaimer: This field is kept for historical reason but since it converts the values to float, it
444+
is not suitable for true decimal storage. Consider using :class:`~mongoengine.fields.Decimal128Field`.
445+
446+
Fixed-point decimal number field. Stores the value as a float by default unless `force_string` is used.
444447
If using floats, beware of Decimal to float conversion (potential precision loss)
445448
"""
446449

@@ -2655,6 +2658,11 @@ def to_mongo(self, document):
26552658

26562659

26572660
class Decimal128Field(BaseField):
2661+
"""
2662+
128-bit decimal-based floating-point field capable of emulating decimal
2663+
rounding with exact precision. This field will expose decimal.Decimal but stores the value as a
2664+
`bson.Decimal128` behind the scene, this field is intended for monetary data, scientific computations, etc.
2665+
"""
26582666

26592667
DECIMAL_CONTEXT = create_decimal128_context()
26602668

@@ -2663,13 +2671,6 @@ def __init__(self, min_value=None, max_value=None, **kwargs):
26632671
self.max_value = max_value
26642672
super().__init__(**kwargs)
26652673

2666-
"""
2667-
128-bit decimal-based floating-point field capable of emulating decimal
2668-
rounding with exact precision. Stores the value as a `Decimal128`
2669-
intended for monetary data, such as financial, tax, and scientific
2670-
computations.
2671-
"""
2672-
26732674
def to_mongo(self, value):
26742675
if value is None:
26752676
return None

tests/fields/test_decimal128_field.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pytest
66
from bson.decimal128 import Decimal128
77

8-
from mongoengine import *
8+
from mongoengine import Decimal128Field, Document, ValidationError
99
from tests.utils import MongoDBTestCase, get_as_pymongo
1010

1111

@@ -24,8 +24,6 @@ def generate_test_cls() -> Document:
2424

2525
class TestDecimal128Field(MongoDBTestCase):
2626
def test_decimal128_validation_good(self):
27-
"""Ensure that invalid values cannot be assigned."""
28-
2927
doc = Decimal128Document()
3028

3129
doc.dec128_fld = Decimal(0)
@@ -37,7 +35,7 @@ def test_decimal128_validation_good(self):
3735
doc.dec128_fld = Decimal(110)
3836
doc.validate()
3937

40-
doc.dec128_fld = Decimal(110)
38+
doc.dec128_fld = Decimal("110")
4139
doc.validate()
4240

4341
def test_decimal128_validation_invalid(self):
@@ -76,22 +74,30 @@ def test_decimal128_validation_max(self):
7674

7775
def test_eq_operator(self):
7876
cls = generate_test_cls()
79-
assert 1 == cls.objects(dec128_fld=1.0).count()
80-
assert 0 == cls.objects(dec128_fld=2.0).count()
77+
assert cls.objects(dec128_fld=1.0).count() == 1
78+
assert cls.objects(dec128_fld=2.0).count() == 0
8179

8280
def test_ne_operator(self):
8381
cls = generate_test_cls()
84-
assert 1 == cls.objects(dec128_fld__ne=None).count()
85-
assert 1 == cls.objects(dec128_fld__ne=1).count()
86-
assert 1 == cls.objects(dec128_fld__ne=1.0).count()
82+
assert cls.objects(dec128_fld__ne=None).count() == 1
83+
assert cls.objects(dec128_fld__ne=1).count() == 1
84+
assert cls.objects(dec128_fld__ne=1.0).count() == 1
8785

8886
def test_gt_operator(self):
8987
cls = generate_test_cls()
90-
assert 1 == cls.objects(dec128_fld__gt=0.5).count()
88+
assert cls.objects(dec128_fld__gt=0.5).count() == 1
9189

9290
def test_lt_operator(self):
9391
cls = generate_test_cls()
94-
assert 1 == cls.objects(dec128_fld__lt=1.5).count()
92+
assert cls.objects(dec128_fld__lt=1.5).count() == 1
93+
94+
def test_field_exposed_as_python_Decimal(self):
95+
# from int
96+
model = Decimal128Document(dec128_fld=100).save()
97+
assert isinstance(model.dec128_fld, Decimal)
98+
model = Decimal128Document.objects.get(id=model.id)
99+
assert isinstance(model.dec128_fld, Decimal)
100+
assert model.dec128_fld == Decimal("100")
95101

96102
def test_storage(self):
97103
# from int
@@ -101,6 +107,13 @@ def test_storage(self):
101107
"dec128_fld": Decimal128("100"),
102108
}
103109

110+
# from str
111+
model = Decimal128Document(dec128_fld="100.0").save()
112+
assert get_as_pymongo(model) == {
113+
"_id": model.id,
114+
"dec128_fld": Decimal128("100.0"),
115+
}
116+
104117
# from float
105118
model = Decimal128Document(dec128_fld=100.0).save()
106119
assert get_as_pymongo(model) == {
@@ -114,6 +127,11 @@ def test_storage(self):
114127
"_id": model.id,
115128
"dec128_fld": Decimal128("100"),
116129
}
130+
model = Decimal128Document(dec128_fld=Decimal("100.0")).save()
131+
assert get_as_pymongo(model) == {
132+
"_id": model.id,
133+
"dec128_fld": Decimal128("100.0"),
134+
}
117135

118136
# from Decimal128
119137
model = Decimal128Document(dec128_fld=Decimal128("100")).save()

0 commit comments

Comments
 (0)