Add _attr class attributes to TextToSpeechEntity (#115684)

This commit is contained in:
Sid 2024-07-07 16:21:04 +02:00 committed by GitHub
parent 790d22dc46
commit e2141dc208
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 128 additions and 24 deletions

View File

@ -2,11 +2,10 @@
from __future__ import annotations from __future__ import annotations
from abc import abstractmethod
import asyncio import asyncio
from collections.abc import Mapping from collections.abc import Mapping
from datetime import datetime from datetime import datetime
from functools import partial from functools import cached_property, partial
import hashlib import hashlib
from http import HTTPStatus from http import HTTPStatus
import io import io
@ -373,12 +372,25 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return await component.async_unload_entry(entry) return await component.async_unload_entry(entry)
class TextToSpeechEntity(RestoreEntity): CACHED_PROPERTIES_WITH_ATTR_ = {
"default_language",
"default_options",
"supported_languages",
"supported_options",
}
class TextToSpeechEntity(RestoreEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Represent a single TTS engine.""" """Represent a single TTS engine."""
_attr_should_poll = False _attr_should_poll = False
__last_tts_loaded: str | None = None __last_tts_loaded: str | None = None
_attr_default_language: str
_attr_default_options: Mapping[str, Any] | None = None
_attr_supported_languages: list[str]
_attr_supported_options: list[str] | None = None
@property @property
@final @final
def state(self) -> str | None: def state(self) -> str | None:
@ -387,25 +399,25 @@ class TextToSpeechEntity(RestoreEntity):
return None return None
return self.__last_tts_loaded return self.__last_tts_loaded
@property @cached_property
@abstractmethod
def supported_languages(self) -> list[str]: def supported_languages(self) -> list[str]:
"""Return a list of supported languages.""" """Return a list of supported languages."""
return self._attr_supported_languages
@property @cached_property
@abstractmethod
def default_language(self) -> str: def default_language(self) -> str:
"""Return the default language.""" """Return the default language."""
return self._attr_default_language
@property @cached_property
def supported_options(self) -> list[str] | None: def supported_options(self) -> list[str] | None:
"""Return a list of supported options like voice, emotions.""" """Return a list of supported options like voice, emotions."""
return None return self._attr_supported_options
@property @cached_property
def default_options(self) -> Mapping[str, Any] | None: def default_options(self) -> Mapping[str, Any] | None:
"""Return a mapping with the default options.""" """Return a mapping with the default options."""
return None return self._attr_default_options
@callback @callback
def async_get_supported_voices(self, language: str) -> list[Voice] | None: def async_get_supported_voices(self, language: str) -> list[Voice] | None:
@ -415,6 +427,18 @@ class TextToSpeechEntity(RestoreEntity):
async def async_internal_added_to_hass(self) -> None: async def async_internal_added_to_hass(self) -> None:
"""Call when the entity is added to hass.""" """Call when the entity is added to hass."""
await super().async_internal_added_to_hass() await super().async_internal_added_to_hass()
try:
_ = self.default_language
except AttributeError as err:
raise AttributeError(
"TTS entities must either set the '_attr_default_language' attribute or override the 'default_language' property"
) from err
try:
_ = self.supported_languages
except AttributeError as err:
raise AttributeError(
"TTS entities must either set the '_attr_supported_languages' attribute or override the 'supported_languages' property"
) from err
state = await self.async_get_last_state() state = await self.async_get_last_state()
if ( if (
state is not None state is not None

View File

@ -47,15 +47,8 @@ ORIG_WRITE_TAGS = tts.SpeechManager.write_tags
class DefaultEntity(tts.TextToSpeechEntity): class DefaultEntity(tts.TextToSpeechEntity):
"""Test entity.""" """Test entity."""
@property _attr_supported_languages = SUPPORT_LANGUAGES
def supported_languages(self) -> list[str]: _attr_default_language = DEFAULT_LANG
"""Return a list of supported languages."""
return SUPPORT_LANGUAGES
@property
def default_language(self) -> str:
"""Return the default language."""
return DEFAULT_LANG
async def test_default_entity_attributes() -> None: async def test_default_entity_attributes() -> None:
@ -523,10 +516,7 @@ class MockProviderWithDefaults(MockProvider):
class MockEntityWithDefaults(MockTTSEntity): class MockEntityWithDefaults(MockTTSEntity):
"""Mock entity with default options.""" """Mock entity with default options."""
@property _attr_default_options = {"voice": "alex"}
def default_options(self):
"""Return a mapping with the default options."""
return {"voice": "alex"}
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -1758,3 +1748,93 @@ async def test_async_convert_audio_error(hass: HomeAssistant) -> None:
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
# Simulate a bad WAV file # Simulate a bad WAV file
await tts.async_convert_audio(hass, "wav", bytes(0), "mp3") await tts.async_convert_audio(hass, "wav", bytes(0), "mp3")
async def test_ttsentity_subclass_properties(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test for errors when subclasses of the TextToSpeechEntity are missing required properties."""
class TestClass1(tts.TextToSpeechEntity):
_attr_default_language = DEFAULT_LANG
_attr_supported_languages = SUPPORT_LANGUAGES
await mock_config_entry_setup(hass, TestClass1())
class TestClass2(tts.TextToSpeechEntity):
@property
def default_language(self) -> str:
return DEFAULT_LANG
@property
def supported_languages(self) -> list[str]:
return SUPPORT_LANGUAGES
await mock_config_entry_setup(hass, TestClass2())
assert all(record.exc_info is None for record in caplog.records)
caplog.clear()
class TestClass3(tts.TextToSpeechEntity):
_attr_default_language = DEFAULT_LANG
await mock_config_entry_setup(hass, TestClass3())
assert (
"TTS entities must either set the '_attr_supported_languages' attribute or override the 'supported_languages' property"
in [
str(record.exc_info[1])
for record in caplog.records
if record.exc_info is not None
]
)
caplog.clear()
class TestClass4(tts.TextToSpeechEntity):
_attr_supported_languages = SUPPORT_LANGUAGES
await mock_config_entry_setup(hass, TestClass4())
assert (
"TTS entities must either set the '_attr_default_language' attribute or override the 'default_language' property"
in [
str(record.exc_info[1])
for record in caplog.records
if record.exc_info is not None
]
)
caplog.clear()
class TestClass5(tts.TextToSpeechEntity):
@property
def default_language(self) -> str:
return DEFAULT_LANG
await mock_config_entry_setup(hass, TestClass5())
assert (
"TTS entities must either set the '_attr_supported_languages' attribute or override the 'supported_languages' property"
in [
str(record.exc_info[1])
for record in caplog.records
if record.exc_info is not None
]
)
caplog.clear()
class TestClass6(tts.TextToSpeechEntity):
@property
def supported_languages(self) -> list[str]:
return SUPPORT_LANGUAGES
await mock_config_entry_setup(hass, TestClass6())
assert (
"TTS entities must either set the '_attr_default_language' attribute or override the 'default_language' property"
in [
str(record.exc_info[1])
for record in caplog.records
if record.exc_info is not None
]
)