Skip to content

Commit

Permalink
Cache AST module parsing in hassfest (#132244)
Browse files Browse the repository at this point in the history
  • Loading branch information
epenet authored Dec 6, 2024
1 parent e54d929 commit 9771998
Show file tree
Hide file tree
Showing 10 changed files with 32 additions and 10 deletions.
13 changes: 13 additions & 0 deletions script/hassfest/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
"""Manifest validator."""

import ast
from functools import lru_cache
from pathlib import Path


@lru_cache
def ast_parse_module(file_path: Path) -> ast.Module:
"""Parse a module.
Cached to avoid parsing the same file for each plugin.
"""
return ast.parse(file_path.read_text())
5 changes: 3 additions & 2 deletions script/hassfest/config_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN

from . import ast_parse_module
from .model import Config, Integration

CONFIG_SCHEMA_IGNORE = {
Expand Down Expand Up @@ -60,7 +61,7 @@ def _validate_integration(config: Config, integration: Integration) -> None:
# Virtual integrations don't have any implementation
return

init = ast.parse(init_file.read_text())
init = ast_parse_module(init_file)

# No YAML Support
if not _has_function(
Expand All @@ -81,7 +82,7 @@ def _validate_integration(config: Config, integration: Integration) -> None:

config_file = integration.path / "config.py"
if config_file.is_file():
config_module = ast.parse(config_file.read_text())
config_module = ast_parse_module(config_file)
if _has_function(config_module, ast.AsyncFunctionDef, "async_validate_config"):
return

Expand Down
3 changes: 2 additions & 1 deletion script/hassfest/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from homeassistant.const import Platform
from homeassistant.requirements import DISCOVERY_INTEGRATIONS

from . import ast_parse_module
from .model import Config, Integration


Expand All @@ -33,7 +34,7 @@ def collect(self) -> None:
self._cur_fil_dir = fil.relative_to(self.integration.path)
self.referenced[self._cur_fil_dir] = set()
try:
self.visit(ast.parse(fil.read_text()))
self.visit(ast_parse_module(fil))
except SyntaxError as e:
e.add_note(f"File: {fil}")
raise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import ast

from script.hassfest import ast_parse_module
from script.hassfest.model import Integration


Expand All @@ -20,7 +21,7 @@ def validate(integration: Integration) -> list[str] | None:
"""Validate that the integration has a config flow."""

init_file = integration.path / "__init__.py"
init = ast.parse(init_file.read_text())
init = ast_parse_module(init_file)

if not _has_unload_entry_function(init):
return [
Expand Down
3 changes: 2 additions & 1 deletion script/hassfest/quality_scale_validation/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import ast

from script.hassfest import ast_parse_module
from script.hassfest.model import Integration

DIAGNOSTICS_FUNCTIONS = {
Expand All @@ -31,7 +32,7 @@ def validate(integration: Integration) -> list[str] | None:
"(is missing diagnostics.py)",
]

diagnostics = ast.parse(diagnostics_file.read_text())
diagnostics = ast_parse_module(diagnostics_file)

if not _has_diagnostics_function(diagnostics):
return [
Expand Down
3 changes: 2 additions & 1 deletion script/hassfest/quality_scale_validation/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import ast

from script.hassfest import ast_parse_module
from script.hassfest.model import Integration

MANIFEST_KEYS = [
Expand Down Expand Up @@ -49,7 +50,7 @@ def validate(integration: Integration) -> list[str] | None:
return None

# Fallback => check config_flow step
config_flow = ast.parse(config_flow_file.read_text())
config_flow = ast_parse_module(config_flow_file)
if not (_has_discovery_function(config_flow)):
return [
f"Integration is missing one of {CONFIG_FLOW_STEPS} "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import ast

from script.hassfest import ast_parse_module
from script.hassfest.model import Integration


Expand All @@ -20,7 +21,7 @@ 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())
config_flow = ast_parse_module(config_flow_file)

if not _has_step_reauth_function(config_flow):
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import ast

from script.hassfest import ast_parse_module
from script.hassfest.model import Integration


Expand All @@ -20,7 +21,7 @@ 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())
config_flow = ast_parse_module(config_flow_file)

if not _has_step_reconfigure_function(config_flow):
return [
Expand Down
3 changes: 2 additions & 1 deletion script/hassfest/quality_scale_validation/runtime_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import ast

from script.hassfest import ast_parse_module
from script.hassfest.model import Integration


Expand Down Expand Up @@ -35,7 +36,7 @@ def _get_setup_entry_function(module: ast.Module) -> ast.AsyncFunctionDef | None
def validate(integration: Integration) -> list[str] | None:
"""Validate correct use of ConfigEntry.runtime_data."""
init_file = integration.path / "__init__.py"
init = ast.parse(init_file.read_text())
init = ast_parse_module(init_file)

# Should not happen, but better to be safe
if not (async_setup_entry := _get_setup_entry_function(init)):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import ast

from script.hassfest import ast_parse_module
from script.hassfest.model import Integration


Expand Down Expand Up @@ -36,7 +37,7 @@ def validate(integration: Integration) -> list[str] | None:
return None

config_flow_file = integration.path / "config_flow.py"
config_flow = ast.parse(config_flow_file.read_text())
config_flow = ast_parse_module(config_flow_file)

if not (
_has_abort_entries_match(config_flow)
Expand Down

0 comments on commit 9771998

Please sign in to comment.