Skip to content

Commit 21e6a15

Browse files
authored
Fix configuration file unmarshalling of JSON floating-point values (#253)
This PR fixes unmarshalling of `JsonValue` fields if they contain floating point values; prior to this PR they were incorrectly unmarshalled as integers, truncating precision. The reason for this error is that unmarshalling is somewhat forgiving with coercion allowed between primitives: if the target is a string, boolean/int/float will be converted to a string. Previous this allowed `int(float_value)` to succeed; this PR blocks that specifically, and unit tests have been updated to ensure the values being produced are of the correct type.
1 parent 5bb70b3 commit 21e6a15

File tree

2 files changed

+16
-2
lines changed

2 files changed

+16
-2
lines changed

src/databricks/labs/blueprint/installation.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,11 +849,15 @@ def _unmarshal_primitive(cls, inst: Any, path: list[str], type_ref: type) -> Any
849849
# No coercion necessary.
850850
if isinstance(inst, type_ref):
851851
return inst
852+
852853
# Only attempt coercion between primitive types.
853854
if type(inst) not in cls._PRIMITIVES:
854855
msg = f"{'.'.join(path)}: Expected {type_ref.__name__}, got: {inst}"
855856
raise SerdeError(msg)
856-
# Special case for strings to bool
857+
858+
# Some special cases:
859+
# - str -> bool: only accept "true" or "false" (case-insensitive).
860+
# - float -> int: refuse to truncate
857861
if type_ref == bool:
858862
if isinstance(inst, str):
859863
if inst.lower() == "true":
@@ -862,6 +866,10 @@ def _unmarshal_primitive(cls, inst: Any, path: list[str], type_ref: type) -> Any
862866
return False
863867
msg = f"{'.'.join(path)}: Expected bool, got: {inst}"
864868
raise SerdeError(msg)
869+
if type_ref == int and isinstance(inst, float):
870+
msg = f"{'.'.join(path)}: Expected int, got float: {inst}"
871+
raise SerdeError(msg)
872+
865873
# Everything else.
866874
try:
867875
converted = type_ref(inst)

tests/unit/test_installation.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ class SampleClass:
468468
saved = SampleClass(field={"a": "b", "b": "c"})
469469
installation.save(saved, filename="backups/SampleClass.json")
470470
loaded = installation.load(SampleClass, filename="backups/SampleClass.json")
471+
assert isinstance(loaded.field["a"], str)
471472
assert loaded == saved
472473

473474

@@ -480,6 +481,7 @@ class SampleClass:
480481
saved = SampleClass(field={"a": 1, "b": 1})
481482
installation.save(saved, filename="backups/SampleClass.json")
482483
loaded = installation.load(SampleClass, filename="backups/SampleClass.json")
484+
assert isinstance(loaded.field["a"], int)
483485
assert loaded == saved
484486

485487

@@ -492,6 +494,7 @@ class SampleClass:
492494
saved = SampleClass(field={"a": 1.1, "b": 1.2})
493495
installation.save(saved, filename="backups/SampleClass.json")
494496
loaded = installation.load(SampleClass, filename="backups/SampleClass.json")
497+
assert isinstance(loaded.field["a"], float)
495498
assert loaded == saved
496499

497500

@@ -514,10 +517,12 @@ class SampleClass:
514517

515518
installation = MockInstallation()
516519

517-
json_like: dict[str, JsonValue] = {"a": ["x", "y"], "b": [], "c": 3, "d": True, "e": {"a": "b"}}
520+
json_like: dict[str, JsonValue] = {"a": ["x", "y"], "b": [], "c": 3, "d": True, "e": {"a": "b"}, "f": 0.1}
518521
saved = SampleClass(field=json_like)
519522
installation.save(saved, filename="backups/SampleClass.json")
520523
loaded = installation.load(SampleClass, filename="backups/SampleClass.json")
524+
assert isinstance(loaded.field["c"], int)
525+
assert isinstance(loaded.field["f"], float)
521526
assert loaded == saved
522527

523528

@@ -581,6 +586,7 @@ class SampleClass:
581586
3,
582587
True,
583588
{"a": "b"},
589+
0.1,
584590
]
585591
saved = SampleClass(field=json_like)
586592
installation.save(saved, filename="backups/SampleClass.json")

0 commit comments

Comments
 (0)