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 abc import abstractmethod
import asyncio
from collections.abc import Mapping
from datetime import datetime
from functools import partial
from functools import cached_property, partial
import hashlib
from http import HTTPStatus
import io
@ -373,12 +372,25 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
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."""
_attr_should_poll = False
__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
@final
def state(self) -> str | None:
@ -387,25 +399,25 @@ class TextToSpeechEntity(RestoreEntity):
return None
return self.__last_tts_loaded
@property
@abstractmethod
@cached_property
def supported_languages(self) -> list[str]:
"""Return a list of supported languages."""
return self._attr_supported_languages
@property
@abstractmethod
@cached_property
def default_language(self) -> str:
"""Return the default language."""
return self._attr_default_language
@property
@cached_property
def supported_options(self) -> list[str] | None:
"""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:
"""Return a mapping with the default options."""
return None
return self._attr_default_options
@callback
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:
"""Call when the entity is 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()
if (
state is not None

View file

@ -47,15 +47,8 @@ ORIG_WRITE_TAGS = tts.SpeechManager.write_tags
class DefaultEntity(tts.TextToSpeechEntity):
"""Test entity."""
@property
def supported_languages(self) -> list[str]:
"""Return a list of supported languages."""
return SUPPORT_LANGUAGES
@property
def default_language(self) -> str:
"""Return the default language."""
return DEFAULT_LANG
_attr_supported_languages = SUPPORT_LANGUAGES
_attr_default_language = DEFAULT_LANG
async def test_default_entity_attributes() -> None:
@ -523,10 +516,7 @@ class MockProviderWithDefaults(MockProvider):
class MockEntityWithDefaults(MockTTSEntity):
"""Mock entity with default options."""
@property
def default_options(self):
"""Return a mapping with the default options."""
return {"voice": "alex"}
_attr_default_options = {"voice": "alex"}
@pytest.mark.parametrize(
@ -1758,3 +1748,93 @@ async def test_async_convert_audio_error(hass: HomeAssistant) -> None:
with pytest.raises(RuntimeError):
# Simulate a bad WAV file
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
]
)