Skip to content

Commit

Permalink
Merge pull request #20 from PiotrMachowski/dev
Browse files Browse the repository at this point in the history
v1.3.0
  • Loading branch information
PiotrMachowski committed Feb 6, 2024
2 parents 3ae5302 + f08e1d4 commit e0616ae
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 28 deletions.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[![HACS Default][hacs_shield]][hacs]
[![GitHub Latest Release][releases_shield]][latest_release]
[![GitHub All Releases][downloads_total_shield]][releases]
[![Installations][installations_shield]][releases]
[![Community Forum][community_forum_shield]][community_forum]<!-- piotrmachowski_support_badges_start -->
[![Ko-Fi][ko_fi_shield]][ko_fi]
[![buycoffee.to][buycoffee_to_shield]][buycoffee_to]
Expand All @@ -18,6 +19,8 @@
[releases]: https://github.com/PiotrMachowski/Home-Assistant-custom-components-Custom-Templates/releases
[downloads_total_shield]: https://img.shields.io/github/downloads/PiotrMachowski/Home-Assistant-custom-components-Custom-Templates/total

[installations_shield]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fanalytics.home-assistant.io%2Fcustom_integrations.json&query=%24.custom_templates.total&style=popout&color=41bdf5&label=analytics

[community_forum_shield]: https://img.shields.io/static/v1.svg?label=%20&message=Forum&style=popout&color=41bdf5&logo=HomeAssistant&logoColor=white
[community_forum]: https://community.home-assistant.io/t/custom-templates/528378

Expand All @@ -30,6 +33,7 @@ This integration adds possibility to use new functions in Home Assistant Jinja2
- `ct_translated` - returns translation for a given key
- `ct_all_translations` - returns all available translations (that can be used with `ct_translated`)
- `ct_eval` - evaluates text as a template
- `ct_is_available` - checks if given entity is available

## Usage

Expand Down Expand Up @@ -216,6 +220,43 @@ below_horizon
</tr>
</table>

### `ct_is_available`

This function checks if given entity has an available state.
By default the following states are treated as not available: `unknown`, `unavailable`, `<empty_text>`, `None`.
It is possible to override this list by providing a second argument.

<table>
<tr>
<th>
Input
</th>
<th>
Output
</th>
</tr>
<tr>
<td>

```yaml
{{ states('sensor.invalid') }}
{{ ct_is_available('sensor.invalid') }}
{{ ct_is_available('sensor.invalid', ['', 'unknown']) }}
```

</td>
<td>

```
unavailable
true
false
```

</td>
</tr>
</table>

## Configuration

To use this integration you have to add following config in `configuration.yaml`:
Expand Down
83 changes: 57 additions & 26 deletions custom_components/custom_templates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
from homeassistant.core import Event, HomeAssistant, valid_entity_id
from homeassistant.helpers.entity_registry import async_get
from homeassistant.helpers.template import _get_state_if_valid, _RESERVED_NAMES, Template, TemplateEnvironment
from homeassistant.helpers.translation import _TranslationCache, TRANSLATION_FLATTEN_CACHE, TRANSLATION_LOAD_LOCK
from homeassistant.helpers.translation import _TranslationCache, TRANSLATION_FLATTEN_CACHE
from homeassistant.loader import bind_hass

from .const import (DOMAIN, CUSTOM_TEMPLATES_SCHEMA, CONF_PRELOAD_TRANSLATIONS, CONST_EVAL_FUNCTION_NAME,
CONST_STATE_TRANSLATED_FUNCTION_NAME, CONST_STATE_ATTR_TRANSLATED_FUNCTION_NAME,
CONST_TRANSLATED_FUNCTION_NAME, CONST_ALL_TRANSLATIONS_FUNCTION_NAME)
CONST_TRANSLATED_FUNCTION_NAME, CONST_ALL_TRANSLATIONS_FUNCTION_NAME,
DEFAULT_UNAVAILABLE_STATES, CONST_IS_AVAILABLE_FUNCTION_NAME)

_LOGGER = logging.getLogger(__name__)

Expand All @@ -32,6 +33,33 @@ def validate_language(self, language):
raise TemplateError(f"Language {language} is not loaded") # type: ignore[arg-type]


class IsAvailable:

def __init__(self, hass: HomeAssistant):
self._hass = hass

def __call__(self, entity_id: str, unavailable_states=DEFAULT_UNAVAILABLE_STATES):
unavailable_states = [s.lower() if type(s) is str else s for s in unavailable_states]
state = None
if "." in entity_id:
state = _get_state_if_valid(self._hass, entity_id)
else:
if entity_id in _RESERVED_NAMES:
return None
if not valid_entity_id(f"{entity_id}.entity"):
raise TemplateError(f"Invalid domain name '{entity_id}'")

if state is not None:
state = state.state
if state is str:
state = state.lower()
result = state not in unavailable_states
return result

def __repr__(self):
return f"<template CT_IsAvailable>"


class StateTranslated(TranslatableTemplate):

def __init__(self, hass: HomeAssistant, available_languages):
Expand Down Expand Up @@ -79,7 +107,7 @@ def __call__(self, entity_id: str, language: str):
return state.state

def __repr__(self):
return "<template StateTranslated>"
return "<template CT_StateTranslated>"


class StateAttrTranslated(TranslatableTemplate):
Expand Down Expand Up @@ -127,7 +155,7 @@ def __call__(self, entity_id: str, attribute: str, language: str):
return attribute_value

def __repr__(self):
return "<template StateAttrTranslated>"
return "<template CT_StateAttrTranslated>"


class Translated(TranslatableTemplate):
Expand All @@ -150,7 +178,7 @@ def __call__(self, key: str, language: str):
return key

def __repr__(self):
return "<template Translated>"
return "<template CT_Translated>"


class AllTranslations(TranslatableTemplate):
Expand All @@ -167,7 +195,7 @@ def __call__(self, language: str):
return translations

def __repr__(self):
return "<template AllTranslations>"
return "<template CT_AllTranslations>"


class EvalTemplate:
Expand All @@ -180,7 +208,7 @@ def __call__(self, content: str):
return tpl.async_render()

def __repr__(self):
return "<template EvalTemplate>"
return "<template CT_EvalTemplate>"


def get_cached(
Expand All @@ -189,27 +217,28 @@ def get_cached(
category: str,
components: set[str],
):
cached = self.cache.get(language, {})
return [cached.get(component, {}).get(category, {}) for component in components]
category_cache = self.cache.get(language, {}).get(category, {})
if len(components) == 1 and (component := next(iter(components))):
return category_cache.get(component, {})
result: dict[str, str] = {}
for component in components.intersection(category_cache):
result.update(category_cache[component])
return result


@bind_hass
async def load_translations_to_cache(
hass: HomeAssistant,
language: str,
):
lock = hass.data.setdefault(TRANSLATION_LOAD_LOCK, asyncio.Lock())

components_entities = {
component for component in hass.config.components if "." not in component
}
components_state = set(hass.config.components)

async with lock:
cache = hass.data.setdefault(TRANSLATION_FLATTEN_CACHE, _TranslationCache(hass))
await cache.async_fetch(language, "entity", components_entities)
await cache.async_fetch(language, "states", components_state)
await cache.async_fetch(language, "entity_component", components_state)
cache = hass.data.setdefault(TRANSLATION_FLATTEN_CACHE, _TranslationCache(hass))
await cache.async_fetch(language, "entity", components_entities)
await cache.async_fetch(language, "states", components_state)
await cache.async_fetch(language, "entity_component", components_state)


@bind_hass
Expand All @@ -230,9 +259,7 @@ def get_cached_translations(

cache = hass.data.setdefault(TRANSLATION_FLATTEN_CACHE, _TranslationCache(hass))
# noinspection PyUnresolvedReferences
cached = cache.ct_patched_get_cached(language, category, components)

return dict(ChainMap(*cached))
return cache.ct_patched_get_cached(language, category, components)


# noinspection PyProtectedMember
Expand All @@ -254,12 +281,14 @@ async def load_translations(_event: Event):
translated_template = Translated(hass, languages)
all_translations_template = AllTranslations(hass, languages)
eval_template = EvalTemplate(hass)
is_available_template = IsAvailable(hass)

_TranslationCache.ct_patched_get_cached = get_cached

def is_safe_callable(self: TemplateEnvironment, obj):
# noinspection PyUnresolvedReferences
return (isinstance(obj, (StateTranslated, StateAttrTranslated, EvalTemplate, Translated, AllTranslations))
return (isinstance(obj, (
StateTranslated, StateAttrTranslated, EvalTemplate, Translated, AllTranslations, IsAvailable))
or self.ct_original_is_safe_callable(obj))

def patch_environment(env: TemplateEnvironment):
Expand All @@ -268,17 +297,19 @@ def patch_environment(env: TemplateEnvironment):
env.globals[CONST_TRANSLATED_FUNCTION_NAME] = translated_template
env.globals[CONST_ALL_TRANSLATIONS_FUNCTION_NAME] = all_translations_template
env.globals[CONST_EVAL_FUNCTION_NAME] = eval_template
env.globals[CONST_IS_AVAILABLE_FUNCTION_NAME] = is_available_template
env.filters[CONST_STATE_TRANSLATED_FUNCTION_NAME] = state_translated_template
env.filters[CONST_STATE_ATTR_TRANSLATED_FUNCTION_NAME] = state_attr_translated_template
env.filters[CONST_TRANSLATED_FUNCTION_NAME] = translated_template
env.filters[CONST_EVAL_FUNCTION_NAME] = eval_template
env.filters[CONST_IS_AVAILABLE_FUNCTION_NAME] = is_available_template

def patched_init(
self: TemplateEnvironment,
hass_param: HomeAssistant | None,
limited: bool | None = False,
strict: bool | None = False,
log_fn: Callable[[int, str], None] | None = None,
self: TemplateEnvironment,
hass_param: HomeAssistant | None,
limited: bool | None = False,
strict: bool | None = False,
log_fn: Callable[[int, str], None] | None = None,
):
# noinspection PyUnresolvedReferences
self.ct_original__init__(hass_param, limited, strict, log_fn)
Expand Down
8 changes: 8 additions & 0 deletions custom_components/custom_templates/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,16 @@
extra=vol.ALLOW_EXTRA
)

DEFAULT_UNAVAILABLE_STATES = [
'unknown',
'unavailable',
'',
None,
]

CONST_EVAL_FUNCTION_NAME = "ct_eval"
CONST_STATE_TRANSLATED_FUNCTION_NAME = "ct_state_translated"
CONST_STATE_ATTR_TRANSLATED_FUNCTION_NAME = "ct_state_attr_translated"
CONST_TRANSLATED_FUNCTION_NAME = "ct_translated"
CONST_ALL_TRANSLATIONS_FUNCTION_NAME = "ct_all_translations"
CONST_IS_AVAILABLE_FUNCTION_NAME = "ct_is_available"
2 changes: 1 addition & 1 deletion custom_components/custom_templates/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"iot_class": "calculated",
"issue_tracker": "https://github.com/PiotrMachowski/Home-Assistant-custom-components-Custom-Templates/issues",
"requirements": [],
"version": "v1.2.3"
"version": "v1.3.0"
}
3 changes: 2 additions & 1 deletion hacs.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"name": "Custom Templates",
"render_readme": true,
"zip_release": true,
"filename": "custom_templates.zip"
"filename": "custom_templates.zip",
"homeassistant": "2024.2.0b0"
}

0 comments on commit e0616ae

Please sign in to comment.