Skip to content

Commit 993c332

Browse files
committed
style: ruff format on files touched by D4 fix
Mirrors commit 50a7149's pattern from earlier in this PR: the D4 logic commit added new edits to ``tools_config_automations.py`` and ``tools_config_scripts.py``, bringing them into the changed-files scope of CI's ``ruff format --check`` gate (per ``pr.yml``'s "new edits must be formatted; pre-existing format-debt on untouched files stays grandfathered" policy). Applies ``ruff format`` to absorb the pre-existing line-wrap debt those files carried at #1318 sweep granularity. No logic changes — purely whitespace and line-break adjustments.
1 parent 8e96914 commit 993c332

2 files changed

Lines changed: 201 additions & 118 deletions

File tree

src/ha_mcp/tools/tools_config_automations.py

Lines changed: 142 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,9 @@ async def _resolve_automation_entity_id(self, identifier: str) -> str | None:
280280
):
281281
return str(state["entity_id"])
282282
except Exception as e:
283-
logger.debug(f"Failed to resolve entity_id for automation {identifier}: {e}")
283+
logger.debug(
284+
f"Failed to resolve entity_id for automation {identifier}: {e}"
285+
)
284286
return None
285287

286288
@tool(
@@ -335,13 +337,17 @@ async def ha_config_get_automation(
335337
"Use ha_search_entities(domain_filter='automation') to list automations",
336338
],
337339
)
338-
normalized_config, config_hash = await self._get_automation_config_internal(identifier)
340+
normalized_config, config_hash = await self._get_automation_config_internal(
341+
identifier
342+
)
339343

340344
# Resolve entity_id and fetch category from entity registry
341345
# (injected after hash so transient registry failures don't affect the hash)
342346
entity_id = await self._resolve_automation_entity_id(identifier)
343347
if entity_id:
344-
cat_id = await fetch_entity_category(self._client, entity_id, "automation")
348+
cat_id = await fetch_entity_category(
349+
self._client, entity_id, "automation"
350+
)
345351
if cat_id:
346352
normalized_config["category"] = cat_id
347353

@@ -645,7 +651,10 @@ async def ha_config_set_automation(
645651
"Provide the automation entity_id or unique_id",
646652
"Use ha_search_entities(domain_filter='automation') to find automations",
647653
],
648-
context={"action": "python_transform", "identifier": identifier},
654+
context={
655+
"action": "python_transform",
656+
"identifier": identifier,
657+
},
649658
)
650659
)
651660
if config_hash is None:
@@ -657,7 +666,10 @@ async def ha_config_set_automation(
657666
"Call ha_config_get_automation() first",
658667
"Use the config_hash from that response",
659668
],
660-
context={"action": "python_transform", "identifier": identifier},
669+
context={
670+
"action": "python_transform",
671+
"identifier": identifier,
672+
},
661673
)
662674
)
663675

@@ -676,7 +688,10 @@ async def ha_config_set_automation(
676688
ErrorCode.VALIDATION_FAILED,
677689
message,
678690
suggestions=suggestions,
679-
context={"action": "python_transform", "identifier": identifier},
691+
context={
692+
"action": "python_transform",
693+
"identifier": identifier,
694+
},
680695
)
681696
)
682697

@@ -699,11 +714,20 @@ async def ha_config_set_automation(
699714

700715
# Re-apply category if present
701716
entity_id = result.get("entity_id")
702-
if not entity_id and identifier and identifier.startswith("automation."):
717+
if (
718+
not entity_id
719+
and identifier
720+
and identifier.startswith("automation.")
721+
):
703722
entity_id = identifier
704723
if transform_category and entity_id:
705724
await apply_entity_category(
706-
self._client, entity_id, transform_category, "automation", result, "automation"
725+
self._client,
726+
entity_id,
727+
transform_category,
728+
"automation",
729+
result,
730+
"automation",
707731
)
708732

709733
response: dict[str, Any] = {
@@ -763,7 +787,9 @@ async def ha_config_set_automation(
763787
self._client, config_dict
764788
)
765789

766-
result = await self._client.upsert_automation_config(config_dict, identifier)
790+
result = await self._client.upsert_automation_config(
791+
config_dict, identifier
792+
)
767793

768794
# If the client could not verify the entity was registered, warn but don't hard-fail.
769795
if result.get("entity_not_verified"):
@@ -785,7 +811,9 @@ async def ha_config_set_automation(
785811
if wait_bool and entity_id:
786812
action_word = "created" if identifier is None else "updated"
787813
try:
788-
registered = await wait_for_entity_registered(self._client, entity_id)
814+
registered = await wait_for_entity_registered(
815+
self._client, entity_id
816+
)
789817
if not registered:
790818
result.setdefault("warnings", []).append(
791819
f"Automation {action_word} but {entity_id} not yet queryable. "
@@ -799,7 +827,12 @@ async def ha_config_set_automation(
799827
# Apply category to entity registry if provided
800828
if effective_category and entity_id:
801829
await apply_entity_category(
802-
self._client, entity_id, effective_category, "automation", result, "automation"
830+
self._client,
831+
entity_id,
832+
effective_category,
833+
"automation",
834+
result,
835+
"automation",
803836
)
804837

805838
if bp_warnings:
@@ -852,9 +885,7 @@ async def _list_automation_entity_ids(self) -> list[str]:
852885
{"type": "config/entity_registry/list"}
853886
)
854887
except Exception as e:
855-
logger.debug(
856-
"Failed to list automation entity_ids from registry: %s", e
857-
)
888+
logger.debug("Failed to list automation entity_ids from registry: %s", e)
858889
return []
859890
entries = result.get("result", []) if isinstance(result, dict) else result
860891
if not isinstance(entries, list):
@@ -913,7 +944,9 @@ async def _fetch_and_verify_hash(
913944
Returns the current normalized config dict.
914945
Raises ToolError if the hash does not match (conflict).
915946
"""
916-
current_config, current_hash = await self._get_automation_config_internal(identifier)
947+
current_config, current_hash = await self._get_automation_config_internal(
948+
identifier
949+
)
917950
if current_hash != config_hash:
918951
raise_tool_error(
919952
create_error_response(
@@ -934,22 +967,26 @@ def _parse_and_validate_config(config: str | dict[str, Any]) -> dict[str, Any]:
934967
try:
935968
parsed_config = parse_json_param(config, "config")
936969
except ValueError as e:
937-
raise_tool_error(create_error_response(
938-
code=ErrorCode.VALIDATION_INVALID_JSON,
939-
message=f"Invalid config parameter: {e}",
940-
suggestions=[
941-
"Pass 'config' as a dict, not a JSON string, to avoid escaping issues.",
942-
"Check for JSON syntax errors: unquoted keys, trailing commas, or invalid escape sequences.",
943-
],
944-
context={"parameter": "config"},
945-
))
970+
raise_tool_error(
971+
create_error_response(
972+
code=ErrorCode.VALIDATION_INVALID_JSON,
973+
message=f"Invalid config parameter: {e}",
974+
suggestions=[
975+
"Pass 'config' as a dict, not a JSON string, to avoid escaping issues.",
976+
"Check for JSON syntax errors: unquoted keys, trailing commas, or invalid escape sequences.",
977+
],
978+
context={"parameter": "config"},
979+
)
980+
)
946981

947982
if parsed_config is None or not isinstance(parsed_config, dict):
948-
raise_tool_error(create_validation_error(
949-
"Config parameter must be a JSON object",
950-
parameter="config",
951-
details=f"Received type: {type(parsed_config).__name__}",
952-
))
983+
raise_tool_error(
984+
create_validation_error(
985+
"Config parameter must be a JSON object",
986+
parameter="config",
987+
details=f"Received type: {type(parsed_config).__name__}",
988+
)
989+
)
953990

954991
return cast(dict[str, Any], parsed_config)
955992

@@ -978,24 +1015,28 @@ def _validate_required_fields(
9781015
context: dict[str, Any] = {"missing_fields": missing_fields}
9791016
if identifier:
9801017
context["identifier"] = identifier
981-
raise_tool_error(create_error_response(
982-
code=ErrorCode.CONFIG_MISSING_REQUIRED_FIELDS,
983-
message=f"Missing required fields: {', '.join(missing_fields)}",
984-
details=(
985-
"Config contains 'sequence', which belongs to scripts. "
986-
"Automations use 'trigger' and 'action'; scripts use 'sequence'."
987-
),
988-
suggestions=[
989-
"Did you mean ha_config_set_script? Scripts use 'sequence' directly.",
990-
"For an automation, replace 'sequence' with 'action' and add a 'trigger'.",
991-
],
992-
context=context,
993-
))
994-
raise_tool_error(create_config_error(
995-
f"Missing required fields: {', '.join(missing_fields)}",
996-
identifier=identifier,
997-
missing_fields=missing_fields,
998-
))
1018+
raise_tool_error(
1019+
create_error_response(
1020+
code=ErrorCode.CONFIG_MISSING_REQUIRED_FIELDS,
1021+
message=f"Missing required fields: {', '.join(missing_fields)}",
1022+
details=(
1023+
"Config contains 'sequence', which belongs to scripts. "
1024+
"Automations use 'trigger' and 'action'; scripts use 'sequence'."
1025+
),
1026+
suggestions=[
1027+
"Did you mean ha_config_set_script? Scripts use 'sequence' directly.",
1028+
"For an automation, replace 'sequence' with 'action' and add a 'trigger'.",
1029+
],
1030+
context=context,
1031+
)
1032+
)
1033+
raise_tool_error(
1034+
create_config_error(
1035+
f"Missing required fields: {', '.join(missing_fields)}",
1036+
identifier=identifier,
1037+
missing_fields=missing_fields,
1038+
)
1039+
)
9991040

10001041
# Issue #1169: reject configs that wrap ``scene.create`` in an
10011042
# automation with no functional trigger. Models occasionally produce
@@ -1020,62 +1061,68 @@ def _validate_required_fields(
10201061
if _action_contains_scene_create(a)
10211062
]
10221063
if scene_create_indices:
1023-
raise_tool_error(create_error_response(
1024-
code=ErrorCode.VALIDATION_INVALID_PARAMETER,
1025-
message=(
1026-
"Empty trigger paired with a scene.create action — "
1027-
"this automation can never fire. For a state snapshot "
1028-
"of one or more entities, use ha_config_set_scene "
1029-
"directly instead of wrapping scene.create in an "
1030-
"automation."
1031-
),
1032-
suggestions=[
1033-
"ha_config_set_scene(scene_id='...', config={'name': "
1034-
"'...', 'entities': {'<entity_id>': {...}}}) creates "
1035-
"a scene without a trigger.",
1036-
"If the snapshot really should be the result of an "
1037-
"event, add the trigger that should fire it and keep "
1038-
"the automation.",
1039-
"For a state-derived value that recomputes when its "
1040-
"inputs change, use "
1041-
"ha_config_set_helper(helper_type='template') instead.",
1042-
],
1043-
context={
1044-
"scene_create_action_indices": scene_create_indices,
1045-
"identifier": identifier,
1046-
},
1047-
))
1064+
raise_tool_error(
1065+
create_error_response(
1066+
code=ErrorCode.VALIDATION_INVALID_PARAMETER,
1067+
message=(
1068+
"Empty trigger paired with a scene.create action — "
1069+
"this automation can never fire. For a state snapshot "
1070+
"of one or more entities, use ha_config_set_scene "
1071+
"directly instead of wrapping scene.create in an "
1072+
"automation."
1073+
),
1074+
suggestions=[
1075+
"ha_config_set_scene(scene_id='...', config={'name': "
1076+
"'...', 'entities': {'<entity_id>': {...}}}) creates "
1077+
"a scene without a trigger.",
1078+
"If the snapshot really should be the result of an "
1079+
"event, add the trigger that should fire it and keep "
1080+
"the automation.",
1081+
"For a state-derived value that recomputes when its "
1082+
"inputs change, use "
1083+
"ha_config_set_helper(helper_type='template') instead.",
1084+
],
1085+
context={
1086+
"scene_create_action_indices": scene_create_indices,
1087+
"identifier": identifier,
1088+
},
1089+
)
1090+
)
10481091

10491092
# HA accepts conditions with 'platform' (trigger syntax) but then crashes
10501093
# with an unhelpful 500 rather than a 400 validation error.
10511094
for idx, cond in enumerate(coerce_to_list(config_dict.get("condition"))):
10521095
if not isinstance(cond, dict):
10531096
continue
10541097
if "platform" in cond and "condition" not in cond:
1055-
raise_tool_error(create_error_response(
1056-
code=ErrorCode.VALIDATION_INVALID_PARAMETER,
1057-
message=(
1058-
f"Condition at index {idx} uses 'platform' (trigger syntax). "
1059-
"Conditions use 'condition', not 'platform'."
1060-
),
1061-
suggestions=[
1062-
f"Replace 'platform' with 'condition': "
1063-
f"{{'condition': '{cond['platform']}', ...}}",
1064-
"Triggers use 'platform'; conditions use 'condition'.",
1065-
],
1066-
context={"condition_index": idx, "found_key": "platform"},
1067-
))
1098+
raise_tool_error(
1099+
create_error_response(
1100+
code=ErrorCode.VALIDATION_INVALID_PARAMETER,
1101+
message=(
1102+
f"Condition at index {idx} uses 'platform' (trigger syntax). "
1103+
"Conditions use 'condition', not 'platform'."
1104+
),
1105+
suggestions=[
1106+
f"Replace 'platform' with 'condition': "
1107+
f"{{'condition': '{cond['platform']}', ...}}",
1108+
"Triggers use 'platform'; conditions use 'condition'.",
1109+
],
1110+
context={"condition_index": idx, "found_key": "platform"},
1111+
)
1112+
)
10681113

10691114
# Prevent duplicate creation when config contains an existing automation id
10701115
if identifier is None and "id" in config_dict:
10711116
existing_id = config_dict["id"]
1072-
raise_tool_error(create_validation_error(
1073-
f"Config contains 'id' field ('{existing_id}') but no identifier was provided. "
1074-
"This would create a duplicate automation instead of updating the existing one.",
1075-
parameter="identifier",
1076-
details=f"To update, pass identifier='{existing_id}' (or the automation's entity_id). "
1077-
"To create a genuinely new automation, remove the 'id' field from the config.",
1078-
))
1117+
raise_tool_error(
1118+
create_validation_error(
1119+
f"Config contains 'id' field ('{existing_id}') but no identifier was provided. "
1120+
"This would create a duplicate automation instead of updating the existing one.",
1121+
parameter="identifier",
1122+
details=f"To update, pass identifier='{existing_id}' (or the automation's entity_id). "
1123+
"To create a genuinely new automation, remove the 'id' field from the config.",
1124+
)
1125+
)
10791126

10801127
@tool(
10811128
name="ha_config_remove_automation",
@@ -1140,7 +1187,9 @@ async def ha_config_remove_automation(
11401187
wait_bool = coerce_bool_param(wait, "wait", default=True)
11411188
if wait_bool and entity_id_for_wait:
11421189
try:
1143-
removed = await wait_for_entity_removed(self._client, entity_id_for_wait)
1190+
removed = await wait_for_entity_removed(
1191+
self._client, entity_id_for_wait
1192+
)
11441193
if not removed:
11451194
result.setdefault("warnings", []).append(
11461195
f"Deletion confirmed by API but {entity_id_for_wait} may still appear briefly."

0 commit comments

Comments
 (0)