Inverse json import logic (#88099)
* Fix helpers and util * Adjust components * Move back errors * Add report * mypy * mypy * Assert deprecation messages * Move test_json_loads_object * Adjust tests * Fix rebase * Adjust pylint plugin * Fix plugin * Adjust references * Adjust backup tests
This commit is contained in:
parent
580869a9a6
commit
ba23816a0c
44 changed files with 291 additions and 197 deletions
|
@ -28,9 +28,10 @@ import homeassistant.core as ha
|
|||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceNotFound, TemplateError, Unauthorized
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.helpers.json import json_dumps, json_loads
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
from homeassistant.helpers.service import async_get_all_descriptions
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util.json import json_loads
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -17,7 +17,8 @@ from homeassistant.const import __version__ as HAVERSION
|
|||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import integration_platform
|
||||
from homeassistant.util import dt, json as json_util
|
||||
from homeassistant.helpers.json import save_json
|
||||
from homeassistant.util import dt
|
||||
|
||||
from .const import DOMAIN, EXCLUDE_FROM_BACKUP, LOGGER
|
||||
|
||||
|
@ -229,7 +230,7 @@ class BackupManager:
|
|||
tar_file_path, "w", gzip=False
|
||||
) as tar_file:
|
||||
tmp_dir_path = Path(tmp_dir)
|
||||
json_util.save_json(
|
||||
save_json(
|
||||
tmp_dir_path.joinpath("./backup.json").as_posix(),
|
||||
backup_data,
|
||||
)
|
||||
|
|
|
@ -24,7 +24,7 @@ from homeassistant.helpers import (
|
|||
template,
|
||||
translation,
|
||||
)
|
||||
from homeassistant.helpers.json import JsonObjectType, json_loads_object
|
||||
from homeassistant.util.json import JsonObjectType, json_loads_object
|
||||
|
||||
from .agent import AbstractConversationAgent, ConversationInput, ConversationResult
|
||||
from .const import DOMAIN
|
||||
|
|
|
@ -16,14 +16,14 @@ from homeassistant.config_entries import ConfigEntry
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import integration_platform
|
||||
from homeassistant.helpers.device_registry import DeviceEntry, async_get
|
||||
from homeassistant.helpers.json import ExtendedJSONEncoder
|
||||
from homeassistant.helpers.json import (
|
||||
ExtendedJSONEncoder,
|
||||
find_paths_unserializable_data,
|
||||
)
|
||||
from homeassistant.helpers.system_info import async_get_system_info
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.loader import async_get_custom_components, async_get_integration
|
||||
from homeassistant.util.json import (
|
||||
find_paths_unserializable_data,
|
||||
format_unserializable_data,
|
||||
)
|
||||
from homeassistant.util.json import format_unserializable_data
|
||||
|
||||
from .const import DOMAIN, REDACTED, DiagnosticsSubType, DiagnosticsType
|
||||
from .util import async_redact_data
|
||||
|
|
|
@ -24,9 +24,10 @@ from homeassistant.core import HomeAssistant
|
|||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.icon import icon_for_battery_level
|
||||
from homeassistant.helpers.json import save_json
|
||||
from homeassistant.helpers.network import NoURLAvailableError, get_url
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
from homeassistant.util.json import load_json
|
||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||
|
||||
from .const import (
|
||||
|
|
|
@ -32,9 +32,10 @@ from homeassistant.const import ATTR_NAME, URL_ROOT
|
|||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.json import save_json
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import ensure_unique_string
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
from homeassistant.util.json import load_json
|
||||
|
||||
from .const import DOMAIN, SERVICE_DISMISS
|
||||
|
||||
|
|
|
@ -20,11 +20,12 @@ import voluptuous as vol
|
|||
from homeassistant import exceptions
|
||||
from homeassistant.const import CONTENT_TYPE_JSON
|
||||
from homeassistant.core import Context, is_callback
|
||||
from homeassistant.helpers.json import JSON_ENCODE_EXCEPTIONS, json_bytes, json_dumps
|
||||
from homeassistant.util.json import (
|
||||
from homeassistant.helpers.json import (
|
||||
find_paths_unserializable_data,
|
||||
format_unserializable_data,
|
||||
json_bytes,
|
||||
json_dumps,
|
||||
)
|
||||
from homeassistant.util.json import JSON_ENCODE_EXCEPTIONS, format_unserializable_data
|
||||
|
||||
from .const import KEY_AUTHENTICATED, KEY_HASS
|
||||
|
||||
|
|
|
@ -12,8 +12,9 @@ from homeassistant.core import HomeAssistant, callback
|
|||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, discovery
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.json import save_json
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
from homeassistant.util.json import load_json
|
||||
|
||||
from .const import (
|
||||
CONF_ACTION_BACKGROUND_COLOR,
|
||||
|
|
|
@ -19,8 +19,9 @@ from homeassistant.const import (
|
|||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.json import save_json
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
from homeassistant.util.json import load_json
|
||||
|
||||
from .const import DOMAIN, FORMAT_HTML, FORMAT_TEXT, SERVICE_SEND_MESSAGE
|
||||
|
||||
|
|
|
@ -14,7 +14,8 @@ from nacl.secret import SecretBox
|
|||
from homeassistant.const import ATTR_DEVICE_ID, CONTENT_TYPE_JSON
|
||||
from homeassistant.core import Context, HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.json import JSONEncoder, JsonValueType, json_loads
|
||||
from homeassistant.helpers.json import JSONEncoder
|
||||
from homeassistant.util.json import JsonValueType, json_loads
|
||||
|
||||
from .const import (
|
||||
ATTR_APP_DATA,
|
||||
|
|
|
@ -28,7 +28,7 @@ from homeassistant.const import (
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.json import JSON_DECODE_EXCEPTIONS, json_dumps, json_loads
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
from homeassistant.helpers.selector import (
|
||||
BooleanSelector,
|
||||
FileSelector,
|
||||
|
@ -44,6 +44,7 @@ from homeassistant.helpers.selector import (
|
|||
TextSelectorConfig,
|
||||
TextSelectorType,
|
||||
)
|
||||
from homeassistant.util.json import JSON_DECODE_EXCEPTIONS, json_loads
|
||||
|
||||
from .client import MqttClientSetup
|
||||
from .config_integration import CONFIG_SCHEMA_ENTRY
|
||||
|
|
|
@ -31,9 +31,9 @@ from homeassistant.const import (
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.json import JSON_DECODE_EXCEPTIONS, json_loads
|
||||
from homeassistant.helpers.service_info.mqtt import ReceivePayloadType
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util.json import JSON_DECODE_EXCEPTIONS, json_loads
|
||||
|
||||
from . import subscription
|
||||
from .config import MQTT_BASE_SCHEMA
|
||||
|
|
|
@ -18,10 +18,10 @@ from homeassistant.helpers.dispatcher import (
|
|||
async_dispatcher_connect,
|
||||
async_dispatcher_send,
|
||||
)
|
||||
from homeassistant.helpers.json import json_loads_object
|
||||
from homeassistant.helpers.service_info.mqtt import MqttServiceInfo
|
||||
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||
from homeassistant.loader import async_get_mqtt
|
||||
from homeassistant.util.json import json_loads_object
|
||||
|
||||
from .. import mqtt
|
||||
from .abbreviations import ABBREVIATIONS, DEVICE_ABBREVIATIONS
|
||||
|
|
|
@ -47,10 +47,11 @@ from homeassistant.const import (
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.json import json_dumps, json_loads_object
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
import homeassistant.util.color as color_util
|
||||
from homeassistant.util.json import json_loads_object
|
||||
|
||||
from .. import subscription
|
||||
from ..config import DEFAULT_QOS, DEFAULT_RETAIN, MQTT_RW_SCHEMA
|
||||
|
|
|
@ -51,8 +51,8 @@ from homeassistant.helpers.entity import (
|
|||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import async_track_entity_registry_updated_event
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.json import json_loads
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util.json import json_loads
|
||||
|
||||
from . import debug_info, subscription
|
||||
from .client import async_publish
|
||||
|
|
|
@ -31,13 +31,10 @@ from homeassistant.const import (
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.json import (
|
||||
JSON_DECODE_EXCEPTIONS,
|
||||
json_dumps,
|
||||
json_loads_object,
|
||||
)
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
from homeassistant.helpers.template import Template
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, TemplateVarsType
|
||||
from homeassistant.util.json import JSON_DECODE_EXCEPTIONS, json_loads_object
|
||||
|
||||
from . import subscription
|
||||
from .config import MQTT_RW_SCHEMA
|
||||
|
|
|
@ -11,10 +11,10 @@ import voluptuous as vol
|
|||
from homeassistant.const import CONF_PAYLOAD, CONF_PLATFORM, CONF_VALUE_TEMPLATE
|
||||
from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.json import json_loads
|
||||
from homeassistant.helpers.template import Template
|
||||
from homeassistant.helpers.trigger import TriggerActionType, TriggerData, TriggerInfo
|
||||
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
|
||||
from homeassistant.util.json import json_loads
|
||||
|
||||
from .. import mqtt
|
||||
from .const import CONF_ENCODING, CONF_QOS, CONF_TOPIC, DEFAULT_ENCODING, DEFAULT_QOS
|
||||
|
|
|
@ -18,9 +18,9 @@ from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME, CONF_VALUE_TEMPLAT
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.json import JSON_DECODE_EXCEPTIONS, json_loads
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util.json import JSON_DECODE_EXCEPTIONS, json_loads
|
||||
|
||||
from . import subscription
|
||||
from .config import DEFAULT_RETAIN, MQTT_RO_SCHEMA
|
||||
|
|
|
@ -25,8 +25,9 @@ from homeassistant.const import (
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.json import json_dumps, json_loads_object
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util.json import json_loads_object
|
||||
|
||||
from .. import subscription
|
||||
from ..config import MQTT_BASE_SCHEMA
|
||||
|
|
|
@ -14,7 +14,8 @@ from homeassistant.components import ssdp, zeroconf
|
|||
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
from homeassistant.helpers.json import save_json
|
||||
from homeassistant.util.json import load_json
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
|
|
@ -25,9 +25,10 @@ from homeassistant.core import HomeAssistant, ServiceCall, split_entity_id
|
|||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.json import save_json
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import location
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
from homeassistant.util.json import load_json
|
||||
|
||||
from .config_flow import PlayStation4FlowHandler # noqa: F401
|
||||
from .const import (
|
||||
|
|
|
@ -35,10 +35,10 @@ from homeassistant.helpers.event import (
|
|||
async_track_time_interval,
|
||||
async_track_utc_time_change,
|
||||
)
|
||||
from homeassistant.helpers.json import JSON_ENCODE_EXCEPTIONS
|
||||
from homeassistant.helpers.start import async_at_started
|
||||
from homeassistant.helpers.typing import UNDEFINED, UndefinedType
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util.json import JSON_ENCODE_EXCEPTIONS
|
||||
|
||||
from . import migration, statistics
|
||||
from .const import (
|
||||
|
|
|
@ -41,15 +41,13 @@ from homeassistant.const import (
|
|||
MAX_LENGTH_STATE_STATE,
|
||||
)
|
||||
from homeassistant.core import Context, Event, EventOrigin, State, split_entity_id
|
||||
from homeassistant.helpers.json import (
|
||||
from homeassistant.helpers.json import JSON_DUMP, json_bytes, json_bytes_strip_null
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util.json import (
|
||||
JSON_DECODE_EXCEPTIONS,
|
||||
JSON_DUMP,
|
||||
json_bytes,
|
||||
json_bytes_strip_null,
|
||||
json_loads,
|
||||
json_loads_object,
|
||||
)
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from .const import ALL_DOMAIN_EXCLUDE_ATTRS, SupportedDialect
|
||||
from .models import (
|
||||
|
|
|
@ -16,8 +16,8 @@ from homeassistant.const import (
|
|||
COMPRESSED_STATE_STATE,
|
||||
)
|
||||
from homeassistant.core import Context, State
|
||||
from homeassistant.helpers.json import json_loads_object
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util.json import json_loads_object
|
||||
|
||||
from .const import SupportedDialect
|
||||
|
||||
|
|
|
@ -25,10 +25,11 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.exceptions import PlatformNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.json import json_dumps, json_loads
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
from homeassistant.helpers.template_entity import TemplateSensor
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from homeassistant.util.json import json_loads
|
||||
|
||||
from . import async_get_config_and_coordinator, create_rest_data_from_config
|
||||
from .const import CONF_JSON_ATTRS, CONF_JSON_ATTRS_PATH, DEFAULT_SENSOR_NAME
|
||||
|
|
|
@ -13,8 +13,9 @@ from homeassistant.config_entries import ConfigEntry
|
|||
from homeassistant.const import ATTR_NAME
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.json import save_json
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
from homeassistant.util.json import load_json
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
|
|
|
@ -29,7 +29,11 @@ from homeassistant.helpers.event import (
|
|||
TrackTemplateResult,
|
||||
async_track_template_result,
|
||||
)
|
||||
from homeassistant.helpers.json import JSON_DUMP, ExtendedJSONEncoder
|
||||
from homeassistant.helpers.json import (
|
||||
JSON_DUMP,
|
||||
ExtendedJSONEncoder,
|
||||
find_paths_unserializable_data,
|
||||
)
|
||||
from homeassistant.helpers.service import async_get_all_descriptions
|
||||
from homeassistant.loader import (
|
||||
Integration,
|
||||
|
@ -39,10 +43,7 @@ from homeassistant.loader import (
|
|||
async_get_integrations,
|
||||
)
|
||||
from homeassistant.setup import DATA_SETUP_TIME, async_get_loaded_integrations
|
||||
from homeassistant.util.json import (
|
||||
find_paths_unserializable_data,
|
||||
format_unserializable_data,
|
||||
)
|
||||
from homeassistant.util.json import format_unserializable_data
|
||||
|
||||
from . import const, decorators, messages
|
||||
from .connection import ActiveConnection
|
||||
|
|
|
@ -16,7 +16,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
|||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.helpers.json import json_loads
|
||||
from homeassistant.util.json import json_loads
|
||||
|
||||
from .auth import AuthPhase, auth_required_message
|
||||
from .const import (
|
||||
|
|
|
@ -16,11 +16,8 @@ from homeassistant.const import (
|
|||
)
|
||||
from homeassistant.core import Event, State
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.json import JSON_DUMP
|
||||
from homeassistant.util.json import (
|
||||
find_paths_unserializable_data,
|
||||
format_unserializable_data,
|
||||
)
|
||||
from homeassistant.helpers.json import JSON_DUMP, find_paths_unserializable_data
|
||||
from homeassistant.util.json import format_unserializable_data
|
||||
from homeassistant.util.yaml.loader import JSON_TYPE
|
||||
|
||||
from . import const
|
||||
|
|
|
@ -20,9 +20,10 @@ from homeassistant.const import APPLICATION_NAME, EVENT_HOMEASSISTANT_CLOSE, __v
|
|||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.util import ssl as ssl_util
|
||||
from homeassistant.util.json import json_loads
|
||||
|
||||
from .frame import warn_use
|
||||
from .json import json_dumps, json_loads
|
||||
from .json import json_dumps
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiohttp.typedefs import JSONDecoder
|
||||
|
|
|
@ -14,16 +14,13 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
|
|||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError, RequiredParameterMissing
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.util.json import (
|
||||
find_paths_unserializable_data,
|
||||
format_unserializable_data,
|
||||
)
|
||||
from homeassistant.util.json import format_unserializable_data
|
||||
import homeassistant.util.uuid as uuid_util
|
||||
|
||||
from . import storage
|
||||
from .debounce import Debouncer
|
||||
from .frame import report
|
||||
from .json import JSON_DUMP
|
||||
from .json import JSON_DUMP, find_paths_unserializable_data
|
||||
from .typing import UNDEFINED, UndefinedType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
|
@ -43,15 +43,12 @@ from homeassistant.core import (
|
|||
from homeassistant.exceptions import MaxLengthExceeded
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.util import slugify, uuid as uuid_util
|
||||
from homeassistant.util.json import (
|
||||
find_paths_unserializable_data,
|
||||
format_unserializable_data,
|
||||
)
|
||||
from homeassistant.util.json import format_unserializable_data
|
||||
|
||||
from . import device_registry as dr, storage
|
||||
from .device_registry import EVENT_DEVICE_REGISTRY_UPDATED
|
||||
from .frame import report
|
||||
from .json import JSON_DUMP
|
||||
from .json import JSON_DUMP, find_paths_unserializable_data
|
||||
from .typing import UNDEFINED, UndefinedType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
"""Helpers to help with encoding Home Assistant objects in JSON."""
|
||||
from collections import deque
|
||||
from collections.abc import Callable
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
import orjson
|
||||
|
||||
JsonValueType = (
|
||||
dict[str, "JsonValueType"] | list["JsonValueType"] | str | int | float | bool | None
|
||||
from homeassistant.core import Event, State
|
||||
from homeassistant.util.file import write_utf8_file, write_utf8_file_atomic
|
||||
from homeassistant.util.json import ( # pylint: disable=unused-import # noqa: F401
|
||||
JSON_DECODE_EXCEPTIONS,
|
||||
JSON_ENCODE_EXCEPTIONS,
|
||||
SerializationError,
|
||||
format_unserializable_data,
|
||||
json_loads,
|
||||
)
|
||||
"""Any data that can be returned by the standard JSON deserializing process."""
|
||||
JsonObjectType = dict[str, JsonValueType]
|
||||
"""Dictionary that can be returned by the standard JSON deserializing process."""
|
||||
|
||||
JSON_ENCODE_EXCEPTIONS = (TypeError, ValueError)
|
||||
JSON_DECODE_EXCEPTIONS = (orjson.JSONDecodeError,)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JSONEncoder(json.JSONEncoder):
|
||||
|
@ -140,18 +144,99 @@ def json_dumps_sorted(data: Any) -> str:
|
|||
).decode("utf-8")
|
||||
|
||||
|
||||
json_loads: Callable[[bytes | bytearray | memoryview | str], JsonValueType]
|
||||
json_loads = orjson.loads
|
||||
"""Parse JSON data."""
|
||||
|
||||
|
||||
def json_loads_object(__obj: bytes | bytearray | memoryview | str) -> JsonObjectType:
|
||||
"""Parse JSON data and ensure result is a dictionary."""
|
||||
value: JsonValueType = json_loads(__obj)
|
||||
# Avoid isinstance overhead as we are not interested in dict subclasses
|
||||
if type(value) is dict: # pylint: disable=unidiomatic-typecheck
|
||||
return value
|
||||
raise ValueError(f"Expected JSON to be parsed as a dict got {type(value)}")
|
||||
|
||||
|
||||
JSON_DUMP: Final = json_dumps
|
||||
|
||||
|
||||
def _orjson_default_encoder(data: Any) -> str:
|
||||
"""JSON encoder that uses orjson with hass defaults."""
|
||||
return orjson.dumps(
|
||||
data,
|
||||
option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS,
|
||||
default=json_encoder_default,
|
||||
).decode("utf-8")
|
||||
|
||||
|
||||
def save_json(
|
||||
filename: str,
|
||||
data: list | dict,
|
||||
private: bool = False,
|
||||
*,
|
||||
encoder: type[json.JSONEncoder] | None = None,
|
||||
atomic_writes: bool = False,
|
||||
) -> None:
|
||||
"""Save JSON data to a file."""
|
||||
dump: Callable[[Any], Any]
|
||||
try:
|
||||
# For backwards compatibility, if they pass in the
|
||||
# default json encoder we use _orjson_default_encoder
|
||||
# which is the orjson equivalent to the default encoder.
|
||||
if encoder and encoder is not JSONEncoder:
|
||||
# If they pass a custom encoder that is not the
|
||||
# default JSONEncoder, we use the slow path of json.dumps
|
||||
dump = json.dumps
|
||||
json_data = json.dumps(data, indent=2, cls=encoder)
|
||||
else:
|
||||
dump = _orjson_default_encoder
|
||||
json_data = _orjson_default_encoder(data)
|
||||
except TypeError as error:
|
||||
formatted_data = format_unserializable_data(
|
||||
find_paths_unserializable_data(data, dump=dump)
|
||||
)
|
||||
msg = f"Failed to serialize to JSON: {filename}. Bad data at {formatted_data}"
|
||||
_LOGGER.error(msg)
|
||||
raise SerializationError(msg) from error
|
||||
|
||||
if atomic_writes:
|
||||
write_utf8_file_atomic(filename, json_data, private)
|
||||
else:
|
||||
write_utf8_file(filename, json_data, private)
|
||||
|
||||
|
||||
def find_paths_unserializable_data(
|
||||
bad_data: Any, *, dump: Callable[[Any], str] = json.dumps
|
||||
) -> dict[str, Any]:
|
||||
"""Find the paths to unserializable data.
|
||||
|
||||
This method is slow! Only use for error handling.
|
||||
"""
|
||||
to_process = deque([(bad_data, "$")])
|
||||
invalid = {}
|
||||
|
||||
while to_process:
|
||||
obj, obj_path = to_process.popleft()
|
||||
|
||||
try:
|
||||
dump(obj)
|
||||
continue
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# We convert objects with as_dict to their dict values
|
||||
# so we can find bad data inside it
|
||||
if hasattr(obj, "as_dict"):
|
||||
desc = obj.__class__.__name__
|
||||
if isinstance(obj, State):
|
||||
desc += f": {obj.entity_id}"
|
||||
elif isinstance(obj, Event):
|
||||
desc += f": {obj.event_type}"
|
||||
|
||||
obj_path += f"({desc})"
|
||||
obj = obj.as_dict()
|
||||
|
||||
if isinstance(obj, dict):
|
||||
for key, value in obj.items():
|
||||
try:
|
||||
# Is key valid?
|
||||
dump({key: None})
|
||||
except TypeError:
|
||||
invalid[f"{obj_path}<key: {key}>"] = key
|
||||
else:
|
||||
# Process value
|
||||
to_process.append((value, f"{obj_path}.{key}"))
|
||||
elif isinstance(obj, list):
|
||||
for idx, value in enumerate(obj):
|
||||
to_process.append((value, f"{obj_path}[{idx}]"))
|
||||
else:
|
||||
invalid[obj_path] = obj
|
||||
|
||||
return invalid
|
||||
|
|
|
@ -17,6 +17,8 @@ from homeassistant.loader import MAX_LOAD_CONCURRENTLY, bind_hass
|
|||
from homeassistant.util import json as json_util
|
||||
from homeassistant.util.file import WriteError
|
||||
|
||||
from . import json as json_helper
|
||||
|
||||
# mypy: allow-untyped-calls, allow-untyped-defs, no-warn-return-any
|
||||
# mypy: no-check-untyped-defs
|
||||
|
||||
|
@ -290,7 +292,7 @@ class Store(Generic[_T]):
|
|||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
|
||||
_LOGGER.debug("Writing data for %s to %s", self.key, path)
|
||||
json_util.save_json(
|
||||
json_helper.save_json(
|
||||
path,
|
||||
data,
|
||||
self._private,
|
||||
|
|
|
@ -68,11 +68,11 @@ from homeassistant.util import (
|
|||
slugify as slugify_util,
|
||||
)
|
||||
from homeassistant.util.async_ import run_callback_threadsafe
|
||||
from homeassistant.util.json import JSON_DECODE_EXCEPTIONS, json_loads
|
||||
from homeassistant.util.read_only_dict import ReadOnlyDict
|
||||
from homeassistant.util.thread import ThreadWithException
|
||||
|
||||
from . import area_registry, device_registry, entity_registry, location as loc_helper
|
||||
from .json import JSON_DECODE_EXCEPTIONS, json_loads
|
||||
from .typing import TemplateVarsType
|
||||
|
||||
# mypy: allow-untyped-defs, no-check-untyped-defs
|
||||
|
|
|
@ -31,7 +31,7 @@ from .generated.mqtt import MQTT
|
|||
from .generated.ssdp import SSDP
|
||||
from .generated.usb import USB
|
||||
from .generated.zeroconf import HOMEKIT, ZEROCONF
|
||||
from .helpers.json import JSON_DECODE_EXCEPTIONS, json_loads
|
||||
from .util.json import JSON_DECODE_EXCEPTIONS, json_loads
|
||||
|
||||
# Typing imports that create a circular dependency
|
||||
if TYPE_CHECKING:
|
||||
|
|
|
@ -10,7 +10,7 @@ from aiohttp import payload, web
|
|||
from aiohttp.typedefs import JSONDecoder
|
||||
from multidict import CIMultiDict, MultiDict
|
||||
|
||||
from homeassistant.helpers.json import json_loads
|
||||
from .json import json_loads
|
||||
|
||||
|
||||
class MockStreamReader:
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""JSON utility functions."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import deque
|
||||
from collections.abc import Callable
|
||||
import json
|
||||
import logging
|
||||
|
@ -9,26 +8,41 @@ from typing import Any
|
|||
|
||||
import orjson
|
||||
|
||||
from homeassistant.core import Event, State
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.json import (
|
||||
JSONEncoder as DefaultHASSJSONEncoder,
|
||||
json_encoder_default as default_hass_orjson_encoder,
|
||||
)
|
||||
|
||||
from .file import ( # pylint: disable=unused-import # noqa: F401
|
||||
WriteError,
|
||||
write_utf8_file,
|
||||
write_utf8_file_atomic,
|
||||
)
|
||||
from .file import WriteError # pylint: disable=unused-import # noqa: F401
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
JsonValueType = (
|
||||
dict[str, "JsonValueType"] | list["JsonValueType"] | str | int | float | bool | None
|
||||
)
|
||||
"""Any data that can be returned by the standard JSON deserializing process."""
|
||||
JsonObjectType = dict[str, JsonValueType]
|
||||
"""Dictionary that can be returned by the standard JSON deserializing process."""
|
||||
|
||||
JSON_ENCODE_EXCEPTIONS = (TypeError, ValueError)
|
||||
JSON_DECODE_EXCEPTIONS = (orjson.JSONDecodeError,)
|
||||
|
||||
|
||||
class SerializationError(HomeAssistantError):
|
||||
"""Error serializing the data to JSON."""
|
||||
|
||||
|
||||
json_loads: Callable[[bytes | bytearray | memoryview | str], JsonValueType]
|
||||
json_loads = orjson.loads
|
||||
"""Parse JSON data."""
|
||||
|
||||
|
||||
def json_loads_object(__obj: bytes | bytearray | memoryview | str) -> JsonObjectType:
|
||||
"""Parse JSON data and ensure result is a dictionary."""
|
||||
value: JsonValueType = json_loads(__obj)
|
||||
# Avoid isinstance overhead as we are not interested in dict subclasses
|
||||
if type(value) is dict: # pylint: disable=unidiomatic-typecheck
|
||||
return value
|
||||
raise ValueError(f"Expected JSON to be parsed as a dict got {type(value)}")
|
||||
|
||||
|
||||
def load_json(filename: str, default: list | dict | None = None) -> list | dict:
|
||||
"""Load JSON data from a file and return as dict or list.
|
||||
|
||||
|
@ -49,15 +63,6 @@ def load_json(filename: str, default: list | dict | None = None) -> list | dict:
|
|||
return {} if default is None else default
|
||||
|
||||
|
||||
def _orjson_default_encoder(data: Any) -> str:
|
||||
"""JSON encoder that uses orjson with hass defaults."""
|
||||
return orjson.dumps(
|
||||
data,
|
||||
option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS,
|
||||
default=default_hass_orjson_encoder,
|
||||
).decode("utf-8")
|
||||
|
||||
|
||||
def save_json(
|
||||
filename: str,
|
||||
data: list | dict,
|
||||
|
@ -66,35 +71,25 @@ def save_json(
|
|||
encoder: type[json.JSONEncoder] | None = None,
|
||||
atomic_writes: bool = False,
|
||||
) -> None:
|
||||
"""Save JSON data to a file.
|
||||
"""Save JSON data to a file."""
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from homeassistant.helpers.frame import report
|
||||
|
||||
Returns True on success.
|
||||
"""
|
||||
dump: Callable[[Any], Any]
|
||||
try:
|
||||
# For backwards compatibility, if they pass in the
|
||||
# default json encoder we use _orjson_default_encoder
|
||||
# which is the orjson equivalent to the default encoder.
|
||||
if encoder and encoder is not DefaultHASSJSONEncoder:
|
||||
# If they pass a custom encoder that is not the
|
||||
# DefaultHASSJSONEncoder, we use the slow path of json.dumps
|
||||
dump = json.dumps
|
||||
json_data = json.dumps(data, indent=2, cls=encoder)
|
||||
else:
|
||||
dump = _orjson_default_encoder
|
||||
json_data = _orjson_default_encoder(data)
|
||||
except TypeError as error:
|
||||
formatted_data = format_unserializable_data(
|
||||
find_paths_unserializable_data(data, dump=dump)
|
||||
)
|
||||
msg = f"Failed to serialize to JSON: {filename}. Bad data at {formatted_data}"
|
||||
_LOGGER.error(msg)
|
||||
raise SerializationError(msg) from error
|
||||
report(
|
||||
(
|
||||
"uses save_json from homeassistant.util.json module."
|
||||
" This is deprecated and will stop working in Home Assistant 2022.4, it"
|
||||
" should be updated to use homeassistant.helpers.json module instead"
|
||||
),
|
||||
error_if_core=False,
|
||||
)
|
||||
|
||||
if atomic_writes:
|
||||
write_utf8_file_atomic(filename, json_data, private)
|
||||
else:
|
||||
write_utf8_file(filename, json_data, private)
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
import homeassistant.helpers.json as json_helper
|
||||
|
||||
json_helper.save_json(
|
||||
filename, data, private, encoder=encoder, atomic_writes=atomic_writes
|
||||
)
|
||||
|
||||
|
||||
def format_unserializable_data(data: dict[str, Any]) -> str:
|
||||
|
@ -112,44 +107,19 @@ def find_paths_unserializable_data(
|
|||
|
||||
This method is slow! Only use for error handling.
|
||||
"""
|
||||
to_process = deque([(bad_data, "$")])
|
||||
invalid = {}
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from homeassistant.helpers.frame import report
|
||||
|
||||
while to_process:
|
||||
obj, obj_path = to_process.popleft()
|
||||
report(
|
||||
(
|
||||
"uses find_paths_unserializable_data from homeassistant.util.json module."
|
||||
" This is deprecated and will stop working in Home Assistant 2022.4, it"
|
||||
" should be updated to use homeassistant.helpers.json module instead"
|
||||
),
|
||||
error_if_core=False,
|
||||
)
|
||||
|
||||
try:
|
||||
dump(obj)
|
||||
continue
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
import homeassistant.helpers.json as json_helper
|
||||
|
||||
# We convert objects with as_dict to their dict values
|
||||
# so we can find bad data inside it
|
||||
if hasattr(obj, "as_dict"):
|
||||
desc = obj.__class__.__name__
|
||||
if isinstance(obj, State):
|
||||
desc += f": {obj.entity_id}"
|
||||
elif isinstance(obj, Event):
|
||||
desc += f": {obj.event_type}"
|
||||
|
||||
obj_path += f"({desc})"
|
||||
obj = obj.as_dict()
|
||||
|
||||
if isinstance(obj, dict):
|
||||
for key, value in obj.items():
|
||||
try:
|
||||
# Is key valid?
|
||||
dump({key: None})
|
||||
except TypeError:
|
||||
invalid[f"{obj_path}<key: {key}>"] = key
|
||||
else:
|
||||
# Process value
|
||||
to_process.append((value, f"{obj_path}.{key}"))
|
||||
elif isinstance(obj, list):
|
||||
for idx, value in enumerate(obj):
|
||||
to_process.append((value, f"{obj_path}[{idx}]"))
|
||||
else:
|
||||
invalid[obj_path] = obj
|
||||
|
||||
return invalid
|
||||
return json_helper.find_paths_unserializable_data(bad_data, dump=dump)
|
||||
|
|
|
@ -350,6 +350,14 @@ _OBSOLETE_IMPORT: dict[str, list[ObsoleteImportMatch]] = {
|
|||
constant=re.compile(r"^DISABLED_(\w*)$"),
|
||||
),
|
||||
],
|
||||
"homeassistant.helpers.json": [
|
||||
ObsoleteImportMatch(
|
||||
reason="moved to homeassistant.util.json",
|
||||
constant=re.compile(
|
||||
r"^JSON_DECODE_EXCEPTIONS|JSON_ENCODE_EXCEPTIONS|json_loads$"
|
||||
),
|
||||
),
|
||||
],
|
||||
"homeassistant.util": [
|
||||
ObsoleteImportMatch(
|
||||
reason="replaced by unit_conversion.***Converter",
|
||||
|
@ -362,6 +370,12 @@ _OBSOLETE_IMPORT: dict[str, list[ObsoleteImportMatch]] = {
|
|||
constant=re.compile(r"^IMPERIAL_SYSTEM$"),
|
||||
),
|
||||
],
|
||||
"homeassistant.util.json": [
|
||||
ObsoleteImportMatch(
|
||||
reason="moved to homeassistant.helpers.json",
|
||||
constant=re.compile(r"^save_json|find_paths_unserializable_data$"),
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -46,15 +46,15 @@ async def _mock_backup_generation(manager: BackupManager):
|
|||
"pathlib.Path.mkdir",
|
||||
MagicMock(),
|
||||
), patch(
|
||||
"homeassistant.components.backup.manager.json_util.save_json"
|
||||
) as mocked_json_util, patch(
|
||||
"homeassistant.components.backup.manager.save_json"
|
||||
) as mocked_save_json, patch(
|
||||
"homeassistant.components.backup.manager.HAVERSION",
|
||||
"2025.1.0",
|
||||
):
|
||||
await manager.generate_backup()
|
||||
|
||||
assert mocked_json_util.call_count == 1
|
||||
assert mocked_json_util.call_args[0][1]["homeassistant"] == {
|
||||
assert mocked_save_json.call_count == 1
|
||||
assert mocked_save_json.call_args[0][1]["homeassistant"] == {
|
||||
"version": "2025.1.0"
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ from typing import cast
|
|||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
from homeassistant.helpers.json import JsonObjectType
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.json import JsonObjectType
|
||||
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
|
|
@ -97,8 +97,8 @@ from homeassistant.const import (
|
|||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers.json import JsonValueType, json_loads
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.json import JsonValueType, json_loads
|
||||
|
||||
from .test_common import (
|
||||
help_test_availability_when_connection_lost,
|
||||
|
|
|
@ -13,7 +13,6 @@ from homeassistant.helpers.json import (
|
|||
json_bytes_strip_null,
|
||||
json_dumps,
|
||||
json_dumps_sorted,
|
||||
json_loads_object,
|
||||
)
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.color import RGBColor
|
||||
|
@ -136,20 +135,3 @@ def test_json_bytes_strip_null() -> None:
|
|||
json_bytes_strip_null([[{"k1": {"k2": ["silly\0stuff"]}}]])
|
||||
== b'[[{"k1":{"k2":["silly"]}}]]'
|
||||
)
|
||||
|
||||
|
||||
def test_json_loads_object():
|
||||
"""Test json_loads_object validates result."""
|
||||
assert json_loads_object('{"c":1.2}') == {"c": 1.2}
|
||||
with pytest.raises(
|
||||
ValueError, match="Expected JSON to be parsed as a dict got <class 'list'>"
|
||||
):
|
||||
json_loads_object("[]")
|
||||
with pytest.raises(
|
||||
ValueError, match="Expected JSON to be parsed as a dict got <class 'bool'>"
|
||||
):
|
||||
json_loads_object("true")
|
||||
with pytest.raises(
|
||||
ValueError, match="Expected JSON to be parsed as a dict got <class 'NoneType'>"
|
||||
):
|
||||
json_loads_object("null")
|
||||
|
|
|
@ -15,6 +15,7 @@ from homeassistant.helpers.json import JSONEncoder as DefaultHASSJSONEncoder
|
|||
from homeassistant.util.json import (
|
||||
SerializationError,
|
||||
find_paths_unserializable_data,
|
||||
json_loads_object,
|
||||
load_json,
|
||||
save_json,
|
||||
)
|
||||
|
@ -191,3 +192,40 @@ def test_find_unserializable_data() -> None:
|
|||
BadData(),
|
||||
dump=partial(dumps, cls=MockJSONEncoder),
|
||||
) == {"$(BadData).bla": bad_data}
|
||||
|
||||
|
||||
def test_json_loads_object() -> None:
|
||||
"""Test json_loads_object validates result."""
|
||||
assert json_loads_object('{"c":1.2}') == {"c": 1.2}
|
||||
with pytest.raises(
|
||||
ValueError, match="Expected JSON to be parsed as a dict got <class 'list'>"
|
||||
):
|
||||
json_loads_object("[]")
|
||||
with pytest.raises(
|
||||
ValueError, match="Expected JSON to be parsed as a dict got <class 'bool'>"
|
||||
):
|
||||
json_loads_object("true")
|
||||
with pytest.raises(
|
||||
ValueError, match="Expected JSON to be parsed as a dict got <class 'NoneType'>"
|
||||
):
|
||||
json_loads_object("null")
|
||||
|
||||
|
||||
async def test_deprecated_test_find_unserializable_data(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test deprecated test_find_unserializable_data logs a warning."""
|
||||
find_paths_unserializable_data(1)
|
||||
assert (
|
||||
"uses find_paths_unserializable_data from homeassistant.util.json"
|
||||
in caplog.text
|
||||
)
|
||||
assert "should be updated to use homeassistant.helpers.json module" in caplog.text
|
||||
|
||||
|
||||
async def test_deprecated_save_json(caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Test deprecated save_json logs a warning."""
|
||||
fname = _path_for("test1")
|
||||
save_json(fname, TEST_JSON_A)
|
||||
assert "uses save_json from homeassistant.util.json" in caplog.text
|
||||
assert "should be updated to use homeassistant.helpers.json module" in caplog.text
|
||||
|
|
Loading…
Add table
Reference in a new issue