mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 18:28:14 +02:00
Black
This commit is contained in:
@ -17,162 +17,164 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
# mypy: allow-untyped-calls, allow-untyped-defs
|
||||
|
||||
REQUIREMENTS = ('colorlog==4.0.2',)
|
||||
REQUIREMENTS = ("colorlog==4.0.2",)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
# pylint: disable=protected-access
|
||||
MOCKS = {
|
||||
'load': ("homeassistant.util.yaml.loader.load_yaml",
|
||||
yaml_loader.load_yaml),
|
||||
'load*': ("homeassistant.config.load_yaml", yaml_loader.load_yaml),
|
||||
'secrets': ("homeassistant.util.yaml.loader.secret_yaml",
|
||||
yaml_loader.secret_yaml),
|
||||
"load": ("homeassistant.util.yaml.loader.load_yaml", yaml_loader.load_yaml),
|
||||
"load*": ("homeassistant.config.load_yaml", yaml_loader.load_yaml),
|
||||
"secrets": ("homeassistant.util.yaml.loader.secret_yaml", yaml_loader.secret_yaml),
|
||||
} # type: Dict[str, Tuple[str, Callable]]
|
||||
SILENCE = (
|
||||
'homeassistant.scripts.check_config.yaml_loader.clear_secret_cache',
|
||||
)
|
||||
SILENCE = ("homeassistant.scripts.check_config.yaml_loader.clear_secret_cache",)
|
||||
|
||||
PATCHES = {}
|
||||
|
||||
C_HEAD = 'bold'
|
||||
ERROR_STR = 'General Errors'
|
||||
C_HEAD = "bold"
|
||||
ERROR_STR = "General Errors"
|
||||
|
||||
|
||||
def color(the_color, *args, reset=None):
|
||||
"""Color helper."""
|
||||
from colorlog.escape_codes import escape_codes, parse_colors
|
||||
|
||||
try:
|
||||
if not args:
|
||||
assert reset is None, "You cannot reset if nothing being printed"
|
||||
return parse_colors(the_color)
|
||||
return parse_colors(the_color) + ' '.join(args) + \
|
||||
escape_codes[reset or 'reset']
|
||||
return parse_colors(the_color) + " ".join(args) + escape_codes[reset or "reset"]
|
||||
except KeyError as k:
|
||||
raise ValueError("Invalid color {} in {}".format(str(k), the_color))
|
||||
|
||||
|
||||
def run(script_args: List) -> int:
|
||||
"""Handle check config commandline script."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Check Home Assistant configuration.")
|
||||
parser = argparse.ArgumentParser(description="Check Home Assistant configuration.")
|
||||
parser.add_argument("--script", choices=["check_config"])
|
||||
parser.add_argument(
|
||||
'--script', choices=['check_config'])
|
||||
parser.add_argument(
|
||||
'-c', '--config',
|
||||
"-c",
|
||||
"--config",
|
||||
default=get_default_config_dir(),
|
||||
help="Directory that contains the Home Assistant configuration")
|
||||
help="Directory that contains the Home Assistant configuration",
|
||||
)
|
||||
parser.add_argument(
|
||||
'-i', '--info', nargs='?',
|
||||
default=None, const='all',
|
||||
help="Show a portion of the config")
|
||||
"-i",
|
||||
"--info",
|
||||
nargs="?",
|
||||
default=None,
|
||||
const="all",
|
||||
help="Show a portion of the config",
|
||||
)
|
||||
parser.add_argument(
|
||||
'-f', '--files',
|
||||
action='store_true',
|
||||
help="Show used configuration files")
|
||||
"-f", "--files", action="store_true", help="Show used configuration files"
|
||||
)
|
||||
parser.add_argument(
|
||||
'-s', '--secrets',
|
||||
action='store_true',
|
||||
help="Show secret information")
|
||||
"-s", "--secrets", action="store_true", help="Show secret information"
|
||||
)
|
||||
|
||||
args, unknown = parser.parse_known_args()
|
||||
if unknown:
|
||||
print(color('red', "Unknown arguments:", ', '.join(unknown)))
|
||||
print(color("red", "Unknown arguments:", ", ".join(unknown)))
|
||||
|
||||
config_dir = os.path.join(os.getcwd(), args.config)
|
||||
|
||||
print(color('bold', "Testing configuration at", config_dir))
|
||||
print(color("bold", "Testing configuration at", config_dir))
|
||||
|
||||
res = check(config_dir, args.secrets)
|
||||
|
||||
domain_info = [] # type: List[str]
|
||||
if args.info:
|
||||
domain_info = args.info.split(',')
|
||||
domain_info = args.info.split(",")
|
||||
|
||||
if args.files:
|
||||
print(color(C_HEAD, 'yaml files'), '(used /',
|
||||
color('red', 'not used') + ')')
|
||||
deps = os.path.join(config_dir, 'deps')
|
||||
yaml_files = [f for f in glob(os.path.join(config_dir, '**/*.yaml'),
|
||||
recursive=True)
|
||||
if not f.startswith(deps)]
|
||||
print(color(C_HEAD, "yaml files"), "(used /", color("red", "not used") + ")")
|
||||
deps = os.path.join(config_dir, "deps")
|
||||
yaml_files = [
|
||||
f
|
||||
for f in glob(os.path.join(config_dir, "**/*.yaml"), recursive=True)
|
||||
if not f.startswith(deps)
|
||||
]
|
||||
|
||||
for yfn in sorted(yaml_files):
|
||||
the_color = '' if yfn in res['yaml_files'] else 'red'
|
||||
print(color(the_color, '-', yfn))
|
||||
the_color = "" if yfn in res["yaml_files"] else "red"
|
||||
print(color(the_color, "-", yfn))
|
||||
|
||||
if res['except']:
|
||||
print(color('bold_white', 'Failed config'))
|
||||
for domain, config in res['except'].items():
|
||||
if res["except"]:
|
||||
print(color("bold_white", "Failed config"))
|
||||
for domain, config in res["except"].items():
|
||||
domain_info.append(domain)
|
||||
print(' ', color('bold_red', domain + ':'),
|
||||
color('red', '', reset='red'))
|
||||
dump_dict(config, reset='red')
|
||||
print(color('reset'))
|
||||
print(" ", color("bold_red", domain + ":"), color("red", "", reset="red"))
|
||||
dump_dict(config, reset="red")
|
||||
print(color("reset"))
|
||||
|
||||
if domain_info:
|
||||
if 'all' in domain_info:
|
||||
print(color('bold_white', 'Successful config (all)'))
|
||||
for domain, config in res['components'].items():
|
||||
print(' ', color(C_HEAD, domain + ':'))
|
||||
if "all" in domain_info:
|
||||
print(color("bold_white", "Successful config (all)"))
|
||||
for domain, config in res["components"].items():
|
||||
print(" ", color(C_HEAD, domain + ":"))
|
||||
dump_dict(config)
|
||||
else:
|
||||
print(color('bold_white', 'Successful config (partial)'))
|
||||
print(color("bold_white", "Successful config (partial)"))
|
||||
for domain in domain_info:
|
||||
if domain == ERROR_STR:
|
||||
continue
|
||||
print(' ', color(C_HEAD, domain + ':'))
|
||||
dump_dict(res['components'].get(domain, None))
|
||||
print(" ", color(C_HEAD, domain + ":"))
|
||||
dump_dict(res["components"].get(domain, None))
|
||||
|
||||
if args.secrets:
|
||||
flatsecret = {} # type: Dict[str, str]
|
||||
|
||||
for sfn, sdict in res['secret_cache'].items():
|
||||
for sfn, sdict in res["secret_cache"].items():
|
||||
sss = []
|
||||
for skey in sdict:
|
||||
if skey in flatsecret:
|
||||
_LOGGER.error('Duplicated secrets in files %s and %s',
|
||||
flatsecret[skey], sfn)
|
||||
_LOGGER.error(
|
||||
"Duplicated secrets in files %s and %s", flatsecret[skey], sfn
|
||||
)
|
||||
flatsecret[skey] = sfn
|
||||
sss.append(color('green', skey) if skey in res['secrets']
|
||||
else skey)
|
||||
print(color(C_HEAD, 'Secrets from', sfn + ':'), ', '.join(sss))
|
||||
sss.append(color("green", skey) if skey in res["secrets"] else skey)
|
||||
print(color(C_HEAD, "Secrets from", sfn + ":"), ", ".join(sss))
|
||||
|
||||
print(color(C_HEAD, 'Used Secrets:'))
|
||||
for skey, sval in res['secrets'].items():
|
||||
print(color(C_HEAD, "Used Secrets:"))
|
||||
for skey, sval in res["secrets"].items():
|
||||
if sval is None:
|
||||
print(' -', skey + ':', color('red', "not found"))
|
||||
print(" -", skey + ":", color("red", "not found"))
|
||||
continue
|
||||
print(' -', skey + ':', sval, color('cyan', '[from:', flatsecret
|
||||
.get(skey, 'keyring') + ']'))
|
||||
print(
|
||||
" -",
|
||||
skey + ":",
|
||||
sval,
|
||||
color("cyan", "[from:", flatsecret.get(skey, "keyring") + "]"),
|
||||
)
|
||||
|
||||
return len(res['except'])
|
||||
return len(res["except"])
|
||||
|
||||
|
||||
def check(config_dir, secrets=False):
|
||||
"""Perform a check by mocking hass load functions."""
|
||||
logging.getLogger('homeassistant.loader').setLevel(logging.CRITICAL)
|
||||
logging.getLogger("homeassistant.loader").setLevel(logging.CRITICAL)
|
||||
res = {
|
||||
'yaml_files': OrderedDict(), # yaml_files loaded
|
||||
'secrets': OrderedDict(), # secret cache and secrets loaded
|
||||
'except': OrderedDict(), # exceptions raised (with config)
|
||||
"yaml_files": OrderedDict(), # yaml_files loaded
|
||||
"secrets": OrderedDict(), # secret cache and secrets loaded
|
||||
"except": OrderedDict(), # exceptions raised (with config)
|
||||
#'components' is a HomeAssistantConfig # noqa: E265
|
||||
'secret_cache': None,
|
||||
"secret_cache": None,
|
||||
} # type: Dict[str, Any]
|
||||
|
||||
# pylint: disable=possibly-unused-variable
|
||||
def mock_load(filename):
|
||||
"""Mock hass.util.load_yaml to save config file names."""
|
||||
res['yaml_files'][filename] = True
|
||||
return MOCKS['load'][1](filename)
|
||||
res["yaml_files"][filename] = True
|
||||
return MOCKS["load"][1](filename)
|
||||
|
||||
# pylint: disable=possibly-unused-variable
|
||||
def mock_secrets(ldr, node):
|
||||
"""Mock _get_secrets."""
|
||||
try:
|
||||
val = MOCKS['secrets'][1](ldr, node)
|
||||
val = MOCKS["secrets"][1](ldr, node)
|
||||
except HomeAssistantError:
|
||||
val = None
|
||||
res['secrets'][node.value] = val
|
||||
res["secrets"][node.value] = val
|
||||
return val
|
||||
|
||||
# Patches to skip functions
|
||||
@ -181,11 +183,11 @@ def check(config_dir, secrets=False):
|
||||
|
||||
# Patches with local mock functions
|
||||
for key, val in MOCKS.items():
|
||||
if not secrets and key == 'secrets':
|
||||
if not secrets and key == "secrets":
|
||||
continue
|
||||
# The * in the key is removed to find the mock_function (side_effect)
|
||||
# This allows us to use one side_effect to patch multiple locations
|
||||
mock_function = locals()['mock_' + key.replace('*', '')]
|
||||
mock_function = locals()["mock_" + key.replace("*", "")]
|
||||
PATCHES[key] = patch(val[0], side_effect=mock_function)
|
||||
|
||||
# Start all patches
|
||||
@ -194,26 +196,26 @@ def check(config_dir, secrets=False):
|
||||
|
||||
if secrets:
|
||||
# Ensure !secrets point to the patched function
|
||||
yaml_loader.yaml.SafeLoader.add_constructor('!secret',
|
||||
yaml_loader.secret_yaml)
|
||||
yaml_loader.yaml.SafeLoader.add_constructor("!secret", yaml_loader.secret_yaml)
|
||||
|
||||
try:
|
||||
hass = core.HomeAssistant()
|
||||
hass.config.config_dir = config_dir
|
||||
|
||||
res['components'] = hass.loop.run_until_complete(
|
||||
async_check_ha_config_file(hass))
|
||||
res['secret_cache'] = OrderedDict(yaml_loader.__SECRET_CACHE)
|
||||
for err in res['components'].errors:
|
||||
res["components"] = hass.loop.run_until_complete(
|
||||
async_check_ha_config_file(hass)
|
||||
)
|
||||
res["secret_cache"] = OrderedDict(yaml_loader.__SECRET_CACHE)
|
||||
for err in res["components"].errors:
|
||||
domain = err.domain or ERROR_STR
|
||||
res['except'].setdefault(domain, []).append(err.message)
|
||||
res["except"].setdefault(domain, []).append(err.message)
|
||||
if err.config:
|
||||
res['except'].setdefault(domain, []).append(err.config)
|
||||
res["except"].setdefault(domain, []).append(err.config)
|
||||
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
_LOGGER.exception("BURB")
|
||||
print(color('red', 'Fatal error while loading config:'), str(err))
|
||||
res['except'].setdefault(ERROR_STR, []).append(str(err))
|
||||
print(color("red", "Fatal error while loading config:"), str(err))
|
||||
res["except"].setdefault(ERROR_STR, []).append(str(err))
|
||||
finally:
|
||||
# Stop all patches
|
||||
for pat in PATCHES.values():
|
||||
@ -221,7 +223,8 @@ def check(config_dir, secrets=False):
|
||||
if secrets:
|
||||
# Ensure !secrets point to the original function
|
||||
yaml_loader.yaml.SafeLoader.add_constructor(
|
||||
'!secret', yaml_loader.secret_yaml)
|
||||
"!secret", yaml_loader.secret_yaml
|
||||
)
|
||||
bootstrap.clear_secret_cache()
|
||||
|
||||
return res
|
||||
@ -229,11 +232,13 @@ def check(config_dir, secrets=False):
|
||||
|
||||
def line_info(obj, **kwargs):
|
||||
"""Display line config source."""
|
||||
if hasattr(obj, '__config_file__'):
|
||||
return color('cyan', "[source {}:{}]"
|
||||
.format(obj.__config_file__, obj.__line__ or '?'),
|
||||
**kwargs)
|
||||
return '?'
|
||||
if hasattr(obj, "__config_file__"):
|
||||
return color(
|
||||
"cyan",
|
||||
"[source {}:{}]".format(obj.__config_file__, obj.__line__ or "?"),
|
||||
**kwargs,
|
||||
)
|
||||
return "?"
|
||||
|
||||
|
||||
def dump_dict(layer, indent_count=3, listi=False, **kwargs):
|
||||
@ -241,25 +246,26 @@ def dump_dict(layer, indent_count=3, listi=False, **kwargs):
|
||||
|
||||
A friendly version of print yaml_loader.yaml.dump(config).
|
||||
"""
|
||||
|
||||
def sort_dict_key(val):
|
||||
"""Return the dict key for sorting."""
|
||||
key = str(val[0]).lower()
|
||||
return '0' if key == 'platform' else key
|
||||
return "0" if key == "platform" else key
|
||||
|
||||
indent_str = indent_count * ' '
|
||||
indent_str = indent_count * " "
|
||||
if listi or isinstance(layer, list):
|
||||
indent_str = indent_str[:-1] + '-'
|
||||
indent_str = indent_str[:-1] + "-"
|
||||
if isinstance(layer, Dict):
|
||||
for key, value in sorted(layer.items(), key=sort_dict_key):
|
||||
if isinstance(value, (dict, list)):
|
||||
print(indent_str, str(key) + ':', line_info(value, **kwargs))
|
||||
print(indent_str, str(key) + ":", line_info(value, **kwargs))
|
||||
dump_dict(value, indent_count + 2)
|
||||
else:
|
||||
print(indent_str, str(key) + ':', value)
|
||||
indent_str = indent_count * ' '
|
||||
print(indent_str, str(key) + ":", value)
|
||||
indent_str = indent_count * " "
|
||||
if isinstance(layer, Sequence):
|
||||
for i in layer:
|
||||
if isinstance(i, dict):
|
||||
dump_dict(i, indent_count + 2, True)
|
||||
else:
|
||||
print(' ', indent_str, i)
|
||||
print(" ", indent_str, i)
|
||||
|
Reference in New Issue
Block a user