Compare commits

...

6 Commits

Author SHA1 Message Date
Paulus Schoutsen
570d1e7d8f Bumped version to 0.116.0b5 2020-10-06 07:55:25 +00:00
J. Nick Koston
23be039b7f Prevent collecting states already referenced by domain or all (#41308)
The template engine would collect all the states in
a domain or all states while iterating even though
they were already included in all or the domain

This lead to the rate limit not being applied to
templates that iterated all states that also
accessed a collectable property because the engine
incorrectly believed they were specifically
referenced.
2020-10-06 07:55:18 +00:00
Bram Kragten
859632d636 Exclude media_dirs from YAML config check (#41299) 2020-10-06 07:55:18 +00:00
Andrew Sayre
874cbf808e Update pysmartthings (#41294) 2020-10-06 07:55:17 +00:00
Bram Kragten
56b5ddb06d Bumped version to 0.116.0b4 2020-10-05 18:05:31 +02:00
Bram Kragten
df371d99dc Updated frontend to 20201001.1 (#41273) 2020-10-05 18:03:37 +02:00
11 changed files with 153 additions and 58 deletions

View File

@@ -2,7 +2,7 @@
"domain": "frontend",
"name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/integrations/frontend",
"requirements": ["home-assistant-frontend==20201001.0"],
"requirements": ["home-assistant-frontend==20201001.1"],
"dependencies": [
"api",
"auth",

View File

@@ -3,7 +3,7 @@
"name": "SmartThings",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/smartthings",
"requirements": ["pysmartapp==0.3.2", "pysmartthings==0.7.3"],
"requirements": ["pysmartapp==0.3.2", "pysmartthings==0.7.4"],
"dependencies": ["webhook"],
"after_dependencies": ["cloud"],
"codeowners": ["@andrewsayre"]

View File

@@ -488,7 +488,6 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: Dict) -> Non
CONF_UNIT_SYSTEM,
CONF_EXTERNAL_URL,
CONF_INTERNAL_URL,
CONF_MEDIA_DIRS,
]
):
hac.config_source = SOURCE_YAML

View File

@@ -1,7 +1,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 116
PATCH_VERSION = "0b3"
PATCH_VERSION = "0b5"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER = (3, 7, 1)

View File

@@ -587,17 +587,18 @@ class DomainStates:
class TemplateState(State):
"""Class to represent a state object in a template."""
__slots__ = ("_hass", "_state")
__slots__ = ("_hass", "_state", "_collect")
# Inheritance is done so functions that check against State keep working
# pylint: disable=super-init-not-called
def __init__(self, hass, state):
def __init__(self, hass, state, collect=True):
"""Initialize template state."""
self._hass = hass
self._state = state
self._collect = collect
def _collect_state(self):
if _RENDER_INFO in self._hass.data:
if self._collect and _RENDER_INFO in self._hass.data:
self._hass.data[_RENDER_INFO].entities.add(self._state.entity_id)
# Jinja will try __getitem__ first and it avoids the need
@@ -606,7 +607,7 @@ class TemplateState(State):
"""Return a property as an attribute for jinja."""
if item in _COLLECTABLE_STATE_ATTRIBUTES:
# _collect_state inlined here for performance
if _RENDER_INFO in self._hass.data:
if self._collect and _RENDER_INFO in self._hass.data:
self._hass.data[_RENDER_INFO].entities.add(self._state.entity_id)
return getattr(self._state, item)
if item == "entity_id":
@@ -697,7 +698,7 @@ def _collect_state(hass: HomeAssistantType, entity_id: str) -> None:
def _state_generator(hass: HomeAssistantType, domain: Optional[str]) -> Generator:
"""State generator for a domain or all states."""
for state in sorted(hass.states.async_all(domain), key=attrgetter("entity_id")):
yield TemplateState(hass, state)
yield TemplateState(hass, state, collect=False)
def _get_state_if_valid(

View File

@@ -13,7 +13,7 @@ defusedxml==0.6.0
distro==1.5.0
emoji==0.5.4
hass-nabucasa==0.37.0
home-assistant-frontend==20201001.0
home-assistant-frontend==20201001.1
importlib-metadata==1.6.0;python_version<'3.8'
jinja2>=2.11.2
netdisco==2.8.2

View File

@@ -753,7 +753,7 @@ hole==0.5.1
holidays==0.10.3
# homeassistant.components.frontend
home-assistant-frontend==20201001.0
home-assistant-frontend==20201001.1
# homeassistant.components.zwave
homeassistant-pyozw==0.1.10
@@ -1653,7 +1653,7 @@ pysmappee==0.2.13
pysmartapp==0.3.2
# homeassistant.components.smartthings
pysmartthings==0.7.3
pysmartthings==0.7.4
# homeassistant.components.smarty
pysmarty==0.8

View File

@@ -376,7 +376,7 @@ hole==0.5.1
holidays==0.10.3
# homeassistant.components.frontend
home-assistant-frontend==20201001.0
home-assistant-frontend==20201001.1
# homeassistant.components.zwave
homeassistant-pyozw==0.1.10
@@ -800,7 +800,7 @@ pysmappee==0.2.13
pysmartapp==0.3.2
# homeassistant.components.smartthings
pysmartthings==0.7.3
pysmartthings==0.7.4
# homeassistant.components.soma
pysoma==0.0.10

View File

@@ -1284,7 +1284,7 @@ async def test_track_template_result_iterator(hass):
assert info.listeners == {
"all": False,
"domains": {"sensor"},
"entities": {"sensor.test"},
"entities": set(),
}
hass.states.async_set("sensor.test", 6)
@@ -1488,6 +1488,80 @@ async def test_track_template_rate_limit_five(hass):
assert refresh_runs == ["0", "1"]
async def test_track_template_has_default_rate_limit(hass):
"""Test template has a rate limit by default."""
hass.states.async_set("sensor.zero", "any")
template_refresh = Template("{{ states | list | count }}", hass)
refresh_runs = []
@ha.callback
def refresh_listener(event, updates):
refresh_runs.append(updates.pop().result)
info = async_track_template_result(
hass,
[TrackTemplate(template_refresh, None)],
refresh_listener,
)
await hass.async_block_till_done()
info.async_refresh()
await hass.async_block_till_done()
assert refresh_runs == ["1"]
hass.states.async_set("sensor.one", "any")
await hass.async_block_till_done()
assert refresh_runs == ["1"]
info.async_refresh()
assert refresh_runs == ["1", "2"]
hass.states.async_set("sensor.two", "any")
await hass.async_block_till_done()
assert refresh_runs == ["1", "2"]
hass.states.async_set("sensor.three", "any")
await hass.async_block_till_done()
assert refresh_runs == ["1", "2"]
async def test_track_template_unavailable_sates_has_default_rate_limit(hass):
"""Test template watching for unavailable states has a rate limit by default."""
hass.states.async_set("sensor.zero", "unknown")
template_refresh = Template(
"{{ states | selectattr('state', 'in', ['unavailable', 'unknown', 'none']) | list | count }}",
hass,
)
refresh_runs = []
@ha.callback
def refresh_listener(event, updates):
refresh_runs.append(updates.pop().result)
info = async_track_template_result(
hass,
[TrackTemplate(template_refresh, None)],
refresh_listener,
)
await hass.async_block_till_done()
info.async_refresh()
await hass.async_block_till_done()
assert refresh_runs == ["1"]
hass.states.async_set("sensor.one", "unknown")
await hass.async_block_till_done()
assert refresh_runs == ["1"]
info.async_refresh()
assert refresh_runs == ["1", "2"]
hass.states.async_set("sensor.two", "any")
await hass.async_block_till_done()
assert refresh_runs == ["1", "2"]
hass.states.async_set("sensor.three", "unknown")
await hass.async_block_till_done()
assert refresh_runs == ["1", "2"]
info.async_refresh()
await hass.async_block_till_done()
assert refresh_runs == ["1", "2", "3"]
async def test_specifically_referenced_entity_is_not_rate_limited(hass):
"""Test template rate limit of 5 seconds."""
hass.states.async_set("sensor.one", "none")

View File

@@ -155,9 +155,25 @@ def test_iterating_all_states(hass):
hass.states.async_set("sensor.temperature", 10)
info = render_to_info(hass, tmpl_str)
assert_result_info(
info, "10happy", entities=["test.object", "sensor.temperature"], all_states=True
)
assert_result_info(info, "10happy", entities=[], all_states=True)
def test_iterating_all_states_unavailable(hass):
"""Test iterating all states unavailable."""
hass.states.async_set("test.object", "on")
tmpl_str = "{{ states | selectattr('state', 'in', ['unavailable', 'unknown', 'none']) | list | count }}"
info = render_to_info(hass, tmpl_str)
assert info.all_states is True
assert info.rate_limit == template.DEFAULT_RATE_LIMIT
hass.states.async_set("test.object", "unknown")
hass.states.async_set("sensor.temperature", 10)
info = render_to_info(hass, tmpl_str)
assert_result_info(info, "1", entities=[], all_states=True)
def test_iterating_domain_states(hass):
@@ -176,7 +192,7 @@ def test_iterating_domain_states(hass):
assert_result_info(
info,
"open10",
entities=["sensor.back_door", "sensor.temperature"],
entities=[],
domains=["sensor"],
)
@@ -1426,9 +1442,7 @@ async def test_expand(hass):
info = render_to_info(
hass, "{{ expand(states.group) | map(attribute='entity_id') | join(', ') }}"
)
assert_result_info(
info, "test.object", {"test.object", "group.new_group"}, ["group"]
)
assert_result_info(info, "test.object", {"test.object"}, ["group"])
assert info.rate_limit == template.DEFAULT_RATE_LIMIT
info = render_to_info(
@@ -1587,7 +1601,7 @@ async def test_async_render_to_info_with_wildcard_matching_entity_id(hass):
"""Test tracking template with a wildcard."""
template_complex_str = r"""
{% for state in states %}
{% for state in states.cover %}
{% if state.entity_id | regex_match('.*\.office_') %}
{{ state.entity_id }}={{ state.state }}
{% endif %}
@@ -1599,13 +1613,9 @@ async def test_async_render_to_info_with_wildcard_matching_entity_id(hass):
hass.states.async_set("cover.office_skylight", "open")
info = render_to_info(hass, template_complex_str)
assert not info.domains
assert info.entities == {
"cover.office_drapes",
"cover.office_window",
"cover.office_skylight",
}
assert info.all_states is True
assert info.domains == {"cover"}
assert info.entities == set()
assert info.all_states is False
assert info.rate_limit == template.DEFAULT_RATE_LIMIT
@@ -1629,13 +1639,7 @@ async def test_async_render_to_info_with_wildcard_matching_state(hass):
info = render_to_info(hass, template_complex_str)
assert not info.domains
assert info.entities == {
"cover.x_skylight",
"binary_sensor.door",
"cover.office_drapes",
"cover.office_window",
"cover.office_skylight",
}
assert info.entities == set()
assert info.all_states is True
assert info.rate_limit == template.DEFAULT_RATE_LIMIT
@@ -1643,13 +1647,7 @@ async def test_async_render_to_info_with_wildcard_matching_state(hass):
info = render_to_info(hass, template_complex_str)
assert not info.domains
assert info.entities == {
"cover.x_skylight",
"binary_sensor.door",
"cover.office_drapes",
"cover.office_window",
"cover.office_skylight",
}
assert info.entities == set()
assert info.all_states is True
assert info.rate_limit == template.DEFAULT_RATE_LIMIT
@@ -1666,12 +1664,7 @@ async def test_async_render_to_info_with_wildcard_matching_state(hass):
info = render_to_info(hass, template_cover_str)
assert info.domains == {"cover"}
assert info.entities == {
"cover.x_skylight",
"cover.office_drapes",
"cover.office_window",
"cover.office_skylight",
}
assert info.entities == set()
assert info.all_states is False
assert info.rate_limit == template.DEFAULT_RATE_LIMIT
@@ -1965,9 +1958,7 @@ def test_generate_filter_iterators(hass):
{% endfor %}
""",
)
assert_result_info(
info, "sensor.test_sensor=off,", ["sensor.test_sensor"], ["sensor"]
)
assert_result_info(info, "sensor.test_sensor=off,", [], ["sensor"])
info = render_to_info(
hass,
@@ -1977,9 +1968,7 @@ def test_generate_filter_iterators(hass):
{% endfor %}
""",
)
assert_result_info(
info, "sensor.test_sensor=value,", ["sensor.test_sensor"], ["sensor"]
)
assert_result_info(info, "sensor.test_sensor=value,", [], ["sensor"])
def test_generate_select(hass):
@@ -2001,7 +1990,7 @@ def test_generate_select(hass):
assert_result_info(
info,
"sensor.test_sensor",
["sensor.test_sensor", "sensor.test_sensor_on"],
[],
["sensor"],
)
assert info.domains_lifecycle == {"sensor"}
@@ -2542,7 +2531,9 @@ async def test_lights(hass):
tmp = template.Template(tmpl, hass)
info = tmp.async_render_to_info()
assert info.entities == set(states)
assert info.entities == set()
assert info.domains == {"light"}
assert "lights are on" in info.result()
for i in range(10):
assert f"sensor{i}" in info.result()

View File

@@ -369,6 +369,36 @@ async def test_loading_configuration_from_storage(hass, hass_storage):
assert hass.config.config_source == SOURCE_STORAGE
async def test_loading_configuration_from_storage_with_yaml_only(hass, hass_storage):
"""Test loading core and YAML config onto hass object."""
hass_storage["core.config"] = {
"data": {
"elevation": 10,
"latitude": 55,
"location_name": "Home",
"longitude": 13,
"time_zone": "Europe/Copenhagen",
"unit_system": "metric",
},
"key": "core.config",
"version": 1,
}
await config_util.async_process_ha_core_config(
hass, {"media_dirs": {"mymedia": "/usr"}, "allowlist_external_dirs": "/etc"}
)
assert hass.config.latitude == 55
assert hass.config.longitude == 13
assert hass.config.elevation == 10
assert hass.config.location_name == "Home"
assert hass.config.units.name == CONF_UNIT_SYSTEM_METRIC
assert hass.config.time_zone.zone == "Europe/Copenhagen"
assert len(hass.config.allowlist_external_dirs) == 3
assert "/etc" in hass.config.allowlist_external_dirs
assert hass.config.media_dirs == {"mymedia": "/usr"}
assert hass.config.config_source == SOURCE_STORAGE
async def test_updating_configuration(hass, hass_storage):
"""Test updating configuration stores the new configuration."""
core_data = {