Await returns coroutine instead of result

I am trying to create an integration that is a little like Sun but wraps a simple and synchronous python library which does some IO. Here is my pseudocode:

async def async_setup(hass: core.HomeAssistant, config: dict) -> bool:
    """Set up the component."""
    # @TODO: Add setup code.
    Lupt(hass)
    return True


class Lupt(Entity):

    entity_id = ENTITY_ID

    async def async_init_timetable(self):
        config = lupt_config.load_config(None)
        tt = await self.hass.async_add_executor_job(lupt_cache.init_timetable(config))
        return tt

    def __init__(self, hass):
        """Initialise lupt."""
        self.hass = hass

        try:
            self.timetable = lupt_cache.load_timetable()
        except Exception:
            self.timetable = self.async_init_timetable()

        self.async_write_ha_state()

I’ve taken the await line from https://developers.home-assistant.io/docs/asyncio_working_with_async#calling-sync-functions-from-async

I initially had the await line in the exception handler but kept getting a “await called outside of async function” error. So I made it an async function. However, async_add_executor_job returns a coroutine instead of the timetable I’m expecting.

I suspect I’m falling in and out of an async context somewhere but I can’t figure out why, as I presume async_setup is the entry point.

The reason why I want to use async is because eventually I will be scheduling callbacks to update state using event.async_track_point_in_utc_time (a little like Sun, from where I’m getting most of my inspiration).

Second minimal try:

async def async_setup(hass: core.HomeAssistant, config: dict) -> bool:
    # @TODO: Add setup code.
    lupt = Lupt(hass)
    await lupt.async_init()
    return True


class Lupt(Entity):
    def __init__(self, hass):
        """Initialise lupt."""
        self.hass = hass

    async def async_init(self):
        config = lupt_config.load_config(None)
        self.timetable = await self.hass.async_add_executor_job(lupt_cache.init_timetable(config))

        await self.async_write_ha_state()

    @property
     def state(self):
         return "Hello world!"

Amusingly I am getting:

RuntimeError: I/O must be done in the executor; Use `await hass.async_add_executor_job()` at custom_components/lupt/__init__.py, line 47: self.timetable = await self.hass.async_add_executor_job(lupt_cache.init_timetable(config))

Which is patently not true so I’m not quite sure where else to go from here.

A friendly dev on Discord spotted my mistake straight away. The issue is that hass.async_add_executor_job needs a function and my code was actually executing the function I was trying to pass. hass.async_add_executor_job allows the caller to pass a parameter list:

self.timetable = await self.hass.async_add_executor_job(lupt_cache.init_timetable, config)

or you can use my preference (can define non anoymously if you prefer):

self.timetable = await self.hass.async_add_executor_job(lambda: lupt_cache.init_timetable(config))
1 Like

Im also building a component where I use a Synch call to write data to a cloud DB. And Im getting the same error when calling the self.client.ingest_from_stream. I cant figure out what the “target” parameter is for in await hass.async_add_executor_job, and what I need to do to provide it(I imagine “none” is not enough) or what I can do to get this to work

Error:

I/O must be done in the executor; Use `await hass.async_add_executor_job()

async def test_connection(self) -> None:
        """Making synch call asynch"""
        await self.hass.async_add_executor_job(self.test_connection_synch(), None)
        return None

def test_connection_synch(self) -> None:
        """Test connection, will throw Exception when it cannot connect."""

        ingestion_properties = IngestionProperties(
            database=self.database,
            table=self.table,
            data_format=DataFormat.CSV,
        )

        csvStr = "2019-05-02 15:23:50.0369439,1,2,3,4"

        try:
            bytes_stream = io.StringIO(csvStr)
            stream_descriptor = StreamDescriptor(bytes_stream)

            self.client.ingest_from_stream(
                stream_descriptor, ingestion_properties=ingestion_properties
            )

        except Exception as e:
            return Exception
        return None

Found that I needed to remove the () so I pass the function, not execute it. but now I get this error:

‘function’ object has no attribute ‘loop’

Traceback (most recent call last):
File “/workspaces/core/homeassistant/components/azure_data_explorer/client.py”, line 77, in test_connection
await self.hass.async_add_executor_job(self.test_connection_synch, None)
File “/workspaces/core/homeassistant/core.py”, line 430, in async_add_executor_job
task = self.loop.run_in_executor(None, target, *args)
2022-01-12 16:06:50 ERROR (MainThread) [homeassistant.components.azure_data_explorer.client] ‘function’ object has no attribute ‘loop’
AttributeError: ‘function’ object has no attribute ‘loop’

I guees its back to setting target to None??