From 9cdeb9f4175f036ddd27d4e6304f3fd12415c110 Mon Sep 17 00:00:00 2001 From: SMKRV Date: Thu, 12 Mar 2026 02:06:21 +0300 Subject: [PATCH] fix: HA 2024.11+ compatibility and deprecation fixes CRITICAL: - Remove OptionsFlowHandler.__init__ (deprecated HA 2024.11+) - Replace FlowResult with ConfigFlowResult (deprecated HA 2024.4+) Compatibility: - Reorder async_unload_entry: unload platforms before coordinator cleanup - Clean up hass.data[DOMAIN] when last entry removed - Remove aiohttp from manifest requirements (provided by HA core) - Replace blocking file I/O in const.py with hardcoded VERSION - Remove unused os, json, logging imports from const.py - Fix f-string logger calls in api_client.py to use %s formatting --- custom_components/ha_text_ai/__init__.py | 11 +++++++---- custom_components/ha_text_ai/api_client.py | 6 +++--- custom_components/ha_text_ai/config_flow.py | 20 +++++++++----------- custom_components/ha_text_ai/const.py | 20 +------------------- custom_components/ha_text_ai/manifest.json | 1 - 5 files changed, 20 insertions(+), 38 deletions(-) diff --git a/custom_components/ha_text_ai/__init__.py b/custom_components/ha_text_ai/__init__.py index 64ad642..ac2f104 100644 --- a/custom_components/ha_text_ai/__init__.py +++ b/custom_components/ha_text_ai/__init__.py @@ -343,16 +343,19 @@ async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" try: - if entry.entry_id in hass.data[DOMAIN]: - coordinator = hass.data[DOMAIN][entry.entry_id] + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + if unload_ok and entry.entry_id in hass.data[DOMAIN]: + coordinator = hass.data[DOMAIN].pop(entry.entry_id) if hasattr(coordinator.client, 'shutdown'): await coordinator.client.shutdown() await coordinator.async_shutdown() - hass.data[DOMAIN].pop(entry.entry_id) - return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + if not hass.data.get(DOMAIN): + hass.data.pop(DOMAIN, None) + + return unload_ok except Exception as ex: _LOGGER.exception("Error unloading entry: %s", str(ex)) diff --git a/custom_components/ha_text_ai/api_client.py b/custom_components/ha_text_ai/api_client.py index 5851060..e744d45 100644 --- a/custom_components/ha_text_ai/api_client.py +++ b/custom_components/ha_text_ai/api_client.py @@ -216,7 +216,7 @@ class APIClient: } _LOGGER.debug("DeepSeek structured output enabled with schema") except json.JSONDecodeError as e: - _LOGGER.warning(f"Invalid JSON schema provided: {e}. Falling back to json_object mode.") + _LOGGER.warning("Invalid JSON schema provided: %s. Falling back to json_object mode.", e) payload["response_format"] = {"type": "json_object"} data = await self._make_request(url, payload) @@ -266,7 +266,7 @@ class APIClient: } _LOGGER.debug("OpenAI structured output enabled with schema") except json.JSONDecodeError as e: - _LOGGER.warning(f"Invalid JSON schema provided: {e}. Falling back to json_object mode.") + _LOGGER.warning("Invalid JSON schema provided: %s. Falling back to json_object mode.", e) payload["response_format"] = {"type": "json_object"} data = await self._make_request(url, payload) @@ -407,7 +407,7 @@ class APIClient: parsed_schema = json.loads(json_schema) _LOGGER.debug("Gemini structured output enabled with schema") except json.JSONDecodeError as e: - _LOGGER.warning(f"Invalid JSON schema provided: {e}. Structured output disabled.") + _LOGGER.warning("Invalid JSON schema provided: %s. Structured output disabled.", e) # Create configuration def create_config(): diff --git a/custom_components/ha_text_ai/config_flow.py b/custom_components/ha_text_ai/config_flow.py index 7afede8..99415e9 100644 --- a/custom_components/ha_text_ai/config_flow.py +++ b/custom_components/ha_text_ai/config_flow.py @@ -13,7 +13,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_API_KEY, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers import selector @@ -72,7 +72,7 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._data = {} self._provider = None - async def async_step_user(self, user_input: Optional[Dict[str, Any]] = None) -> FlowResult: + async def async_step_user(self, user_input: Optional[Dict[str, Any]] = None) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -132,7 +132,7 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ), }) - async def async_step_provider(self, user_input: Optional[Dict[str, Any]] = None) -> FlowResult: + async def async_step_provider(self, user_input: Optional[Dict[str, Any]] = None) -> ConfigFlowResult: """Handle provider configuration step.""" self._errors = {} @@ -263,7 +263,7 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._errors["base"] = "cannot_connect" return False - async def _create_entry(self, user_input: Dict[str, Any]) -> FlowResult: + async def _create_entry(self, user_input: Dict[str, Any]) -> ConfigFlowResult: """Create the config entry with comprehensive data preservation.""" instance_name = user_input[CONF_NAME] normalized_name = normalize_name(instance_name) @@ -305,11 +305,6 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class OptionsFlowHandler(config_entries.OptionsFlow): """Handle options flow.""" - def __init__(self) -> None: - """Initialize options flow.""" - self._errors = {} - self._selected_provider = None - async def _async_validate_api(self, provider: str, api_key: str, endpoint: str) -> bool: """Validate API connection using provider registry.""" try: @@ -348,8 +343,11 @@ class OptionsFlowHandler(config_entries.OptionsFlow): self._errors["base"] = "cannot_connect" return False - async def async_step_init(self, user_input: Optional[Dict[str, Any]] = None) -> FlowResult: + async def async_step_init(self, user_input: Optional[Dict[str, Any]] = None) -> ConfigFlowResult: """Handle provider selection step.""" + if not hasattr(self, "_errors"): + self._errors: dict[str, str] = {} + self._selected_provider: Optional[str] = None current_data = {**self.config_entry.data, **self.config_entry.options} current_provider = current_data.get(CONF_API_PROVIDER, API_PROVIDER_OPENAI) @@ -375,7 +373,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): } ) - async def async_step_settings(self, user_input: Optional[Dict[str, Any]] = None) -> FlowResult: + async def async_step_settings(self, user_input: Optional[Dict[str, Any]] = None) -> ConfigFlowResult: """Handle settings configuration step.""" self._errors = {} current_data = {**self.config_entry.data, **self.config_entry.options} diff --git a/custom_components/ha_text_ai/const.py b/custom_components/ha_text_ai/const.py index ea7b0f1..7c6ec9e 100644 --- a/custom_components/ha_text_ai/const.py +++ b/custom_components/ha_text_ai/const.py @@ -6,12 +6,8 @@ Constants for the HA text AI integration. @github: https://github.com/smkrv/ha-text-ai @source: https://github.com/smkrv/ha-text-ai """ -import os -import json from typing import Final from homeassistant.const import Platform -import logging -_LOGGER = logging.getLogger(__name__) # Domain and platforms DOMAIN: Final = "ha_text_ai" @@ -31,21 +27,7 @@ API_PROVIDERS: Final = [ API_PROVIDER_GEMINI ] -# Read version from manifest.json -MANIFEST_PATH = os.path.join(os.path.dirname(__file__), "manifest.json") -try: - with open(MANIFEST_PATH) as manifest_file: - manifest = json.load(manifest_file) - VERSION = manifest.get("version", "unknown") -except FileNotFoundError: - VERSION = "unknown" - _LOGGER.warning("manifest.json not found at %s", MANIFEST_PATH) -except json.JSONDecodeError as err: - VERSION = "unknown" - _LOGGER.error("Error decoding JSON from manifest.json: %s", err) -except Exception as err: - VERSION = "unknown" - _LOGGER.error("Error reading manifest.json: %s", err) +VERSION: Final = "2.4.0" # Default endpoints DEFAULT_OPENAI_ENDPOINT: Final = "https://api.openai.com/v1" diff --git a/custom_components/ha_text_ai/manifest.json b/custom_components/ha_text_ai/manifest.json index c800da7..c51048a 100644 --- a/custom_components/ha_text_ai/manifest.json +++ b/custom_components/ha_text_ai/manifest.json @@ -12,7 +12,6 @@ "loggers": ["custom_components.ha_text_ai"], "requirements": [ "aiofiles>=23.0.0", - "aiohttp>=3.8.0", "google-genai>=1.16.0" ], "single_config_entry": false,