Home Assistant Unofficial Reference 2024.12.1
template.py
Go to the documentation of this file.
1 """Template helper methods for rendering strings with Home Assistant data."""
2 
3 from __future__ import annotations
4 
5 from ast import literal_eval
6 import asyncio
7 import base64
8 import collections.abc
9 from collections.abc import Callable, Generator, Iterable
10 from contextlib import AbstractContextManager
11 from contextvars import ContextVar
12 from copy import deepcopy
13 from datetime import date, datetime, time, timedelta
14 from functools import cache, lru_cache, partial, wraps
15 import json
16 import logging
17 import math
18 from operator import contains
19 import pathlib
20 import random
21 import re
22 import statistics
23 from struct import error as StructError, pack, unpack_from
24 import sys
25 from types import CodeType, TracebackType
26 from typing import Any, Concatenate, Literal, NoReturn, Self, cast, overload
27 from urllib.parse import urlencode as urllib_urlencode
28 import weakref
29 
30 from awesomeversion import AwesomeVersion
31 import jinja2
32 from jinja2 import pass_context, pass_environment, pass_eval_context
33 from jinja2.runtime import AsyncLoopContext, LoopContext
34 from jinja2.sandbox import ImmutableSandboxedEnvironment
35 from jinja2.utils import Namespace
36 from lru import LRU
37 import orjson
38 from propcache import under_cached_property
39 import voluptuous as vol
40 
41 from homeassistant.const import (
42  ATTR_ENTITY_ID,
43  ATTR_LATITUDE,
44  ATTR_LONGITUDE,
45  ATTR_PERSONS,
46  ATTR_UNIT_OF_MEASUREMENT,
47  EVENT_HOMEASSISTANT_START,
48  EVENT_HOMEASSISTANT_STOP,
49  STATE_UNAVAILABLE,
50  STATE_UNKNOWN,
51  UnitOfLength,
52 )
53 from homeassistant.core import (
54  Context,
55  HomeAssistant,
56  ServiceResponse,
57  State,
58  callback,
59  split_entity_id,
60  valid_domain,
61  valid_entity_id,
62 )
63 from homeassistant.exceptions import TemplateError
64 from homeassistant.loader import bind_hass
65 from homeassistant.util import (
66  convert,
67  dt as dt_util,
68  location as loc_util,
69  slugify as slugify_util,
70 )
71 from homeassistant.util.async_ import run_callback_threadsafe
72 from homeassistant.util.hass_dict import HassKey
73 from homeassistant.util.json import JSON_DECODE_EXCEPTIONS, json_loads
74 from homeassistant.util.read_only_dict import ReadOnlyDict
75 from homeassistant.util.thread import ThreadWithException
76 
77 from . import (
78  area_registry,
79  device_registry,
80  entity_registry,
81  floor_registry as fr,
82  issue_registry,
83  label_registry,
84  location as loc_helper,
85 )
86 from .deprecation import deprecated_function
87 from .singleton import singleton
88 from .translation import async_translate_state
89 from .typing import TemplateVarsType
90 
91 # mypy: allow-untyped-defs, no-check-untyped-defs
92 
93 _LOGGER = logging.getLogger(__name__)
94 _SENTINEL = object()
95 DATE_STR_FORMAT = "%Y-%m-%d %H:%M:%S"
96 
97 _ENVIRONMENT: HassKey[TemplateEnvironment] = HassKey("template.environment")
98 _ENVIRONMENT_LIMITED: HassKey[TemplateEnvironment] = HassKey(
99  "template.environment_limited"
100 )
101 _ENVIRONMENT_STRICT: HassKey[TemplateEnvironment] = HassKey(
102  "template.environment_strict"
103 )
104 _HASS_LOADER = "template.hass_loader"
105 
106 # Match "simple" ints and floats. -1.0, 1, +5, 5.0
107 _IS_NUMERIC = re.compile(r"^[+-]?(?!0\d)\d*(?:\.\d*)?$")
108 
109 _RESERVED_NAMES = {
110  "contextfunction",
111  "evalcontextfunction",
112  "environmentfunction",
113  "jinja_pass_arg",
114 }
115 
116 _COLLECTABLE_STATE_ATTRIBUTES = {
117  "state",
118  "attributes",
119  "last_changed",
120  "last_updated",
121  "context",
122  "domain",
123  "object_id",
124  "name",
125 }
126 
127 ALL_STATES_RATE_LIMIT = 60 # seconds
128 DOMAIN_STATES_RATE_LIMIT = 1 # seconds
129 
130 _render_info: ContextVar[RenderInfo | None] = ContextVar("_render_info", default=None)
131 
132 
133 template_cv: ContextVar[tuple[str, str] | None] = ContextVar(
134  "template_cv", default=None
135 )
136 
137 #
138 # CACHED_TEMPLATE_STATES is a rough estimate of the number of entities
139 # on a typical system. It is used as the initial size of the LRU cache
140 # for TemplateState objects.
141 #
142 # If the cache is too small we will end up creating and destroying
143 # TemplateState objects too often which will cause a lot of GC activity
144 # and slow down the system. For systems with a lot of entities and
145 # templates, this can reach 100000s of object creations and destructions
146 # per minute.
147 #
148 # Since entity counts may grow over time, we will increase
149 # the size if the number of entities grows via _async_adjust_lru_sizes
150 # at the start of the system and every 10 minutes if needed.
151 #
152 CACHED_TEMPLATE_STATES = 512
153 EVAL_CACHE_SIZE = 512
154 
155 MAX_CUSTOM_TEMPLATE_SIZE = 5 * 1024 * 1024
156 MAX_TEMPLATE_OUTPUT = 256 * 1024 # 256KiB
157 
158 CACHED_TEMPLATE_LRU: LRU[State, TemplateState] = LRU(CACHED_TEMPLATE_STATES)
159 CACHED_TEMPLATE_NO_COLLECT_LRU: LRU[State, TemplateState] = LRU(CACHED_TEMPLATE_STATES)
160 ENTITY_COUNT_GROWTH_FACTOR = 1.2
161 
162 ORJSON_PASSTHROUGH_OPTIONS = (
163  orjson.OPT_PASSTHROUGH_DATACLASS | orjson.OPT_PASSTHROUGH_DATETIME
164 )
165 
166 
167 def _template_state_no_collect(hass: HomeAssistant, state: State) -> TemplateState:
168  """Return a TemplateState for a state without collecting."""
169  if template_state := CACHED_TEMPLATE_NO_COLLECT_LRU.get(state):
170  return template_state
171  template_state = _create_template_state_no_collect(hass, state)
172  CACHED_TEMPLATE_NO_COLLECT_LRU[state] = template_state
173  return template_state
174 
175 
176 def _template_state(hass: HomeAssistant, state: State) -> TemplateState:
177  """Return a TemplateState for a state that collects."""
178  if template_state := CACHED_TEMPLATE_LRU.get(state):
179  return template_state
180  template_state = TemplateState(hass, state)
181  CACHED_TEMPLATE_LRU[state] = template_state
182  return template_state
183 
184 
185 def async_setup(hass: HomeAssistant) -> bool:
186  """Set up tracking the template LRUs."""
187 
188  @callback
189  def _async_adjust_lru_sizes(_: Any) -> None:
190  """Adjust the lru cache sizes."""
191  new_size = int(
192  round(hass.states.async_entity_ids_count() * ENTITY_COUNT_GROWTH_FACTOR)
193  )
194  for lru in (CACHED_TEMPLATE_LRU, CACHED_TEMPLATE_NO_COLLECT_LRU):
195  # There is no typing for LRU
196  current_size = lru.get_size()
197  if new_size > current_size:
198  lru.set_size(new_size)
199 
200  from .event import ( # pylint: disable=import-outside-toplevel
201  async_track_time_interval,
202  )
203 
204  cancel = async_track_time_interval(
205  hass, _async_adjust_lru_sizes, timedelta(minutes=10)
206  )
207  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _async_adjust_lru_sizes)
208  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, callback(lambda _: cancel()))
209  return True
210 
211 
212 @bind_hass
213 @deprecated_function( "automatic setting of Template.hass introduced by HA Core PR #89242", breaks_in_ha_version="2025.10", )
214 def attach(hass: HomeAssistant, obj: Any) -> None:
215  """Recursively attach hass to all template instances in list and dict."""
216  return _attach(hass, obj)
217 
218 
219 def _attach(hass: HomeAssistant, obj: Any) -> None:
220  """Recursively attach hass to all template instances in list and dict."""
221  if isinstance(obj, list):
222  for child in obj:
223  _attach(hass, child)
224  elif isinstance(obj, collections.abc.Mapping):
225  for child_key, child_value in obj.items():
226  _attach(hass, child_key)
227  _attach(hass, child_value)
228  elif isinstance(obj, Template):
229  obj.hass = hass
230 
231 
232 def render_complex(
233  value: Any,
234  variables: TemplateVarsType = None,
235  limited: bool = False,
236  parse_result: bool = True,
237 ) -> Any:
238  """Recursive template creator helper function."""
239  if isinstance(value, list):
240  return [
241  render_complex(item, variables, limited, parse_result) for item in value
242  ]
243  if isinstance(value, collections.abc.Mapping):
244  return {
245  render_complex(key, variables, limited, parse_result): render_complex(
246  item, variables, limited, parse_result
247  )
248  for key, item in value.items()
249  }
250  if isinstance(value, Template):
251  return value.async_render(variables, limited=limited, parse_result=parse_result)
252 
253  return value
254 
255 
256 def is_complex(value: Any) -> bool:
257  """Test if data structure is a complex template."""
258  if isinstance(value, Template):
259  return True
260  if isinstance(value, list):
261  return any(is_complex(val) for val in value)
262  if isinstance(value, collections.abc.Mapping):
263  return any(is_complex(val) for val in value) or any(
264  is_complex(val) for val in value.values()
265  )
266  return False
267 
268 
269 def is_template_string(maybe_template: str) -> bool:
270  """Check if the input is a Jinja2 template."""
271  return "{" in maybe_template and (
272  "{%" in maybe_template or "{{" in maybe_template or "{#" in maybe_template
273  )
274 
275 
276 class ResultWrapper:
277  """Result wrapper class to store render result."""
278 
279  render_result: str | None
280 
281 
282 def gen_result_wrapper(kls: type[dict | list | set]) -> type:
283  """Generate a result wrapper."""
284 
285  class Wrapper(kls, ResultWrapper): # type: ignore[valid-type,misc]
286  """Wrapper of a kls that can store render_result."""
287 
288  def __init__(self, *args: Any, render_result: str | None = None) -> None:
289  super().__init__(*args)
290  self.render_result = render_result
291 
292  def __str__(self) -> str:
293  if self.render_result is None:
294  # Can't get set repr to work
295  if kls is set:
296  return str(set(self))
297 
298  return kls.__str__(self)
299 
300  return self.render_result
301 
302  return Wrapper
303 
304 
306  """Wrap a tuple."""
307 
308  __slots__ = ()
309 
310  # This is all magic to be allowed to subclass a tuple.
311 
312  def __new__(cls, value: tuple, *, render_result: str | None = None) -> Self:
313  """Create a new tuple class."""
314  return super().__new__(cls, tuple(value))
315 
316  def __init__(self, value: tuple, *, render_result: str | None = None) -> None:
317  """Initialize a new tuple class."""
318  self.render_resultrender_result = render_result
319 
320  def __str__(self) -> str:
321  """Return string representation."""
322  if self.render_resultrender_result is None:
323  return super().__str__()
324 
325  return self.render_resultrender_result
326 
327 
328 _types: tuple[type[dict | list | set], ...] = (dict, list, set)
329 RESULT_WRAPPERS: dict[type, type] = {kls: gen_result_wrapper(kls) for kls in _types}
330 RESULT_WRAPPERS[tuple] = TupleWrapper
331 
332 
333 def _true(arg: str) -> bool:
334  return True
335 
336 
337 def _false(arg: str) -> bool:
338  return False
339 
340 
341 @lru_cache(maxsize=EVAL_CACHE_SIZE)
342 def _cached_parse_result(render_result: str) -> Any:
343  """Parse a result and cache the result."""
344  result = literal_eval(render_result)
345  if type(result) in RESULT_WRAPPERS:
346  result = RESULT_WRAPPERS[type(result)](result, render_result=render_result)
347 
348  # If the literal_eval result is a string, use the original
349  # render, by not returning right here. The evaluation of strings
350  # resulting in strings impacts quotes, to avoid unexpected
351  # output; use the original render instead of the evaluated one.
352  # Complex and scientific values are also unexpected. Filter them out.
353  if (
354  # Filter out string and complex numbers
355  not isinstance(result, (str, complex))
356  and (
357  # Pass if not numeric and not a boolean
358  not isinstance(result, (int, float))
359  # Or it's a boolean (inherit from int)
360  or isinstance(result, bool)
361  # Or if it's a digit
362  or _IS_NUMERIC.match(render_result) is not None
363  )
364  ):
365  return result
366 
367  return render_result
368 
369 
370 class RenderInfo:
371  """Holds information about a template render."""
372 
373  __slots__ = (
374  "template",
375  "filter_lifecycle",
376  "filter",
377  "_result",
378  "is_static",
379  "exception",
380  "all_states",
381  "all_states_lifecycle",
382  "domains",
383  "domains_lifecycle",
384  "entities",
385  "rate_limit",
386  "has_time",
387  )
388 
389  def __init__(self, template: Template) -> None:
390  """Initialise."""
391  self.templatetemplate = template
392  # Will be set sensibly once frozen.
393  self.filter_lifecyclefilter_lifecycle: Callable[[str], bool] = _true
394  self.filterfilter: Callable[[str], bool] = _true
395  self._result: str | None = None
396  self.is_staticis_static = False
397  self.exception: TemplateError | None = None
398  self.all_statesall_states = False
399  self.all_states_lifecycleall_states_lifecycle = False
400  self.domainsdomains: collections.abc.Set[str] = set()
401  self.domains_lifecycledomains_lifecycle: collections.abc.Set[str] = set()
402  self.entitiesentities: collections.abc.Set[str] = set()
403  self.rate_limitrate_limit: float | None = None
404  self.has_timehas_time = False
405 
406  def __repr__(self) -> str:
407  """Representation of RenderInfo."""
408  return (
409  f"<RenderInfo {self.template}"
410  f" all_states={self.all_states}"
411  f" all_states_lifecycle={self.all_states_lifecycle}"
412  f" domains={self.domains}"
413  f" domains_lifecycle={self.domains_lifecycle}"
414  f" entities={self.entities}"
415  f" rate_limit={self.rate_limit}"
416  f" has_time={self.has_time}"
417  f" exception={self.exception}"
418  f" is_static={self.is_static}"
419  ">"
420  )
421 
422  def _filter_domains_and_entities(self, entity_id: str) -> bool:
423  """Template should re-render if the entity state changes.
424 
425  Only when we match specific domains or entities.
426  """
427  return (
428  split_entity_id(entity_id)[0] in self.domainsdomains or entity_id in self.entitiesentities
429  )
430 
431  def _filter_entities(self, entity_id: str) -> bool:
432  """Template should re-render if the entity state changes.
433 
434  Only when we match specific entities.
435  """
436  return entity_id in self.entitiesentities
437 
438  def _filter_lifecycle_domains(self, entity_id: str) -> bool:
439  """Template should re-render if the entity is added or removed.
440 
441  Only with domains watched.
442  """
443  return split_entity_id(entity_id)[0] in self.domains_lifecycledomains_lifecycle
444 
445  def result(self) -> str:
446  """Results of the template computation."""
447  if self.exception is not None:
448  raise self.exception
449  return cast(str, self._result)
450 
451  def _freeze_static(self) -> None:
452  self.is_staticis_static = True
453  self._freeze_sets_freeze_sets()
454  self.all_statesall_states = False
455 
456  def _freeze_sets(self) -> None:
457  self.entitiesentities = frozenset(self.entitiesentities)
458  self.domainsdomains = frozenset(self.domainsdomains)
459  self.domains_lifecycledomains_lifecycle = frozenset(self.domains_lifecycledomains_lifecycle)
460 
461  def _freeze(self) -> None:
462  self._freeze_sets_freeze_sets()
463 
464  if self.rate_limitrate_limit is None:
465  if self.all_statesall_states or self.exception:
466  self.rate_limitrate_limit = ALL_STATES_RATE_LIMIT
467  elif self.domainsdomains or self.domains_lifecycledomains_lifecycle:
468  self.rate_limitrate_limit = DOMAIN_STATES_RATE_LIMIT
469 
470  if self.exception:
471  return
472 
473  if not self.all_states_lifecycleall_states_lifecycle:
474  if self.domains_lifecycledomains_lifecycle:
475  self.filter_lifecyclefilter_lifecycle = self._filter_lifecycle_domains_filter_lifecycle_domains
476  else:
477  self.filter_lifecyclefilter_lifecycle = _false
478 
479  if self.all_statesall_states:
480  return
481 
482  if self.domainsdomains:
483  self.filterfilter = self._filter_domains_and_entities_filter_domains_and_entities
484  elif self.entitiesentities:
485  self.filterfilter = self._filter_entities_filter_entities
486  else:
487  self.filterfilter = _false
488 
489 
490 class Template:
491  """Class to hold a template and manage caching and rendering."""
492 
493  __slots__ = (
494  "__weakref__",
495  "template",
496  "hass",
497  "is_static",
498  "_compiled_code",
499  "_compiled",
500  "_exc_info",
501  "_limited",
502  "_strict",
503  "_log_fn",
504  "_hash_cache",
505  "_renders",
506  )
507 
508  def __init__(self, template: str, hass: HomeAssistant | None = None) -> None:
509  """Instantiate a template.
510 
511  Note: A valid hass instance should always be passed in. The hass parameter
512  will be non optional in Home Assistant Core 2025.10.
513  """
514  # pylint: disable-next=import-outside-toplevel
515  from .frame import ReportBehavior, report_usage
516 
517  if not isinstance(template, str):
518  raise TypeError("Expected template to be a string")
519 
520  if not hass:
521  report_usage(
522  "creates a template object without passing hass",
523  core_behavior=ReportBehavior.LOG,
524  breaks_in_ha_version="2025.10",
525  )
526 
527  self.templatetemplate: str = template.strip()
528  self._compiled_code_compiled_code: CodeType | None = None
529  self._compiled_compiled: jinja2.Template | None = None
530  self.hasshass = hass
531  self.is_staticis_static = not is_template_string(template)
532  self._exc_info_exc_info: sys._OptExcInfo | None = None
533  self._limited_limited: bool | None = None
534  self._strict_strict: bool | None = None
535  self._log_fn_log_fn: Callable[[int, str], None] | None = None
536  self._hash_cache: int = hash(self.templatetemplate)
537  self._renders: int = 0
538 
539  @property
540  def _env(self) -> TemplateEnvironment:
541  if self.hasshass is None:
542  return _NO_HASS_ENV
543  # Bypass cache if a custom log function is specified
544  if self._log_fn_log_fn is not None:
545  return TemplateEnvironment(
546  self.hasshass, self._limited_limited, self._strict_strict, self._log_fn_log_fn
547  )
548  if self._limited_limited:
549  wanted_env = _ENVIRONMENT_LIMITED
550  elif self._strict_strict:
551  wanted_env = _ENVIRONMENT_STRICT
552  else:
553  wanted_env = _ENVIRONMENT
554  if (ret := self.hasshass.data.get(wanted_env)) is None:
555  ret = self.hasshass.data[wanted_env] = TemplateEnvironment(
556  self.hasshass, self._limited_limited, self._strict_strict, self._log_fn_log_fn
557  )
558  return ret
559 
560  def ensure_valid(self) -> None:
561  """Return if template is valid."""
562  if self.is_staticis_static or self._compiled_code_compiled_code is not None:
563  return
564 
565  if compiled := self._env_env.template_cache.get(self.templatetemplate):
566  self._compiled_code_compiled_code = compiled
567  return
568 
569  with _template_context_manager as cm:
570  cm.set_template(self.templatetemplate, "compiling")
571  try:
572  self._compiled_code_compiled_code = self._env_env.compile(self.templatetemplate)
573  except jinja2.TemplateError as err:
574  raise TemplateError(err) from err
575 
576  def render(
577  self,
578  variables: TemplateVarsType = None,
579  parse_result: bool = True,
580  limited: bool = False,
581  **kwargs: Any,
582  ) -> Any:
583  """Render given template.
584 
585  If limited is True, the template is not allowed to access any function
586  or filter depending on hass or the state machine.
587  """
588  if self.is_staticis_static:
589  if not parse_result or self.hasshass and self.hasshass.config.legacy_templates:
590  return self.templatetemplate
591  return self._parse_result_parse_result(self.templatetemplate)
592  assert self.hasshass is not None, "hass variable not set on template"
593  return run_callback_threadsafe(
594  self.hasshass.loop,
595  partial(self.async_renderasync_render, variables, parse_result, limited, **kwargs),
596  ).result()
597 
598  @callback
599  def async_render(
600  self,
601  variables: TemplateVarsType = None,
602  parse_result: bool = True,
603  limited: bool = False,
604  strict: bool = False,
605  log_fn: Callable[[int, str], None] | None = None,
606  **kwargs: Any,
607  ) -> Any:
608  """Render given template.
609 
610  This method must be run in the event loop.
611 
612  If limited is True, the template is not allowed to access any function
613  or filter depending on hass or the state machine.
614  """
615  self._renders += 1
616 
617  if self.is_staticis_static:
618  if not parse_result or self.hasshass and self.hasshass.config.legacy_templates:
619  return self.templatetemplate
620  return self._parse_result_parse_result(self.templatetemplate)
621 
622  compiled = self._compiled_compiled or self._ensure_compiled_ensure_compiled(limited, strict, log_fn)
623 
624  if variables is not None:
625  kwargs.update(variables)
626 
627  try:
628  render_result = _render_with_context(self.templatetemplate, compiled, **kwargs)
629  except Exception as err:
630  raise TemplateError(err) from err
631 
632  if len(render_result) > MAX_TEMPLATE_OUTPUT:
633  raise TemplateError(
634  f"Template output exceeded maximum size of {MAX_TEMPLATE_OUTPUT} characters"
635  )
636 
637  render_result = render_result.strip()
638 
639  if not parse_result or self.hasshass and self.hasshass.config.legacy_templates:
640  return render_result
641 
642  return self._parse_result_parse_result(render_result)
643 
644  def _parse_result(self, render_result: str) -> Any:
645  """Parse the result."""
646  try:
647  return _cached_parse_result(render_result)
648  except (ValueError, TypeError, SyntaxError, MemoryError):
649  pass
650 
651  return render_result
652 
653  async def async_render_will_timeout(
654  self,
655  timeout: float,
656  variables: TemplateVarsType = None,
657  strict: bool = False,
658  log_fn: Callable[[int, str], None] | None = None,
659  **kwargs: Any,
660  ) -> bool:
661  """Check to see if rendering a template will timeout during render.
662 
663  This is intended to check for expensive templates
664  that will make the system unstable. The template
665  is rendered in the executor to ensure it does not
666  tie up the event loop.
667 
668  This function is not a security control and is only
669  intended to be used as a safety check when testing
670  templates.
671 
672  This method must be run in the event loop.
673  """
674  self._renders += 1
675 
676  if self.is_staticis_static:
677  return False
678 
679  compiled = self._compiled_compiled or self._ensure_compiled_ensure_compiled(strict=strict, log_fn=log_fn)
680 
681  if variables is not None:
682  kwargs.update(variables)
683 
684  self._exc_info_exc_info = None
685  finish_event = asyncio.Event()
686 
687  def _render_template() -> None:
688  assert self.hasshass is not None, "hass variable not set on template"
689  try:
690  _render_with_context(self.templatetemplate, compiled, **kwargs)
691  except TimeoutError:
692  pass
693  except Exception: # noqa: BLE001
694  self._exc_info_exc_info = sys.exc_info()
695  finally:
696  self.hasshass.loop.call_soon_threadsafe(finish_event.set)
697 
698  try:
699  template_render_thread = ThreadWithException(target=_render_template)
700  template_render_thread.start()
701  async with asyncio.timeout(timeout):
702  await finish_event.wait()
703  if self._exc_info_exc_info:
704  raise TemplateError(self._exc_info_exc_info[1].with_traceback(self._exc_info_exc_info[2]))
705  except TimeoutError:
706  template_render_thread.raise_exc(TimeoutError)
707  return True
708  finally:
709  template_render_thread.join()
710 
711  return False
712 
713  @callback
715  self,
716  variables: TemplateVarsType = None,
717  strict: bool = False,
718  log_fn: Callable[[int, str], None] | None = None,
719  **kwargs: Any,
720  ) -> RenderInfo:
721  """Render the template and collect an entity filter."""
722  if self.hasshass and self.hasshass.config.debug:
723  self.hasshass.verify_event_loop_thread("async_render_to_info")
724  self._renders += 1
725 
726  render_info = RenderInfo(self)
727 
728  if not self.hasshass:
729  raise RuntimeError(f"hass not set while rendering {self}")
730 
731  if _render_info.get() is not None:
732  raise RuntimeError(
733  f"RenderInfo already set while rendering {self}, "
734  "this usually indicates the template is being rendered "
735  "in the wrong thread"
736  )
737 
738  if self.is_staticis_static:
739  render_info._result = self.templatetemplate.strip() # noqa: SLF001
740  render_info._freeze_static() # noqa: SLF001
741  return render_info
742 
743  token = _render_info.set(render_info)
744  try:
745  render_info._result = self.async_renderasync_render( # noqa: SLF001
746  variables, strict=strict, log_fn=log_fn, **kwargs
747  )
748  except TemplateError as ex:
749  render_info.exception = ex
750  finally:
751  _render_info.reset(token)
752 
753  render_info._freeze() # noqa: SLF001
754  return render_info
755 
756  def render_with_possible_json_value(self, value, error_value=_SENTINEL):
757  """Render template with value exposed.
758 
759  If valid JSON will expose value_json too.
760  """
761  if self.is_staticis_static:
762  return self.templatetemplate
763 
764  return run_callback_threadsafe(
765  self.hasshass.loop,
766  self.async_render_with_possible_json_valueasync_render_with_possible_json_value,
767  value,
768  error_value,
769  ).result()
770 
771  @callback
773  self,
774  value: Any,
775  error_value: Any = _SENTINEL,
776  variables: dict[str, Any] | None = None,
777  parse_result: bool = False,
778  ) -> Any:
779  """Render template with value exposed.
780 
781  If valid JSON will expose value_json too.
782 
783  This method must be run in the event loop.
784  """
785  self._renders += 1
786 
787  if self.is_staticis_static:
788  return self.templatetemplate
789 
790  compiled = self._compiled_compiled or self._ensure_compiled_ensure_compiled()
791 
792  variables = dict(variables or {})
793  variables["value"] = value
794 
795  try: # noqa: SIM105 - suppress is much slower
796  variables["value_json"] = json_loads(value)
797  except JSON_DECODE_EXCEPTIONS:
798  pass
799 
800  try:
801  render_result = _render_with_context(
802  self.templatetemplate, compiled, **variables
803  ).strip()
804  except jinja2.TemplateError as ex:
805  if error_value is _SENTINEL:
806  _LOGGER.error(
807  "Error parsing value: %s (value: %s, template: %s)",
808  ex,
809  value,
810  self.templatetemplate,
811  )
812  return value if error_value is _SENTINEL else error_value
813 
814  if not parse_result or self.hasshass and self.hasshass.config.legacy_templates:
815  return render_result
816 
817  return self._parse_result_parse_result(render_result)
818 
819  def _ensure_compiled(
820  self,
821  limited: bool = False,
822  strict: bool = False,
823  log_fn: Callable[[int, str], None] | None = None,
824  ) -> jinja2.Template:
825  """Bind a template to a specific hass instance."""
826  self.ensure_validensure_valid()
827 
828  assert self.hasshass is not None, "hass variable not set on template"
829  assert (
830  self._limited_limited is None or self._limited_limited == limited
831  ), "can't change between limited and non limited template"
832  assert (
833  self._strict_strict is None or self._strict_strict == strict
834  ), "can't change between strict and non strict template"
835  assert not (strict and limited), "can't combine strict and limited template"
836  assert (
837  self._log_fn_log_fn is None or self._log_fn_log_fn == log_fn
838  ), "can't change custom log function"
839  assert self._compiled_code_compiled_code is not None, "template code was not compiled"
840 
841  self._limited_limited = limited
842  self._strict_strict = strict
843  self._log_fn_log_fn = log_fn
844  env = self._env_env
845 
846  self._compiled_compiled = jinja2.Template.from_code(
847  env, self._compiled_code_compiled_code, env.globals, None
848  )
849 
850  return self._compiled_compiled
851 
852  def __eq__(self, other):
853  """Compare template with another."""
854  return (
855  self.__class____class__ == other.__class__
856  and self.templatetemplate == other.template
857  and self.hasshass == other.hass
858  )
859 
860  def __hash__(self) -> int:
861  """Hash code for template."""
862  return self._hash_cache
863 
864  def __repr__(self) -> str:
865  """Representation of Template."""
866  return f"Template<template=({self.template}) renders={self._renders}>"
867 
868 
869 @cache
870 def _domain_states(hass: HomeAssistant, name: str) -> DomainStates:
871  return DomainStates(hass, name)
872 
873 
874 def _readonly(*args: Any, **kwargs: Any) -> Any:
875  """Raise an exception when a states object is modified."""
876  raise RuntimeError(f"Cannot modify template States object: {args} {kwargs}")
877 
878 
879 class AllStates:
880  """Class to expose all HA states as attributes."""
881 
882  __setitem__ = _readonly
883  __delitem__ = _readonly
884  __slots__ = ("_hass",)
885 
886  def __init__(self, hass: HomeAssistant) -> None:
887  """Initialize all states."""
888  self._hass_hass = hass
889 
890  def __getattr__(self, name):
891  """Return the domain state."""
892  if "." in name:
893  return _get_state_if_valid(self._hass_hass, name)
894 
895  if name in _RESERVED_NAMES:
896  return None
897 
898  if not valid_domain(name):
899  raise TemplateError(f"Invalid domain name '{name}'")
900 
901  return _domain_states(self._hass_hass, name)
902 
903  # Jinja will try __getitem__ first and it avoids the need
904  # to call is_safe_attribute
905  __getitem__ = __getattr__
906 
907  def _collect_all(self) -> None:
908  if (render_info := _render_info.get()) is not None:
909  render_info.all_states = True
910 
911  def _collect_all_lifecycle(self) -> None:
912  if (render_info := _render_info.get()) is not None:
913  render_info.all_states_lifecycle = True
914 
915  def __iter__(self) -> Generator[TemplateState]:
916  """Return all states."""
917  self._collect_all_collect_all()
918  return _state_generator(self._hass_hass, None)
919 
920  def __len__(self) -> int:
921  """Return number of states."""
922  self._collect_all_lifecycle_collect_all_lifecycle()
923  return self._hass_hass.states.async_entity_ids_count()
924 
925  def __call__(
926  self,
927  entity_id: str,
928  rounded: bool | object = _SENTINEL,
929  with_unit: bool = False,
930  ) -> str:
931  """Return the states."""
932  state = _get_state(self._hass_hass, entity_id)
933  if state is None:
934  return STATE_UNKNOWN
935  if rounded is _SENTINEL:
936  rounded = with_unit
937  if rounded or with_unit:
938  return state.format_state(rounded, with_unit) # type: ignore[arg-type]
939  return state.state
940 
941  def __repr__(self) -> str:
942  """Representation of All States."""
943  return "<template AllStates>"
944 
945 
946 class StateTranslated:
947  """Class to represent a translated state in a template."""
948 
949  def __init__(self, hass: HomeAssistant) -> None:
950  """Initialize all states."""
951  self._hass_hass = hass
952 
953  def __call__(self, entity_id: str) -> str | None:
954  """Retrieve translated state if available."""
955  state = _get_state_if_valid(self._hass_hass, entity_id)
956 
957  if state is None:
958  return STATE_UNKNOWN
959 
960  state_value = state.state
961  domain = state.domain
962  device_class = state.attributes.get("device_class")
963  entry = entity_registry.async_get(self._hass_hass).async_get(entity_id)
964  platform = None if entry is None else entry.platform
965  translation_key = None if entry is None else entry.translation_key
966 
967  return async_translate_state(
968  self._hass_hass, state_value, domain, platform, translation_key, device_class
969  )
970 
971  def __repr__(self) -> str:
972  """Representation of Translated state."""
973  return "<template StateTranslated>"
974 
975 
976 class DomainStates:
977  """Class to expose a specific HA domain as attributes."""
978 
979  __slots__ = ("_hass", "_domain")
980 
981  __setitem__ = _readonly
982  __delitem__ = _readonly
983 
984  def __init__(self, hass: HomeAssistant, domain: str) -> None:
985  """Initialize the domain states."""
986  self._hass_hass = hass
987  self._domain_domain = domain
988 
989  def __getattr__(self, name: str) -> TemplateState | None:
990  """Return the states."""
991  return _get_state_if_valid(self._hass_hass, f"{self._domain}.{name}")
992 
993  # Jinja will try __getitem__ first and it avoids the need
994  # to call is_safe_attribute
995  __getitem__ = __getattr__
996 
997  def _collect_domain(self) -> None:
998  if (entity_collect := _render_info.get()) is not None:
999  entity_collect.domains.add(self._domain_domain) # type: ignore[attr-defined]
1001  def _collect_domain_lifecycle(self) -> None:
1002  if (entity_collect := _render_info.get()) is not None:
1003  entity_collect.domains_lifecycle.add(self._domain_domain) # type: ignore[attr-defined]
1005  def __iter__(self) -> Generator[TemplateState]:
1006  """Return the iteration over all the states."""
1007  self._collect_domain_collect_domain()
1008  return _state_generator(self._hass_hass, self._domain_domain)
1009 
1010  def __len__(self) -> int:
1011  """Return number of states."""
1012  self._collect_domain_lifecycle_collect_domain_lifecycle()
1013  return self._hass_hass.states.async_entity_ids_count(self._domain_domain)
1014 
1015  def __repr__(self) -> str:
1016  """Representation of Domain States."""
1017  return f"<template DomainStates('{self._domain}')>"
1019 
1020 class TemplateStateBase(State):
1021  """Class to represent a state object in a template."""
1022 
1023  __slots__ = ("_hass", "_collect", "_entity_id", "_state")
1024 
1025  _state: State
1027  __setitem__ = _readonly
1028  __delitem__ = _readonly
1029 
1030  # Inheritance is done so functions that check against State keep working
1031  # pylint: disable-next=super-init-not-called
1032  def __init__(self, hass: HomeAssistant, collect: bool, entity_id: str) -> None:
1033  """Initialize template state."""
1034  self._hass_hass = hass
1035  self._collect_collect = collect
1036  self._entity_id_entity_id = entity_id
1037  self._cache: dict[str, Any] = {}
1039  def _collect_state(self) -> None:
1040  if self._collect_collect and (render_info := _render_info.get()):
1041  render_info.entities.add(self._entity_id_entity_id) # type: ignore[attr-defined]
1043  # Jinja will try __getitem__ first and it avoids the need
1044  # to call is_safe_attribute
1045  def __getitem__(self, item: str) -> Any:
1046  """Return a property as an attribute for jinja."""
1047  if item in _COLLECTABLE_STATE_ATTRIBUTES:
1048  # _collect_state inlined here for performance
1049  if self._collect_collect and (render_info := _render_info.get()):
1050  render_info.entities.add(self._entity_id_entity_id) # type: ignore[attr-defined]
1051  return getattr(self._state, item)
1052  if item == "entity_id":
1053  return self._entity_id_entity_id
1054  if item == "state_with_unit":
1055  return self.state_with_unitstate_with_unit
1056  raise KeyError
1057 
1058  @under_cached_property
1059  def entity_id(self) -> str: # type: ignore[override]
1060  """Wrap State.entity_id.
1061 
1062  Intentionally does not collect state
1063  """
1064  return self._entity_id_entity_id
1065 
1066  @property
1067  def state(self) -> str: # type: ignore[override]
1068  """Wrap State.state."""
1069  self._collect_state_collect_state()
1070  return self._state.state
1071 
1072  @property
1073  def attributes(self) -> ReadOnlyDict[str, Any]: # type: ignore[override]
1074  """Wrap State.attributes."""
1075  self._collect_state_collect_state()
1076  return self._state.attributes
1077 
1078  @property
1079  def last_changed(self) -> datetime: # type: ignore[override]
1080  """Wrap State.last_changed."""
1081  self._collect_state_collect_state()
1082  return self._state.last_changed
1083 
1084  @property
1085  def last_reported(self) -> datetime: # type: ignore[override]
1086  """Wrap State.last_reported."""
1087  self._collect_state_collect_state()
1088  return self._state.last_reported
1089 
1090  @property
1091  def last_updated(self) -> datetime: # type: ignore[override]
1092  """Wrap State.last_updated."""
1093  self._collect_state_collect_state()
1094  return self._state.last_updated
1095 
1096  @property
1097  def context(self) -> Context: # type: ignore[override]
1098  """Wrap State.context."""
1099  self._collect_state_collect_state()
1100  return self._state.context
1101 
1102  @property
1103  def domain(self) -> str: # type: ignore[override]
1104  """Wrap State.domain."""
1105  self._collect_state_collect_state()
1106  return self._state.domain
1107 
1108  @property
1109  def object_id(self) -> str: # type: ignore[override]
1110  """Wrap State.object_id."""
1111  self._collect_state_collect_state()
1112  return self._state.object_id
1113 
1114  @property
1115  def name(self) -> str: # type: ignore[override]
1116  """Wrap State.name."""
1117  self._collect_state_collect_state()
1118  return self._state.name
1119 
1120  @property
1121  def state_with_unit(self) -> str:
1122  """Return the state concatenated with the unit if available."""
1123  return self.format_stateformat_state(rounded=True, with_unit=True)
1125  def format_state(self, rounded: bool, with_unit: bool) -> str:
1126  """Return a formatted version of the state."""
1127  # Import here, not at top-level, to avoid circular import
1128  # pylint: disable-next=import-outside-toplevel
1129  from homeassistant.components.sensor import (
1130  DOMAIN as SENSOR_DOMAIN,
1131  async_rounded_state,
1132  )
1133 
1134  self._collect_state_collect_state()
1135  if rounded and self._state.domain == SENSOR_DOMAIN:
1136  state = async_rounded_state(self._hass_hass, self._entity_id_entity_id, self._state)
1137  else:
1138  state = self._state.state
1139  if with_unit and (unit := self._state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)):
1140  return f"{state} {unit}"
1141  return state
1142 
1143  def __eq__(self, other: object) -> bool:
1144  """Ensure we collect on equality check."""
1145  self._collect_state_collect_state()
1146  return self._state.__eq__(other)
1147 
1148 
1150  """Class to represent a state object in a template."""
1151 
1152  __slots__ = ()
1153 
1154  # Inheritance is done so functions that check against State keep working
1155  def __init__(self, hass: HomeAssistant, state: State, collect: bool = True) -> None:
1156  """Initialize template state."""
1157  super().__init__(hass, collect, state.entity_id)
1158  self._state_state = state
1159 
1160  def __repr__(self) -> str:
1161  """Representation of Template State."""
1162  return f"<template TemplateState({self._state!r})>"
1164 
1166  """Class to represent a state object in a template."""
1167 
1168  __slots__ = ()
1169 
1170  def __init__(
1171  self, hass: HomeAssistant, entity_id: str, collect: bool = True
1172  ) -> None:
1173  """Initialize template state."""
1174  super().__init__(hass, collect, entity_id)
1175 
1176  @property
1177  def _state(self) -> State: # type: ignore[override]
1178  state = self._hass_hass.states.get(self._entity_id_entity_id)
1179  if not state:
1180  state = State(self._entity_id_entity_id, STATE_UNKNOWN)
1181  return state
1182 
1183  def __repr__(self) -> str:
1184  """Representation of Template State."""
1185  return f"<template TemplateStateFromEntityId({self._entity_id})>"
1187 
1188 _create_template_state_no_collect = partial(TemplateState, collect=False)
1189 
1190 
1191 def _collect_state(hass: HomeAssistant, entity_id: str) -> None:
1192  if (entity_collect := _render_info.get()) is not None:
1193  entity_collect.entities.add(entity_id) # type: ignore[attr-defined]
1195 
1196 def _state_generator(
1197  hass: HomeAssistant, domain: str | None
1198 ) -> Generator[TemplateState]:
1199  """State generator for a domain or all states."""
1200  states = hass.states
1201  # If domain is None, we want to iterate over all states, but making
1202  # a copy of the dict is expensive. So we iterate over the protected
1203  # _states dict instead. This is safe because we're not modifying it
1204  # and everything is happening in the same thread (MainThread).
1205  #
1206  # We do not want to expose this method in the public API though to
1207  # ensure it does not get misused.
1208  #
1209  container: Iterable[State]
1210  if domain is None:
1211  container = states._states.values() # noqa: SLF001
1212  else:
1213  container = states.async_all(domain)
1214  for state in container:
1215  yield _template_state_no_collect(hass, state)
1216 
1217 
1218 def _get_state_if_valid(hass: HomeAssistant, entity_id: str) -> TemplateState | None:
1219  state = hass.states.get(entity_id)
1220  if state is None and not valid_entity_id(entity_id):
1221  raise TemplateError(f"Invalid entity ID '{entity_id}'")
1222  return _get_template_state_from_state(hass, entity_id, state)
1223 
1224 
1225 def _get_state(hass: HomeAssistant, entity_id: str) -> TemplateState | None:
1226  return _get_template_state_from_state(hass, entity_id, hass.states.get(entity_id))
1227 
1230  hass: HomeAssistant, entity_id: str, state: State | None
1231 ) -> TemplateState | None:
1232  if state is None:
1233  # Only need to collect if none, if not none collect first actual
1234  # access to the state properties in the state wrapper.
1235  _collect_state(hass, entity_id)
1236  return None
1237  return _template_state(hass, state)
1238 
1239 
1240 def _resolve_state(
1241  hass: HomeAssistant, entity_id_or_state: Any
1242 ) -> State | TemplateState | None:
1243  """Return state or entity_id if given."""
1244  if isinstance(entity_id_or_state, State):
1245  return entity_id_or_state
1246  if isinstance(entity_id_or_state, str):
1247  return _get_state(hass, entity_id_or_state)
1248  return None
1249 
1250 
1251 @overload
1252 def forgiving_boolean(value: Any) -> bool | object: ...
1253 
1254 
1255 @overload
1256 def forgiving_boolean[_T](value: Any, default: _T) -> bool | _T: ...
1257 
1258 
1260  value: Any, default: _T | object = _SENTINEL
1261 ) -> bool | _T | object:
1262  """Try to convert value to a boolean."""
1263  try:
1264  # Import here, not at top-level to avoid circular import
1265  from . import config_validation as cv # pylint: disable=import-outside-toplevel
1266 
1267  return cv.boolean(value)
1268  except vol.Invalid:
1269  if default is _SENTINEL:
1270  raise_no_default("bool", value)
1271  return default
1272 
1273 
1274 def result_as_boolean(template_result: Any | None) -> bool:
1275  """Convert the template result to a boolean.
1276 
1277  True/not 0/'1'/'true'/'yes'/'on'/'enable' are considered truthy
1278  False/0/None/'0'/'false'/'no'/'off'/'disable' are considered falsy
1279  All other values are falsy
1280  """
1281  if template_result is None:
1282  return False
1283 
1284  return forgiving_boolean(template_result, default=False)
1285 
1286 
1287 def expand(hass: HomeAssistant, *args: Any) -> Iterable[State]:
1288  """Expand out any groups and zones into entity states."""
1289  # circular import.
1290  from . import entity as entity_helper # pylint: disable=import-outside-toplevel
1291 
1292  search = list(args)
1293  found = {}
1294  sources = entity_helper.entity_sources(hass)
1295  while search:
1296  entity = search.pop()
1297  if isinstance(entity, str):
1298  entity_id = entity
1299  if (entity := _get_state(hass, entity)) is None:
1300  continue
1301  elif isinstance(entity, State):
1302  entity_id = entity.entity_id
1303  elif isinstance(entity, collections.abc.Iterable):
1304  search += entity
1305  continue
1306  else:
1307  # ignore other types
1308  continue
1309 
1310  if entity_id in found:
1311  continue
1312 
1313  domain = entity.domain
1314  if domain == "group" or (
1315  (source := sources.get(entity_id)) and source["domain"] == "group"
1316  ):
1317  # Collect state will be called in here since it's wrapped
1318  if group_entities := entity.attributes.get(ATTR_ENTITY_ID):
1319  search += group_entities
1320  elif domain == "zone":
1321  if zone_entities := entity.attributes.get(ATTR_PERSONS):
1322  search += zone_entities
1323  else:
1324  _collect_state(hass, entity_id)
1325  found[entity_id] = entity
1326 
1327  return list(found.values())
1328 
1329 
1330 def device_entities(hass: HomeAssistant, _device_id: str) -> Iterable[str]:
1331  """Get entity ids for entities tied to a device."""
1332  entity_reg = entity_registry.async_get(hass)
1333  entries = entity_registry.async_entries_for_device(entity_reg, _device_id)
1334  return [entry.entity_id for entry in entries]
1335 
1336 
1337 def integration_entities(hass: HomeAssistant, entry_name: str) -> Iterable[str]:
1338  """Get entity ids for entities tied to an integration/domain.
1339 
1340  Provide entry_name as domain to get all entity id's for a integration/domain
1341  or provide a config entry title for filtering between instances of the same
1342  integration.
1343  """
1344 
1345  # Don't allow searching for config entries without title
1346  if not entry_name:
1347  return []
1348 
1349  # first try if there are any config entries with a matching title
1350  entities: list[str] = []
1351  ent_reg = entity_registry.async_get(hass)
1352  for entry in hass.config_entries.async_entries():
1353  if entry.title != entry_name:
1354  continue
1355  entries = entity_registry.async_entries_for_config_entry(
1356  ent_reg, entry.entry_id
1357  )
1358  entities.extend(entry.entity_id for entry in entries)
1359  if entities:
1360  return entities
1361 
1362  # fallback to just returning all entities for a domain
1363  # pylint: disable-next=import-outside-toplevel
1364  from .entity import entity_sources
1365 
1366  return [
1367  entity_id
1368  for entity_id, info in entity_sources(hass).items()
1369  if info["domain"] == entry_name
1370  ]
1371 
1372 
1373 def config_entry_id(hass: HomeAssistant, entity_id: str) -> str | None:
1374  """Get an config entry ID from an entity ID."""
1375  entity_reg = entity_registry.async_get(hass)
1376  if entity := entity_reg.async_get(entity_id):
1377  return entity.config_entry_id
1378  return None
1379 
1380 
1381 def device_id(hass: HomeAssistant, entity_id_or_device_name: str) -> str | None:
1382  """Get a device ID from an entity ID or device name."""
1383  entity_reg = entity_registry.async_get(hass)
1384  entity = entity_reg.async_get(entity_id_or_device_name)
1385  if entity is not None:
1386  return entity.device_id
1387 
1388  dev_reg = device_registry.async_get(hass)
1389  return next(
1390  (
1391  device_id
1392  for device_id, device in dev_reg.devices.items()
1393  if (name := device.name_by_user or device.name)
1394  and (str(entity_id_or_device_name) == name)
1395  ),
1396  None,
1397  )
1398 
1399 
1400 def device_attr(hass: HomeAssistant, device_or_entity_id: str, attr_name: str) -> Any:
1401  """Get the device specific attribute."""
1402  device_reg = device_registry.async_get(hass)
1403  if not isinstance(device_or_entity_id, str):
1404  raise TemplateError("Must provide a device or entity ID")
1405  device = None
1406  if (
1407  "." in device_or_entity_id
1408  and (_device_id := device_id(hass, device_or_entity_id)) is not None
1409  ):
1410  device = device_reg.async_get(_device_id)
1411  elif "." not in device_or_entity_id:
1412  device = device_reg.async_get(device_or_entity_id)
1413  if device is None or not hasattr(device, attr_name):
1414  return None
1415  return getattr(device, attr_name)
1416 
1417 
1418 def config_entry_attr(
1419  hass: HomeAssistant, config_entry_id_: str, attr_name: str
1420 ) -> Any:
1421  """Get config entry specific attribute."""
1422  if not isinstance(config_entry_id_, str):
1423  raise TemplateError("Must provide a config entry ID")
1424 
1425  if attr_name not in ("domain", "title", "state", "source", "disabled_by"):
1426  raise TemplateError("Invalid config entry attribute")
1427 
1428  config_entry = hass.config_entries.async_get_entry(config_entry_id_)
1429 
1430  if config_entry is None:
1431  return None
1432 
1433  return getattr(config_entry, attr_name)
1434 
1435 
1436 def is_device_attr(
1437  hass: HomeAssistant, device_or_entity_id: str, attr_name: str, attr_value: Any
1438 ) -> bool:
1439  """Test if a device's attribute is a specific value."""
1440  return bool(device_attr(hass, device_or_entity_id, attr_name) == attr_value)
1441 
1442 
1443 def issues(hass: HomeAssistant) -> dict[tuple[str, str], dict[str, Any]]:
1444  """Return all open issues."""
1445  current_issues = issue_registry.async_get(hass).issues
1446  # Use JSON for safe representation
1447  return {k: v.to_json() for (k, v) in current_issues.items()}
1448 
1449 
1450 def issue(hass: HomeAssistant, domain: str, issue_id: str) -> dict[str, Any] | None:
1451  """Get issue by domain and issue_id."""
1452  result = issue_registry.async_get(hass).async_get_issue(domain, issue_id)
1453  if result:
1454  return result.to_json()
1455  return None
1456 
1457 
1458 def floors(hass: HomeAssistant) -> Iterable[str | None]:
1459  """Return all floors."""
1460  floor_registry = fr.async_get(hass)
1461  return [floor.floor_id for floor in floor_registry.async_list_floors()]
1462 
1463 
1464 def floor_id(hass: HomeAssistant, lookup_value: Any) -> str | None:
1465  """Get the floor ID from a floor name."""
1466  floor_registry = fr.async_get(hass)
1467  if floor := floor_registry.async_get_floor_by_name(str(lookup_value)):
1468  return floor.floor_id
1469 
1470  if aid := area_id(hass, lookup_value):
1471  area_reg = area_registry.async_get(hass)
1472  if area := area_reg.async_get_area(aid):
1473  return area.floor_id
1474 
1475  return None
1476 
1477 
1478 def floor_name(hass: HomeAssistant, lookup_value: str) -> str | None:
1479  """Get the floor name from a floor id."""
1480  floor_registry = fr.async_get(hass)
1481  if floor := floor_registry.async_get_floor(lookup_value):
1482  return floor.name
1483 
1484  if aid := area_id(hass, lookup_value):
1485  area_reg = area_registry.async_get(hass)
1486  if (
1487  (area := area_reg.async_get_area(aid))
1488  and area.floor_id
1489  and (floor := floor_registry.async_get_floor(area.floor_id))
1490  ):
1491  return floor.name
1492 
1493  return None
1494 
1495 
1496 def floor_areas(hass: HomeAssistant, floor_id_or_name: str) -> Iterable[str]:
1497  """Return area IDs for a given floor ID or name."""
1498  _floor_id: str | None
1499  # If floor_name returns a value, we know the input was an ID, otherwise we
1500  # assume it's a name, and if it's neither, we return early
1501  if floor_name(hass, floor_id_or_name) is not None:
1502  _floor_id = floor_id_or_name
1503  else:
1504  _floor_id = floor_id(hass, floor_id_or_name)
1505  if _floor_id is None:
1506  return []
1507 
1508  area_reg = area_registry.async_get(hass)
1509  entries = area_registry.async_entries_for_floor(area_reg, _floor_id)
1510  return [entry.id for entry in entries if entry.id]
1511 
1512 
1513 def areas(hass: HomeAssistant) -> Iterable[str | None]:
1514  """Return all areas."""
1515  return list(area_registry.async_get(hass).areas)
1517 
1518 def area_id(hass: HomeAssistant, lookup_value: str) -> str | None:
1519  """Get the area ID from an area name, device id, or entity id."""
1520  area_reg = area_registry.async_get(hass)
1521  if area := area_reg.async_get_area_by_name(str(lookup_value)):
1522  return area.id
1523 
1524  ent_reg = entity_registry.async_get(hass)
1525  dev_reg = device_registry.async_get(hass)
1526  # Import here, not at top-level to avoid circular import
1527  from . import config_validation as cv # pylint: disable=import-outside-toplevel
1528 
1529  try:
1530  cv.entity_id(lookup_value)
1531  except vol.Invalid:
1532  pass
1533  else:
1534  if entity := ent_reg.async_get(lookup_value):
1535  # If entity has an area ID, return that
1536  if entity.area_id:
1537  return entity.area_id
1538  # If entity has a device ID, return the area ID for the device
1539  if entity.device_id and (device := dev_reg.async_get(entity.device_id)):
1540  return device.area_id
1541 
1542  # Check if this could be a device ID
1543  if device := dev_reg.async_get(lookup_value):
1544  return device.area_id
1545 
1546  return None
1547 
1548 
1549 def _get_area_name(area_reg: area_registry.AreaRegistry, valid_area_id: str) -> str:
1550  """Get area name from valid area ID."""
1551  area = area_reg.async_get_area(valid_area_id)
1552  assert area
1553  return area.name
1554 
1555 
1556 def area_name(hass: HomeAssistant, lookup_value: str) -> str | None:
1557  """Get the area name from an area id, device id, or entity id."""
1558  area_reg = area_registry.async_get(hass)
1559  if area := area_reg.async_get_area(lookup_value):
1560  return area.name
1561 
1562  dev_reg = device_registry.async_get(hass)
1563  ent_reg = entity_registry.async_get(hass)
1564  # Import here, not at top-level to avoid circular import
1565  from . import config_validation as cv # pylint: disable=import-outside-toplevel
1566 
1567  try:
1568  cv.entity_id(lookup_value)
1569  except vol.Invalid:
1570  pass
1571  else:
1572  if entity := ent_reg.async_get(lookup_value):
1573  # If entity has an area ID, get the area name for that
1574  if entity.area_id:
1575  return _get_area_name(area_reg, entity.area_id)
1576  # If entity has a device ID and the device exists with an area ID, get the
1577  # area name for that
1578  if (
1579  entity.device_id
1580  and (device := dev_reg.async_get(entity.device_id))
1581  and device.area_id
1582  ):
1583  return _get_area_name(area_reg, device.area_id)
1584 
1585  if (device := dev_reg.async_get(lookup_value)) and device.area_id:
1586  return _get_area_name(area_reg, device.area_id)
1587 
1588  return None
1589 
1590 
1591 def area_entities(hass: HomeAssistant, area_id_or_name: str) -> Iterable[str]:
1592  """Return entities for a given area ID or name."""
1593  _area_id: str | None
1594  # if area_name returns a value, we know the input was an ID, otherwise we
1595  # assume it's a name, and if it's neither, we return early
1596  if area_name(hass, area_id_or_name) is None:
1597  _area_id = area_id(hass, area_id_or_name)
1598  else:
1599  _area_id = area_id_or_name
1600  if _area_id is None:
1601  return []
1602  ent_reg = entity_registry.async_get(hass)
1603  entity_ids = [
1604  entry.entity_id
1605  for entry in entity_registry.async_entries_for_area(ent_reg, _area_id)
1606  ]
1607  dev_reg = device_registry.async_get(hass)
1608  # We also need to add entities tied to a device in the area that don't themselves
1609  # have an area specified since they inherit the area from the device.
1610  entity_ids.extend(
1611  [
1612  entity.entity_id
1613  for device in device_registry.async_entries_for_area(dev_reg, _area_id)
1614  for entity in entity_registry.async_entries_for_device(ent_reg, device.id)
1615  if entity.area_id is None
1616  ]
1617  )
1618  return entity_ids
1619 
1620 
1621 def area_devices(hass: HomeAssistant, area_id_or_name: str) -> Iterable[str]:
1622  """Return device IDs for a given area ID or name."""
1623  _area_id: str | None
1624  # if area_name returns a value, we know the input was an ID, otherwise we
1625  # assume it's a name, and if it's neither, we return early
1626  if area_name(hass, area_id_or_name) is not None:
1627  _area_id = area_id_or_name
1628  else:
1629  _area_id = area_id(hass, area_id_or_name)
1630  if _area_id is None:
1631  return []
1632  dev_reg = device_registry.async_get(hass)
1633  entries = device_registry.async_entries_for_area(dev_reg, _area_id)
1634  return [entry.id for entry in entries]
1635 
1636 
1637 def labels(hass: HomeAssistant, lookup_value: Any = None) -> Iterable[str | None]:
1638  """Return all labels, or those from a area ID, device ID, or entity ID."""
1639  label_reg = label_registry.async_get(hass)
1640  if lookup_value is None:
1641  return list(label_reg.labels)
1642 
1643  ent_reg = entity_registry.async_get(hass)
1644 
1645  # Import here, not at top-level to avoid circular import
1646  from . import config_validation as cv # pylint: disable=import-outside-toplevel
1647 
1648  lookup_value = str(lookup_value)
1649 
1650  try:
1651  cv.entity_id(lookup_value)
1652  except vol.Invalid:
1653  pass
1654  else:
1655  if entity := ent_reg.async_get(lookup_value):
1656  return list(entity.labels)
1657 
1658  # Check if this could be a device ID
1659  dev_reg = device_registry.async_get(hass)
1660  if device := dev_reg.async_get(lookup_value):
1661  return list(device.labels)
1662 
1663  # Check if this could be a area ID
1664  area_reg = area_registry.async_get(hass)
1665  if area := area_reg.async_get_area(lookup_value):
1666  return list(area.labels)
1667 
1668  return []
1669 
1670 
1671 def label_id(hass: HomeAssistant, lookup_value: Any) -> str | None:
1672  """Get the label ID from a label name."""
1673  label_reg = label_registry.async_get(hass)
1674  if label := label_reg.async_get_label_by_name(str(lookup_value)):
1675  return label.label_id
1676  return None
1677 
1678 
1679 def label_name(hass: HomeAssistant, lookup_value: str) -> str | None:
1680  """Get the label name from a label ID."""
1681  label_reg = label_registry.async_get(hass)
1682  if label := label_reg.async_get_label(lookup_value):
1683  return label.name
1684  return None
1685 
1686 
1687 def _label_id_or_name(hass: HomeAssistant, label_id_or_name: str) -> str | None:
1688  """Get the label ID from a label name or ID."""
1689  # If label_name returns a value, we know the input was an ID, otherwise we
1690  # assume it's a name, and if it's neither, we return early.
1691  if label_name(hass, label_id_or_name) is not None:
1692  return label_id_or_name
1693  return label_id(hass, label_id_or_name)
1694 
1695 
1696 def label_areas(hass: HomeAssistant, label_id_or_name: str) -> Iterable[str]:
1697  """Return areas for a given label ID or name."""
1698  if (_label_id := _label_id_or_name(hass, label_id_or_name)) is None:
1699  return []
1700  area_reg = area_registry.async_get(hass)
1701  entries = area_registry.async_entries_for_label(area_reg, _label_id)
1702  return [entry.id for entry in entries]
1703 
1704 
1705 def label_devices(hass: HomeAssistant, label_id_or_name: str) -> Iterable[str]:
1706  """Return device IDs for a given label ID or name."""
1707  if (_label_id := _label_id_or_name(hass, label_id_or_name)) is None:
1708  return []
1709  dev_reg = device_registry.async_get(hass)
1710  entries = device_registry.async_entries_for_label(dev_reg, _label_id)
1711  return [entry.id for entry in entries]
1712 
1713 
1714 def label_entities(hass: HomeAssistant, label_id_or_name: str) -> Iterable[str]:
1715  """Return entities for a given label ID or name."""
1716  if (_label_id := _label_id_or_name(hass, label_id_or_name)) is None:
1717  return []
1718  ent_reg = entity_registry.async_get(hass)
1719  entries = entity_registry.async_entries_for_label(ent_reg, _label_id)
1720  return [entry.entity_id for entry in entries]
1721 
1722 
1723 def closest(hass, *args):
1724  """Find closest entity.
1725 
1726  Closest to home:
1727  closest(states)
1728  closest(states.device_tracker)
1729  closest('group.children')
1730  closest(states.group.children)
1731 
1732  Closest to a point:
1733  closest(23.456, 23.456, 'group.children')
1734  closest('zone.school', 'group.children')
1735  closest(states.zone.school, 'group.children')
1736 
1737  As a filter:
1738  states | closest
1739  states.device_tracker | closest
1740  ['group.children', states.device_tracker] | closest
1741  'group.children' | closest(23.456, 23.456)
1742  states.device_tracker | closest('zone.school')
1743  'group.children' | closest(states.zone.school)
1744 
1745  """
1746  if len(args) == 1:
1747  latitude = hass.config.latitude
1748  longitude = hass.config.longitude
1749  entities = args[0]
1750 
1751  elif len(args) == 2:
1752  point_state = _resolve_state(hass, args[0])
1753 
1754  if point_state is None:
1755  _LOGGER.warning("Closest:Unable to find state %s", args[0])
1756  return None
1757  if not loc_helper.has_location(point_state):
1758  _LOGGER.warning(
1759  "Closest:State does not contain valid location: %s", point_state
1760  )
1761  return None
1762 
1763  latitude = point_state.attributes.get(ATTR_LATITUDE)
1764  longitude = point_state.attributes.get(ATTR_LONGITUDE)
1765 
1766  entities = args[1]
1767 
1768  else:
1769  latitude = convert(args[0], float)
1770  longitude = convert(args[1], float)
1771 
1772  if latitude is None or longitude is None:
1773  _LOGGER.warning(
1774  "Closest:Received invalid coordinates: %s, %s", args[0], args[1]
1775  )
1776  return None
1777 
1778  entities = args[2]
1779 
1780  states = expand(hass, entities)
1781 
1782  # state will already be wrapped here
1783  return loc_helper.closest(latitude, longitude, states)
1784 
1785 
1786 def closest_filter(hass, *args):
1787  """Call closest as a filter. Need to reorder arguments."""
1788  new_args = list(args[1:])
1789  new_args.append(args[0])
1790  return closest(hass, *new_args)
1791 
1792 
1793 def distance(hass, *args):
1794  """Calculate distance.
1795 
1796  Will calculate distance from home to a point or between points.
1797  Points can be passed in using state objects or lat/lng coordinates.
1798  """
1799  locations = []
1800 
1801  to_process = list(args)
1802 
1803  while to_process:
1804  value = to_process.pop(0)
1805  if isinstance(value, str) and not valid_entity_id(value):
1806  point_state = None
1807  else:
1808  point_state = _resolve_state(hass, value)
1809 
1810  if point_state is None:
1811  # We expect this and next value to be lat&lng
1812  if not to_process:
1813  _LOGGER.warning(
1814  "Distance:Expected latitude and longitude, got %s", value
1815  )
1816  return None
1817 
1818  value_2 = to_process.pop(0)
1819  latitude = convert(value, float)
1820  longitude = convert(value_2, float)
1821 
1822  if latitude is None or longitude is None:
1823  _LOGGER.warning(
1824  "Distance:Unable to process latitude and longitude: %s, %s",
1825  value,
1826  value_2,
1827  )
1828  return None
1829 
1830  else:
1831  if not loc_helper.has_location(point_state):
1832  _LOGGER.warning(
1833  "Distance:State does not contain valid location: %s", point_state
1834  )
1835  return None
1836 
1837  latitude = point_state.attributes.get(ATTR_LATITUDE)
1838  longitude = point_state.attributes.get(ATTR_LONGITUDE)
1839 
1840  locations.append((latitude, longitude))
1841 
1842  if len(locations) == 1:
1843  return hass.config.distance(*locations[0])
1844 
1845  return hass.config.units.length(
1846  loc_util.distance(*locations[0] + locations[1]), UnitOfLength.METERS
1847  )
1848 
1849 
1850 def is_hidden_entity(hass: HomeAssistant, entity_id: str) -> bool:
1851  """Test if an entity is hidden."""
1852  entity_reg = entity_registry.async_get(hass)
1853  entry = entity_reg.async_get(entity_id)
1854  return entry is not None and entry.hidden
1855 
1856 
1857 def is_state(hass: HomeAssistant, entity_id: str, state: str | list[str]) -> bool:
1858  """Test if a state is a specific value."""
1859  state_obj = _get_state(hass, entity_id)
1860  return state_obj is not None and (
1861  state_obj.state == state or isinstance(state, list) and state_obj.state in state
1862  )
1863 
1864 
1865 def is_state_attr(hass: HomeAssistant, entity_id: str, name: str, value: Any) -> bool:
1866  """Test if a state's attribute is a specific value."""
1867  attr = state_attr(hass, entity_id, name)
1868  return attr is not None and attr == value
1869 
1870 
1871 def state_attr(hass: HomeAssistant, entity_id: str, name: str) -> Any:
1872  """Get a specific attribute from a state."""
1873  if (state_obj := _get_state(hass, entity_id)) is not None:
1874  return state_obj.attributes.get(name)
1875  return None
1876 
1877 
1878 def has_value(hass: HomeAssistant, entity_id: str) -> bool:
1879  """Test if an entity has a valid value."""
1880  state_obj = _get_state(hass, entity_id)
1882  return state_obj is not None and (
1883  state_obj.state not in [STATE_UNAVAILABLE, STATE_UNKNOWN]
1884  )
1885 
1886 
1887 def now(hass: HomeAssistant) -> datetime:
1888  """Record fetching now."""
1889  if (render_info := _render_info.get()) is not None:
1890  render_info.has_time = True
1891 
1892  return dt_util.now()
1893 
1894 
1895 def utcnow(hass: HomeAssistant) -> datetime:
1896  """Record fetching utcnow."""
1897  if (render_info := _render_info.get()) is not None:
1898  render_info.has_time = True
1899 
1900  return dt_util.utcnow()
1901 
1902 
1903 def raise_no_default(function: str, value: Any) -> NoReturn:
1904  """Log warning if no default is specified."""
1905  template, action = template_cv.get() or ("", "rendering or compiling")
1906  raise ValueError(
1907  f"Template error: {function} got invalid input '{value}' when {action} template"
1908  f" '{template}' but no default was specified"
1909  )
1910 
1911 
1912 def forgiving_round(value, precision=0, method="common", default=_SENTINEL):
1913  """Filter to round a value."""
1914  try:
1915  # support rounding methods like jinja
1916  multiplier = float(10**precision)
1917  if method == "ceil":
1918  value = math.ceil(float(value) * multiplier) / multiplier
1919  elif method == "floor":
1920  value = math.floor(float(value) * multiplier) / multiplier
1921  elif method == "half":
1922  value = round(float(value) * 2) / 2
1923  else:
1924  # if method is common or something else, use common rounding
1925  value = round(float(value), precision)
1926  return int(value) if precision == 0 else value
1927  except (ValueError, TypeError):
1928  # If value can't be converted to float
1929  if default is _SENTINEL:
1930  raise_no_default("round", value)
1931  return default
1932 
1933 
1934 def multiply(value, amount, default=_SENTINEL):
1935  """Filter to convert value to float and multiply it."""
1936  try:
1937  return float(value) * amount
1938  except (ValueError, TypeError):
1939  # If value can't be converted to float
1940  if default is _SENTINEL:
1941  raise_no_default("multiply", value)
1942  return default
1943 
1944 
1945 def add(value, amount, default=_SENTINEL):
1946  """Filter to convert value to float and add it."""
1947  try:
1948  return float(value) + amount
1949  except (ValueError, TypeError):
1950  # If value can't be converted to float
1951  if default is _SENTINEL:
1952  raise_no_default("add", value)
1953  return default
1954 
1955 
1956 def logarithm(value, base=math.e, default=_SENTINEL):
1957  """Filter and function to get logarithm of the value with a specific base."""
1958  try:
1959  base_float = float(base)
1960  except (ValueError, TypeError):
1961  if default is _SENTINEL:
1962  raise_no_default("log", base)
1963  return default
1964  try:
1965  value_float = float(value)
1966  return math.log(value_float, base_float)
1967  except (ValueError, TypeError):
1968  if default is _SENTINEL:
1969  raise_no_default("log", value)
1970  return default
1971 
1972 
1973 def sine(value, default=_SENTINEL):
1974  """Filter and function to get sine of the value."""
1975  try:
1976  return math.sin(float(value))
1977  except (ValueError, TypeError):
1978  if default is _SENTINEL:
1979  raise_no_default("sin", value)
1980  return default
1981 
1982 
1983 def cosine(value, default=_SENTINEL):
1984  """Filter and function to get cosine of the value."""
1985  try:
1986  return math.cos(float(value))
1987  except (ValueError, TypeError):
1988  if default is _SENTINEL:
1989  raise_no_default("cos", value)
1990  return default
1991 
1992 
1993 def tangent(value, default=_SENTINEL):
1994  """Filter and function to get tangent of the value."""
1995  try:
1996  return math.tan(float(value))
1997  except (ValueError, TypeError):
1998  if default is _SENTINEL:
1999  raise_no_default("tan", value)
2000  return default
2001 
2002 
2003 def arc_sine(value, default=_SENTINEL):
2004  """Filter and function to get arc sine of the value."""
2005  try:
2006  return math.asin(float(value))
2007  except (ValueError, TypeError):
2008  if default is _SENTINEL:
2009  raise_no_default("asin", value)
2010  return default
2011 
2012 
2013 def arc_cosine(value, default=_SENTINEL):
2014  """Filter and function to get arc cosine of the value."""
2015  try:
2016  return math.acos(float(value))
2017  except (ValueError, TypeError):
2018  if default is _SENTINEL:
2019  raise_no_default("acos", value)
2020  return default
2021 
2022 
2023 def arc_tangent(value, default=_SENTINEL):
2024  """Filter and function to get arc tangent of the value."""
2025  try:
2026  return math.atan(float(value))
2027  except (ValueError, TypeError):
2028  if default is _SENTINEL:
2029  raise_no_default("atan", value)
2030  return default
2031 
2032 
2033 def arc_tangent2(*args, default=_SENTINEL):
2034  """Filter and function to calculate four quadrant arc tangent of y / x.
2035 
2036  The parameters to atan2 may be passed either in an iterable or as separate arguments
2037  The default value may be passed either as a positional or in a keyword argument
2038  """
2039  try:
2040  if 1 <= len(args) <= 2 and isinstance(args[0], (list, tuple)):
2041  if len(args) == 2 and default is _SENTINEL:
2042  # Default value passed as a positional argument
2043  default = args[1]
2044  args = args[0]
2045  elif len(args) == 3 and default is _SENTINEL:
2046  # Default value passed as a positional argument
2047  default = args[2]
2048 
2049  return math.atan2(float(args[0]), float(args[1]))
2050  except (ValueError, TypeError):
2051  if default is _SENTINEL:
2052  raise_no_default("atan2", args)
2053  return default
2054 
2055 
2056 def version(value):
2057  """Filter and function to get version object of the value."""
2058  return AwesomeVersion(value)
2060 
2061 def square_root(value, default=_SENTINEL):
2062  """Filter and function to get square root of the value."""
2063  try:
2064  return math.sqrt(float(value))
2065  except (ValueError, TypeError):
2066  if default is _SENTINEL:
2067  raise_no_default("sqrt", value)
2068  return default
2069 
2070 
2071 def timestamp_custom(value, date_format=DATE_STR_FORMAT, local=True, default=_SENTINEL):
2072  """Filter to convert given timestamp to format."""
2073  try:
2074  result = dt_util.utc_from_timestamp(value)
2075 
2076  if local:
2077  result = dt_util.as_local(result)
2078 
2079  return result.strftime(date_format)
2080  except (ValueError, TypeError):
2081  # If timestamp can't be converted
2082  if default is _SENTINEL:
2083  raise_no_default("timestamp_custom", value)
2084  return default
2085 
2086 
2087 def timestamp_local(value, default=_SENTINEL):
2088  """Filter to convert given timestamp to local date/time."""
2089  try:
2090  return dt_util.as_local(dt_util.utc_from_timestamp(value)).isoformat()
2091  except (ValueError, TypeError):
2092  # If timestamp can't be converted
2093  if default is _SENTINEL:
2094  raise_no_default("timestamp_local", value)
2095  return default
2096 
2097 
2098 def timestamp_utc(value, default=_SENTINEL):
2099  """Filter to convert given timestamp to UTC date/time."""
2100  try:
2101  return dt_util.utc_from_timestamp(value).isoformat()
2102  except (ValueError, TypeError):
2103  # If timestamp can't be converted
2104  if default is _SENTINEL:
2105  raise_no_default("timestamp_utc", value)
2106  return default
2107 
2108 
2109 def forgiving_as_timestamp(value, default=_SENTINEL):
2110  """Filter and function which tries to convert value to timestamp."""
2111  try:
2112  return dt_util.as_timestamp(value)
2113  except (ValueError, TypeError):
2114  if default is _SENTINEL:
2115  raise_no_default("as_timestamp", value)
2116  return default
2117 
2118 
2119 def as_datetime(value: Any, default: Any = _SENTINEL) -> Any:
2120  """Filter and to convert a time string or UNIX timestamp to datetime object."""
2121  # Return datetime.datetime object without changes
2122  if type(value) is datetime:
2123  return value
2124  # Add midnight to datetime.date object
2125  if type(value) is date:
2126  return datetime.combine(value, time(0, 0, 0))
2127  try:
2128  # Check for a valid UNIX timestamp string, int or float
2129  timestamp = float(value)
2130  return dt_util.utc_from_timestamp(timestamp)
2131  except (ValueError, TypeError):
2132  # Try to parse datetime string to datetime object
2133  try:
2134  return dt_util.parse_datetime(value, raise_on_error=True)
2135  except (ValueError, TypeError):
2136  if default is _SENTINEL:
2137  # Return None on string input
2138  # to ensure backwards compatibility with HA Core 2024.1 and before.
2139  if isinstance(value, str):
2140  return None
2141  raise_no_default("as_datetime", value)
2142  return default
2143 
2144 
2145 def as_timedelta(value: str) -> timedelta | None:
2146  """Parse a ISO8601 duration like 'PT10M' to a timedelta."""
2147  return dt_util.parse_duration(value)
2149 
2150 def merge_response(value: ServiceResponse) -> list[Any]:
2151  """Merge action responses into single list.
2152 
2153  Checks that the input is a correct service response:
2154  {
2155  "entity_id": {str: dict[str, Any]},
2156  }
2157  If response is a single list, it will extend the list with the items
2158  and add the entity_id and value_key to each dictionary for reference.
2159  If response is a dictionary or multiple lists,
2160  it will append the dictionary/lists to the list
2161  and add the entity_id to each dictionary for reference.
2162  """
2163  if not isinstance(value, dict):
2164  raise TypeError("Response is not a dictionary")
2165  if not value:
2166  # Bail out early if response is an empty dictionary
2167  return []
2168 
2169  is_single_list = False
2170  response_items: list = []
2171  input_service_response = deepcopy(value)
2172  for entity_id, entity_response in input_service_response.items(): # pylint: disable=too-many-nested-blocks
2173  if not isinstance(entity_response, dict):
2174  raise TypeError("Response is not a dictionary")
2175  for value_key, type_response in entity_response.items():
2176  if len(entity_response) == 1 and isinstance(type_response, list):
2177  # Provides special handling for responses such as calendar events
2178  # and weather forecasts where the response contains a single list with multiple
2179  # dictionaries inside.
2180  is_single_list = True
2181  for dict_in_list in type_response:
2182  if isinstance(dict_in_list, dict):
2183  if ATTR_ENTITY_ID in dict_in_list:
2184  raise ValueError(
2185  f"Response dictionary already contains key '{ATTR_ENTITY_ID}'"
2186  )
2187  dict_in_list[ATTR_ENTITY_ID] = entity_id
2188  dict_in_list["value_key"] = value_key
2189  response_items.extend(type_response)
2190  else:
2191  # Break the loop if not a single list as the logic is then managed in the outer loop
2192  # which handles both dictionaries and in the case of multiple lists.
2193  break
2194 
2195  if not is_single_list:
2196  _response = entity_response.copy()
2197  if ATTR_ENTITY_ID in _response:
2198  raise ValueError(
2199  f"Response dictionary already contains key '{ATTR_ENTITY_ID}'"
2200  )
2201  _response[ATTR_ENTITY_ID] = entity_id
2202  response_items.append(_response)
2203 
2204  return response_items
2205 
2206 
2207 def strptime(string, fmt, default=_SENTINEL):
2208  """Parse a time string to datetime."""
2209  try:
2210  return datetime.strptime(string, fmt)
2211  except (ValueError, AttributeError, TypeError):
2212  if default is _SENTINEL:
2213  raise_no_default("strptime", string)
2214  return default
2215 
2216 
2217 def fail_when_undefined(value):
2218  """Filter to force a failure when the value is undefined."""
2219  if isinstance(value, jinja2.Undefined):
2220  value()
2221  return value
2222 
2223 
2224 def min_max_from_filter(builtin_filter: Any, name: str) -> Any:
2225  """Convert a built-in min/max Jinja filter to a global function.
2226 
2227  The parameters may be passed as an iterable or as separate arguments.
2228  """
2229 
2230  @pass_environment
2231  @wraps(builtin_filter)
2232  def wrapper(environment: jinja2.Environment, *args: Any, **kwargs: Any) -> Any:
2233  if len(args) == 0:
2234  raise TypeError(f"{name} expected at least 1 argument, got 0")
2235 
2236  if len(args) == 1:
2237  if isinstance(args[0], Iterable):
2238  return builtin_filter(environment, args[0], **kwargs)
2239 
2240  raise TypeError(f"'{type(args[0]).__name__}' object is not iterable")
2241 
2242  return builtin_filter(environment, args, **kwargs)
2243 
2244  return pass_environment(wrapper)
2245 
2246 
2247 def average(*args: Any, default: Any = _SENTINEL) -> Any:
2248  """Filter and function to calculate the arithmetic mean.
2249 
2250  Calculates of an iterable or of two or more arguments.
2251 
2252  The parameters may be passed as an iterable or as separate arguments.
2253  """
2254  if len(args) == 0:
2255  raise TypeError("average expected at least 1 argument, got 0")
2256 
2257  # If first argument is iterable and more than 1 argument provided but not a named
2258  # default, then use 2nd argument as default.
2259  if isinstance(args[0], Iterable):
2260  average_list = args[0]
2261  if len(args) > 1 and default is _SENTINEL:
2262  default = args[1]
2263  elif len(args) == 1:
2264  raise TypeError(f"'{type(args[0]).__name__}' object is not iterable")
2265  else:
2266  average_list = args
2267 
2268  try:
2269  return statistics.fmean(average_list)
2270  except (TypeError, statistics.StatisticsError):
2271  if default is _SENTINEL:
2272  raise_no_default("average", args)
2273  return default
2274 
2275 
2276 def median(*args: Any, default: Any = _SENTINEL) -> Any:
2277  """Filter and function to calculate the median.
2278 
2279  Calculates median of an iterable of two or more arguments.
2280 
2281  The parameters may be passed as an iterable or as separate arguments.
2282  """
2283  if len(args) == 0:
2284  raise TypeError("median expected at least 1 argument, got 0")
2285 
2286  # If first argument is a list or tuple and more than 1 argument provided but not a named
2287  # default, then use 2nd argument as default.
2288  if isinstance(args[0], Iterable):
2289  median_list = args[0]
2290  if len(args) > 1 and default is _SENTINEL:
2291  default = args[1]
2292  elif len(args) == 1:
2293  raise TypeError(f"'{type(args[0]).__name__}' object is not iterable")
2294  else:
2295  median_list = args
2296 
2297  try:
2298  return statistics.median(median_list)
2299  except (TypeError, statistics.StatisticsError):
2300  if default is _SENTINEL:
2301  raise_no_default("median", args)
2302  return default
2303 
2304 
2305 def statistical_mode(*args: Any, default: Any = _SENTINEL) -> Any:
2306  """Filter and function to calculate the statistical mode.
2307 
2308  Calculates mode of an iterable of two or more arguments.
2309 
2310  The parameters may be passed as an iterable or as separate arguments.
2311  """
2312  if not args:
2313  raise TypeError("statistical_mode expected at least 1 argument, got 0")
2314 
2315  # If first argument is a list or tuple and more than 1 argument provided but not a named
2316  # default, then use 2nd argument as default.
2317  if len(args) == 1 and isinstance(args[0], Iterable):
2318  mode_list = args[0]
2319  elif isinstance(args[0], list | tuple):
2320  mode_list = args[0]
2321  if len(args) > 1 and default is _SENTINEL:
2322  default = args[1]
2323  elif len(args) == 1:
2324  raise TypeError(f"'{type(args[0]).__name__}' object is not iterable")
2325  else:
2326  mode_list = args
2327 
2328  try:
2329  return statistics.mode(mode_list)
2330  except (TypeError, statistics.StatisticsError):
2331  if default is _SENTINEL:
2332  raise_no_default("statistical_mode", args)
2333  return default
2334 
2335 
2336 def forgiving_float(value, default=_SENTINEL):
2337  """Try to convert value to a float."""
2338  try:
2339  return float(value)
2340  except (ValueError, TypeError):
2341  if default is _SENTINEL:
2342  raise_no_default("float", value)
2343  return default
2344 
2345 
2346 def forgiving_float_filter(value, default=_SENTINEL):
2347  """Try to convert value to a float."""
2348  try:
2349  return float(value)
2350  except (ValueError, TypeError):
2351  if default is _SENTINEL:
2352  raise_no_default("float", value)
2353  return default
2354 
2355 
2356 def forgiving_int(value, default=_SENTINEL, base=10):
2357  """Try to convert value to an int, and raise if it fails."""
2358  result = jinja2.filters.do_int(value, default=default, base=base)
2359  if result is _SENTINEL:
2360  raise_no_default("int", value)
2361  return result
2362 
2363 
2364 def forgiving_int_filter(value, default=_SENTINEL, base=10):
2365  """Try to convert value to an int, and raise if it fails."""
2366  result = jinja2.filters.do_int(value, default=default, base=base)
2367  if result is _SENTINEL:
2368  raise_no_default("int", value)
2369  return result
2370 
2371 
2372 def is_number(value):
2373  """Try to convert value to a float."""
2374  try:
2375  fvalue = float(value)
2376  except (ValueError, TypeError):
2377  return False
2378  if not math.isfinite(fvalue):
2379  return False
2380  return True
2381 
2382 
2383 def _is_list(value: Any) -> bool:
2384  """Return whether a value is a list."""
2385  return isinstance(value, list)
2387 
2388 def _is_set(value: Any) -> bool:
2389  """Return whether a value is a set."""
2390  return isinstance(value, set)
2392 
2393 def _is_tuple(value: Any) -> bool:
2394  """Return whether a value is a tuple."""
2395  return isinstance(value, tuple)
2397 
2398 def _to_set(value: Any) -> set[Any]:
2399  """Convert value to set."""
2400  return set(value)
2402 
2403 def _to_tuple(value):
2404  """Convert value to tuple."""
2405  return tuple(value)
2407 
2408 def _is_datetime(value: Any) -> bool:
2409  """Return whether a value is a datetime."""
2410  return isinstance(value, datetime)
2412 
2413 def _is_string_like(value: Any) -> bool:
2414  """Return whether a value is a string or string like object."""
2415  return isinstance(value, (str, bytes, bytearray))
2417 
2418 def regex_match(value, find="", ignorecase=False):
2419  """Match value using regex."""
2420  if not isinstance(value, str):
2421  value = str(value)
2422  flags = re.IGNORECASE if ignorecase else 0
2423  return bool(_regex_cache(find, flags).match(value))
2424 
2425 
2426 _regex_cache = lru_cache(maxsize=128)(re.compile)
2427 
2428 
2429 def regex_replace(value="", find="", replace="", ignorecase=False):
2430  """Replace using regex."""
2431  if not isinstance(value, str):
2432  value = str(value)
2433  flags = re.IGNORECASE if ignorecase else 0
2434  return _regex_cache(find, flags).sub(replace, value)
2435 
2436 
2437 def regex_search(value, find="", ignorecase=False):
2438  """Search using regex."""
2439  if not isinstance(value, str):
2440  value = str(value)
2441  flags = re.IGNORECASE if ignorecase else 0
2442  return bool(_regex_cache(find, flags).search(value))
2443 
2444 
2445 def regex_findall_index(value, find="", index=0, ignorecase=False):
2446  """Find all matches using regex and then pick specific match index."""
2447  return regex_findall(value, find, ignorecase)[index]
2449 
2450 def regex_findall(value, find="", ignorecase=False):
2451  """Find all matches using regex."""
2452  if not isinstance(value, str):
2453  value = str(value)
2454  flags = re.IGNORECASE if ignorecase else 0
2455  return _regex_cache(find, flags).findall(value)
2456 
2457 
2458 def bitwise_and(first_value, second_value):
2459  """Perform a bitwise and operation."""
2460  return first_value & second_value
2462 
2463 def bitwise_or(first_value, second_value):
2464  """Perform a bitwise or operation."""
2465  return first_value | second_value
2467 
2468 def bitwise_xor(first_value, second_value):
2469  """Perform a bitwise xor operation."""
2470  return first_value ^ second_value
2472 
2473 def struct_pack(value: Any | None, format_string: str) -> bytes | None:
2474  """Pack an object into a bytes object."""
2475  try:
2476  return pack(format_string, value)
2477  except StructError:
2478  _LOGGER.warning(
2479  (
2480  "Template warning: 'pack' unable to pack object '%s' with type '%s' and"
2481  " format_string '%s' see https://docs.python.org/3/library/struct.html"
2482  " for more information"
2483  ),
2484  str(value),
2485  type(value).__name__,
2486  format_string,
2487  )
2488  return None
2489 
2490 
2491 def struct_unpack(value: bytes, format_string: str, offset: int = 0) -> Any | None:
2492  """Unpack an object from bytes an return the first native object."""
2493  try:
2494  return unpack_from(format_string, value, offset)[0]
2495  except StructError:
2496  _LOGGER.warning(
2497  (
2498  "Template warning: 'unpack' unable to unpack object '%s' with"
2499  " format_string '%s' and offset %s see"
2500  " https://docs.python.org/3/library/struct.html for more information"
2501  ),
2502  value,
2503  format_string,
2504  offset,
2505  )
2506  return None
2507 
2508 
2509 def base64_encode(value: str) -> str:
2510  """Perform base64 encode."""
2511  return base64.b64encode(value.encode("utf-8")).decode("utf-8")
2513 
2514 def base64_decode(value: str, encoding: str | None = "utf-8") -> str | bytes:
2515  """Perform base64 decode."""
2516  decoded = base64.b64decode(value)
2517  if encoding:
2518  return decoded.decode(encoding)
2519 
2520  return decoded
2521 
2522 
2523 def ordinal(value):
2524  """Perform ordinal conversion."""
2525  suffixes = ["th", "st", "nd", "rd"] + ["th"] * 6 # codespell:ignore nd
2526  return str(value) + (
2527  suffixes[(int(str(value)[-1])) % 10]
2528  if int(str(value)[-2:]) % 100 not in range(11, 14)
2529  else "th"
2530  )
2531 
2532 
2533 def from_json(value):
2534  """Convert a JSON string to an object."""
2535  return json_loads(value)
2537 
2538 def _to_json_default(obj: Any) -> None:
2539  """Disable custom types in json serialization."""
2540  raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
2542 
2543 def to_json(
2544  value: Any,
2545  ensure_ascii: bool = False,
2546  pretty_print: bool = False,
2547  sort_keys: bool = False,
2548 ) -> str:
2549  """Convert an object to a JSON string."""
2550  if ensure_ascii:
2551  # For those who need ascii, we can't use orjson, so we fall back to the json library.
2552  return json.dumps(
2553  value,
2554  ensure_ascii=ensure_ascii,
2555  indent=2 if pretty_print else None,
2556  sort_keys=sort_keys,
2557  )
2558 
2559  option = (
2560  ORJSON_PASSTHROUGH_OPTIONS
2561  # OPT_NON_STR_KEYS is added as a workaround to
2562  # ensure subclasses of str are allowed as dict keys
2563  # See: https://github.com/ijl/orjson/issues/445
2564  | orjson.OPT_NON_STR_KEYS
2565  | (orjson.OPT_INDENT_2 if pretty_print else 0)
2566  | (orjson.OPT_SORT_KEYS if sort_keys else 0)
2567  )
2568 
2569  return orjson.dumps(
2570  value,
2571  option=option,
2572  default=_to_json_default,
2573  ).decode("utf-8")
2574 
2575 
2576 @pass_context
2577 def random_every_time(context, values):
2578  """Choose a random value.
2579 
2580  Unlike Jinja's random filter,
2581  this is context-dependent to avoid caching the chosen value.
2582  """
2583  return random.choice(values)
2584 
2585 
2586 def today_at(hass: HomeAssistant, time_str: str = "") -> datetime:
2587  """Record fetching now where the time has been replaced with value."""
2588  if (render_info := _render_info.get()) is not None:
2589  render_info.has_time = True
2590 
2591  today = dt_util.start_of_local_day()
2592  if not time_str:
2593  return today
2594 
2595  if (time_today := dt_util.parse_time(time_str)) is None:
2596  raise ValueError(
2597  f"could not convert {type(time_str).__name__} to datetime: '{time_str}'"
2598  )
2599 
2600  return datetime.combine(today, time_today, today.tzinfo)
2601 
2602 
2603 def relative_time(hass: HomeAssistant, value: Any) -> Any:
2604  """Take a datetime and return its "age" as a string.
2605 
2606  The age can be in second, minute, hour, day, month or year. Only the
2607  biggest unit is considered, e.g. if it's 2 days and 3 hours, "2 days" will
2608  be returned.
2609  If the input datetime is in the future,
2610  the input datetime will be returned.
2611 
2612  If the input are not a datetime object the input will be returned unmodified.
2613 
2614  Note: This template function is deprecated in favor of `time_until`, but is still
2615  supported so as not to break old templates.
2616  """
2617 
2618  if (render_info := _render_info.get()) is not None:
2619  render_info.has_time = True
2620 
2621  if not isinstance(value, datetime):
2622  return value
2623  if not value.tzinfo:
2624  value = dt_util.as_local(value)
2625  if dt_util.now() < value:
2626  return value
2627  return dt_util.get_age(value)
2628 
2629 
2630 def time_since(hass: HomeAssistant, value: Any | datetime, precision: int = 1) -> Any:
2631  """Take a datetime and return its "age" as a string.
2632 
2633  The age can be in seconds, minutes, hours, days, months and year.
2634 
2635  precision is the number of units to return, with the last unit rounded.
2636 
2637  If the value not a datetime object the input will be returned unmodified.
2638  """
2639  if (render_info := _render_info.get()) is not None:
2640  render_info.has_time = True
2641 
2642  if not isinstance(value, datetime):
2643  return value
2644  if not value.tzinfo:
2645  value = dt_util.as_local(value)
2646  if dt_util.now() < value:
2647  return value
2648 
2649  return dt_util.get_age(value, precision)
2650 
2651 
2652 def time_until(hass: HomeAssistant, value: Any | datetime, precision: int = 1) -> Any:
2653  """Take a datetime and return the amount of time until that time as a string.
2654 
2655  The time until can be in seconds, minutes, hours, days, months and years.
2656 
2657  precision is the number of units to return, with the last unit rounded.
2658 
2659  If the value not a datetime object the input will be returned unmodified.
2660  """
2661  if (render_info := _render_info.get()) is not None:
2662  render_info.has_time = True
2663 
2664  if not isinstance(value, datetime):
2665  return value
2666  if not value.tzinfo:
2667  value = dt_util.as_local(value)
2668  if dt_util.now() > value:
2669  return value
2670 
2671  return dt_util.get_time_remaining(value, precision)
2672 
2673 
2674 def urlencode(value):
2675  """Urlencode dictionary and return as UTF-8 string."""
2676  return urllib_urlencode(value).encode("utf-8")
2678 
2679 def slugify(value, separator="_"):
2680  """Convert a string into a slug, such as what is used for entity ids."""
2681  return slugify_util(value, separator=separator)
2683 
2684 def iif(
2685  value: Any, if_true: Any = True, if_false: Any = False, if_none: Any = _SENTINEL
2686 ) -> Any:
2687  """Immediate if function/filter that allow for common if/else constructs.
2688 
2689  https://en.wikipedia.org/wiki/IIf
2690 
2691  Examples:
2692  {{ is_state("device_tracker.frenck", "home") | iif("yes", "no") }}
2693  {{ iif(1==2, "yes", "no") }}
2694  {{ (1 == 1) | iif("yes", "no") }}
2695 
2696  """
2697  if value is None and if_none is not _SENTINEL:
2698  return if_none
2699  if bool(value):
2700  return if_true
2701  return if_false
2702 
2703 
2704 class TemplateContextManager(AbstractContextManager):
2705  """Context manager to store template being parsed or rendered in a ContextVar."""
2706 
2707  def set_template(self, template_str: str, action: str) -> None:
2708  """Store template being parsed or rendered in a Contextvar to aid error handling."""
2709  template_cv.set((template_str, action))
2711  def __exit__(
2712  self,
2713  exc_type: type[BaseException] | None,
2714  exc_value: BaseException | None,
2715  traceback: TracebackType | None,
2716  ) -> None:
2717  """Raise any exception triggered within the runtime context."""
2718  template_cv.set(None)
2719 
2720 
2721 _template_context_manager = TemplateContextManager()
2722 
2723 
2725  template_str: str, template: jinja2.Template, **kwargs: Any
2726 ) -> str:
2727  """Store template being rendered in a ContextVar to aid error handling."""
2728  with _template_context_manager as cm:
2729  cm.set_template(template_str, "rendering")
2730  return template.render(**kwargs)
2731 
2732 
2734  strict: bool | None, log_fn: Callable[[int, str], None] | None
2735 ) -> type[jinja2.Undefined]:
2736  """Log on undefined variables."""
2737 
2738  if strict:
2739  return jinja2.StrictUndefined
2740 
2741  def _log_with_logger(level: int, msg: str) -> None:
2742  template, action = template_cv.get() or ("", "rendering or compiling")
2743  _LOGGER.log(
2744  level,
2745  "Template variable %s: %s when %s '%s'",
2746  logging.getLevelName(level).lower(),
2747  msg,
2748  action,
2749  template,
2750  )
2751 
2752  _log_fn = log_fn or _log_with_logger
2753 
2754  class LoggingUndefined(jinja2.Undefined):
2755  """Log on undefined variables."""
2756 
2757  def _log_message(self) -> None:
2758  _log_fn(logging.WARNING, self._undefined_message)
2759 
2760  def _fail_with_undefined_error(self, *args, **kwargs):
2761  try:
2762  return super()._fail_with_undefined_error(*args, **kwargs)
2763  except self._undefined_exception:
2764  _log_fn(logging.ERROR, self._undefined_message)
2765  raise
2766 
2767  def __str__(self) -> str:
2768  """Log undefined __str___."""
2769  self._log_message()
2770  return super().__str__()
2771 
2772  def __iter__(self):
2773  """Log undefined __iter___."""
2774  self._log_message()
2775  return super().__iter__()
2776 
2777  def __bool__(self) -> bool:
2778  """Log undefined __bool___."""
2779  self._log_message()
2780  return super().__bool__()
2781 
2782  return LoggingUndefined
2783 
2784 
2785 async def async_load_custom_templates(hass: HomeAssistant) -> None:
2786  """Load all custom jinja files under 5MiB into memory."""
2787  custom_templates = await hass.async_add_executor_job(_load_custom_templates, hass)
2788  _get_hass_loader(hass).sources = custom_templates
2789 
2790 
2791 def _load_custom_templates(hass: HomeAssistant) -> dict[str, str]:
2792  result = {}
2793  jinja_path = hass.config.path("custom_templates")
2794  all_files = [
2795  item
2796  for item in pathlib.Path(jinja_path).rglob("*.jinja")
2797  if item.is_file() and item.stat().st_size <= MAX_CUSTOM_TEMPLATE_SIZE
2798  ]
2799  for file in all_files:
2800  content = file.read_text()
2801  path = str(file.relative_to(jinja_path))
2802  result[path] = content
2803  return result
2804 
2805 
2806 @singleton(_HASS_LOADER)
2807 def _get_hass_loader(hass: HomeAssistant) -> HassLoader:
2808  return HassLoader({})
2809 
2811 class HassLoader(jinja2.BaseLoader):
2812  """An in-memory jinja loader that keeps track of templates that need to be reloaded."""
2813 
2814  def __init__(self, sources: dict[str, str]) -> None:
2815  """Initialize an empty HassLoader."""
2816  self._sources_sources = sources
2817  self._reload_reload = 0
2818 
2819  @property
2820  def sources(self) -> dict[str, str]:
2821  """Map filename to jinja source."""
2822  return self._sources_sources
2824  @sources.setter
2825  def sources(self, value: dict[str, str]) -> None:
2826  self._sources_sources = value
2827  self._reload_reload += 1
2829  def get_source(
2830  self, environment: jinja2.Environment, template: str
2831  ) -> tuple[str, str | None, Callable[[], bool] | None]:
2832  """Get in-memory sources."""
2833  if template not in self._sources_sources:
2834  raise jinja2.TemplateNotFound(template)
2835  cur_reload = self._reload_reload
2836  return self._sources_sources[template], template, lambda: cur_reload == self._reload_reload
2837 
2838 
2839 class TemplateEnvironment(ImmutableSandboxedEnvironment):
2840  """The Home Assistant template environment."""
2841 
2843  self,
2844  hass: HomeAssistant | None,
2845  limited: bool | None = False,
2846  strict: bool | None = False,
2847  log_fn: Callable[[int, str], None] | None = None,
2848  ) -> None:
2849  """Initialise template environment."""
2850  super().__init__(undefined=make_logging_undefined(strict, log_fn))
2851  self.hasshass = hass
2852  self.template_cache: weakref.WeakValueDictionary[
2853  str | jinja2.nodes.Template, CodeType | None
2854  ] = weakref.WeakValueDictionary()
2855  self.add_extension("jinja2.ext.loopcontrols")
2856  self.filters["round"] = forgiving_round
2857  self.filters["multiply"] = multiply
2858  self.filters["add"] = add
2859  self.filters["log"] = logarithm
2860  self.filters["sin"] = sine
2861  self.filters["cos"] = cosine
2862  self.filters["tan"] = tangent
2863  self.filters["asin"] = arc_sine
2864  self.filters["acos"] = arc_cosine
2865  self.filters["atan"] = arc_tangent
2866  self.filters["atan2"] = arc_tangent2
2867  self.filters["sqrt"] = square_root
2868  self.filters["as_datetime"] = as_datetime
2869  self.filters["as_timedelta"] = as_timedelta
2870  self.filters["as_timestamp"] = forgiving_as_timestamp
2871  self.filters["as_local"] = dt_util.as_local
2872  self.filters["timestamp_custom"] = timestamp_custom
2873  self.filters["timestamp_local"] = timestamp_local
2874  self.filters["timestamp_utc"] = timestamp_utc
2875  self.filters["to_json"] = to_json
2876  self.filters["from_json"] = from_json
2877  self.filters["is_defined"] = fail_when_undefined
2878  self.filters["average"] = average
2879  self.filters["median"] = median
2880  self.filters["statistical_mode"] = statistical_mode
2881  self.filters["random"] = random_every_time
2882  self.filters["base64_encode"] = base64_encode
2883  self.filters["base64_decode"] = base64_decode
2884  self.filters["ordinal"] = ordinal
2885  self.filters["regex_match"] = regex_match
2886  self.filters["regex_replace"] = regex_replace
2887  self.filters["regex_search"] = regex_search
2888  self.filters["regex_findall"] = regex_findall
2889  self.filters["regex_findall_index"] = regex_findall_index
2890  self.filters["bitwise_and"] = bitwise_and
2891  self.filters["bitwise_or"] = bitwise_or
2892  self.filters["bitwise_xor"] = bitwise_xor
2893  self.filters["pack"] = struct_pack
2894  self.filters["unpack"] = struct_unpack
2895  self.filters["ord"] = ord
2896  self.filters["is_number"] = is_number
2897  self.filters["float"] = forgiving_float_filter
2898  self.filters["int"] = forgiving_int_filter
2899  self.filters["slugify"] = slugify
2900  self.filters["iif"] = iif
2901  self.filters["bool"] = forgiving_boolean
2902  self.filters["version"] = version
2903  self.filters["contains"] = contains
2904  self.globals["log"] = logarithm
2905  self.globals["sin"] = sine
2906  self.globals["cos"] = cosine
2907  self.globals["tan"] = tangent
2908  self.globals["sqrt"] = square_root
2909  self.globals["pi"] = math.pi
2910  self.globals["tau"] = math.pi * 2
2911  self.globals["e"] = math.e
2912  self.globals["asin"] = arc_sine
2913  self.globals["acos"] = arc_cosine
2914  self.globals["atan"] = arc_tangent
2915  self.globals["atan2"] = arc_tangent2
2916  self.globals["float"] = forgiving_float
2917  self.globals["as_datetime"] = as_datetime
2918  self.globals["as_local"] = dt_util.as_local
2919  self.globals["as_timedelta"] = as_timedelta
2920  self.globals["as_timestamp"] = forgiving_as_timestamp
2921  self.globals["timedelta"] = timedelta
2922  self.globals["merge_response"] = merge_response
2923  self.globals["strptime"] = strptime
2924  self.globals["urlencode"] = urlencode
2925  self.globals["average"] = average
2926  self.globals["median"] = median
2927  self.globals["statistical_mode"] = statistical_mode
2928  self.globals["max"] = min_max_from_filter(self.filters["max"], "max")
2929  self.globals["min"] = min_max_from_filter(self.filters["min"], "min")
2930  self.globals["is_number"] = is_number
2931  self.globals["set"] = _to_set
2932  self.globals["tuple"] = _to_tuple
2933  self.globals["int"] = forgiving_int
2934  self.globals["pack"] = struct_pack
2935  self.globals["unpack"] = struct_unpack
2936  self.globals["slugify"] = slugify
2937  self.globals["iif"] = iif
2938  self.globals["bool"] = forgiving_boolean
2939  self.globals["version"] = version
2940  self.globals["zip"] = zip
2941  self.tests["is_number"] = is_number
2942  self.tests["list"] = _is_list
2943  self.tests["set"] = _is_set
2944  self.tests["tuple"] = _is_tuple
2945  self.tests["datetime"] = _is_datetime
2946  self.tests["string_like"] = _is_string_like
2947  self.tests["match"] = regex_match
2948  self.tests["search"] = regex_search
2949  self.tests["contains"] = contains
2950 
2951  if hass is None:
2952  return
2953 
2954  # This environment has access to hass, attach its loader to enable imports.
2955  self.loaderloader = _get_hass_loader(hass)
2956 
2957  # We mark these as a context functions to ensure they get
2958  # evaluated fresh with every execution, rather than executed
2959  # at compile time and the value stored. The context itself
2960  # can be discarded, we only need to get at the hass object.
2961  def hassfunction[**_P, _R](
2962  func: Callable[Concatenate[HomeAssistant, _P], _R],
2963  jinja_context: Callable[
2964  [Callable[Concatenate[Any, _P], _R]],
2965  Callable[Concatenate[Any, _P], _R],
2966  ] = pass_context,
2967  ) -> Callable[Concatenate[Any, _P], _R]:
2968  """Wrap function that depend on hass."""
2969 
2970  @wraps(func)
2971  def wrapper(_: Any, *args: _P.args, **kwargs: _P.kwargs) -> _R:
2972  return func(hass, *args, **kwargs)
2973 
2974  return jinja_context(wrapper)
2975 
2976  self.globals["device_entities"] = hassfunction(device_entities)
2977  self.filters["device_entities"] = self.globals["device_entities"]
2978 
2979  self.globals["device_attr"] = hassfunction(device_attr)
2980  self.filters["device_attr"] = self.globals["device_attr"]
2981 
2982  self.globals["config_entry_attr"] = hassfunction(config_entry_attr)
2983  self.filters["config_entry_attr"] = self.globals["config_entry_attr"]
2984 
2985  self.globals["is_device_attr"] = hassfunction(is_device_attr)
2986  self.tests["is_device_attr"] = hassfunction(is_device_attr, pass_eval_context)
2987 
2988  self.globals["config_entry_id"] = hassfunction(config_entry_id)
2989  self.filters["config_entry_id"] = self.globals["config_entry_id"]
2990 
2991  self.globals["device_id"] = hassfunction(device_id)
2992  self.filters["device_id"] = self.globals["device_id"]
2993 
2994  self.globals["issues"] = hassfunction(issues)
2995 
2996  self.globals["issue"] = hassfunction(issue)
2997  self.filters["issue"] = self.globals["issue"]
2998 
2999  self.globals["areas"] = hassfunction(areas)
3000 
3001  self.globals["area_id"] = hassfunction(area_id)
3002  self.filters["area_id"] = self.globals["area_id"]
3003 
3004  self.globals["area_name"] = hassfunction(area_name)
3005  self.filters["area_name"] = self.globals["area_name"]
3006 
3007  self.globals["area_entities"] = hassfunction(area_entities)
3008  self.filters["area_entities"] = self.globals["area_entities"]
3009 
3010  self.globals["area_devices"] = hassfunction(area_devices)
3011  self.filters["area_devices"] = self.globals["area_devices"]
3012 
3013  self.globals["floors"] = hassfunction(floors)
3014  self.filters["floors"] = self.globals["floors"]
3015 
3016  self.globals["floor_id"] = hassfunction(floor_id)
3017  self.filters["floor_id"] = self.globals["floor_id"]
3018 
3019  self.globals["floor_name"] = hassfunction(floor_name)
3020  self.filters["floor_name"] = self.globals["floor_name"]
3021 
3022  self.globals["floor_areas"] = hassfunction(floor_areas)
3023  self.filters["floor_areas"] = self.globals["floor_areas"]
3024 
3025  self.globals["integration_entities"] = hassfunction(integration_entities)
3026  self.filters["integration_entities"] = self.globals["integration_entities"]
3027 
3028  self.globals["labels"] = hassfunction(labels)
3029  self.filters["labels"] = self.globals["labels"]
3030 
3031  self.globals["label_id"] = hassfunction(label_id)
3032  self.filters["label_id"] = self.globals["label_id"]
3033 
3034  self.globals["label_name"] = hassfunction(label_name)
3035  self.filters["label_name"] = self.globals["label_name"]
3036 
3037  self.globals["label_areas"] = hassfunction(label_areas)
3038  self.filters["label_areas"] = self.globals["label_areas"]
3039 
3040  self.globals["label_devices"] = hassfunction(label_devices)
3041  self.filters["label_devices"] = self.globals["label_devices"]
3042 
3043  self.globals["label_entities"] = hassfunction(label_entities)
3044  self.filters["label_entities"] = self.globals["label_entities"]
3045 
3046  if limited:
3047  # Only device_entities is available to limited templates, mark other
3048  # functions and filters as unsupported.
3049  def unsupported(name: str) -> Callable[[], NoReturn]:
3050  def warn_unsupported(*args: Any, **kwargs: Any) -> NoReturn:
3051  raise TemplateError(
3052  f"Use of '{name}' is not supported in limited templates"
3053  )
3054 
3055  return warn_unsupported
3056 
3057  hass_globals = [
3058  "closest",
3059  "distance",
3060  "expand",
3061  "is_hidden_entity",
3062  "is_state",
3063  "is_state_attr",
3064  "state_attr",
3065  "states",
3066  "state_translated",
3067  "has_value",
3068  "utcnow",
3069  "now",
3070  "device_attr",
3071  "is_device_attr",
3072  "device_id",
3073  "area_id",
3074  "area_name",
3075  "floor_id",
3076  "floor_name",
3077  "relative_time",
3078  "time_since",
3079  "time_until",
3080  "today_at",
3081  "label_id",
3082  "label_name",
3083  ]
3084  hass_filters = [
3085  "closest",
3086  "expand",
3087  "device_id",
3088  "area_id",
3089  "area_name",
3090  "floor_id",
3091  "floor_name",
3092  "has_value",
3093  "label_id",
3094  "label_name",
3095  ]
3096  hass_tests = [
3097  "has_value",
3098  "is_hidden_entity",
3099  "is_state",
3100  "is_state_attr",
3101  ]
3102  for glob in hass_globals:
3103  self.globals[glob] = unsupported(glob)
3104  for filt in hass_filters:
3105  self.filters[filt] = unsupported(filt)
3106  for test in hass_tests:
3107  self.filters[test] = unsupported(test)
3108  return
3109 
3110  self.globals["expand"] = hassfunction(expand)
3111  self.filters["expand"] = self.globals["expand"]
3112  self.globals["closest"] = hassfunction(closest)
3113  self.filters["closest"] = hassfunction(closest_filter)
3114  self.globals["distance"] = hassfunction(distance)
3115  self.globals["is_hidden_entity"] = hassfunction(is_hidden_entity)
3116  self.tests["is_hidden_entity"] = hassfunction(
3117  is_hidden_entity, pass_eval_context
3118  )
3119  self.globals["is_state"] = hassfunction(is_state)
3120  self.tests["is_state"] = hassfunction(is_state, pass_eval_context)
3121  self.globals["is_state_attr"] = hassfunction(is_state_attr)
3122  self.tests["is_state_attr"] = hassfunction(is_state_attr, pass_eval_context)
3123  self.globals["state_attr"] = hassfunction(state_attr)
3124  self.filters["state_attr"] = self.globals["state_attr"]
3125  self.globals["states"] = AllStates(hass)
3126  self.filters["states"] = self.globals["states"]
3127  self.globals["state_translated"] = StateTranslated(hass)
3128  self.filters["state_translated"] = self.globals["state_translated"]
3129  self.globals["has_value"] = hassfunction(has_value)
3130  self.filters["has_value"] = self.globals["has_value"]
3131  self.tests["has_value"] = hassfunction(has_value, pass_eval_context)
3132  self.globals["utcnow"] = hassfunction(utcnow)
3133  self.globals["now"] = hassfunction(now)
3134  self.globals["relative_time"] = hassfunction(relative_time)
3135  self.filters["relative_time"] = self.globals["relative_time"]
3136  self.globals["time_since"] = hassfunction(time_since)
3137  self.filters["time_since"] = self.globals["time_since"]
3138  self.globals["time_until"] = hassfunction(time_until)
3139  self.filters["time_until"] = self.globals["time_until"]
3140  self.globals["today_at"] = hassfunction(today_at)
3141  self.filters["today_at"] = self.globals["today_at"]
3142 
3143  def is_safe_callable(self, obj):
3144  """Test if callback is safe."""
3145  return isinstance(
3146  obj, (AllStates, StateTranslated)
3147  ) or super().is_safe_callable(obj)
3148 
3149  def is_safe_attribute(self, obj, attr, value):
3150  """Test if attribute is safe."""
3151  if isinstance(
3152  obj, (AllStates, DomainStates, TemplateState, LoopContext, AsyncLoopContext)
3153  ):
3154  return attr[0] != "_"
3155 
3156  if isinstance(obj, Namespace):
3157  return True
3158 
3159  return super().is_safe_attribute(obj, attr, value)
3160 
3161  @overload
3162  def compile(
3163  self,
3164  source: str | jinja2.nodes.Template,
3165  name: str | None = None,
3166  filename: str | None = None,
3167  raw: Literal[False] = False,
3168  defer_init: bool = False,
3169  ) -> CodeType: ...
3170 
3171  @overload
3172  def compile(
3173  self,
3174  source: str | jinja2.nodes.Template,
3175  name: str | None = None,
3176  filename: str | None = None,
3177  raw: Literal[True] = ...,
3178  defer_init: bool = False,
3179  ) -> str: ...
3180 
3181  def compile(
3182  self,
3183  source: str | jinja2.nodes.Template,
3184  name: str | None = None,
3185  filename: str | None = None,
3186  raw: bool = False,
3187  defer_init: bool = False,
3188  ) -> CodeType | str:
3189  """Compile the template."""
3190  if (
3191  name is not None
3192  or filename is not None
3193  or raw is not False
3194  or defer_init is not False
3195  ):
3196  # If there are any non-default keywords args, we do
3197  # not cache. In prodution we currently do not have
3198  # any instance of this.
3199  return super().compile( # type: ignore[no-any-return,call-overload]
3200  source,
3201  name,
3202  filename,
3203  raw,
3204  defer_init,
3205  )
3206 
3207  compiled = super().compile(source)
3208  self.template_cache[source] = compiled
3209  return compiled
3210 
3211 
3212 _NO_HASS_ENV = TemplateEnvironment(None)
3213 
str __call__(self, str entity_id, bool|object rounded=_SENTINEL, bool with_unit=False)
Definition: template.py:933
Generator[TemplateState] __iter__(self)
Definition: template.py:918
None __init__(self, HomeAssistant hass)
Definition: template.py:889
None __init__(self, HomeAssistant hass, str domain)
Definition: template.py:987
TemplateState|None __getattr__(self, str name)
Definition: template.py:992
Generator[TemplateState] __iter__(self)
Definition: template.py:1008
None __init__(self, dict[str, str] sources)
Definition: template.py:2817
tuple[str, str|None, Callable[[], bool]|None] get_source(self, jinja2.Environment environment, str template)
Definition: template.py:2834
None __init__(self, Template template)
Definition: template.py:392
bool _filter_domains_and_entities(self, str entity_id)
Definition: template.py:425
bool _filter_lifecycle_domains(self, str entity_id)
Definition: template.py:441
bool _filter_entities(self, str entity_id)
Definition: template.py:434
str|None __call__(self, str entity_id)
Definition: template.py:956
None __init__(self, HomeAssistant hass)
Definition: template.py:952
None set_template(self, str template_str, str action)
Definition: template.py:2710
None __exit__(self, type[BaseException]|None exc_type, BaseException|None exc_value, TracebackType|None traceback)
Definition: template.py:2719
def is_safe_attribute(self, obj, attr, value)
Definition: template.py:3152
CodeType compile(self, str|jinja2.nodes.Template source, str|None name=None, str|None filename=None, Literal[False] raw=False, bool defer_init=False)
Definition: template.py:3172
None __init__(self, HomeAssistant|None hass, bool|None limited=False, bool|None strict=False, Callable[[int, str], None]|None log_fn=None)
Definition: template.py:2851
None __init__(self, HomeAssistant hass, bool collect, str entity_id)
Definition: template.py:1035
str format_state(self, bool rounded, bool with_unit)
Definition: template.py:1128
None __init__(self, HomeAssistant hass, str entity_id, bool collect=True)
Definition: template.py:1175
None __init__(self, HomeAssistant hass, State state, bool collect=True)
Definition: template.py:1158
Any render(self, TemplateVarsType variables=None, bool parse_result=True, bool limited=False, **Any kwargs)
Definition: template.py:585
Any async_render_with_possible_json_value(self, Any value, Any error_value=_SENTINEL, dict[str, Any]|None variables=None, bool parse_result=False)
Definition: template.py:781
RenderInfo async_render_to_info(self, TemplateVarsType variables=None, bool strict=False, Callable[[int, str], None]|None log_fn=None, **Any kwargs)
Definition: template.py:723
Any async_render(self, TemplateVarsType variables=None, bool parse_result=True, bool limited=False, bool strict=False, Callable[[int, str], None]|None log_fn=None, **Any kwargs)
Definition: template.py:610
TemplateEnvironment _env(self)
Definition: template.py:543
None __init__(self, str template, HomeAssistant|None hass=None)
Definition: template.py:511
def render_with_possible_json_value(self, value, error_value=_SENTINEL)
Definition: template.py:759
bool async_render_will_timeout(self, float timeout, TemplateVarsType variables=None, bool strict=False, Callable[[int, str], None]|None log_fn=None, **Any kwargs)
Definition: template.py:663
Any _parse_result(self, str render_result)
Definition: template.py:647
jinja2.Template _ensure_compiled(self, bool limited=False, bool strict=False, Callable[[int, str], None]|None log_fn=None)
Definition: template.py:827
None __init__(self, tuple value, *str|None render_result=None)
Definition: template.py:319
Self __new__(cls, tuple value, *str|None render_result=None)
Definition: template.py:315
None __init__(self, _AOSmithCoordinatorT coordinator, str junction_id)
Definition: entity.py:20
list[_T] match(self, BluetoothServiceInfoBleak service_info)
Definition: match.py:246
str async_rounded_state(HomeAssistant hass, str entity_id, State state)
Definition: __init__.py:971
bool valid_entity_id(str entity_id)
Definition: core.py:235
bool valid_domain(str domain)
Definition: core.py:229
tuple[str, str] split_entity_id(str entity_id)
Definition: core.py:214
AreaRegistry async_get(HomeAssistant hass)
bool time(HomeAssistant hass, dt_time|str|None before=None, dt_time|str|None after=None, str|Container[str]|None weekday=None)
Definition: condition.py:802
dict[str, EntityInfo] entity_sources(HomeAssistant hass)
Definition: entity.py:98
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)
Definition: event.py:1679
None report_usage(str what, *str|None breaks_in_ha_version=None, ReportBehavior core_behavior=ReportBehavior.ERROR, ReportBehavior core_integration_behavior=ReportBehavior.LOG, ReportBehavior custom_integration_behavior=ReportBehavior.LOG, set[str]|None exclude_integrations=None, str|None integration_domain=None, int level=logging.WARNING)
Definition: frame.py:195
str to_json(Any value, bool ensure_ascii=False, bool pretty_print=False, bool sort_keys=False)
Definition: template.py:2551
bool is_hidden_entity(HomeAssistant hass, str entity_id)
Definition: template.py:1853
None async_load_custom_templates(HomeAssistant hass)
Definition: template.py:2788
Iterable[str] integration_entities(HomeAssistant hass, str entry_name)
Definition: template.py:1340
type gen_result_wrapper(type[dict|list|set] kls)
Definition: template.py:285
TemplateState|None _get_template_state_from_state(HomeAssistant hass, str entity_id, State|None state)
Definition: template.py:1234
bool _is_datetime(Any value)
Definition: template.py:2411
def closest_filter(hass, *args)
Definition: template.py:1789
None _attach(HomeAssistant hass, Any obj)
Definition: template.py:222
def random_every_time(context, values)
Definition: template.py:2580
Iterable[State] expand(HomeAssistant hass, *Any args)
Definition: template.py:1290
Any time_until(HomeAssistant hass, Any|datetime value, int precision=1)
Definition: template.py:2655
def timestamp_local(value, default=_SENTINEL)
Definition: template.py:2090
Any statistical_mode(*Any args, Any default=_SENTINEL)
Definition: template.py:2308
def add(value, amount, default=_SENTINEL)
Definition: template.py:1948
Any device_attr(HomeAssistant hass, str device_or_entity_id, str attr_name)
Definition: template.py:1403
type[jinja2.Undefined] make_logging_undefined(bool|None strict, Callable[[int, str], None]|None log_fn)
Definition: template.py:2738
HassLoader _get_hass_loader(HomeAssistant hass)
Definition: template.py:2810
Any config_entry_attr(HomeAssistant hass, str config_entry_id_, str attr_name)
Definition: template.py:1423
def arc_tangent2(*args, default=_SENTINEL)
Definition: template.py:2036
def closest(hass, *args)
Definition: template.py:1726
None _collect_state(HomeAssistant hass, str entity_id)
Definition: template.py:1194
def regex_findall(value, find="", ignorecase=False)
Definition: template.py:2453
Iterable[str|None] labels(HomeAssistant hass, Any lookup_value=None)
Definition: template.py:1640
bool async_setup(HomeAssistant hass)
Definition: template.py:185
datetime now(HomeAssistant hass)
Definition: template.py:1890
def sine(value, default=_SENTINEL)
Definition: template.py:1976
TemplateState|None _get_state_if_valid(HomeAssistant hass, str entity_id)
Definition: template.py:1221
bool is_device_attr(HomeAssistant hass, str device_or_entity_id, str attr_name, Any attr_value)
Definition: template.py:1441
Any state_attr(HomeAssistant hass, str entity_id, str name)
Definition: template.py:1874
def multiply(value, amount, default=_SENTINEL)
Definition: template.py:1937
def bitwise_or(first_value, second_value)
Definition: template.py:2466
State|TemplateState|None _resolve_state(HomeAssistant hass, Any entity_id_or_state)
Definition: template.py:1245
def arc_sine(value, default=_SENTINEL)
Definition: template.py:2006
Any render_complex(Any value, TemplateVarsType variables=None, bool limited=False, bool parse_result=True)
Definition: template.py:240
None _to_json_default(Any obj)
Definition: template.py:2541
Generator[TemplateState] _state_generator(HomeAssistant hass, str|None domain)
Definition: template.py:1201
Any relative_time(HomeAssistant hass, Any value)
Definition: template.py:2606
str base64_encode(str value)
Definition: template.py:2512
str|None area_id(HomeAssistant hass, str lookup_value)
Definition: template.py:1521
bool _is_string_like(Any value)
Definition: template.py:2416
bool is_state_attr(HomeAssistant hass, str entity_id, str name, Any value)
Definition: template.py:1868
Any average(*Any args, Any default=_SENTINEL)
Definition: template.py:2250
Iterable[str] device_entities(HomeAssistant hass, str _device_id)
Definition: template.py:1333
Iterable[str] floor_areas(HomeAssistant hass, str floor_id_or_name)
Definition: template.py:1499
def cosine(value, default=_SENTINEL)
Definition: template.py:1986
str|None label_name(HomeAssistant hass, str lookup_value)
Definition: template.py:1682
str|None area_name(HomeAssistant hass, str lookup_value)
Definition: template.py:1559
set[Any] _to_set(Any value)
Definition: template.py:2401
Iterable[str] area_devices(HomeAssistant hass, str area_id_or_name)
Definition: template.py:1624
str|bytes base64_decode(str value, str|None encoding="utf-8")
Definition: template.py:2517
Any _readonly(*Any args, **Any kwargs)
Definition: template.py:877
str|None _label_id_or_name(HomeAssistant hass, str label_id_or_name)
Definition: template.py:1690
bool is_state(HomeAssistant hass, str entity_id, str|list[str] state)
Definition: template.py:1860
str|None floor_name(HomeAssistant hass, str lookup_value)
Definition: template.py:1481
def timestamp_custom(value, date_format=DATE_STR_FORMAT, local=True, default=_SENTINEL)
Definition: template.py:2074
TemplateState _template_state(HomeAssistant hass, State state)
Definition: template.py:176
bool has_value(HomeAssistant hass, str entity_id)
Definition: template.py:1881
bool is_template_string(str maybe_template)
Definition: template.py:272
Any min_max_from_filter(Any builtin_filter, str name)
Definition: template.py:2227
str|None device_id(HomeAssistant hass, str entity_id_or_device_name)
Definition: template.py:1384
Any median(*Any args, Any default=_SENTINEL)
Definition: template.py:2279
dict[tuple[str, str], dict[str, Any]] issues(HomeAssistant hass)
Definition: template.py:1446
bool is_complex(Any value)
Definition: template.py:259
TemplateState|None _get_state(HomeAssistant hass, str entity_id)
Definition: template.py:1228
str _render_with_context(str template_str, jinja2.Template template, **Any kwargs)
Definition: template.py:2729
def forgiving_int_filter(value, default=_SENTINEL, base=10)
Definition: template.py:2367
None attach(HomeAssistant hass, Any obj)
Definition: template.py:217
def arc_cosine(value, default=_SENTINEL)
Definition: template.py:2016
TemplateState _template_state_no_collect(HomeAssistant hass, State state)
Definition: template.py:167
def regex_match(value, find="", ignorecase=False)
Definition: template.py:2421
def regex_search(value, find="", ignorecase=False)
Definition: template.py:2440
str|None config_entry_id(HomeAssistant hass, str entity_id)
Definition: template.py:1376
def forgiving_float_filter(value, default=_SENTINEL)
Definition: template.py:2349
timedelta|None as_timedelta(str value)
Definition: template.py:2148
def bitwise_and(first_value, second_value)
Definition: template.py:2461
str|None floor_id(HomeAssistant hass, Any lookup_value)
Definition: template.py:1467
bytes|None struct_pack(Any|None value, str format_string)
Definition: template.py:2476
str|None label_id(HomeAssistant hass, Any lookup_value)
Definition: template.py:1674
Any _cached_parse_result(str render_result)
Definition: template.py:345
Iterable[str|None] areas(HomeAssistant hass)
Definition: template.py:1516
Iterable[str] area_entities(HomeAssistant hass, str area_id_or_name)
Definition: template.py:1594
str _get_area_name(area_registry.AreaRegistry area_reg, str valid_area_id)
Definition: template.py:1552
def slugify(value, separator="_")
Definition: template.py:2682
def regex_findall_index(value, find="", index=0, ignorecase=False)
Definition: template.py:2448
def bitwise_xor(first_value, second_value)
Definition: template.py:2471
def square_root(value, default=_SENTINEL)
Definition: template.py:2064
def regex_replace(value="", find="", replace="", ignorecase=False)
Definition: template.py:2432
def arc_tangent(value, default=_SENTINEL)
Definition: template.py:2026
datetime today_at(HomeAssistant hass, str time_str="")
Definition: template.py:2589
def forgiving_int(value, default=_SENTINEL, base=10)
Definition: template.py:2359
dict[str, Any]|None issue(HomeAssistant hass, str domain, str issue_id)
Definition: template.py:1453
Iterable[str|None] floors(HomeAssistant hass)
Definition: template.py:1461
def strptime(string, fmt, default=_SENTINEL)
Definition: template.py:2210
def logarithm(value, base=math.e, default=_SENTINEL)
Definition: template.py:1959
dict[str, str] _load_custom_templates(HomeAssistant hass)
Definition: template.py:2794
list[Any] merge_response(ServiceResponse value)
Definition: template.py:2153
Any iif(Any value, Any if_true=True, Any if_false=False, Any if_none=_SENTINEL)
Definition: template.py:2689
Any time_since(HomeAssistant hass, Any|datetime value, int precision=1)
Definition: template.py:2633
def timestamp_utc(value, default=_SENTINEL)
Definition: template.py:2101
def distance(hass, *args)
Definition: template.py:1796
Iterable[str] label_devices(HomeAssistant hass, str label_id_or_name)
Definition: template.py:1708
def forgiving_as_timestamp(value, default=_SENTINEL)
Definition: template.py:2112
datetime utcnow(HomeAssistant hass)
Definition: template.py:1898
Any|None struct_unpack(bytes value, str format_string, int offset=0)
Definition: template.py:2494
bool result_as_boolean(Any|None template_result)
Definition: template.py:1277
def forgiving_round(value, precision=0, method="common", default=_SENTINEL)
Definition: template.py:1915
def tangent(value, default=_SENTINEL)
Definition: template.py:1996
Any as_datetime(Any value, Any default=_SENTINEL)
Definition: template.py:2122
bool|object forgiving_boolean(Any value)
Definition: template.py:1255
Iterable[str] label_entities(HomeAssistant hass, str label_id_or_name)
Definition: template.py:1717
NoReturn raise_no_default(str function, Any value)
Definition: template.py:1906
Iterable[str] label_areas(HomeAssistant hass, str label_id_or_name)
Definition: template.py:1699
DomainStates _domain_states(HomeAssistant hass, str name)
Definition: template.py:873
def forgiving_float(value, default=_SENTINEL)
Definition: template.py:2339
str async_translate_state(HomeAssistant hass, str state, str domain, str|None platform, str|None translation_key, str|None device_class)
Definition: translation.py:470