2019-04-13 20:17:01 +00:00
|
|
|
"""Models for manifest validator."""
|
2021-03-18 21:58:19 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2019-12-09 15:24:03 +00:00
|
|
|
import importlib
|
2019-04-13 20:17:01 +00:00
|
|
|
import json
|
|
|
|
import pathlib
|
2021-03-18 21:58:19 +00:00
|
|
|
from typing import Any
|
2019-04-13 20:17:01 +00:00
|
|
|
|
|
|
|
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)
|
2019-04-13 20:17:01 +00:00
|
|
|
|
|
|
|
def __str__(self) -> str:
|
|
|
|
"""Represent error as string."""
|
2020-04-05 10:49:57 +00:00
|
|
|
return f"[{self.plugin.upper()}] {self.error}"
|
2019-04-13 20:17:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
@attr.s
|
|
|
|
class Config:
|
|
|
|
"""Config for the run."""
|
|
|
|
|
2021-03-18 21:58:19 +00:00
|
|
|
specific_integrations: pathlib.Path | None = attr.ib()
|
2020-04-16 16:00:04 +00:00
|
|
|
root: pathlib.Path = attr.ib()
|
|
|
|
action: str = attr.ib()
|
2020-09-06 21:41:41 +00:00
|
|
|
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)
|
2019-04-13 20:17:01 +00:00
|
|
|
|
2021-04-26 12:23:21 +00:00
|
|
|
def add_error(self, *args: Any, **kwargs: Any) -> None:
|
2019-04-13 20:17:01 +00:00
|
|
|
"""Add an error."""
|
|
|
|
self.errors.append(Error(*args, **kwargs))
|
|
|
|
|
|
|
|
|
|
|
|
@attr.s
|
|
|
|
class Integration:
|
|
|
|
"""Represent an integration in our validator."""
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def load_dir(cls, path: pathlib.Path):
|
|
|
|
"""Load all integrations in a directory."""
|
|
|
|
assert path.is_dir()
|
|
|
|
integrations = {}
|
|
|
|
for fil in path.iterdir():
|
2019-07-31 19:25:30 +00:00
|
|
|
if fil.is_file() or fil.name == "__pycache__":
|
2019-04-13 20:17:01 +00:00
|
|
|
continue
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
init = fil / "__init__.py"
|
2019-04-29 08:53:27 +00:00
|
|
|
if not init.exists():
|
2019-07-31 19:25:30 +00:00
|
|
|
print(
|
2020-01-03 13:47:06 +00:00
|
|
|
f"Warning: {init} missing, skipping directory. "
|
2019-07-31 19:25:30 +00:00
|
|
|
"If this is your development environment, "
|
2020-01-03 13:47:06 +00:00
|
|
|
"you can safely delete this folder."
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-04-29 08:53:27 +00:00
|
|
|
continue
|
|
|
|
|
2019-04-13 20:17:01 +00:00
|
|
|
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()
|
2021-04-15 08:21:38 +00:00
|
|
|
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)
|
2019-04-13 20:17:01 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def domain(self) -> str:
|
|
|
|
"""Integration domain."""
|
|
|
|
return self.path.name
|
|
|
|
|
2021-01-25 12:31:14 +00:00
|
|
|
@property
|
|
|
|
def core(self) -> bool:
|
|
|
|
"""Core integration."""
|
|
|
|
return self.path.as_posix().startswith("homeassistant/components")
|
|
|
|
|
2020-08-26 08:20:14 +00:00
|
|
|
@property
|
2021-03-18 21:58:19 +00:00
|
|
|
def disabled(self) -> str | None:
|
2021-05-17 06:12:23 +00:00
|
|
|
"""Return if integration is disabled."""
|
2020-08-26 08:20:14 +00:00
|
|
|
return self.manifest.get("disabled")
|
|
|
|
|
2021-07-22 06:37:33 +00:00
|
|
|
@property
|
|
|
|
def name(self) -> str:
|
|
|
|
"""Return name of the integration."""
|
|
|
|
return self.manifest["name"]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def quality_scale(self) -> str:
|
|
|
|
"""Return quality scale of the integration."""
|
|
|
|
return self.manifest.get("quality_scale")
|
|
|
|
|
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."""
|
|
|
|
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."""
|
|
|
|
return self.manifest.get("dependencies", [])
|
|
|
|
|
2021-04-26 12:23:21 +00:00
|
|
|
def add_error(self, *args: Any, **kwargs: Any) -> None:
|
2019-04-13 20:17:01 +00:00
|
|
|
"""Add an error."""
|
|
|
|
self.errors.append(Error(*args, **kwargs))
|
|
|
|
|
2021-04-29 09:43:23 +00:00
|
|
|
def add_warning(self, *args: Any, **kwargs: Any) -> None:
|
2020-04-17 01:00:30 +00:00
|
|
|
"""Add an warning."""
|
|
|
|
self.warnings.append(Error(*args, **kwargs))
|
|
|
|
|
2019-04-13 20:17:01 +00:00
|
|
|
def load_manifest(self) -> None:
|
|
|
|
"""Load manifest."""
|
2019-07-31 19:25:30 +00:00
|
|
|
manifest_path = self.path / "manifest.json"
|
2019-04-18 20:40:46 +00:00
|
|
|
if not manifest_path.is_file():
|
2019-08-23 16:53:33 +00:00
|
|
|
self.add_error("model", f"Manifest file {manifest_path} not found")
|
2019-04-13 20:17:01 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
2019-04-18 20:40:46 +00:00
|
|
|
manifest = json.loads(manifest_path.read_text())
|
2019-04-13 20:17:01 +00:00
|
|
|
except ValueError as err:
|
2019-08-23 16:53:33 +00:00
|
|
|
self.add_error("model", f"Manifest contains invalid JSON: {err}")
|
2019-04-13 20:17:01 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
self.manifest = manifest
|
2019-05-13 08:16:55 +00:00
|
|
|
|
|
|
|
def import_pkg(self, platform=None):
|
|
|
|
"""Import the Python file."""
|
2019-08-23 16:53:33 +00:00
|
|
|
pkg = f"homeassistant.components.{self.domain}"
|
2019-05-13 08:16:55 +00:00
|
|
|
if platform is not None:
|
2019-08-23 16:53:33 +00:00
|
|
|
pkg += f".{platform}"
|
2019-05-13 08:16:55 +00:00
|
|
|
return importlib.import_module(pkg)
|