Add services.yaml validator (#23205)
* Add services.yaml validator * Fix path
This commit is contained in:
parent
37cd711c96
commit
33b8241d26
9 changed files with 172 additions and 71 deletions
|
@ -3,12 +3,13 @@ import pathlib
|
|||
import sys
|
||||
|
||||
from .model import Integration, Config
|
||||
from . import dependencies, manifest, codeowners
|
||||
from . import dependencies, manifest, codeowners, services
|
||||
|
||||
PLUGINS = [
|
||||
manifest,
|
||||
dependencies,
|
||||
codeowners,
|
||||
services,
|
||||
]
|
||||
|
||||
|
||||
|
@ -37,6 +38,7 @@ def main():
|
|||
manifest.validate(integrations, config)
|
||||
dependencies.validate(integrations, config)
|
||||
codeowners.validate(integrations, config)
|
||||
services.validate(integrations, config)
|
||||
|
||||
# When we generate, all errors that are fixable will be ignored,
|
||||
# as generating them will be fixed.
|
||||
|
|
|
@ -61,26 +61,22 @@ class Integration:
|
|||
"""Integration domain."""
|
||||
return self.path.name
|
||||
|
||||
@property
|
||||
def manifest_path(self) -> pathlib.Path:
|
||||
"""Integration manifest path."""
|
||||
return self.path / 'manifest.json'
|
||||
|
||||
def add_error(self, *args, **kwargs):
|
||||
"""Add an error."""
|
||||
self.errors.append(Error(*args, **kwargs))
|
||||
|
||||
def load_manifest(self) -> None:
|
||||
"""Load manifest."""
|
||||
if not self.manifest_path.is_file():
|
||||
manifest_path = self.path / 'manifest.json'
|
||||
if not manifest_path.is_file():
|
||||
self.add_error(
|
||||
'model',
|
||||
"Manifest file {} not found".format(self.manifest_path)
|
||||
"Manifest file {} not found".format(manifest_path)
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
manifest = json.loads(self.manifest_path.read_text())
|
||||
manifest = json.loads(manifest_path.read_text())
|
||||
except ValueError as err:
|
||||
self.add_error(
|
||||
'model',
|
||||
|
|
104
script/hassfest/services.py
Normal file
104
script/hassfest/services.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
"""Validate dependencies."""
|
||||
import pathlib
|
||||
from typing import Dict
|
||||
|
||||
import re
|
||||
import voluptuous as vol
|
||||
from voluptuous.humanize import humanize_error
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.util.yaml import load_yaml
|
||||
|
||||
from .model import Integration
|
||||
|
||||
|
||||
def exists(value):
|
||||
"""Check if value exists."""
|
||||
if value is None:
|
||||
raise vol.Invalid("Value cannot be None")
|
||||
return value
|
||||
|
||||
|
||||
FIELD_SCHEMA = vol.Schema({
|
||||
vol.Required('description'): str,
|
||||
vol.Optional('example'): exists,
|
||||
vol.Optional('default'): exists,
|
||||
vol.Optional('values'): exists,
|
||||
vol.Optional('required'): bool,
|
||||
})
|
||||
|
||||
SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Required('description'): str,
|
||||
vol.Optional('fields'): vol.Schema({
|
||||
str: FIELD_SCHEMA
|
||||
})
|
||||
})
|
||||
|
||||
SERVICES_SCHEMA = vol.Schema({
|
||||
cv.slug: SERVICE_SCHEMA
|
||||
})
|
||||
|
||||
|
||||
def grep_dir(path: pathlib.Path, glob_pattern: str, search_pattern: str) \
|
||||
-> bool:
|
||||
"""Recursively go through a dir and it's children and find the regex."""
|
||||
pattern = re.compile(search_pattern)
|
||||
|
||||
for fil in path.glob(glob_pattern):
|
||||
if not fil.is_file():
|
||||
continue
|
||||
|
||||
if pattern.search(fil.read_text()):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def validate_services(integration: Integration):
|
||||
"""Validate services."""
|
||||
# Find if integration uses services
|
||||
has_services = grep_dir(integration.path, "**/*.py",
|
||||
r"hass\.(services|async_register)")
|
||||
|
||||
if not has_services:
|
||||
return
|
||||
|
||||
try:
|
||||
data = load_yaml(str(integration.path / 'services.yaml'))
|
||||
except FileNotFoundError:
|
||||
print(
|
||||
"Warning: {} registeres services but has no services.yaml".format(
|
||||
integration.domain))
|
||||
# integration.add_error(
|
||||
# 'services', 'Registers services but has no services.yaml')
|
||||
return
|
||||
except HomeAssistantError:
|
||||
integration.add_error(
|
||||
'services', 'Registers services but unable to load services.yaml')
|
||||
return
|
||||
|
||||
try:
|
||||
SERVICES_SCHEMA(data)
|
||||
except vol.Invalid as err:
|
||||
integration.add_error(
|
||||
'services',
|
||||
"Invalid services.yaml: {}".format(humanize_error(data, err)))
|
||||
|
||||
|
||||
def validate(integrations: Dict[str, Integration], config):
|
||||
"""Handle dependencies for integrations."""
|
||||
# check services.yaml is cool
|
||||
for integration in integrations.values():
|
||||
if not integration.manifest:
|
||||
continue
|
||||
|
||||
validate_services(integration)
|
||||
|
||||
# check that all referenced dependencies exist
|
||||
for dep in integration.manifest['dependencies']:
|
||||
if dep not in integrations:
|
||||
integration.add_error(
|
||||
'dependencies',
|
||||
"Dependency {} does not exist"
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue