Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do async_setup_platform in background #36244

Merged
merged 17 commits into from Jun 1, 2020
6 changes: 3 additions & 3 deletions homeassistant/components/camera/__init__.py
Expand Up @@ -34,6 +34,7 @@
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_FILENAME,
EVENT_HOMEASSISTANT_START,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
Expand All @@ -48,7 +49,6 @@
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.network import get_url
from homeassistant.loader import bind_hass
from homeassistant.setup import async_when_setup

from .const import DATA_CAMERA_PREFS, DOMAIN
from .prefs import CameraPreferences
Expand Down Expand Up @@ -259,7 +259,7 @@ async def async_setup(hass, config):

await component.async_setup(config)

async def preload_stream(hass, _):
async def preload_stream(_):
for camera in component.entities:
camera_prefs = prefs.get(camera.entity_id)
if not camera_prefs.preload_stream:
Expand All @@ -273,7 +273,7 @@ async def preload_stream(hass, _):

request_stream(hass, source, keepalive=True, options=camera.stream_options)

async_when_setup(hass, DOMAIN_STREAM, preload_stream)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, preload_stream)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't the old approach good? I'm just curious.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bug. It needs to iterate over the entities. When stream is set up it's not ready with adding entities. So listening to START at least guarantees all platforms have set up their entities. It's still not perfect as camera entities might be loaded after start that also have preload stream activated…


@callback
def update_tokens(time):
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/xiaomi_miio/vacuum.py
Expand Up @@ -291,7 +291,7 @@ def fan_speed(self):
@property
def fan_speed_list(self):
"""Get the list of available fan speed steps of the vacuum cleaner."""
return list(self._fan_speeds)
return list(self._fan_speeds) if self._fan_speeds else []

@property
def device_state_attributes(self):
Expand Down
8 changes: 2 additions & 6 deletions homeassistant/helpers/entity_component.py
Expand Up @@ -123,15 +123,11 @@ async def async_setup(self, config: ConfigType) -> None:
self.config = config

# Look in config for Domain, Domain 2, Domain 3 etc and load them
tasks = []
for p_type, p_config in config_per_platform(config, self.domain):
tasks.append(self.async_setup_platform(p_type, p_config))

if tasks:
await asyncio.gather(*tasks)
self.hass.async_create_task(self.async_setup_platform(p_type, p_config))

# Generic discovery listener for loading platform dynamically
# Refer to: homeassistant.components.discovery.load_platform()
# Refer to: homeassistant.helpers.discovery.async_load_platform()
async def component_platform_discovered(
platform: str, info: Optional[Dict[str, Any]]
) -> None:
Expand Down
22 changes: 3 additions & 19 deletions homeassistant/helpers/entity_platform.py
Expand Up @@ -4,7 +4,7 @@
from datetime import datetime, timedelta
from logging import Logger
from types import ModuleType
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, cast
from typing import TYPE_CHECKING, Dict, Iterable, Optional

from homeassistant.const import DEVICE_DEFAULT_NAME
from homeassistant.core import CALLBACK_TYPE, callback, split_entity_id, valid_entity_id
Expand Down Expand Up @@ -51,7 +51,6 @@ def __init__(
self.entity_namespace = entity_namespace
self.config_entry = None
self.entities: Dict[str, Entity] = {} # pylint: disable=used-before-assignment
self._tasks: List[asyncio.Future] = []
# Method to cancel the state change listener
self._async_unsub_polling: Optional[CALLBACK_TYPE] = None
# Method to cancel the retry of setup
Expand Down Expand Up @@ -177,14 +176,6 @@ async def _async_setup_platform(self, async_create_setup_task, tries=0):

await asyncio.wait_for(asyncio.shield(task), SLOW_SETUP_MAX_WAIT)

# Block till all entities are done
if self._tasks:
pending = [task for task in self._tasks if not task.done()]
self._tasks.clear()

if pending:
await asyncio.gather(*pending)
MartinHjelmare marked this conversation as resolved.
Show resolved Hide resolved

hass.config.components.add(full_name)
return True
except PlatformNotReady:
Expand Down Expand Up @@ -239,15 +230,8 @@ def _async_schedule_add_entities(
self, new_entities: Iterable["Entity"], update_before_add: bool = False
) -> None:
"""Schedule adding entities for a single platform async."""
self._tasks.append(
cast(
asyncio.Future,
self.hass.async_add_job(
self.async_add_entities( # type: ignore
new_entities, update_before_add=update_before_add
),
),
)
self.hass.async_create_task(
self.async_add_entities(new_entities, update_before_add=update_before_add),
)

def add_entities(
Expand Down
2 changes: 2 additions & 0 deletions tests/components/air_quality/test_air_quality.py
Expand Up @@ -17,6 +17,7 @@ async def test_state(hass):
config = {"air_quality": {"platform": "demo"}}

assert await async_setup_component(hass, "air_quality", config)
await hass.async_block_till_done()

state = hass.states.get("air_quality.demo_air_quality_home")
assert state is not None
Expand All @@ -29,6 +30,7 @@ async def test_attributes(hass):
config = {"air_quality": {"platform": "demo"}}

assert await async_setup_component(hass, "air_quality", config)
await hass.async_block_till_done()

state = hass.states.get("air_quality.demo_air_quality_office")
assert state is not None
Expand Down
5 changes: 4 additions & 1 deletion tests/components/airvisual/test_config_flow.py
Expand Up @@ -111,8 +111,11 @@ async def test_migration(hass):

assert len(hass.config_entries.async_entries(DOMAIN)) == 1

with patch("pyairvisual.api.API.nearest_city"):
with patch("pyairvisual.api.API.nearest_city"), patch.object(
hass.config_entries, "async_forward_entry_setup"
):
assert await async_setup_component(hass, DOMAIN, {DOMAIN: conf})
await hass.async_block_till_done()

config_entries = hass.config_entries.async_entries(DOMAIN)

Expand Down
14 changes: 7 additions & 7 deletions tests/components/alexa/test_smart_home.py
Expand Up @@ -55,19 +55,19 @@ def events(hass):


@pytest.fixture
def mock_camera(hass):
async def mock_camera(hass):
"""Initialize a demo camera platform."""
assert hass.loop.run_until_complete(
async_setup_component(hass, "camera", {camera.DOMAIN: {"platform": "demo"}})
assert await async_setup_component(
hass, "camera", {camera.DOMAIN: {"platform": "demo"}}
)
await hass.async_block_till_done()


@pytest.fixture
def mock_stream(hass):
async def mock_stream(hass):
"""Initialize a demo camera platform with streaming."""
assert hass.loop.run_until_complete(
async_setup_component(hass, "stream", {"stream": {}})
)
assert await async_setup_component(hass, "stream", {"stream": {}})
await hass.async_block_till_done()


def test_create_api_message_defaults(hass):
Expand Down
19 changes: 19 additions & 0 deletions tests/components/androidtv/test_media_player.py
Expand Up @@ -116,6 +116,7 @@ async def _test_reconnect(hass, caplog, config):
patch_key
], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER:
assert await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done()

await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id)
Expand Down Expand Up @@ -187,6 +188,7 @@ async def _test_adb_shell_returns_none(hass, config):
patch_key
], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER:
assert await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done()
await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id)
assert state is not None
Expand Down Expand Up @@ -295,6 +297,7 @@ async def test_setup_with_adbkey(hass):
patch_key
], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER, patchers.PATCH_ISFILE, patchers.PATCH_ACCESS:
assert await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done()
await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id)
assert state is not None
Expand All @@ -315,6 +318,7 @@ async def _test_sources(hass, config0):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done()
await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id)
assert state is not None
Expand Down Expand Up @@ -395,6 +399,7 @@ async def _test_exclude_sources(hass, config0, expected_sources):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done()
await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id)
assert state is not None
Expand Down Expand Up @@ -463,6 +468,7 @@ async def _test_select_source(hass, config0, source, expected_arg, method_patch)
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done()
await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id)
assert state is not None
Expand Down Expand Up @@ -666,6 +672,7 @@ async def _test_setup_fail(hass, config):
patch_key
], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER:
assert await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done()
await hass.helpers.entity_component.async_update_entity(entity_id)
state = hass.states.get(entity_id)
assert state is None
Expand Down Expand Up @@ -698,6 +705,7 @@ async def test_setup_two_devices(hass):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done()

for entity_id in ["media_player.android_tv", "media_player.fire_tv"]:
await hass.helpers.entity_component.async_update_entity(entity_id)
Expand All @@ -714,6 +722,7 @@ async def test_setup_same_device_twice(hass):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state is not None

Expand All @@ -723,6 +732,7 @@ async def test_setup_same_device_twice(hass):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER)
await hass.async_block_till_done()


async def test_adb_command(hass):
Expand All @@ -735,6 +745,7 @@ async def test_adb_command(hass):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER)
await hass.async_block_till_done()

with patch(
"androidtv.basetv.BaseTV.adb_shell", return_value=response
Expand Down Expand Up @@ -762,6 +773,7 @@ async def test_adb_command_unicode_decode_error(hass):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER)
await hass.async_block_till_done()

with patch(
"androidtv.basetv.BaseTV.adb_shell",
Expand Down Expand Up @@ -791,6 +803,7 @@ async def test_adb_command_key(hass):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER)
await hass.async_block_till_done()

with patch(
"androidtv.basetv.BaseTV.adb_shell", return_value=response
Expand Down Expand Up @@ -819,6 +832,7 @@ async def test_adb_command_get_properties(hass):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER)
await hass.async_block_till_done()

with patch(
"androidtv.androidtv.AndroidTV.get_properties_dict", return_value=response
Expand All @@ -844,6 +858,7 @@ async def test_update_lock_not_acquired(hass):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER)
await hass.async_block_till_done()

with patchers.patch_shell("")[patch_key]:
await hass.helpers.entity_component.async_update_entity(entity_id)
Expand Down Expand Up @@ -877,6 +892,7 @@ async def test_download(hass):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER)
await hass.async_block_till_done()

# Failed download because path is not whitelisted
with patch("androidtv.basetv.BaseTV.adb_pull") as patch_pull:
Expand Down Expand Up @@ -919,6 +935,7 @@ async def test_upload(hass):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER)
await hass.async_block_till_done()

# Failed upload because path is not whitelisted
with patch("androidtv.basetv.BaseTV.adb_push") as patch_push:
Expand Down Expand Up @@ -959,6 +976,7 @@ async def test_androidtv_volume_set(hass):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER)
await hass.async_block_till_done()

with patch(
"androidtv.basetv.BaseTV.set_volume_level", return_value=0.5
Expand All @@ -984,6 +1002,7 @@ async def test_get_image(hass, hass_ws_client):
patch_key
], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER)
await hass.async_block_till_done()

with patchers.patch_shell("11")[patch_key]:
await hass.helpers.entity_component.async_update_entity(entity_id)
Expand Down
9 changes: 9 additions & 0 deletions tests/components/bayesian/test_binary_sensor.py
Expand Up @@ -45,6 +45,7 @@ def test_load_values_when_added_to_hass(self):
self.hass.block_till_done()

assert setup_component(self.hass, "binary_sensor", config)
self.hass.block_till_done()

state = self.hass.states.get("binary_sensor.test_binary")
assert state.attributes.get("observations")[0]["prob_given_true"] == 0.8
Expand Down Expand Up @@ -75,6 +76,7 @@ def test_unknown_state_does_not_influence_probability(self):
self.hass.block_till_done()

assert setup_component(self.hass, "binary_sensor", config)
self.hass.block_till_done()

state = self.hass.states.get("binary_sensor.test_binary")
assert state.attributes.get("observations") == []
Expand Down Expand Up @@ -107,6 +109,7 @@ def test_sensor_numeric_state(self):
}

assert setup_component(self.hass, "binary_sensor", config)
self.hass.block_till_done()

self.hass.states.set("sensor.test_monitored", 4)
self.hass.block_till_done()
Expand Down Expand Up @@ -173,6 +176,7 @@ def test_sensor_state(self):
}

assert setup_component(self.hass, "binary_sensor", config)
self.hass.block_till_done()

self.hass.states.set("sensor.test_monitored", "on")

Expand Down Expand Up @@ -227,6 +231,7 @@ def test_sensor_value_template(self):
}

assert setup_component(self.hass, "binary_sensor", config)
self.hass.block_till_done()

self.hass.states.set("sensor.test_monitored", "on")

Expand Down Expand Up @@ -281,6 +286,7 @@ def test_threshold(self):
}

assert setup_component(self.hass, "binary_sensor", config)
self.hass.block_till_done()

self.hass.states.set("sensor.test_monitored", "on")
self.hass.block_till_done()
Expand Down Expand Up @@ -318,6 +324,7 @@ def test_multiple_observations(self):
}

assert setup_component(self.hass, "binary_sensor", config)
self.hass.block_till_done()

self.hass.states.set("sensor.test_monitored", "off")

Expand Down Expand Up @@ -401,6 +408,7 @@ def test_observed_entities(self):
}

assert setup_component(self.hass, "binary_sensor", config)
self.hass.block_till_done()

self.hass.states.set("sensor.test_monitored", "on")
self.hass.block_till_done()
Expand Down Expand Up @@ -452,6 +460,7 @@ def test_state_attributes_are_serializable(self):
}

assert setup_component(self.hass, "binary_sensor", config)
self.hass.block_till_done()

self.hass.states.set("sensor.test_monitored", "on")
self.hass.block_till_done()
Expand Down