mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Fix docker hassfest (#132823)
This commit is contained in:
parent
5e17721568
commit
af838077cc
2
.github/workflows/builder.yml
vendored
2
.github/workflows/builder.yml
vendored
@ -517,7 +517,7 @@ jobs:
|
|||||||
tags: ${{ env.HASSFEST_IMAGE_TAG }}
|
tags: ${{ env.HASSFEST_IMAGE_TAG }}
|
||||||
|
|
||||||
- name: Run hassfest against core
|
- 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
|
- name: Push Docker image
|
||||||
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
|
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
|
||||||
|
@ -628,7 +628,6 @@ def _get_hassfest_config() -> Config:
|
|||||||
specific_integrations=None,
|
specific_integrations=None,
|
||||||
action="validate",
|
action="validate",
|
||||||
requirements=True,
|
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",
|
help="Comma-separate list of plugins to run. Valid plugin names: %(default)s",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--core-integrations-path",
|
"--core-path",
|
||||||
type=Path,
|
type=Path,
|
||||||
default=Path("homeassistant/components"),
|
default=Path(),
|
||||||
help="Path to core integrations",
|
help="Path to core",
|
||||||
)
|
)
|
||||||
parsed = parser.parse_args()
|
parsed = parser.parse_args()
|
||||||
|
|
||||||
@ -125,16 +125,18 @@ def get_config() -> Config:
|
|||||||
"Generate is not allowed when limiting to specific integrations"
|
"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")
|
raise RuntimeError("Run from Home Assistant root")
|
||||||
|
|
||||||
return Config(
|
return Config(
|
||||||
root=Path().absolute(),
|
root=parsed.core_path.absolute(),
|
||||||
specific_integrations=parsed.integration_path,
|
specific_integrations=parsed.integration_path,
|
||||||
action=parsed.action,
|
action=parsed.action,
|
||||||
requirements=parsed.requirements,
|
requirements=parsed.requirements,
|
||||||
plugins=set(parsed.plugins),
|
plugins=set(parsed.plugins),
|
||||||
core_integrations_path=parsed.core_integrations_path,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -185,12 +185,12 @@ def _generate_files(config: Config) -> list[File]:
|
|||||||
+ 10
|
+ 10
|
||||||
) * 1000
|
) * 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(
|
package_versions |= _get_package_versions(
|
||||||
Path("requirements_test.txt"), {"pipdeptree", "tqdm"}
|
config.root / "requirements_test.txt", {"pipdeptree", "tqdm"}
|
||||||
)
|
)
|
||||||
package_versions |= _get_package_versions(
|
package_versions |= _get_package_versions(
|
||||||
Path("requirements_test_pre_commit.txt"), {"ruff"}
|
config.root / "requirements_test_pre_commit.txt", {"ruff"}
|
||||||
)
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -2,16 +2,28 @@
|
|||||||
|
|
||||||
integrations=""
|
integrations=""
|
||||||
integration_path=""
|
integration_path=""
|
||||||
|
core_path_provided=false
|
||||||
|
|
||||||
# Enable recursive globbing using find
|
for arg in "$@"; do
|
||||||
for manifest in $(find . -name "manifest.json"); do
|
case "$arg" in
|
||||||
manifest_path=$(realpath "${manifest}")
|
--core-path=*)
|
||||||
integrations="$integrations --integration-path ${manifest_path%/*}"
|
core_path_provided=true
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -z "$integrations" ]; then
|
if [ "$core_path_provided" = false ]; then
|
||||||
echo "Error: No integrations found!"
|
# Enable recursive globbing using find
|
||||||
exit 1
|
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
|
fi
|
||||||
|
|
||||||
cd /usr/src/homeassistant || exit 1
|
cd /usr/src/homeassistant || exit 1
|
||||||
|
@ -30,11 +30,15 @@ class Config:
|
|||||||
root: pathlib.Path
|
root: pathlib.Path
|
||||||
action: Literal["validate", "generate"]
|
action: Literal["validate", "generate"]
|
||||||
requirements: bool
|
requirements: bool
|
||||||
core_integrations_path: pathlib.Path
|
core_integrations_path: pathlib.Path = field(init=False)
|
||||||
errors: list[Error] = field(default_factory=list)
|
errors: list[Error] = field(default_factory=list)
|
||||||
cache: dict[str, Any] = field(default_factory=dict)
|
cache: dict[str, Any] = field(default_factory=dict)
|
||||||
plugins: set[str] = field(default_factory=set)
|
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:
|
def add_error(self, *args: Any, **kwargs: Any) -> None:
|
||||||
"""Add an error."""
|
"""Add an error."""
|
||||||
self.errors.append(Error(*args, **kwargs))
|
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:
|
for rule_name in rules_done:
|
||||||
if (validator := VALIDATORS.get(rule_name)) and (
|
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:
|
for error in errors:
|
||||||
integration.add_error("quality_scale", f"[{rule_name}] {error}")
|
integration.add_error("quality_scale", f"[{rule_name}] {error}")
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
from typing import Protocol
|
from typing import Protocol
|
||||||
|
|
||||||
from script.hassfest.model import Integration
|
from script.hassfest.model import Config, Integration
|
||||||
|
|
||||||
|
|
||||||
class RuleValidationProtocol(Protocol):
|
class RuleValidationProtocol(Protocol):
|
||||||
"""Protocol for rule validation."""
|
"""Protocol for rule validation."""
|
||||||
|
|
||||||
def validate(
|
def validate(
|
||||||
self, integration: Integration, *, rules_done: set[str]
|
self, config: Config, integration: Integration, *, rules_done: set[str]
|
||||||
) -> list[str] | None:
|
) -> list[str] | None:
|
||||||
"""Validate a quality scale rule.
|
"""Validate a quality scale rule.
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/c
|
|||||||
import ast
|
import ast
|
||||||
|
|
||||||
from script.hassfest import ast_parse_module
|
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:
|
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."""
|
"""Validate that the integration has a config flow."""
|
||||||
|
|
||||||
init_file = integration.path / "__init__.py"
|
init_file = integration.path / "__init__.py"
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/config-flow/
|
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."""
|
"""Validate that the integration implements config flow."""
|
||||||
|
|
||||||
if not integration.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
|
import ast
|
||||||
|
|
||||||
from script.hassfest import ast_parse_module
|
from script.hassfest import ast_parse_module
|
||||||
from script.hassfest.model import Integration
|
from script.hassfest.model import Config, Integration
|
||||||
|
|
||||||
DIAGNOSTICS_FUNCTIONS = {
|
DIAGNOSTICS_FUNCTIONS = {
|
||||||
"async_get_config_entry_diagnostics",
|
"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."""
|
"""Validate that the integration implements diagnostics."""
|
||||||
|
|
||||||
diagnostics_file = integration.path / "diagnostics.py"
|
diagnostics_file = integration.path / "diagnostics.py"
|
||||||
|
@ -6,7 +6,7 @@ https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/d
|
|||||||
import ast
|
import ast
|
||||||
|
|
||||||
from script.hassfest import ast_parse_module
|
from script.hassfest import ast_parse_module
|
||||||
from script.hassfest.model import Integration
|
from script.hassfest.model import Config, Integration
|
||||||
|
|
||||||
MANIFEST_KEYS = [
|
MANIFEST_KEYS = [
|
||||||
"bluetooth",
|
"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."""
|
"""Validate that the integration implements diagnostics."""
|
||||||
|
|
||||||
config_flow_file = integration.path / "config_flow.py"
|
config_flow_file = integration.path / "config_flow.py"
|
||||||
|
@ -7,7 +7,7 @@ import ast
|
|||||||
|
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from script.hassfest import ast_parse_module
|
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:
|
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."""
|
"""Validate that the integration sets PARALLEL_UPDATES constant."""
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
|
@ -6,7 +6,7 @@ https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/r
|
|||||||
import ast
|
import ast
|
||||||
|
|
||||||
from script.hassfest import ast_parse_module
|
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:
|
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."""
|
"""Validate that the integration has a reauthentication flow."""
|
||||||
|
|
||||||
config_flow_file = integration.path / "config_flow.py"
|
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
|
import ast
|
||||||
|
|
||||||
from script.hassfest import ast_parse_module
|
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:
|
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."""
|
"""Validate that the integration has a reconfiguration flow."""
|
||||||
|
|
||||||
config_flow_file = integration.path / "config_flow.py"
|
config_flow_file = integration.path / "config_flow.py"
|
||||||
|
@ -8,7 +8,7 @@ import re
|
|||||||
|
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from script.hassfest import ast_parse_module
|
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$")
|
_ANNOTATION_MATCH = re.compile(r"^[A-Za-z]+ConfigEntry$")
|
||||||
_FUNCTIONS: dict[str, dict[str, int]] = {
|
_FUNCTIONS: dict[str, dict[str, int]] = {
|
||||||
@ -102,7 +102,9 @@ def _check_typed_config_entry(integration: Integration) -> list[str]:
|
|||||||
return errors
|
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."""
|
"""Validate correct use of ConfigEntry.runtime_data."""
|
||||||
init_file = integration.path / "__init__.py"
|
init_file = integration.path / "__init__.py"
|
||||||
init = ast_parse_module(init_file)
|
init = ast_parse_module(init_file)
|
||||||
|
@ -7,27 +7,30 @@ from functools import lru_cache
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from script.hassfest.model import Integration
|
from script.hassfest.model import Config, Integration
|
||||||
|
|
||||||
_STRICT_TYPING_FILE = Path(".strict-typing")
|
_STRICT_TYPING_FILE = Path(".strict-typing")
|
||||||
_COMPONENT_REGEX = r"homeassistant.components.([^.]+).*"
|
_COMPONENT_REGEX = r"homeassistant.components.([^.]+).*"
|
||||||
|
|
||||||
|
|
||||||
@lru_cache
|
@lru_cache
|
||||||
def _strict_typing_components() -> set[str]:
|
def _strict_typing_components(strict_typing_file: Path) -> set[str]:
|
||||||
return set(
|
return set(
|
||||||
{
|
{
|
||||||
match.group(1)
|
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
|
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."""
|
"""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 [
|
return [
|
||||||
"Integration does not have strict typing enabled "
|
"Integration does not have strict typing enabled "
|
||||||
"(is missing from .strict-typing)"
|
"(is missing from .strict-typing)"
|
||||||
|
@ -6,7 +6,7 @@ https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/u
|
|||||||
import ast
|
import ast
|
||||||
|
|
||||||
from script.hassfest import ast_parse_module
|
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:
|
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."""
|
"""Validate that the integration prevents duplicate devices."""
|
||||||
|
|
||||||
if integration.manifest.get("single_config_entry"):
|
if integration.manifest.get("single_config_entry"):
|
||||||
|
@ -12,13 +12,12 @@ from script.hassfest.requirements import validate_requirements_format
|
|||||||
def integration():
|
def integration():
|
||||||
"""Fixture for hassfest integration model."""
|
"""Fixture for hassfest integration model."""
|
||||||
return Integration(
|
return Integration(
|
||||||
path=Path("homeassistant/components/test"),
|
path=Path("homeassistant/components/test").absolute(),
|
||||||
_config=Config(
|
_config=Config(
|
||||||
root=Path(".").absolute(),
|
root=Path(".").absolute(),
|
||||||
specific_integrations=None,
|
specific_integrations=None,
|
||||||
action="validate",
|
action="validate",
|
||||||
requirements=True,
|
requirements=True,
|
||||||
core_integrations_path=Path("homeassistant/components"),
|
|
||||||
),
|
),
|
||||||
_manifest={
|
_manifest={
|
||||||
"domain": "test",
|
"domain": "test",
|
||||||
|
@ -16,13 +16,12 @@ from script.hassfest.model import Config, Integration
|
|||||||
def integration():
|
def integration():
|
||||||
"""Fixture for hassfest integration model."""
|
"""Fixture for hassfest integration model."""
|
||||||
integration = Integration(
|
integration = Integration(
|
||||||
"",
|
Path(),
|
||||||
_config=Config(
|
_config=Config(
|
||||||
root=Path(".").absolute(),
|
root=Path(".").absolute(),
|
||||||
specific_integrations=None,
|
specific_integrations=None,
|
||||||
action="validate",
|
action="validate",
|
||||||
requirements=True,
|
requirements=True,
|
||||||
core_integrations_path=Path("homeassistant/components"),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
integration._manifest = {
|
integration._manifest = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user