From 9771998415a5be3e419ada2e8547956acf105fd6 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 6 Dec 2024 20:55:34 +0100 Subject: [PATCH] Cache AST module parsing in hassfest (#132244) --- script/hassfest/__init__.py | 13 +++++++++++++ script/hassfest/config_schema.py | 5 +++-- script/hassfest/dependencies.py | 3 ++- .../config_entry_unloading.py | 3 ++- .../quality_scale_validation/diagnostics.py | 3 ++- .../hassfest/quality_scale_validation/discovery.py | 3 ++- .../reauthentication_flow.py | 3 ++- .../reconfiguration_flow.py | 3 ++- .../quality_scale_validation/runtime_data.py | 3 ++- .../quality_scale_validation/unique_config_entry.py | 3 ++- 10 files changed, 32 insertions(+), 10 deletions(-) diff --git a/script/hassfest/__init__.py b/script/hassfest/__init__.py index 2fa7997162f..c8c9aa9ef39 100644 --- a/script/hassfest/__init__.py +++ b/script/hassfest/__init__.py @@ -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()) diff --git a/script/hassfest/config_schema.py b/script/hassfest/config_schema.py index 6b863ab9ecd..70dff1194bc 100644 --- a/script/hassfest/config_schema.py +++ b/script/hassfest/config_schema.py @@ -6,6 +6,7 @@ import ast from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN +from . import ast_parse_module from .model import Config, Integration CONFIG_SCHEMA_IGNORE = { @@ -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( @@ -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 diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index 0c7f4f11a8c..62644e19c5e 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -10,6 +10,7 @@ from pathlib import Path from homeassistant.const import Platform from homeassistant.requirements import DISCOVERY_INTEGRATIONS +from . import ast_parse_module from .model import Config, Integration @@ -33,7 +34,7 @@ class ImportCollector(ast.NodeVisitor): 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 diff --git a/script/hassfest/quality_scale_validation/config_entry_unloading.py b/script/hassfest/quality_scale_validation/config_entry_unloading.py index 50f42752bf6..b25a72e427f 100644 --- a/script/hassfest/quality_scale_validation/config_entry_unloading.py +++ b/script/hassfest/quality_scale_validation/config_entry_unloading.py @@ -5,6 +5,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 @@ -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 [ diff --git a/script/hassfest/quality_scale_validation/diagnostics.py b/script/hassfest/quality_scale_validation/diagnostics.py index 99f067d6500..d3ef38474f8 100644 --- a/script/hassfest/quality_scale_validation/diagnostics.py +++ b/script/hassfest/quality_scale_validation/diagnostics.py @@ -5,6 +5,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 DIAGNOSTICS_FUNCTIONS = { @@ -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 [ diff --git a/script/hassfest/quality_scale_validation/discovery.py b/script/hassfest/quality_scale_validation/discovery.py index d24005b6373..66a08456314 100644 --- a/script/hassfest/quality_scale_validation/discovery.py +++ b/script/hassfest/quality_scale_validation/discovery.py @@ -5,6 +5,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 MANIFEST_KEYS = [ @@ -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} " diff --git a/script/hassfest/quality_scale_validation/reauthentication_flow.py b/script/hassfest/quality_scale_validation/reauthentication_flow.py index 311f8a2429d..4ae8fed5696 100644 --- a/script/hassfest/quality_scale_validation/reauthentication_flow.py +++ b/script/hassfest/quality_scale_validation/reauthentication_flow.py @@ -5,6 +5,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 @@ -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 [ diff --git a/script/hassfest/quality_scale_validation/reconfiguration_flow.py b/script/hassfest/quality_scale_validation/reconfiguration_flow.py index de3b5dcba62..19192cb28d0 100644 --- a/script/hassfest/quality_scale_validation/reconfiguration_flow.py +++ b/script/hassfest/quality_scale_validation/reconfiguration_flow.py @@ -5,6 +5,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 @@ -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 [ diff --git a/script/hassfest/quality_scale_validation/runtime_data.py b/script/hassfest/quality_scale_validation/runtime_data.py index 765db43d1e3..c426496636b 100644 --- a/script/hassfest/quality_scale_validation/runtime_data.py +++ b/script/hassfest/quality_scale_validation/runtime_data.py @@ -5,6 +5,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 @@ -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)): diff --git a/script/hassfest/quality_scale_validation/unique_config_entry.py b/script/hassfest/quality_scale_validation/unique_config_entry.py index eaa879bb05e..bf9991d5635 100644 --- a/script/hassfest/quality_scale_validation/unique_config_entry.py +++ b/script/hassfest/quality_scale_validation/unique_config_entry.py @@ -5,6 +5,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 @@ -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)