mirror of
https://github.com/home-assistant/core.git
synced 2025-06-25 01:21:51 +02:00
Fix docker hassfest (#132823)
This commit is contained in:
2
.github/workflows/builder.yml
vendored
2
.github/workflows/builder.yml
vendored
@ -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'
|
||||
|
@ -628,7 +628,6 @@ def _get_hassfest_config() -> Config:
|
||||
specific_integrations=None,
|
||||
action="validate",
|
||||
requirements=True,
|
||||
core_integrations_path=Path("homeassistant/components"),
|
||||
)
|
||||
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
@ -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 [
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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}")
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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:
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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 = []
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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)"
|
||||
|
@ -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"):
|
||||
|
@ -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",
|
||||
|
@ -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 = {
|
||||
|
Reference in New Issue
Block a user