Compare commits

...

4 Commits

Author SHA1 Message Date
epenet
d0c405d0de Add 'adguard' to quality scale list 2026-02-02 22:18:36 +01:00
epenet
318c214475 Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-02 22:17:26 +01:00
epenet
3e752b027b Merge branch 'dev' into epenet/20260202-1546 2026-02-02 22:12:16 +01:00
epenet
0e615f1459 Add hassfest check for action-setup IQS 2026-02-02 14:48:23 +00:00
2 changed files with 75 additions and 1 deletions

View File

@@ -14,6 +14,7 @@ from homeassistant.util.yaml import load_yaml_dict
from .model import Config, Integration, ScaledQualityScaleTiers
from .quality_scale_validation import (
RuleValidationProtocol,
action_setup,
config_entry_unloading,
config_flow,
diagnostics,
@@ -41,7 +42,7 @@ class Rule:
ALL_RULES = [
# BRONZE
Rule("action-setup", ScaledQualityScaleTiers.BRONZE),
Rule("action-setup", ScaledQualityScaleTiers.BRONZE, action_setup),
Rule("appropriate-polling", ScaledQualityScaleTiers.BRONZE),
Rule("brands", ScaledQualityScaleTiers.BRONZE),
Rule("common-modules", ScaledQualityScaleTiers.BRONZE),

View File

@@ -0,0 +1,73 @@
"""Enforce that the integration service actions are registered in async_setup.
https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/action-setup/
"""
import ast
from script.hassfest import ast_parse_module
from script.hassfest.manifest import Platform
from script.hassfest.model import Config, Integration
def _get_setup_entry_function(module: ast.Module) -> ast.AsyncFunctionDef | None:
"""Get async_setup_entry function."""
for item in module.body:
if isinstance(item, ast.AsyncFunctionDef) and item.name == "async_setup_entry":
return item
return None
def _calls_service_registration(
async_setup_entry_function: ast.AsyncFunctionDef,
) -> bool:
"""Check if there are calls to service registration."""
for node in ast.walk(async_setup_entry_function):
if not (isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute)):
continue
if node.func.attr == "async_register_entity_service":
return True
if (
isinstance(node.func.value, ast.Attribute)
and isinstance(node.func.value.value, ast.Name)
and node.func.value.value.id == "hass"
and node.func.value.attr == "services"
and node.func.attr in {"async_register", "register"}
):
return True
return False
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that service actions are registered in async_setup."""
errors = []
module_file = integration.path / "__init__.py"
module = ast_parse_module(module_file)
if (
async_setup_entry := _get_setup_entry_function(module)
) and _calls_service_registration(async_setup_entry):
errors.append(
f"Integration registers services in {module_file} (async_setup_entry)"
)
for platform in Platform:
module_file = integration.path / f"{platform}.py"
if not module_file.exists():
continue
module = ast_parse_module(module_file)
if (
async_setup_entry := _get_setup_entry_function(module)
) and _calls_service_registration(async_setup_entry):
errors.append(
f"Integration registers services in {module_file} (async_setup_entry)"
)
return errors