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

View File

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

View File

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

View File

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

View File

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