Updated apps

This commit is contained in:
2026-07-20 22:52:35 -04:00
parent 28a8cb98f6
commit a0c3271743
1164 changed files with 94781 additions and 6892 deletions
+57
View File
@@ -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)