Updated apps
This commit is contained in:
@@ -2,13 +2,16 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import re
|
||||
import threading
|
||||
from collections.abc import Callable
|
||||
from datetime import date, datetime
|
||||
from io import StringIO
|
||||
from typing import Any
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
from ruamel.yaml.scalarbool import ScalarBoolean
|
||||
|
||||
|
||||
class _TaggedScalar:
|
||||
@@ -171,3 +174,57 @@ def yaml_dumps(ry: YAML, data: Any) -> str:
|
||||
buf = StringIO()
|
||||
ry.dump(data, buf)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def _jsonify_float(node: float) -> float | str:
|
||||
"""Narrow a float to something json can encode.
|
||||
|
||||
``.inf``/``.nan`` are valid YAML with no JSON encoding, so they render back
|
||||
to their YAML source form — the same treatment a tag gets.
|
||||
"""
|
||||
if math.isnan(node):
|
||||
return ".nan"
|
||||
if math.isinf(node):
|
||||
return ".inf" if node > 0 else "-.inf"
|
||||
return float(node)
|
||||
|
||||
|
||||
def yaml_jsonify(node: Any) -> Any:
|
||||
"""Convert a round-trip node into JSON-serializable plain Python.
|
||||
|
||||
An HA tag is rendered back to its SOURCE form (``!secret api_key``), never
|
||||
resolved: the value behind a ``!secret`` lives in secrets.yaml and is not
|
||||
looked up here, so a parsed view carries no plaintext-secret surface — the
|
||||
same property the round-trip text view has. Lives here because this module
|
||||
owns ``_TaggedScalar`` and the tag registry.
|
||||
|
||||
ruamel's scalar types subclass the builtins (``ScalarInt``/``ScalarFloat``/
|
||||
``ScalarString``), so they are narrowed to the plain type; timestamps
|
||||
(``!!timestamp``) come back as ``date``/``datetime``, which json cannot
|
||||
encode, and become ISO strings. Non-finite floats (``.inf``/``.nan``) have
|
||||
no JSON encoding either, so they render back to their YAML source form —
|
||||
the same treatment a tag gets.
|
||||
"""
|
||||
if isinstance(node, _TaggedScalar):
|
||||
return f"{node.tag} {node.value}".strip()
|
||||
if isinstance(node, dict):
|
||||
return {str(key): yaml_jsonify(value) for key, value in node.items()}
|
||||
if isinstance(node, (list, tuple)):
|
||||
return [yaml_jsonify(item) for item in node]
|
||||
# Both branches must precede int: plain bool subclasses int, and a bool
|
||||
# carrying an anchor loads as ruamel's ScalarBoolean, which subclasses int
|
||||
# WITHOUT subclassing bool — so an `enabled: &flag true` would otherwise
|
||||
# serialize as 1.
|
||||
if node is None or isinstance(node, bool):
|
||||
return node
|
||||
if isinstance(node, ScalarBoolean):
|
||||
return bool(node)
|
||||
if isinstance(node, int):
|
||||
return int(node)
|
||||
if isinstance(node, float):
|
||||
return _jsonify_float(node)
|
||||
if isinstance(node, str):
|
||||
return str(node)
|
||||
if isinstance(node, (datetime, date)):
|
||||
return node.isoformat()
|
||||
return str(node)
|
||||
|
||||
Reference in New Issue
Block a user