def async_track_point_in_utc_time(opp, action, point_in_time): """Add a listener that fires once after a specific point in UTC time.""" # Ensure point_in_time is UTC point_in_time = event.dt_util.as_utc(point_in_time) # Since this is called once, we accept a OppJob so we can avoid # having to figure out how to call the action every time its called. job = action if isinstance(action, ha.OppJob) else ha.OppJob(action) @ha.callback def point_in_time_listener(event): """Listen for matching time_changed events.""" now = event.data[ATTR_NOW] if now < point_in_time or hasattr(point_in_time_listener, "run"): return # Set variable so that we will never run twice. # Because the event bus might have to wait till a thread comes # available to execute this listener it might occur that the # listener gets lined up twice to be executed. This will make # sure the second time it does nothing. setattr(point_in_time_listener, "run", True) async_unsub() opp.async_run_opp_job(job, now) async_unsub = opp.bus.async_listen(EVENT_TIME_CHANGED, point_in_time_listener) return async_unsub
def async_listen_platform( opp: core.OpenPeerPower, component: str, callback: Callable[[str, dict[str, Any] | None], Any], ) -> None: """Register a platform loader listener. This method must be run in the event loop. """ service = EVENT_LOAD_PLATFORM.format(component) job = core.OppJob(callback) async def discovery_platform_listener(discovered: DiscoveryDict) -> None: """Listen for platform discovery events.""" platform = discovered["platform"] if not platform: return task = opp.async_run_opp_job(job, platform, discovered.get("discovered")) if task: await task async_dispatcher_connect(opp, SIGNAL_PLATFORM_DISCOVERED.format(service), discovery_platform_listener)
def async_track_utc_time_change( opp, action, hour=None, minute=None, second=None, local=False ): """Add a listener that will fire if time matches a pattern.""" job = ha.OppJob(action) # We do not have to wrap the function with time pattern matching logic # if no pattern given if all(val is None for val in (hour, minute, second)): @ha.callback def time_change_listener(ev) -> None: """Fire every time event that comes in.""" opp.async_run_opp_job(job, ev.data[ATTR_NOW]) return opp.bus.async_listen(EVENT_TIME_CHANGED, time_change_listener) matching_seconds = event.dt_util.parse_time_expression(second, 0, 59) matching_minutes = event.dt_util.parse_time_expression(minute, 0, 59) matching_hours = event.dt_util.parse_time_expression(hour, 0, 23) next_time = None def calculate_next(now) -> None: """Calculate and set the next time the trigger should fire.""" nonlocal next_time localized_now = event.dt_util.as_local(now) if local else now next_time = event.dt_util.find_next_time_expression_time( localized_now, matching_seconds, matching_minutes, matching_hours ) # Make sure rolling back the clock doesn't prevent the timer from # triggering. last_now = None @ha.callback def pattern_time_change_listener(ev) -> None: """Listen for matching time_changed events.""" nonlocal next_time, last_now now = ev.data[ATTR_NOW] if last_now is None or now < last_now: # Time rolled back or next time not yet calculated calculate_next(now) last_now = now if next_time <= now: opp.async_run_opp_job( job, event.dt_util.as_local(now) if local else now ) calculate_next(now + datetime.timedelta(seconds=1)) # We can't use async_track_point_in_utc_time here because it would # break in the case that the system time abruptly jumps backwards. # Our custom last_now logic takes care of resolving that scenario. return opp.bus.async_listen(EVENT_TIME_CHANGED, pattern_time_change_listener)
def test_async_add_opp_job_schedule_callback(): """Test that we schedule coroutines and add jobs to the job pool.""" opp = MagicMock() job = MagicMock() ha.OpenPeerPower.async_add_opp_job(opp, ha.OppJob(ha.callback(job))) assert len(opp.loop.call_soon.mock_calls) == 1 assert len(opp.loop.create_task.mock_calls) == 0 assert len(opp.add_job.mock_calls) == 0
def test_async_add_opp_job_schedule_partial_callback(): """Test that we schedule partial coros and add jobs to the job pool.""" opp = MagicMock() job = MagicMock() partial = functools.partial(ha.callback(job)) ha.OpenPeerPower.async_add_opp_job(opp, ha.OppJob(partial)) assert len(opp.loop.call_soon.mock_calls) == 1 assert len(opp.loop.create_task.mock_calls) == 0 assert len(opp.add_job.mock_calls) == 0
def test_async_add_opp_job_schedule_coroutinefunction(loop): """Test that we schedule coroutines and add jobs to the job pool.""" opp = MagicMock(loop=MagicMock(wraps=loop)) async def job(): pass ha.OpenPeerPower.async_add_opp_job(opp, ha.OppJob(job)) assert len(opp.loop.call_soon.mock_calls) == 0 assert len(opp.loop.create_task.mock_calls) == 1 assert len(opp.add_job.mock_calls) == 0
def test_async_run_opp_job_delegates_non_async(): """Test that the callback annotation is respected.""" opp = MagicMock() calls = [] def job(): calls.append(1) ha.OpenPeerPower.async_run_opp_job(opp, ha.OppJob(job)) assert len(calls) == 0 assert len(opp.async_add_opp_job.mock_calls) == 1
def test_async_add_job_add_opp_threaded_job_to_pool(): """Test that we schedule coroutines and add jobs to the job pool.""" opp = MagicMock() def job(): pass ha.OpenPeerPower.async_add_opp_job(opp, ha.OppJob(job)) assert len(opp.loop.call_soon.mock_calls) == 0 assert len(opp.loop.create_task.mock_calls) == 0 assert len(opp.loop.run_in_executor.mock_calls) == 1
async def test_oppjob_forbid_coroutine(): """Test oppjob forbids coroutines.""" async def bla(): pass coro = bla() with pytest.raises(ValueError): ha.OppJob(coro) # To avoid warning about unawaited coro await coro
def async_listen( opp: core.OpenPeerPower, service: str, callback: CALLBACK_TYPE, ) -> None: """Set up listener for discovery of specific service. Service can be a string or a list/tuple. """ job = core.OppJob(callback) async def discovery_event_listener(discovered: DiscoveryDict) -> None: """Listen for discovery events.""" task = opp.async_run_opp_job(job, discovered["service"], discovered["discovered"]) if task: await task async_dispatcher_connect(opp, SIGNAL_PLATFORM_DISCOVERED.format(service), discovery_event_listener)