Add runtime_data rule to quality_scale hassfest validation (#131857)

* Add quality scale check for runtime_data

* Linter fixes

* Add developer documentation link

* Update script/hassfest/quality_scale_validation/runtime_data.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update validation to check explicitly for ConfigEntry.runtime_data

* Update script/hassfest/quality_scale_validation/runtime_data.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Refine check for setting attributes

* Patch with changes from epenet

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
This commit is contained in:
Allen Porter 2024-11-29 08:56:26 -08:00 committed by GitHub
parent 0fc365a114
commit 920c958ec7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 55 additions and 1 deletions

View File

@ -20,6 +20,7 @@ from .quality_scale_validation import (
discovery,
reauthentication_flow,
reconfiguration_flow,
runtime_data,
strict_typing,
unique_config_entry,
)
@ -52,7 +53,7 @@ ALL_RULES = [
Rule("entity-event-setup", ScaledQualityScaleTiers.BRONZE),
Rule("entity-unique-id", ScaledQualityScaleTiers.BRONZE),
Rule("has-entity-name", ScaledQualityScaleTiers.BRONZE),
Rule("runtime-data", ScaledQualityScaleTiers.BRONZE),
Rule("runtime-data", ScaledQualityScaleTiers.BRONZE, runtime_data),
Rule("test-before-configure", ScaledQualityScaleTiers.BRONZE),
Rule("test-before-setup", ScaledQualityScaleTiers.BRONZE),
Rule("unique-config-entry", ScaledQualityScaleTiers.BRONZE, unique_config_entry),

View File

@ -0,0 +1,53 @@
"""Enforce that the integration uses ConfigEntry.runtime_data to store runtime data.
https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/runtime-data
"""
import ast
from script.hassfest.model import Integration
def _sets_runtime_data(
async_setup_entry_function: ast.AsyncFunctionDef, config_entry_argument: ast.arg
) -> bool:
"""Check that `entry.runtime` gets set within `async_setup_entry`."""
for node in ast.walk(async_setup_entry_function):
if (
isinstance(node, ast.Attribute)
and isinstance(node.value, ast.Name)
and node.value.id == config_entry_argument.arg
and node.attr == "runtime_data"
and isinstance(node.ctx, ast.Store)
):
return True
return False
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 validate(integration: Integration) -> list[str] | None:
"""Validate correct use of ConfigEntry.runtime_data."""
init_file = integration.path / "__init__.py"
init = ast.parse(init_file.read_text())
# Should not happen, but better to be safe
if not (async_setup_entry := _get_setup_entry_function(init)):
return [f"Could not find `async_setup_entry` in {init_file}"]
if len(async_setup_entry.args.args) != 2:
return [f"async_setup_entry has incorrect signature in {init_file}"]
config_entry_argument = async_setup_entry.args.args[1]
if not _sets_runtime_data(async_setup_entry, config_entry_argument):
return [
"Integration does not set entry.runtime_data in async_setup_entry"
f"({init_file})"
]
return None