mirror of
https://github.com/smkrv/ha-text-ai.git
synced 2026-07-21 22:54:00 +08:00
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
This commit is contained in:
@@ -343,16 +343,19 @@ async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
try:
|
try:
|
||||||
if entry.entry_id in hass.data[DOMAIN]:
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
if unload_ok and entry.entry_id in hass.data[DOMAIN]:
|
||||||
|
coordinator = hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
if hasattr(coordinator.client, 'shutdown'):
|
if hasattr(coordinator.client, 'shutdown'):
|
||||||
await coordinator.client.shutdown()
|
await coordinator.client.shutdown()
|
||||||
|
|
||||||
await coordinator.async_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:
|
except Exception as ex:
|
||||||
_LOGGER.exception("Error unloading entry: %s", str(ex))
|
_LOGGER.exception("Error unloading entry: %s", str(ex))
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ class APIClient:
|
|||||||
}
|
}
|
||||||
_LOGGER.debug("DeepSeek structured output enabled with schema")
|
_LOGGER.debug("DeepSeek structured output enabled with schema")
|
||||||
except json.JSONDecodeError as e:
|
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"}
|
payload["response_format"] = {"type": "json_object"}
|
||||||
|
|
||||||
data = await self._make_request(url, payload)
|
data = await self._make_request(url, payload)
|
||||||
@@ -266,7 +266,7 @@ class APIClient:
|
|||||||
}
|
}
|
||||||
_LOGGER.debug("OpenAI structured output enabled with schema")
|
_LOGGER.debug("OpenAI structured output enabled with schema")
|
||||||
except json.JSONDecodeError as e:
|
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"}
|
payload["response_format"] = {"type": "json_object"}
|
||||||
|
|
||||||
data = await self._make_request(url, payload)
|
data = await self._make_request(url, payload)
|
||||||
@@ -407,7 +407,7 @@ class APIClient:
|
|||||||
parsed_schema = json.loads(json_schema)
|
parsed_schema = json.loads(json_schema)
|
||||||
_LOGGER.debug("Gemini structured output enabled with schema")
|
_LOGGER.debug("Gemini structured output enabled with schema")
|
||||||
except json.JSONDecodeError as e:
|
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
|
# Create configuration
|
||||||
def create_config():
|
def create_config():
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import voluptuous as vol
|
|||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_NAME
|
from homeassistant.const import CONF_API_KEY, CONF_NAME
|
||||||
from homeassistant.core import callback
|
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.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers import selector
|
from homeassistant.helpers import selector
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self._data = {}
|
self._data = {}
|
||||||
self._provider = None
|
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."""
|
"""Handle the initial step."""
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self.async_show_form(
|
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."""
|
"""Handle provider configuration step."""
|
||||||
self._errors = {}
|
self._errors = {}
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self._errors["base"] = "cannot_connect"
|
self._errors["base"] = "cannot_connect"
|
||||||
return False
|
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."""
|
"""Create the config entry with comprehensive data preservation."""
|
||||||
instance_name = user_input[CONF_NAME]
|
instance_name = user_input[CONF_NAME]
|
||||||
normalized_name = normalize_name(instance_name)
|
normalized_name = normalize_name(instance_name)
|
||||||
@@ -305,11 +305,6 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
class OptionsFlowHandler(config_entries.OptionsFlow):
|
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
"""Handle options flow."""
|
"""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:
|
async def _async_validate_api(self, provider: str, api_key: str, endpoint: str) -> bool:
|
||||||
"""Validate API connection using provider registry."""
|
"""Validate API connection using provider registry."""
|
||||||
try:
|
try:
|
||||||
@@ -348,8 +343,11 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
self._errors["base"] = "cannot_connect"
|
self._errors["base"] = "cannot_connect"
|
||||||
return False
|
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."""
|
"""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_data = {**self.config_entry.data, **self.config_entry.options}
|
||||||
current_provider = current_data.get(CONF_API_PROVIDER, API_PROVIDER_OPENAI)
|
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."""
|
"""Handle settings configuration step."""
|
||||||
self._errors = {}
|
self._errors = {}
|
||||||
current_data = {**self.config_entry.data, **self.config_entry.options}
|
current_data = {**self.config_entry.data, **self.config_entry.options}
|
||||||
|
|||||||
@@ -6,12 +6,8 @@ Constants for the HA text AI integration.
|
|||||||
@github: https://github.com/smkrv/ha-text-ai
|
@github: https://github.com/smkrv/ha-text-ai
|
||||||
@source: https://github.com/smkrv/ha-text-ai
|
@source: https://github.com/smkrv/ha-text-ai
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import json
|
|
||||||
from typing import Final
|
from typing import Final
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
import logging
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# Domain and platforms
|
# Domain and platforms
|
||||||
DOMAIN: Final = "ha_text_ai"
|
DOMAIN: Final = "ha_text_ai"
|
||||||
@@ -31,21 +27,7 @@ API_PROVIDERS: Final = [
|
|||||||
API_PROVIDER_GEMINI
|
API_PROVIDER_GEMINI
|
||||||
]
|
]
|
||||||
|
|
||||||
# Read version from manifest.json
|
VERSION: Final = "2.4.0"
|
||||||
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)
|
|
||||||
|
|
||||||
# Default endpoints
|
# Default endpoints
|
||||||
DEFAULT_OPENAI_ENDPOINT: Final = "https://api.openai.com/v1"
|
DEFAULT_OPENAI_ENDPOINT: Final = "https://api.openai.com/v1"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
"loggers": ["custom_components.ha_text_ai"],
|
"loggers": ["custom_components.ha_text_ai"],
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"aiofiles>=23.0.0",
|
"aiofiles>=23.0.0",
|
||||||
"aiohttp>=3.8.0",
|
|
||||||
"google-genai>=1.16.0"
|
"google-genai>=1.16.0"
|
||||||
],
|
],
|
||||||
"single_config_entry": false,
|
"single_config_entry": false,
|
||||||
|
|||||||
Reference in New Issue
Block a user