Fix docker hassfest (#132823)

This commit is contained in:
Robert Resch
2024-12-11 08:55:00 +01:00
committed by GitHub
parent 5e17721568
commit af838077cc
20 changed files with 85 additions and 49 deletions

View File

@ -517,7 +517,7 @@ jobs:
tags: ${{ env.HASSFEST_IMAGE_TAG }}
- name: Run hassfest against core
run: docker run --rm -v ${{ github.workspace }}/homeassistant:/github/workspace/homeassistant ${{ env.HASSFEST_IMAGE_TAG }} --core-integrations-path=/github/workspace/homeassistant/components
run: docker run --rm -v ${{ github.workspace }}:/github/workspace ${{ env.HASSFEST_IMAGE_TAG }} --core-path=/github/workspace
- name: Push Docker image
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'

View File

@ -628,7 +628,6 @@ def _get_hassfest_config() -> Config:
specific_integrations=None,
action="validate",
requirements=True,
core_integrations_path=Path("homeassistant/components"),
)

View File

@ -110,10 +110,10 @@ def get_config() -> Config:
help="Comma-separate list of plugins to run. Valid plugin names: %(default)s",
)
parser.add_argument(
"--core-integrations-path",
"--core-path",
type=Path,
default=Path("homeassistant/components"),
help="Path to core integrations",
default=Path(),
help="Path to core",
)
parsed = parser.parse_args()
@ -125,16 +125,18 @@ def get_config() -> Config:
"Generate is not allowed when limiting to specific integrations"
)
if not parsed.integration_path and not Path("requirements_all.txt").is_file():
if (
not parsed.integration_path
and not (parsed.core_path / "requirements_all.txt").is_file()
):
raise RuntimeError("Run from Home Assistant root")
return Config(
root=Path().absolute(),
root=parsed.core_path.absolute(),
specific_integrations=parsed.integration_path,
action=parsed.action,
requirements=parsed.requirements,
plugins=set(parsed.plugins),
core_integrations_path=parsed.core_integrations_path,
)

View File

@ -185,12 +185,12 @@ def _generate_files(config: Config) -> list[File]:
+ 10
) * 1000
package_versions = _get_package_versions(Path("requirements.txt"), {"uv"})
package_versions = _get_package_versions(config.root / "requirements.txt", {"uv"})
package_versions |= _get_package_versions(
Path("requirements_test.txt"), {"pipdeptree", "tqdm"}
config.root / "requirements_test.txt", {"pipdeptree", "tqdm"}
)
package_versions |= _get_package_versions(
Path("requirements_test_pre_commit.txt"), {"ruff"}
config.root / "requirements_test_pre_commit.txt", {"ruff"}
)
return [

View File

@ -2,16 +2,28 @@
integrations=""
integration_path=""
core_path_provided=false
# Enable recursive globbing using find
for manifest in $(find . -name "manifest.json"); do
manifest_path=$(realpath "${manifest}")
integrations="$integrations --integration-path ${manifest_path%/*}"
for arg in "$@"; do
case "$arg" in
--core-path=*)
core_path_provided=true
break
;;
esac
done
if [ -z "$integrations" ]; then
echo "Error: No integrations found!"
exit 1
if [ "$core_path_provided" = false ]; then
# Enable recursive globbing using find
for manifest in $(find . -name "manifest.json"); do
manifest_path=$(realpath "${manifest}")
integrations="$integrations --integration-path ${manifest_path%/*}"
done
if [ -z "$integrations" ]; then
echo "Error: No integrations found!"
exit 1
fi
fi
cd /usr/src/homeassistant || exit 1

View File

@ -30,11 +30,15 @@ class Config:
root: pathlib.Path
action: Literal["validate", "generate"]
requirements: bool
core_integrations_path: pathlib.Path
core_integrations_path: pathlib.Path = field(init=False)
errors: list[Error] = field(default_factory=list)
cache: dict[str, Any] = field(default_factory=dict)
plugins: set[str] = field(default_factory=set)
def __post_init__(self) -> None:
"""Post init."""
self.core_integrations_path = self.root / "homeassistant/components"
def add_error(self, *args: Any, **kwargs: Any) -> None:
"""Add an error."""
self.errors.append(Error(*args, **kwargs))

View File

@ -1358,7 +1358,7 @@ def validate_iqs_file(config: Config, integration: Integration) -> None:
for rule_name in rules_done:
if (validator := VALIDATORS.get(rule_name)) and (
errors := validator.validate(integration, rules_done=rules_done)
errors := validator.validate(config, integration, rules_done=rules_done)
):
for error in errors:
integration.add_error("quality_scale", f"[{rule_name}] {error}")

View File

@ -2,14 +2,14 @@
from typing import Protocol
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
class RuleValidationProtocol(Protocol):
"""Protocol for rule validation."""
def validate(
self, integration: Integration, *, rules_done: set[str]
self, config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate a quality scale rule.

View File

@ -6,7 +6,7 @@ https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/c
import ast
from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
def _has_unload_entry_function(module: ast.Module) -> bool:
@ -17,7 +17,9 @@ def _has_unload_entry_function(module: ast.Module) -> bool:
)
def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration has a config flow."""
init_file = integration.path / "__init__.py"

View File

@ -3,10 +3,12 @@
https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/config-flow/
"""
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration implements config flow."""
if not integration.config_flow:

View File

@ -6,7 +6,7 @@ https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/d
import ast
from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
DIAGNOSTICS_FUNCTIONS = {
"async_get_config_entry_diagnostics",
@ -22,7 +22,9 @@ def _has_diagnostics_function(module: ast.Module) -> bool:
)
def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration implements diagnostics."""
diagnostics_file = integration.path / "diagnostics.py"

View File

@ -6,7 +6,7 @@ https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/d
import ast
from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
MANIFEST_KEYS = [
"bluetooth",
@ -38,7 +38,9 @@ def _has_discovery_function(module: ast.Module) -> bool:
)
def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration implements diagnostics."""
config_flow_file = integration.path / "config_flow.py"

View File

@ -7,7 +7,7 @@ import ast
from homeassistant.const import Platform
from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
def _has_parallel_updates_defined(module: ast.Module) -> bool:
@ -18,7 +18,9 @@ def _has_parallel_updates_defined(module: ast.Module) -> bool:
)
def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration sets PARALLEL_UPDATES constant."""
errors = []

View File

@ -6,7 +6,7 @@ https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/r
import ast
from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
def _has_step_reauth_function(module: ast.Module) -> bool:
@ -17,7 +17,9 @@ def _has_step_reauth_function(module: ast.Module) -> bool:
)
def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration has a reauthentication flow."""
config_flow_file = integration.path / "config_flow.py"

View File

@ -6,7 +6,7 @@ https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/r
import ast
from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
def _has_step_reconfigure_function(module: ast.Module) -> bool:
@ -17,7 +17,9 @@ def _has_step_reconfigure_function(module: ast.Module) -> bool:
)
def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration has a reconfiguration flow."""
config_flow_file = integration.path / "config_flow.py"

View File

@ -8,7 +8,7 @@ import re
from homeassistant.const import Platform
from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
_ANNOTATION_MATCH = re.compile(r"^[A-Za-z]+ConfigEntry$")
_FUNCTIONS: dict[str, dict[str, int]] = {
@ -102,7 +102,9 @@ def _check_typed_config_entry(integration: Integration) -> list[str]:
return errors
def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate correct use of ConfigEntry.runtime_data."""
init_file = integration.path / "__init__.py"
init = ast_parse_module(init_file)

View File

@ -7,27 +7,30 @@ from functools import lru_cache
from pathlib import Path
import re
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
_STRICT_TYPING_FILE = Path(".strict-typing")
_COMPONENT_REGEX = r"homeassistant.components.([^.]+).*"
@lru_cache
def _strict_typing_components() -> set[str]:
def _strict_typing_components(strict_typing_file: Path) -> set[str]:
return set(
{
match.group(1)
for line in _STRICT_TYPING_FILE.read_text(encoding="utf-8").splitlines()
for line in strict_typing_file.read_text(encoding="utf-8").splitlines()
if (match := re.match(_COMPONENT_REGEX, line)) is not None
}
)
def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration has strict typing enabled."""
strict_typing_file = config.root / _STRICT_TYPING_FILE
if integration.domain not in _strict_typing_components():
if integration.domain not in _strict_typing_components(strict_typing_file):
return [
"Integration does not have strict typing enabled "
"(is missing from .strict-typing)"

View File

@ -6,7 +6,7 @@ https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/u
import ast
from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
def _has_method_call(module: ast.Module, name: str) -> bool:
@ -30,7 +30,9 @@ def _has_abort_unique_id_configured(module: ast.Module) -> bool:
)
def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration prevents duplicate devices."""
if integration.manifest.get("single_config_entry"):

View File

@ -12,13 +12,12 @@ from script.hassfest.requirements import validate_requirements_format
def integration():
"""Fixture for hassfest integration model."""
return Integration(
path=Path("homeassistant/components/test"),
path=Path("homeassistant/components/test").absolute(),
_config=Config(
root=Path(".").absolute(),
specific_integrations=None,
action="validate",
requirements=True,
core_integrations_path=Path("homeassistant/components"),
),
_manifest={
"domain": "test",

View File

@ -16,13 +16,12 @@ from script.hassfest.model import Config, Integration
def integration():
"""Fixture for hassfest integration model."""
integration = Integration(
"",
Path(),
_config=Config(
root=Path(".").absolute(),
specific_integrations=None,
action="validate",
requirements=True,
core_integrations_path=Path("homeassistant/components"),
),
)
integration._manifest = {