diff --git a/homeassistant/components/mqtt/quality_scale.yaml b/homeassistant/components/mqtt/quality_scale.yaml index b3084f67da382bf39607238aab28364f9c917071..d459f0420f15cc6f94fc684cf4107d926521b0d6 100644 --- a/homeassistant/components/mqtt/quality_scale.yaml +++ b/homeassistant/components/mqtt/quality_scale.yaml @@ -86,7 +86,10 @@ rules: comment: > This is not possible because the integrations generates entities 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: status: done comment: | diff --git a/script/hassfest/quality_scale.py b/script/hassfest/quality_scale.py index 980d659b03ee93026b0f4163f278445a9653fdfe..3e8d25c1f324b4cb963acecaa00de89af64d6648 100644 --- a/script/hassfest/quality_scale.py +++ b/script/hassfest/quality_scale.py @@ -12,7 +12,13 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.util.yaml import load_yaml_dict 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} @@ -32,7 +38,7 @@ ALL_RULES = [ Rule("appropriate-polling", ScaledQualityScaleTiers.BRONZE), Rule("brands", 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("dependency-transparency", ScaledQualityScaleTiers.BRONZE), Rule("docs-actions", ScaledQualityScaleTiers.BRONZE), @@ -57,7 +63,9 @@ ALL_RULES = [ Rule("integration-owner", ScaledQualityScaleTiers.SILVER), Rule("log-when-unavailable", ScaledQualityScaleTiers.SILVER), Rule("parallel-updates", ScaledQualityScaleTiers.SILVER), - Rule("reauthentication-flow", ScaledQualityScaleTiers.SILVER), + Rule( + "reauthentication-flow", ScaledQualityScaleTiers.SILVER, reauthentication_flow + ), Rule("test-coverage", ScaledQualityScaleTiers.SILVER), # GOLD: [ Rule("devices", ScaledQualityScaleTiers.GOLD), @@ -78,7 +86,7 @@ ALL_RULES = [ Rule("entity-translations", ScaledQualityScaleTiers.GOLD), Rule("exception-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("stale-devices", ScaledQualityScaleTiers.GOLD), # PLATINUM diff --git a/script/hassfest/quality_scale_validation/config_entry_unloading.py b/script/hassfest/quality_scale_validation/config_entry_unloading.py index 42134e0391e1c27bb03c370d6f76a605139ff4b5..63b0117498e9edc7557541214e4f54010e39d627 100644 --- a/script/hassfest/quality_scale_validation/config_entry_unloading.py +++ b/script/hassfest/quality_scale_validation/config_entry_unloading.py @@ -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 diff --git a/script/hassfest/quality_scale_validation/config_flow.py b/script/hassfest/quality_scale_validation/config_flow.py new file mode 100644 index 0000000000000000000000000000000000000000..e1361d6550f326db07964ae275b2eb6f473a399f --- /dev/null +++ b/script/hassfest/quality_scale_validation/config_flow.py @@ -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 diff --git a/script/hassfest/quality_scale_validation/reauthentication_flow.py b/script/hassfest/quality_scale_validation/reauthentication_flow.py new file mode 100644 index 0000000000000000000000000000000000000000..d4bc8ed6e96d85b8fed775142de28ab581b1f44c --- /dev/null +++ b/script/hassfest/quality_scale_validation/reauthentication_flow.py @@ -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 diff --git a/script/hassfest/quality_scale_validation/reconfiguration_flow.py b/script/hassfest/quality_scale_validation/reconfiguration_flow.py new file mode 100644 index 0000000000000000000000000000000000000000..94547e956257db563cb8fa2d3ac322bbbfa3d25c --- /dev/null +++ b/script/hassfest/quality_scale_validation/reconfiguration_flow.py @@ -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