diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index ec1e13e07fc..56a07a6bcf0 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -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__) diff --git a/homeassistant/components/backup/manager.py b/homeassistant/components/backup/manager.py index cf42f49fa1b..e38312dd6eb 100644 --- a/homeassistant/components/backup/manager.py +++ b/homeassistant/components/backup/manager.py @@ -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, ) diff --git a/homeassistant/components/conversation/default_agent.py b/homeassistant/components/conversation/default_agent.py index b7ae2ddce34..6d9afcbafa0 100644 --- a/homeassistant/components/conversation/default_agent.py +++ b/homeassistant/components/conversation/default_agent.py @@ -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 diff --git a/homeassistant/components/diagnostics/__init__.py b/homeassistant/components/diagnostics/__init__.py index 1a3732ca1e2..ea7b13f4719 100644 --- a/homeassistant/components/diagnostics/__init__.py +++ b/homeassistant/components/diagnostics/__init__.py @@ -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 diff --git a/homeassistant/components/fitbit/sensor.py b/homeassistant/components/fitbit/sensor.py index bc6931a29c6..d703699a433 100644 --- a/homeassistant/components/fitbit/sensor.py +++ b/homeassistant/components/fitbit/sensor.py @@ -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 ( diff --git a/homeassistant/components/html5/notify.py b/homeassistant/components/html5/notify.py index 09de0c37bb4..cea362e4ba8 100644 --- a/homeassistant/components/html5/notify.py +++ b/homeassistant/components/html5/notify.py @@ -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 diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index 98d86ba3524..d39fca28782 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -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 diff --git a/homeassistant/components/ios/__init__.py b/homeassistant/components/ios/__init__.py index 717effa2bb1..e28c1a218ef 100644 --- a/homeassistant/components/ios/__init__.py +++ b/homeassistant/components/ios/__init__.py @@ -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, diff --git a/homeassistant/components/matrix/__init__.py b/homeassistant/components/matrix/__init__.py index 87e692af3c5..84fafd3f068 100644 --- a/homeassistant/components/matrix/__init__.py +++ b/homeassistant/components/matrix/__init__.py @@ -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 diff --git a/homeassistant/components/mobile_app/helpers.py b/homeassistant/components/mobile_app/helpers.py index e0656f158c8..76fa3732d4c 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -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, diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index e7e16fc684d..ff5ea22e7de 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -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 diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 9b1eb81acc5..e7c458e6822 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -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 diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 4573dfcb879..cf565b42390 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -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 diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 87fd7d764c9..55b2f99d536 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -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 diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 1fbe4305dd6..b52c57ce24f 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -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 diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index 6b9cd819445..e279deb70b3 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -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 diff --git a/homeassistant/components/mqtt/trigger.py b/homeassistant/components/mqtt/trigger.py index c10e539f8ec..623c6c53373 100644 --- a/homeassistant/components/mqtt/trigger.py +++ b/homeassistant/components/mqtt/trigger.py @@ -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 diff --git a/homeassistant/components/mqtt/update.py b/homeassistant/components/mqtt/update.py index ecade88f06a..930f4d22506 100644 --- a/homeassistant/components/mqtt/update.py +++ b/homeassistant/components/mqtt/update.py @@ -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 diff --git a/homeassistant/components/mqtt/vacuum/schema_state.py b/homeassistant/components/mqtt/vacuum/schema_state.py index 1f3c35394ef..3f8c6953abe 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -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 diff --git a/homeassistant/components/nanoleaf/config_flow.py b/homeassistant/components/nanoleaf/config_flow.py index cab6c8003d0..be16db310b5 100644 --- a/homeassistant/components/nanoleaf/config_flow.py +++ b/homeassistant/components/nanoleaf/config_flow.py @@ -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 diff --git a/homeassistant/components/ps4/__init__.py b/homeassistant/components/ps4/__init__.py index 3ab1136c7d7..28ebd8ff077 100644 --- a/homeassistant/components/ps4/__init__.py +++ b/homeassistant/components/ps4/__init__.py @@ -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 ( diff --git a/homeassistant/components/recorder/core.py b/homeassistant/components/recorder/core.py index a8f00ca5ba0..2604497eff4 100644 --- a/homeassistant/components/recorder/core.py +++ b/homeassistant/components/recorder/core.py @@ -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 ( diff --git a/homeassistant/components/recorder/db_schema.py b/homeassistant/components/recorder/db_schema.py index 9c9c4071776..1f717f0a737 100644 --- a/homeassistant/components/recorder/db_schema.py +++ b/homeassistant/components/recorder/db_schema.py @@ -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 ( diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 40939bff1ba..52eae0b85a0 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -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 diff --git a/homeassistant/components/rest/sensor.py b/homeassistant/components/rest/sensor.py index 9f14dea076c..07fef4c4eab 100644 --- a/homeassistant/components/rest/sensor.py +++ b/homeassistant/components/rest/sensor.py @@ -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 diff --git a/homeassistant/components/shopping_list/__init__.py b/homeassistant/components/shopping_list/__init__.py index 3c478a3c83a..5bbb2118913 100644 --- a/homeassistant/components/shopping_list/__init__.py +++ b/homeassistant/components/shopping_list/__init__.py @@ -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, diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 4a41c193116..e8008eb49b6 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -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 diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index a363ed80b5b..d92e52dbf84 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -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 ( diff --git a/homeassistant/components/websocket_api/messages.py b/homeassistant/components/websocket_api/messages.py index 15965b37faa..0765c6a5b7c 100644 --- a/homeassistant/components/websocket_api/messages.py +++ b/homeassistant/components/websocket_api/messages.py @@ -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 diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 0437dfc4e84..d623de5e816 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -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 diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index a586a14d161..9ea44db16d5 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -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: diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 426f84c2f89..f779fe00ed1 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -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: diff --git a/homeassistant/helpers/json.py b/homeassistant/helpers/json.py index 0fdb7570bc6..38afa37838a 100644 --- a/homeassistant/helpers/json.py +++ b/homeassistant/helpers/json.py @@ -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 + 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 diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index 03b66843819..19e028af900 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -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, diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 4eb703c0072..9aafe53925c 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -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 diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 36854388e91..600fef5a134 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -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: diff --git a/homeassistant/util/aiohttp.py b/homeassistant/util/aiohttp.py index 804228b67f7..6e2cfc5325d 100644 --- a/homeassistant/util/aiohttp.py +++ b/homeassistant/util/aiohttp.py @@ -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: diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index c1ced053930..9ad1f060dad 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -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 - 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) diff --git a/pylint/plugins/hass_imports.py b/pylint/plugins/hass_imports.py index 8c176644ce2..be44c4256ce 100644 --- a/pylint/plugins/hass_imports.py +++ b/pylint/plugins/hass_imports.py @@ -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$"), + ), + ], } diff --git a/tests/components/backup/test_manager.py b/tests/components/backup/test_manager.py index 50455995db6..aa7d6bae893 100644 --- a/tests/components/backup/test_manager.py +++ b/tests/components/backup/test_manager.py @@ -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" } diff --git a/tests/components/diagnostics/__init__.py b/tests/components/diagnostics/__init__.py index e576fbd974f..5c81917b22c 100644 --- a/tests/components/diagnostics/__init__.py +++ b/tests/components/diagnostics/__init__.py @@ -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 diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 2716ed692ed..be664d1c1f8 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -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, diff --git a/tests/helpers/test_json.py b/tests/helpers/test_json.py index 60b8f193d95..30b20c73adf 100644 --- a/tests/helpers/test_json.py +++ b/tests/helpers/test_json.py @@ -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 " - ): - json_loads_object("[]") - with pytest.raises( - ValueError, match="Expected JSON to be parsed as a dict got " - ): - json_loads_object("true") - with pytest.raises( - ValueError, match="Expected JSON to be parsed as a dict got " - ): - json_loads_object("null") diff --git a/tests/util/test_json.py b/tests/util/test_json.py index 43a320b7b34..91df3ae0d68 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -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 " + ): + json_loads_object("[]") + with pytest.raises( + ValueError, match="Expected JSON to be parsed as a dict got " + ): + json_loads_object("true") + with pytest.raises( + ValueError, match="Expected JSON to be parsed as a dict got " + ): + 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