diff --git a/api/src/automations/errors.py b/api/src/automations/errors.py index e4af818..905527d 100644 --- a/api/src/automations/errors.py +++ b/api/src/automations/errors.py @@ -17,6 +17,10 @@ class FailedDeletion(AutomationLoaderException): pass +class InvalidPackage(AutomationLoaderException): + pass + + class DBError(Exception): pass diff --git a/api/src/automations/loader.py b/api/src/automations/loader.py index 543ed49..7c8f511 100644 --- a/api/src/automations/loader.py +++ b/api/src/automations/loader.py @@ -16,7 +16,7 @@ ) from ..hass_config.loader import HassConfig -from .errors import InvalidAutomationFile +from .errors import InvalidAutomationFile, InvalidPackage from .tags import TagManager from .types import ( AutomationExtractedIter, @@ -46,7 +46,9 @@ def load_and_iter_automations( elif isinstance(ref, InlineAutomation): yield from load_automation_inline_ref(hass_config.root_path, tag_manager, ref) else: - raise AssertionError(f"ref has an invalid type of {type(ref)} = {ref}") + raise logger.error( + AssertionError(f"ref has an invalid type of {type(ref)} = {ref}") + ) except InvalidAutomationFile as e: logger.error(e) @@ -56,12 +58,17 @@ def extract_automation_refs( ) -> AutomationExtractedIter: found = False - for ref in chain( - extract_automation_root_refs(hass_config), extract_automation_package_refs(hass_config) - ): + for ref in extract_automation_root_refs(hass_config): found = True yield ref + try: + for ref in extract_automation_package_refs(hass_config): + found = True + yield ref + except InvalidPackage as err: + logger.fatal(err) + if not found: yield IncludedAutoamtion([""], IncludedYaml(hass_config.root_path / "automations.yaml")) @@ -80,7 +87,7 @@ def extract_automation_root_refs( source_file=hass_config.get_configuration_path(), ) else: - logger.warning(f"found an invalid type in {key}!") + logger.error(f"found an invalid type in {key}!") def extract_automation_package_refs(hass_config: HassConfig) -> AutomationExtractedIter: @@ -93,7 +100,7 @@ def extract_automation_package_refs(hass_config: HassConfig) -> AutomationExtrac packages, ) elif isinstance(packages_config, IncludedYamlDirList): - raise AssertionError("cannot configure packages with 'included_dir_list' directive") + raise InvalidPackage("cannot configure packages with 'included_dir_list' directive") elif isinstance(packages_config, IncludedYamlDirNamed): packages = packages_config.to_normalized_json(use_path_as_keys=True) for package_path, package in packages.items(): @@ -109,7 +116,7 @@ def extract_automation_package_refs(hass_config: HassConfig) -> AutomationExtrac package_set, ) in iter(packages_config): if not isinstance(package_set, dict): - raise AssertionError( + raise InvalidPackage( f"expected {pacakge_set_path} to be a dictionary but got {type(package_set)}" ) yield from extract_automation_inline_package_refs( @@ -124,7 +131,7 @@ def extract_automation_package_refs(hass_config: HassConfig) -> AutomationExtrac packages_config, ) else: - raise AssertionError("configurations.homeassistant.packages must be a dictionary!") + raise InvalidPackage("configurations.homeassistant.packages must be a dictionary!") def extract_automation_inline_package_refs( @@ -134,44 +141,47 @@ def extract_automation_inline_package_refs( ignore_package_name=False, ) -> AutomationExtractedIter: for package_name, package_data in packages_config.items(): - if isinstance(package_data, IncludedYaml): - package_mapping = package_data.to_normalized_json() - yield from extract_automation_from_package( - package_data=package_mapping, - package_name=package_name, - source_file=source_file, - prefix_config_keys=prefix_config_keys, - ignore_package_name=ignore_package_name, - ) - elif isinstance(package_data, IncludedYamlDirList): - raise NotImplementedError(package_data) - elif isinstance(package_data, IncludedYamlDirNamed): - for package_key_path in package_data.to_normalized_json(True).keys(): - if package_key_path.stem == "automation": - yield IncludedAutoamtion( - configuration_key=[package_name], ref=IncludedYaml(package_key_path) + try: + if isinstance(package_data, IncludedYaml): + package_mapping = package_data.to_normalized_json() + yield from extract_automation_from_package( + package_data=package_mapping, + package_name=package_name, + source_file=source_file, + prefix_config_keys=prefix_config_keys, + ignore_package_name=ignore_package_name, + ) + elif isinstance(package_data, IncludedYamlDirList): + raise NotImplementedError(package_data) + elif isinstance(package_data, IncludedYamlDirNamed): + for package_key_path in package_data.to_normalized_json(True).keys(): + if package_key_path.stem == "automation": + yield IncludedAutoamtion( + configuration_key=[package_name], ref=IncludedYaml(package_key_path) + ) + elif isinstance(package_data, IncludedYamlDirMergedNamed): + for data_path, data_set in iter(package_data): + yield from extract_automation_from_package( + package_name=package_name, + package_data=data_set, + source_file=data_path, + prefix_config_keys=[], + ignore_package_name=True, ) - elif isinstance(package_data, IncludedYamlDirMergedNamed): - for data_path, data_set in iter(package_data): + elif isinstance(package_data, dict): yield from extract_automation_from_package( + package_data=package_data, package_name=package_name, - package_data=data_set, - source_file=data_path, - prefix_config_keys=[], - ignore_package_name=True, + source_file=source_file, + prefix_config_keys=prefix_config_keys, + ignore_package_name=ignore_package_name, ) - elif isinstance(package_data, dict): - yield from extract_automation_from_package( - package_data=package_data, - package_name=package_name, - source_file=source_file, - prefix_config_keys=prefix_config_keys, - ignore_package_name=ignore_package_name, - ) - else: - raise AssertionError( - f"configurations.homeassistant.packages[{package_name}] must be a dictionary!" - ) + else: + raise InvalidPackage( + f"configurations.homeassistant.packages[{package_name}] must be a dictionary!" + ) + except Exception as err: + logger.error(err) def extract_automation_from_package( diff --git a/api/tests/samples/config-14/automations.yaml b/api/tests/samples/config-14/automations.yaml new file mode 100644 index 0000000..e125729 --- /dev/null +++ b/api/tests/samples/config-14/automations.yaml @@ -0,0 +1,21 @@ +- alias: UI 1 + description: Example + mode: single + trigger: + - platform: homeassistant + event: start + condition: [] + action: + - service: counter.increment + data: {} + target: + entity_id: counter.up_times +- alias: UI 2 + description: "" + trigger: + - platform: sun + event: sunrise + offset: 0 + condition: [] + action: [] + mode: single diff --git a/api/tests/samples/config-14/configuration.yaml b/api/tests/samples/config-14/configuration.yaml new file mode 100644 index 0000000..0a9299f --- /dev/null +++ b/api/tests/samples/config-14/configuration.yaml @@ -0,0 +1,2 @@ +homeassistant: + packages: !include_dir_named integrations diff --git a/api/tests/samples/config-14/entities/.gitkeep b/api/tests/samples/config-14/entities/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/api/tests/samples/config-14/integrations/automation.yaml b/api/tests/samples/config-14/integrations/automation.yaml new file mode 100644 index 0000000..baad389 --- /dev/null +++ b/api/tests/samples/config-14/integrations/automation.yaml @@ -0,0 +1 @@ +automation: !include ../automations.yaml diff --git a/api/tests/samples/config-14/integrations/media_player.yaml b/api/tests/samples/config-14/integrations/media_player.yaml new file mode 100644 index 0000000..e813406 --- /dev/null +++ b/api/tests/samples/config-14/integrations/media_player.yaml @@ -0,0 +1 @@ +media_player: !include_dir_list ../entities/media_players diff --git a/api/tests/unittests/automations/test_loader.py b/api/tests/unittests/automations/test_loader.py index e6e5994..bbce19b 100644 --- a/api/tests/unittests/automations/test_loader.py +++ b/api/tests/unittests/automations/test_loader.py @@ -10,6 +10,7 @@ from src.automations.types import ExtenededAutomation from src.hass_config.loader import HassConfig from tests.utils import ( + HA_CONFIG14_EXAMPLE, HA_CONFIG2_EXAMPLE, HA_CONFIG3_EXAMPLE, HA_CONFIG4_EXAMPLE, @@ -162,6 +163,7 @@ def test_extract_all_automation_files(self): (HA_CONFIG11_EXAMPLE, 3), (HA_CONFIG12_EXAMPLE, 4), (HA_CONFIG13_EXAMPLE, 4), + (HA_CONFIG14_EXAMPLE, 1), ]: with self.subTest(config_name=ha_path.name, expected=expected): hass_config = HassConfig(ha_path) diff --git a/api/tests/utils.py b/api/tests/utils.py index ec58b71..5425a99 100644 --- a/api/tests/utils.py +++ b/api/tests/utils.py @@ -23,6 +23,7 @@ HA_CONFIG11_EXAMPLE = SAMPLES_FOLDER / "config-11" HA_CONFIG12_EXAMPLE = SAMPLES_FOLDER / "config-12" HA_CONFIG13_EXAMPLE = SAMPLES_FOLDER / "config-13" +HA_CONFIG14_EXAMPLE = SAMPLES_FOLDER / "config-14" def get_example_automation_loader(