63 lines
2.2 KiB
Python
63 lines
2.2 KiB
Python
"""Shared strict scalar and object readers for configuration codecs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
from typing import Any
|
|
|
|
|
|
def json_object(value: Any, context: str) -> dict[str, Any]:
|
|
"""Read an object; a missing or null section keeps its defaults."""
|
|
if value is None:
|
|
return {}
|
|
if not isinstance(value, dict):
|
|
raise ValueError(f"{context} must be a JSON object")
|
|
return value
|
|
|
|
|
|
def _value(payload: dict[str, Any], key: str, default: Any) -> Any:
|
|
value = payload.get(key, default)
|
|
return default if value is None else value
|
|
|
|
|
|
def read_int(payload: dict[str, Any], key: str, default: int) -> int:
|
|
"""Read a JSON integer without truncation or boolean coercion."""
|
|
value = _value(payload, key, default)
|
|
if isinstance(value, bool) or not isinstance(value, int):
|
|
raise ValueError(f"{key} must be a JSON integer")
|
|
return value
|
|
|
|
|
|
def read_str(payload: dict[str, Any], key: str, default: str) -> str:
|
|
"""Read a JSON string without converting other scalar types."""
|
|
value = _value(payload, key, default)
|
|
if not isinstance(value, str):
|
|
raise ValueError(f"{key} must be a JSON string")
|
|
return value
|
|
|
|
|
|
def read_bool(payload: dict[str, Any], key: str, default: bool) -> bool:
|
|
"""Read a JSON boolean."""
|
|
value = _value(payload, key, default)
|
|
if not isinstance(value, bool):
|
|
raise ValueError(f"{key} must be a JSON boolean")
|
|
return value
|
|
|
|
|
|
def read_float(payload: dict[str, Any], key: str, default: float) -> float:
|
|
"""Read a finite JSON number, including overflow-safe integer conversion."""
|
|
return finite_number(_value(payload, key, default), key)
|
|
|
|
|
|
def finite_number(value: Any, context: str) -> float:
|
|
"""Validate a number without accepting booleans, strings or nonfinite values."""
|
|
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
raise ValueError(f"{context} must be a JSON number")
|
|
try:
|
|
result = float(value)
|
|
except OverflowError as exc:
|
|
raise ValueError(f"{context} must be a finite number") from exc
|
|
if not math.isfinite(result):
|
|
raise ValueError(f"{context} must be a finite number")
|
|
return result
|