Add config flow rules to quality_scale hassfest validation (#131791)

* Add config flow rules to quality_scale hassfest validation

* Use integration.config_flow property
This commit is contained in:
epenet 2024-11-28 17:58:56 +01:00 committed by GitHub
parent bbce183faf
commit 62e788c7da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 104 additions and 6 deletions

View File

@ -86,7 +86,10 @@ rules:
comment: > comment: >
This is not possible because the integrations generates entities This is not possible because the integrations generates entities
based on a user supplied config or discovery. based on a user supplied config or discovery.
reconfiguration-flow: done reconfiguration-flow:
status: exempt
comment: >
This integration is reconfigured via options flow.
dynamic-devices: dynamic-devices:
status: done status: done
comment: | comment: |

View File

@ -12,7 +12,13 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.yaml import load_yaml_dict from homeassistant.util.yaml import load_yaml_dict
from .model import Config, Integration, ScaledQualityScaleTiers from .model import Config, Integration, ScaledQualityScaleTiers
from .quality_scale_validation import RuleValidationProtocol, config_entry_unloading from .quality_scale_validation import (
RuleValidationProtocol,
config_entry_unloading,
config_flow,
reauthentication_flow,
reconfiguration_flow,
)
QUALITY_SCALE_TIERS = {value.name.lower(): value for value in ScaledQualityScaleTiers} QUALITY_SCALE_TIERS = {value.name.lower(): value for value in ScaledQualityScaleTiers}
@ -32,7 +38,7 @@ ALL_RULES = [
Rule("appropriate-polling", ScaledQualityScaleTiers.BRONZE), Rule("appropriate-polling", ScaledQualityScaleTiers.BRONZE),
Rule("brands", ScaledQualityScaleTiers.BRONZE), Rule("brands", ScaledQualityScaleTiers.BRONZE),
Rule("common-modules", ScaledQualityScaleTiers.BRONZE), Rule("common-modules", ScaledQualityScaleTiers.BRONZE),
Rule("config-flow", ScaledQualityScaleTiers.BRONZE), Rule("config-flow", ScaledQualityScaleTiers.BRONZE, config_flow),
Rule("config-flow-test-coverage", ScaledQualityScaleTiers.BRONZE), Rule("config-flow-test-coverage", ScaledQualityScaleTiers.BRONZE),
Rule("dependency-transparency", ScaledQualityScaleTiers.BRONZE), Rule("dependency-transparency", ScaledQualityScaleTiers.BRONZE),
Rule("docs-actions", ScaledQualityScaleTiers.BRONZE), Rule("docs-actions", ScaledQualityScaleTiers.BRONZE),
@ -57,7 +63,9 @@ ALL_RULES = [
Rule("integration-owner", ScaledQualityScaleTiers.SILVER), Rule("integration-owner", ScaledQualityScaleTiers.SILVER),
Rule("log-when-unavailable", ScaledQualityScaleTiers.SILVER), Rule("log-when-unavailable", ScaledQualityScaleTiers.SILVER),
Rule("parallel-updates", ScaledQualityScaleTiers.SILVER), Rule("parallel-updates", ScaledQualityScaleTiers.SILVER),
Rule("reauthentication-flow", ScaledQualityScaleTiers.SILVER), Rule(
"reauthentication-flow", ScaledQualityScaleTiers.SILVER, reauthentication_flow
),
Rule("test-coverage", ScaledQualityScaleTiers.SILVER), Rule("test-coverage", ScaledQualityScaleTiers.SILVER),
# GOLD: [ # GOLD: [
Rule("devices", ScaledQualityScaleTiers.GOLD), Rule("devices", ScaledQualityScaleTiers.GOLD),
@ -78,7 +86,7 @@ ALL_RULES = [
Rule("entity-translations", ScaledQualityScaleTiers.GOLD), Rule("entity-translations", ScaledQualityScaleTiers.GOLD),
Rule("exception-translations", ScaledQualityScaleTiers.GOLD), Rule("exception-translations", ScaledQualityScaleTiers.GOLD),
Rule("icon-translations", ScaledQualityScaleTiers.GOLD), Rule("icon-translations", ScaledQualityScaleTiers.GOLD),
Rule("reconfiguration-flow", ScaledQualityScaleTiers.GOLD), Rule("reconfiguration-flow", ScaledQualityScaleTiers.GOLD, reconfiguration_flow),
Rule("repair-issues", ScaledQualityScaleTiers.GOLD), Rule("repair-issues", ScaledQualityScaleTiers.GOLD),
Rule("stale-devices", ScaledQualityScaleTiers.GOLD), Rule("stale-devices", ScaledQualityScaleTiers.GOLD),
# PLATINUM # PLATINUM

View File

@ -1,4 +1,7 @@
"""Enforce that the integration implements entry unloading.""" """Enforce that the integration implements entry unloading.
https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/config-entry-unloading/
"""
import ast import ast

View File

@ -0,0 +1,24 @@
"""Enforce that the integration implements config flow.
https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/config-flow/
"""
from script.hassfest.model import Integration
def validate(integration: Integration) -> list[str] | None:
"""Validate that the integration implements config flow."""
if not integration.config_flow:
return [
"Integration does not set config_flow in its manifest "
f"homeassistant/components/{integration.domain}/manifest.json",
]
config_flow_file = integration.path / "config_flow.py"
if not config_flow_file.exists():
return [
"Integration does not implement config flow (is missing config_flow.py)",
]
return None

View File

@ -0,0 +1,30 @@
"""Enforce that the integration implements reauthentication flow.
https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/reauthentication-flow/
"""
import ast
from script.hassfest.model import Integration
def _has_async_function(module: ast.Module, name: str) -> bool:
"""Test if the module defines a function."""
return any(
type(item) is ast.AsyncFunctionDef and item.name == name
for item in ast.walk(module)
)
def validate(integration: Integration) -> list[str] | None:
"""Validate that the integration has a reauthentication flow."""
config_flow_file = integration.path / "config_flow.py"
config_flow = ast.parse(config_flow_file.read_text())
if not _has_async_function(config_flow, "async_step_reauth"):
return [
"Integration does not support a reauthentication flow "
f"(is missing `async_step_reauth` in {config_flow_file})"
]
return None

View File

@ -0,0 +1,30 @@
"""Enforce that the integration implements reconfiguration flow.
https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/reconfiguration-flow/
"""
import ast
from script.hassfest.model import Integration
def _has_async_function(module: ast.Module, name: str) -> bool:
"""Test if the module defines a function."""
return any(
type(item) is ast.AsyncFunctionDef and item.name == name
for item in ast.walk(module)
)
def validate(integration: Integration) -> list[str] | None:
"""Validate that the integration has a reconfiguration flow."""
config_flow_file = integration.path / "config_flow.py"
config_flow = ast.parse(config_flow_file.read_text())
if not _has_async_function(config_flow, "async_step_reconfigure"):
return [
"Integration does not support a reconfiguration flow "
f"(is missing `async_step_reconfigure` in {config_flow_file})"
]
return None