fix(config): preserve lossy decimal values as strings
This commit is contained in:
@@ -15,6 +15,7 @@ This module provides:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
from decimal import Decimal, InvalidOperation
|
||||||
from hermes_cli.cli_output import line_input
|
from hermes_cli.cli_output import line_input
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
@@ -5434,7 +5435,12 @@ def _coerce_int(value: str):
|
|||||||
|
|
||||||
|
|
||||||
def _coerce_float(value: str):
|
def _coerce_float(value: str):
|
||||||
"""Return float(value) for a clean float literal, else None."""
|
"""Return ``float(value)`` when conversion preserves its decimal value.
|
||||||
|
|
||||||
|
Decimal-looking identifiers can be much more precise than a binary float.
|
||||||
|
Silently rounding one here corrupts it before it reaches ``config.yaml``,
|
||||||
|
so values that do not round-trip through ``float`` remain strings.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
f = float(value)
|
f = float(value)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
@@ -5443,6 +5449,11 @@ def _coerce_float(value: str):
|
|||||||
# and round-trip confusingly through YAML.
|
# and round-trip confusingly through YAML.
|
||||||
if f != f or f in (float("inf"), float("-inf")):
|
if f != f or f in (float("inf"), float("-inf")):
|
||||||
return None
|
return None
|
||||||
|
try:
|
||||||
|
if Decimal(value) != Decimal(str(f)):
|
||||||
|
return None
|
||||||
|
except InvalidOperation:
|
||||||
|
return None
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,18 @@ class TestNumericCoercion:
|
|||||||
v = _read(tmp_path, "agent", "max_turns")
|
v = _read(tmp_path, "agent", "max_turns")
|
||||||
assert v == -2.5 and isinstance(v, float)
|
assert v == -2.5 and isinstance(v, float)
|
||||||
|
|
||||||
|
def test_lossy_decimal_identifier_stays_string(self, tmp_path, monkeypatch):
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||||
|
client_id = "123456789012.98765432109876"
|
||||||
|
|
||||||
|
cfg.set_config_value("mcp_servers.example.oauth.client_id", client_id)
|
||||||
|
|
||||||
|
saved = _read(
|
||||||
|
tmp_path, "mcp_servers", "example", "oauth", "client_id"
|
||||||
|
)
|
||||||
|
assert saved == client_id
|
||||||
|
assert isinstance(saved, str)
|
||||||
|
|
||||||
|
|
||||||
class TestNullCoercion:
|
class TestNullCoercion:
|
||||||
@pytest.mark.parametrize("token", ["null", "none", "None", "~"])
|
@pytest.mark.parametrize("token", ["null", "none", "None", "~"])
|
||||||
|
|||||||
Reference in New Issue
Block a user