Change json error handling (#930)

* Change json error handling

* Typing + modern way to read file

* fix lint
This commit is contained in:
Pascal Vizeli 2019-02-26 00:19:05 +01:00 committed by GitHub
parent b36e178c45
commit 227125cc0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 75 deletions

View File

@ -1,7 +1,6 @@
"""Init file for Hass.io add-ons.""" """Init file for Hass.io add-ons."""
from contextlib import suppress from contextlib import suppress
from copy import deepcopy from copy import deepcopy
import json
import logging import logging
from pathlib import Path, PurePath from pathlib import Path, PurePath
import re import re
@ -29,7 +28,7 @@ from ..const import (
STATE_STARTED, STATE_STOPPED) STATE_STARTED, STATE_STOPPED)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..docker.addon import DockerAddon from ..docker.addon import DockerAddon
from ..exceptions import HostAppArmorError from ..exceptions import HostAppArmorError, JsonFileError
from ..utils import create_token from ..utils import create_token
from ..utils.apparmor import adjust_profile from ..utils.apparmor import adjust_profile
from ..utils.json import read_json_file, write_json_file from ..utils.json import read_json_file, write_json_file
@ -616,8 +615,8 @@ class Addon(CoreSysAttributes):
except vol.Invalid as ex: except vol.Invalid as ex:
_LOGGER.error("Add-on %s have wrong options: %s", self._id, _LOGGER.error("Add-on %s have wrong options: %s", self._id,
humanize_error(options, ex)) humanize_error(options, ex))
except (OSError, json.JSONDecodeError) as err: except JsonFileError:
_LOGGER.error("Add-on %s can't write options: %s", self._id, err) _LOGGER.error("Add-on %s can't write options", self._id)
else: else:
return True return True
@ -892,8 +891,8 @@ class Addon(CoreSysAttributes):
# Store local configs/state # Store local configs/state
try: try:
write_json_file(Path(temp, 'addon.json'), data) write_json_file(Path(temp, 'addon.json'), data)
except (OSError, json.JSONDecodeError) as err: except JsonFileError:
_LOGGER.error("Can't save meta for %s: %s", self._id, err) _LOGGER.error("Can't save meta for %s", self._id)
return False return False
# Store AppArmor Profile # Store AppArmor Profile
@ -940,8 +939,8 @@ class Addon(CoreSysAttributes):
# Read snapshot data # Read snapshot data
try: try:
data = read_json_file(Path(temp, 'addon.json')) data = read_json_file(Path(temp, 'addon.json'))
except (OSError, json.JSONDecodeError) as err: except JsonFileError:
_LOGGER.error("Can't read addon.json: %s", err) return False
# Validate # Validate
try: try:

View File

@ -1,19 +1,25 @@
"""Init file for Hass.io add-on data.""" """Init file for Hass.io add-on data."""
import logging import logging
import json
from pathlib import Path from pathlib import Path
import voluptuous as vol import voluptuous as vol
from voluptuous.humanize import humanize_error from voluptuous.humanize import humanize_error
from .utils import extract_hash_from_path
from .validate import (
SCHEMA_ADDON_CONFIG, SCHEMA_ADDONS_FILE, SCHEMA_REPOSITORY_CONFIG)
from ..const import ( from ..const import (
FILE_HASSIO_ADDONS, ATTR_SLUG, ATTR_REPOSITORY, ATTR_LOCATON, ATTR_LOCATON,
REPOSITORY_CORE, REPOSITORY_LOCAL, ATTR_USER, ATTR_SYSTEM) ATTR_REPOSITORY,
ATTR_SLUG,
ATTR_SYSTEM,
ATTR_USER,
FILE_HASSIO_ADDONS,
REPOSITORY_CORE,
REPOSITORY_LOCAL,
)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import JsonFileError
from ..utils.json import JsonConfig, read_json_file from ..utils.json import JsonConfig, read_json_file
from .utils import extract_hash_from_path
from .validate import SCHEMA_ADDON_CONFIG, SCHEMA_ADDONS_FILE, SCHEMA_REPOSITORY_CONFIG
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -54,12 +60,10 @@ class AddonsData(JsonConfig, CoreSysAttributes):
self._repositories = {} self._repositories = {}
# read core repository # read core repository
self._read_addons_folder( self._read_addons_folder(self.sys_config.path_addons_core, REPOSITORY_CORE)
self.sys_config.path_addons_core, REPOSITORY_CORE)
# read local repository # read local repository
self._read_addons_folder( self._read_addons_folder(self.sys_config.path_addons_local, REPOSITORY_LOCAL)
self.sys_config.path_addons_local, REPOSITORY_LOCAL)
# add built-in repositories information # add built-in repositories information
self._set_builtin_repositories() self._set_builtin_repositories()
@ -76,15 +80,12 @@ class AddonsData(JsonConfig, CoreSysAttributes):
# exists repository json # exists repository json
repository_file = Path(path, "repository.json") repository_file = Path(path, "repository.json")
try: try:
repository_info = SCHEMA_REPOSITORY_CONFIG( repository_info = SCHEMA_REPOSITORY_CONFIG(read_json_file(repository_file))
read_json_file(repository_file) except JsonFileError:
_LOGGER.warning(
"Can't read repository information from %s", repository_file
) )
except (OSError, json.JSONDecodeError, UnicodeDecodeError):
_LOGGER.warning("Can't read repository information from %s",
repository_file)
return return
except vol.Invalid: except vol.Invalid:
_LOGGER.warning("Repository parse error %s", repository_file) _LOGGER.warning("Repository parse error %s", repository_file)
return return
@ -98,23 +99,21 @@ class AddonsData(JsonConfig, CoreSysAttributes):
for addon in path.glob("**/config.json"): for addon in path.glob("**/config.json"):
try: try:
addon_config = read_json_file(addon) addon_config = read_json_file(addon)
except JsonFileError:
except (OSError, json.JSONDecodeError, UnicodeDecodeError): _LOGGER.warning("Can't read %s from repository %s", addon, repository)
_LOGGER.warning("Can't read %s", addon)
continue continue
# validate # validate
try: try:
addon_config = SCHEMA_ADDON_CONFIG(addon_config) addon_config = SCHEMA_ADDON_CONFIG(addon_config)
except vol.Invalid as ex: except vol.Invalid as ex:
_LOGGER.warning("Can't read %s: %s", addon, _LOGGER.warning(
humanize_error(addon_config, ex)) "Can't read %s: %s", addon, humanize_error(addon_config, ex)
)
continue continue
# Generate slug # Generate slug
addon_slug = "{}_{}".format( addon_slug = "{}_{}".format(repository, addon_config[ATTR_SLUG])
repository, addon_config[ATTR_SLUG])
# store # store
addon_config[ATTR_REPOSITORY] = repository addon_config[ATTR_REPOSITORY] = repository
@ -126,14 +125,12 @@ class AddonsData(JsonConfig, CoreSysAttributes):
try: try:
builtin_file = Path(__file__).parent.joinpath("built-in.json") builtin_file = Path(__file__).parent.joinpath("built-in.json")
builtin_data = read_json_file(builtin_file) builtin_data = read_json_file(builtin_file)
except (OSError, json.JSONDecodeError) as err: except JsonFileError:
_LOGGER.warning("Can't read built-in json: %s", err) _LOGGER.warning("Can't read built-in json")
return return
# core repository # core repository
self._repositories[REPOSITORY_CORE] = \ self._repositories[REPOSITORY_CORE] = builtin_data[REPOSITORY_CORE]
builtin_data[REPOSITORY_CORE]
# local repository # local repository
self._repositories[REPOSITORY_LOCAL] = \ self._repositories[REPOSITORY_LOCAL] = builtin_data[REPOSITORY_LOCAL]
builtin_data[REPOSITORY_LOCAL]

View File

@ -1,11 +1,10 @@
"""Handle Arch for underlay maschine/platforms.""" """Handle Arch for underlay maschine/platforms."""
import json
import logging import logging
from typing import List from typing import List
from pathlib import Path from pathlib import Path
from .coresys import CoreSysAttributes, CoreSys from .coresys import CoreSysAttributes, CoreSys
from .exceptions import HassioArchNotFound from .exceptions import HassioArchNotFound, JsonFileError
from .utils.json import read_json_file from .utils.json import read_json_file
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -38,10 +37,9 @@ class CpuArch(CoreSysAttributes):
async def load(self) -> None: async def load(self) -> None:
"""Load data and initialize default arch.""" """Load data and initialize default arch."""
try: try:
arch_file = Path(__file__).parent.joinpath("arch.json") arch_data = read_json_file(Path(__file__).parent.joinpath("arch.json"))
arch_data = read_json_file(arch_file) except JsonFileError:
except (OSError, json.JSONDecodeError) as err: _LOGGER.warning("Can't read arch json")
_LOGGER.warning("Can't read arch json: %s", err)
return return
# Evaluate current CPU/Platform # Evaluate current CPU/Platform

View File

@ -137,3 +137,10 @@ class AppArmorFileError(AppArmorError):
class AppArmorInvalidError(AppArmorError): class AppArmorInvalidError(AppArmorError):
"""AppArmor profile validate error.""" """AppArmor profile validate error."""
# util/json
class JsonFileError(HassioError):
"""Invalid json file."""

View File

@ -1,82 +1,89 @@
"""Tools file for Hass.io.""" """Tools file for Hass.io."""
import json import json
import logging import logging
from pathlib import Path
from typing import Any, Dict
import voluptuous as vol import voluptuous as vol
from voluptuous.humanize import humanize_error from voluptuous.humanize import humanize_error
from ..exceptions import JsonFileError
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def write_json_file(jsonfile, data): def write_json_file(jsonfile: Path, data: Any) -> None:
"""Write a JSON file.""" """Write a JSON file."""
json_str = json.dumps(data, indent=2) try:
with jsonfile.open('w') as conf_file: jsonfile.write_text(json.dumps(data, indent=2))
conf_file.write(json_str) except (OSError, ValueError, TypeError) as err:
_LOGGER.error("Can't write %s: %s", jsonfile, err)
raise JsonFileError() from None
def read_json_file(jsonfile): def read_json_file(jsonfile: Path) -> Any:
"""Read a JSON file and return a dict.""" """Read a JSON file and return a dict."""
with jsonfile.open('r') as cfile: try:
return json.loads(cfile.read()) return json.loads(jsonfile.read_text())
except (OSError, ValueError, TypeError, UnicodeDecodeError) as err:
_LOGGER.error("Can't read json from %s: %s", jsonfile, err)
raise JsonFileError() from None
class JsonConfig: class JsonConfig:
"""Hass core object for handle it.""" """Hass core object for handle it."""
def __init__(self, json_file, schema): def __init__(self, json_file: Path, schema: vol.Schema):
"""Initialize hass object.""" """Initialize hass object."""
self._file = json_file self._file: Path = json_file
self._schema = schema self._schema: vol.Schema = schema
self._data = {} self._data: Dict[str, Any] = {}
self.read_data() self.read_data()
def reset_data(self): def reset_data(self) -> None:
"""Reset JSON file to default.""" """Reset JSON file to default."""
try: try:
self._data = self._schema({}) self._data = self._schema({})
except vol.Invalid as ex: except vol.Invalid as ex:
_LOGGER.error("Can't reset %s: %s", _LOGGER.error(
self._file, humanize_error(self._data, ex)) "Can't reset %s: %s", self._file, humanize_error(self._data, ex)
)
def read_data(self): def read_data(self) -> None:
"""Read JSON file & validate.""" """Read JSON file & validate."""
if self._file.is_file(): if self._file.is_file():
try: try:
self._data = read_json_file(self._file) self._data = read_json_file(self._file)
except (OSError, json.JSONDecodeError, UnicodeDecodeError): except JsonFileError:
_LOGGER.warning("Can't read %s", self._file)
self._data = {} self._data = {}
# Validate # Validate
try: try:
self._data = self._schema(self._data) self._data = self._schema(self._data)
except vol.Invalid as ex: except vol.Invalid as ex:
_LOGGER.error("Can't parse %s: %s", _LOGGER.error(
self._file, humanize_error(self._data, ex)) "Can't parse %s: %s", self._file, humanize_error(self._data, ex)
)
# Reset data to default # Reset data to default
_LOGGER.warning("Reset %s to default", self._file) _LOGGER.warning("Reset %s to default", self._file)
self._data = self._schema({}) self._data = self._schema({})
def save_data(self): def save_data(self) -> None:
"""Store data to configuration file.""" """Store data to configuration file."""
# Validate # Validate
try: try:
self._data = self._schema(self._data) self._data = self._schema(self._data)
except vol.Invalid as ex: except vol.Invalid as ex:
_LOGGER.error("Can't parse data: %s", _LOGGER.error("Can't parse data: %s", humanize_error(self._data, ex))
humanize_error(self._data, ex))
# Load last valid data # Load last valid data
_LOGGER.warning("Reset %s to last version", self._file) _LOGGER.warning("Reset %s to last version", self._file)
self.read_data() self.read_data()
return else:
# write # write
try: try:
write_json_file(self._file, self._data) write_json_file(self._file, self._data)
except (OSError, json.JSONDecodeError) as err: except JsonFileError:
_LOGGER.error( pass
"Can't store configuration in %s: %s", self._file, err)