fix: DecimalField no longer strips the scale it just quantized to - #2271
Open
dylanpulver wants to merge 1 commit into
Open
fix: DecimalField no longer strips the scale it just quantized to#2271dylanpulver wants to merge 1 commit into
dylanpulver wants to merge 1 commit into
Conversation
to_python_value() called .quantize(self.quant).normalize(): quantize sets the
scale to decimal_places and normalize immediately removes it again. A
DecimalField(max_digits=12, decimal_places=2) holding 100.00 came back as
Decimal('1E+2'), which is what str(), f-strings, json.dumps() and
pydantic_model_creator all render. Model.__init__ runs to_python_value on
user input too, so the value is written that way as well.
Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DecimalField.to_python_valuedoesDecimal(value).quantize(self.quant).normalize().quantizesets the scale to
decimal_places;normalizeon the same expression strips it again. So aDecimalField(max_digits=12, decimal_places=2)holding100.00becomesDecimal('1E+2'):Model.__init__runsto_python_valueon user input too, so it is written that way as well — theSQLite column literally holds
'1E+2'— andpydantic_model_creatorthen emits{"id":1,"price":"1E+2"}.I believe this is the cause of #1549 (open since 2024-01-23, reporter's example: created as
200.00, got2E+2). The workaround on that thread attributes it to "how the decimal field issaved in the database"; it reproduces with no database at all, from
to_python_valuealone — theDB row is
1E+2because tortoise wrote it that way.Why the existing tests can't see it:
tests/fields/test_decimal.pyasserts through
obj.decimal == Decimal("…"), andDecimal.__eq__compares numerically —Decimal('1E+2') == Decimal('100.00')isTrue. The whole file passes with the defect live andwith it fixed.
test_aggregate_sum_with_f_expressionexpectsDecimal("4E+1")for forty, which isthe shape of a value transcribed from a run rather than written by hand. The new tests assert on
str()and onas_tuple().exponentinstead.Mutants. Reverting
tortoise/fields/data.pyagainstorigin/developand keeping the new testsfails both of them. Dropping the
quantizeas well (the "stop transforming it" reading) fails 14tests in
test_decimal.pyand 18 suite-wide — the quantize is load-bearing, only thenormalizeisthe defect.
Run: Python 3.14.7, sqlite 3.53.4,
pytest→1912 passed, 148 skipped, 2 xfailed, 2 failed.The two failures are
test_relations.py::test_recursiveandtest_version.py::test_version, bothof which fail identically on unmodified
develophere (the package is not pip-installed, soimportlib.metadatahas no version).ruff format --checkandruff checkclean on the changedfiles with ruff 0.15.4, the version in
uv.lock. I did not run this against MySQL, Postgres,MSSQL or Oracle — no servers here — so only the SQLite path is verified end to end;
to_python_valueitself is backend-independent.
One self-correction worth stating: my first version of the new test expected
12.345→12.35.quantizeuses the context rounding,ROUND_HALF_EVEN, so it is12.34. My expectation was wrong,not the code; the test now uses
12.3456so it does not depend on the rounding mode.AI assistance: found and drafted with Claude Code (Claude Opus 5,
claude-opus-5), from a sweep forfield round trips that do not close, not from production use.