2019-04-13 13:17:01 -07:00
|
|
|
"""Validate manifests."""
|
2024-03-08 16:36:11 +01:00
|
|
|
|
2022-11-23 20:05:31 +02:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2020-04-16 09:00:04 -07:00
|
|
|
import argparse
|
2023-09-05 09:40:25 -05:00
|
|
|
from operator import attrgetter
|
2019-04-13 13:17:01 -07:00
|
|
|
import pathlib
|
|
|
|
import sys
|
2020-01-24 10:25:46 -08:00
|
|
|
from time import monotonic
|
2019-04-13 13:17:01 -07:00
|
|
|
|
2019-12-09 22:43:38 +01:00
|
|
|
from . import (
|
2022-04-30 08:06:43 -07:00
|
|
|
application_credentials,
|
2022-07-08 18:55:31 -05:00
|
|
|
bluetooth,
|
2019-12-09 22:43:38 +01:00
|
|
|
codeowners,
|
|
|
|
config_flow,
|
2023-06-12 10:26:02 +02:00
|
|
|
config_schema,
|
2019-12-09 22:43:38 +01:00
|
|
|
dependencies,
|
2021-01-13 22:09:08 -10:00
|
|
|
dhcp,
|
2023-11-29 11:26:50 +01:00
|
|
|
docker,
|
2024-01-19 16:56:56 +01:00
|
|
|
icons,
|
2020-04-21 23:42:04 +02:00
|
|
|
json,
|
2019-12-09 22:43:38 +01:00
|
|
|
manifest,
|
2022-01-28 13:36:20 +01:00
|
|
|
metadata,
|
2020-10-07 18:30:51 +02:00
|
|
|
mqtt,
|
2021-04-26 13:23:21 +01:00
|
|
|
mypy_config,
|
2020-09-06 23:41:41 +02:00
|
|
|
requirements,
|
2019-12-09 22:43:38 +01:00
|
|
|
services,
|
|
|
|
ssdp,
|
2020-04-15 16:58:20 -07:00
|
|
|
translations,
|
2021-08-20 14:04:18 -05:00
|
|
|
usb,
|
2019-12-09 22:43:38 +01:00
|
|
|
zeroconf,
|
|
|
|
)
|
2019-12-09 16:24:03 +01:00
|
|
|
from .model import Config, Integration
|
2019-07-31 12:25:30 -07:00
|
|
|
|
2020-04-16 09:00:04 -07:00
|
|
|
INTEGRATION_PLUGINS = [
|
2022-04-30 08:06:43 -07:00
|
|
|
application_credentials,
|
2022-07-08 18:55:31 -05:00
|
|
|
bluetooth,
|
2019-12-09 22:43:38 +01:00
|
|
|
codeowners,
|
2023-06-12 10:26:02 +02:00
|
|
|
config_schema,
|
2019-12-09 22:43:38 +01:00
|
|
|
dependencies,
|
2021-08-23 23:51:07 -07:00
|
|
|
dhcp,
|
2024-01-19 16:56:56 +01:00
|
|
|
icons,
|
2021-08-23 23:51:07 -07:00
|
|
|
json,
|
2019-12-09 22:43:38 +01:00
|
|
|
manifest,
|
2020-10-07 18:30:51 +02:00
|
|
|
mqtt,
|
2021-08-23 23:51:07 -07:00
|
|
|
requirements,
|
2019-12-09 22:43:38 +01:00
|
|
|
services,
|
|
|
|
ssdp,
|
2020-04-15 16:58:20 -07:00
|
|
|
translations,
|
2021-08-20 14:04:18 -05:00
|
|
|
usb,
|
2021-08-23 23:51:07 -07:00
|
|
|
zeroconf,
|
2023-06-12 10:26:02 +02:00
|
|
|
config_flow, # This needs to run last, after translations are processed
|
2019-12-09 22:43:38 +01:00
|
|
|
]
|
2020-04-16 09:00:04 -07:00
|
|
|
HASS_PLUGINS = [
|
2023-11-29 11:26:50 +01:00
|
|
|
docker,
|
2021-04-26 13:23:21 +01:00
|
|
|
mypy_config,
|
2022-01-28 13:36:20 +01:00
|
|
|
metadata,
|
2020-04-16 09:00:04 -07:00
|
|
|
]
|
|
|
|
|
2022-01-27 05:52:09 +01:00
|
|
|
ALL_PLUGIN_NAMES = [
|
|
|
|
plugin.__name__.rsplit(".", maxsplit=1)[-1]
|
|
|
|
for plugin in (*INTEGRATION_PLUGINS, *HASS_PLUGINS)
|
|
|
|
]
|
|
|
|
|
2020-04-16 09:00:04 -07:00
|
|
|
|
2022-11-23 20:05:31 +02:00
|
|
|
def valid_integration_path(integration_path: pathlib.Path | str) -> pathlib.Path:
|
2020-04-16 09:00:04 -07:00
|
|
|
"""Test if it's a valid integration."""
|
|
|
|
path = pathlib.Path(integration_path)
|
|
|
|
if not path.is_dir():
|
|
|
|
raise argparse.ArgumentTypeError(f"{integration_path} is not a directory.")
|
|
|
|
|
|
|
|
return path
|
2019-04-13 13:17:01 -07:00
|
|
|
|
|
|
|
|
2022-01-27 05:52:09 +01:00
|
|
|
def validate_plugins(plugin_names: str) -> list[str]:
|
|
|
|
"""Split and validate plugin names."""
|
|
|
|
all_plugin_names = set(ALL_PLUGIN_NAMES)
|
|
|
|
plugins = plugin_names.split(",")
|
|
|
|
for plugin in plugins:
|
|
|
|
if plugin not in all_plugin_names:
|
|
|
|
raise argparse.ArgumentTypeError(f"{plugin} is not a valid plugin name")
|
|
|
|
|
|
|
|
return plugins
|
|
|
|
|
|
|
|
|
2019-04-13 13:17:01 -07:00
|
|
|
def get_config() -> Config:
|
|
|
|
"""Return config."""
|
2020-04-16 09:00:04 -07:00
|
|
|
parser = argparse.ArgumentParser(description="Hassfest")
|
|
|
|
parser.add_argument(
|
|
|
|
"--action", type=str, choices=["validate", "generate"], default=None
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"--integration-path",
|
|
|
|
action="append",
|
|
|
|
type=valid_integration_path,
|
|
|
|
help="Validate a single integration",
|
|
|
|
)
|
2020-09-06 23:41:41 +02:00
|
|
|
parser.add_argument(
|
|
|
|
"--requirements",
|
|
|
|
action="store_true",
|
|
|
|
help="Validate requirements",
|
|
|
|
)
|
2022-01-27 05:52:09 +01:00
|
|
|
parser.add_argument(
|
|
|
|
"-p",
|
|
|
|
"--plugins",
|
|
|
|
type=validate_plugins,
|
|
|
|
default=ALL_PLUGIN_NAMES,
|
|
|
|
help="Comma-separate list of plugins to run. Valid plugin names: %(default)s",
|
|
|
|
)
|
2020-04-16 09:00:04 -07:00
|
|
|
parsed = parser.parse_args()
|
|
|
|
|
|
|
|
if parsed.action is None:
|
|
|
|
parsed.action = "validate" if parsed.integration_path else "generate"
|
|
|
|
|
|
|
|
if parsed.action == "generate" and parsed.integration_path:
|
|
|
|
raise RuntimeError(
|
|
|
|
"Generate is not allowed when limiting to specific integrations"
|
|
|
|
)
|
|
|
|
|
|
|
|
if (
|
|
|
|
not parsed.integration_path
|
|
|
|
and not pathlib.Path("requirements_all.txt").is_file()
|
|
|
|
):
|
|
|
|
raise RuntimeError("Run from Home Assistant root")
|
2019-04-13 13:17:01 -07:00
|
|
|
|
|
|
|
return Config(
|
|
|
|
root=pathlib.Path(".").absolute(),
|
2020-04-16 09:00:04 -07:00
|
|
|
specific_integrations=parsed.integration_path,
|
|
|
|
action=parsed.action,
|
2020-09-06 23:41:41 +02:00
|
|
|
requirements=parsed.requirements,
|
2022-01-27 05:52:09 +01:00
|
|
|
plugins=set(parsed.plugins),
|
2019-04-13 13:17:01 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-11-23 20:05:31 +02:00
|
|
|
def main() -> int:
|
2019-04-13 13:17:01 -07:00
|
|
|
"""Validate manifests."""
|
|
|
|
try:
|
|
|
|
config = get_config()
|
|
|
|
except RuntimeError as err:
|
|
|
|
print(err)
|
|
|
|
return 1
|
|
|
|
|
2020-09-06 23:41:41 +02:00
|
|
|
plugins = [*INTEGRATION_PLUGINS]
|
|
|
|
|
2020-04-16 09:00:04 -07:00
|
|
|
if config.specific_integrations:
|
|
|
|
integrations = {}
|
|
|
|
|
|
|
|
for int_path in config.specific_integrations:
|
|
|
|
integration = Integration(int_path)
|
|
|
|
integration.load_manifest()
|
|
|
|
integrations[integration.domain] = integration
|
2019-04-22 23:34:37 -07:00
|
|
|
|
2020-04-16 09:00:04 -07:00
|
|
|
else:
|
|
|
|
integrations = Integration.load_dir(pathlib.Path("homeassistant/components"))
|
|
|
|
plugins += HASS_PLUGINS
|
|
|
|
|
|
|
|
for plugin in plugins:
|
2022-01-27 05:52:09 +01:00
|
|
|
plugin_name = plugin.__name__.rsplit(".", maxsplit=1)[-1]
|
|
|
|
if plugin_name not in config.plugins:
|
|
|
|
continue
|
2020-01-24 10:25:46 -08:00
|
|
|
try:
|
|
|
|
start = monotonic()
|
2022-01-27 05:52:09 +01:00
|
|
|
print(f"Validating {plugin_name}...", end="", flush=True)
|
2021-08-23 23:51:07 -07:00
|
|
|
if (
|
|
|
|
plugin is requirements
|
|
|
|
and config.requirements
|
|
|
|
and not config.specific_integrations
|
|
|
|
):
|
2020-09-06 23:41:41 +02:00
|
|
|
print()
|
2020-01-24 10:25:46 -08:00
|
|
|
plugin.validate(integrations, config)
|
2021-04-09 18:58:27 +02:00
|
|
|
print(f" done in {monotonic() - start:.2f}s")
|
2020-01-24 10:25:46 -08:00
|
|
|
except RuntimeError as err:
|
|
|
|
print()
|
|
|
|
print()
|
|
|
|
print("Error!")
|
|
|
|
print(err)
|
|
|
|
return 1
|
2019-04-13 13:17:01 -07:00
|
|
|
|
|
|
|
# When we generate, all errors that are fixable will be ignored,
|
|
|
|
# as generating them will be fixed.
|
|
|
|
if config.action == "generate":
|
|
|
|
general_errors = [err for err in config.errors if not err.fixable]
|
|
|
|
invalid_itg = [
|
|
|
|
itg
|
|
|
|
for itg in integrations.values()
|
|
|
|
if any(not error.fixable for error in itg.errors)
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
# action == validate
|
|
|
|
general_errors = config.errors
|
|
|
|
invalid_itg = [itg for itg in integrations.values() if itg.errors]
|
|
|
|
|
2020-04-16 18:00:30 -07:00
|
|
|
warnings_itg = [itg for itg in integrations.values() if itg.warnings]
|
|
|
|
|
2020-04-16 09:00:04 -07:00
|
|
|
print()
|
2019-04-13 13:17:01 -07:00
|
|
|
print("Integrations:", len(integrations))
|
|
|
|
print("Invalid integrations:", len(invalid_itg))
|
2020-04-16 18:00:30 -07:00
|
|
|
print()
|
2019-04-13 13:17:01 -07:00
|
|
|
|
|
|
|
if not invalid_itg and not general_errors:
|
2020-04-16 18:00:30 -07:00
|
|
|
print_integrations_status(config, warnings_itg, show_fixable_errors=False)
|
|
|
|
|
2020-04-16 09:00:04 -07:00
|
|
|
if config.action == "generate":
|
|
|
|
for plugin in plugins:
|
2022-01-27 05:52:09 +01:00
|
|
|
plugin_name = plugin.__name__.rsplit(".", maxsplit=1)[-1]
|
|
|
|
if plugin_name not in config.plugins:
|
|
|
|
continue
|
2020-04-16 09:00:04 -07:00
|
|
|
if hasattr(plugin, "generate"):
|
|
|
|
plugin.generate(integrations, config)
|
2019-04-13 13:17:01 -07:00
|
|
|
return 0
|
|
|
|
|
|
|
|
if config.action == "generate":
|
|
|
|
print("Found errors. Generating files canceled.")
|
|
|
|
print()
|
|
|
|
|
|
|
|
if general_errors:
|
|
|
|
print("General errors:")
|
|
|
|
for error in general_errors:
|
|
|
|
print("*", error)
|
|
|
|
print()
|
|
|
|
|
2020-04-16 18:00:30 -07:00
|
|
|
invalid_itg.extend(itg for itg in warnings_itg if itg not in invalid_itg)
|
|
|
|
|
|
|
|
print_integrations_status(config, invalid_itg, show_fixable_errors=False)
|
|
|
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
2022-11-23 20:05:31 +02:00
|
|
|
def print_integrations_status(
|
|
|
|
config: Config,
|
|
|
|
integrations: list[Integration],
|
|
|
|
*,
|
|
|
|
show_fixable_errors: bool = True,
|
|
|
|
) -> None:
|
2020-04-16 18:00:30 -07:00
|
|
|
"""Print integration status."""
|
2023-09-05 09:40:25 -05:00
|
|
|
for integration in sorted(integrations, key=attrgetter("domain")):
|
2020-04-16 09:00:04 -07:00
|
|
|
extra = f" - {integration.path}" if config.specific_integrations else ""
|
|
|
|
print(f"Integration {integration.domain}{extra}:")
|
2019-04-13 13:17:01 -07:00
|
|
|
for error in integration.errors:
|
2020-04-16 18:00:30 -07:00
|
|
|
if show_fixable_errors or not error.fixable:
|
2021-01-25 13:31:14 +01:00
|
|
|
print("*", "[ERROR]", error)
|
2020-04-16 18:00:30 -07:00
|
|
|
for warning in integration.warnings:
|
|
|
|
print("*", "[WARNING]", warning)
|
2019-04-13 13:17:01 -07:00
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
sys.exit(main())
|