core/script/hassfest/model.py

228 lines
7.2 KiB
Python
Raw Normal View History

"""Models for manifest validator."""
2021-03-18 21:58:19 +00:00
from __future__ import annotations
import json
import pathlib
2021-03-18 21:58:19 +00:00
from typing import Any
import attr
@attr.s
class Error:
"""Error validating an integration."""
2020-07-14 17:30:30 +00:00
plugin: str = attr.ib()
error: str = attr.ib()
fixable: bool = attr.ib(default=False)
def __str__(self) -> str:
"""Represent error as string."""
return f"[{self.plugin.upper()}] {self.error}"
@attr.s
class Config:
"""Config for the run."""
2021-03-18 21:58:19 +00:00
specific_integrations: pathlib.Path | None = attr.ib()
root: pathlib.Path = attr.ib()
action: str = attr.ib()
requirements: bool = attr.ib()
2021-03-18 21:58:19 +00:00
errors: list[Error] = attr.ib(factory=list)
cache: dict[str, Any] = attr.ib(factory=dict)
2022-01-27 04:52:09 +00:00
plugins: set[str] = attr.ib(factory=set)
def add_error(self, *args: Any, **kwargs: Any) -> None:
"""Add an error."""
self.errors.append(Error(*args, **kwargs))
@attr.s
class Brand:
"""Represent a brand in our validator."""
@classmethod
def load_dir(cls, path: pathlib.Path, config: Config) -> dict[str, Brand]:
"""Load all brands in a directory."""
assert path.is_dir()
brands: dict[str, Brand] = {}
for fil in path.iterdir():
brand = cls(fil)
brand.load_brand(config)
brands[brand.domain] = brand
return brands
path: pathlib.Path = attr.ib()
brand: dict[str, Any] | None = attr.ib(default=None)
@property
def domain(self) -> str:
"""Integration domain."""
return self.path.stem
@property
def name(self) -> str | None:
"""Return name of the integration."""
assert self.brand is not None, "brand has not been loaded"
return self.brand.get("name")
@property
def integrations(self) -> list[str]:
"""Return the sub integrations of this brand."""
assert self.brand is not None, "brand has not been loaded"
return self.brand.get("integrations", [])
@property
def iot_standards(self) -> list[str]:
"""Return list of supported IoT standards."""
assert self.brand is not None, "brand has not been loaded"
return self.brand.get("iot_standards", [])
def load_brand(self, config: Config) -> None:
"""Load brand file."""
if not self.path.is_file():
config.add_error("model", f"Brand file {self.path} not found")
return
try:
brand: dict[str, Any] = json.loads(self.path.read_text())
except ValueError as err:
config.add_error(
"model", f"Brand file {self.path.name} contains invalid JSON: {err}"
)
return
self.brand = brand
@attr.s
class Integration:
"""Represent an integration in our validator."""
@classmethod
def load_dir(cls, path: pathlib.Path) -> dict[str, Integration]:
"""Load all integrations in a directory."""
assert path.is_dir()
integrations: dict[str, Integration] = {}
for fil in path.iterdir():
2019-07-31 19:25:30 +00:00
if fil.is_file() or fil.name == "__pycache__":
continue
2019-07-31 19:25:30 +00:00
init = fil / "__init__.py"
manifest = fil / "manifest.json"
if not init.exists() and not manifest.exists():
2019-07-31 19:25:30 +00:00
print(
f"Warning: {init} and manifest.json missing, "
"skipping directory. If this is your development "
"environment, you can safely delete this folder."
2019-07-31 19:25:30 +00:00
)
continue
integration = cls(fil)
integration.load_manifest()
integrations[integration.domain] = integration
return integrations
2020-07-14 17:30:30 +00:00
path: pathlib.Path = attr.ib()
manifest: dict[str, Any] | None = attr.ib(default=None)
2021-03-18 21:58:19 +00:00
errors: list[Error] = attr.ib(factory=list)
warnings: list[Error] = attr.ib(factory=list)
translated_name: bool = attr.ib(default=False)
@property
def domain(self) -> str:
"""Integration domain."""
return self.path.name
@property
def core(self) -> bool:
"""Core integration."""
return self.path.as_posix().startswith("homeassistant/components")
@property
2021-03-18 21:58:19 +00:00
def disabled(self) -> str | None:
"""Return if integration is disabled."""
assert self.manifest is not None, "manifest has not been loaded"
return self.manifest.get("disabled")
@property
def name(self) -> str:
"""Return name of the integration."""
assert self.manifest is not None, "manifest has not been loaded"
name: str = self.manifest["name"]
return name
@property
def quality_scale(self) -> str | None:
"""Return quality scale of the integration."""
assert self.manifest is not None, "manifest has not been loaded"
return self.manifest.get("quality_scale")
@property
def config_flow(self) -> bool:
"""Return if the integration has a config flow."""
assert self.manifest is not None, "manifest has not been loaded"
return self.manifest.get("config_flow", False)
2020-04-03 19:58:19 +00:00
@property
2021-03-18 21:58:19 +00:00
def requirements(self) -> list[str]:
2020-04-03 19:58:19 +00:00
"""List of requirements."""
assert self.manifest is not None, "manifest has not been loaded"
2020-04-03 19:58:19 +00:00
return self.manifest.get("requirements", [])
@property
2021-03-18 21:58:19 +00:00
def dependencies(self) -> list[str]:
2020-04-03 19:58:19 +00:00
"""List of dependencies."""
assert self.manifest is not None, "manifest has not been loaded"
2020-04-03 19:58:19 +00:00
return self.manifest.get("dependencies", [])
@property
def supported_by(self) -> str:
"""Return the integration supported by this virtual integration."""
assert self.manifest is not None, "manifest has not been loaded"
return self.manifest.get("supported_by", {})
2022-03-21 03:38:13 +00:00
@property
def integration_type(self) -> str:
"""Get integration_type."""
assert self.manifest is not None, "manifest has not been loaded"
return self.manifest.get("integration_type", "hub")
2022-03-21 03:38:13 +00:00
@property
def iot_class(self) -> str | None:
"""Return the integration IoT Class."""
assert self.manifest is not None, "manifest has not been loaded"
return self.manifest.get("iot_class")
@property
def iot_standards(self) -> list[str]:
"""Return the IoT standard supported by this virtual integration."""
assert self.manifest is not None, "manifest has not been loaded"
return self.manifest.get("iot_standards", [])
def add_error(self, *args: Any, **kwargs: Any) -> None:
"""Add an error."""
self.errors.append(Error(*args, **kwargs))
def add_warning(self, *args: Any, **kwargs: Any) -> None:
"""Add a warning."""
self.warnings.append(Error(*args, **kwargs))
def load_manifest(self) -> None:
"""Load manifest."""
2019-07-31 19:25:30 +00:00
manifest_path = self.path / "manifest.json"
if not manifest_path.is_file():
self.add_error("model", f"Manifest file {manifest_path} not found")
return
try:
manifest: dict[str, Any] = json.loads(manifest_path.read_text())
except ValueError as err:
self.add_error("model", f"Manifest contains invalid JSON: {err}")
return
self.manifest = manifest