Skip to content

Sync Client

sync

Synchronous convenience wrapper around HAClient.

The wrapper runs a dedicated event loop in a background thread and submits coroutines to it via asyncio.run_coroutine_threadsafe. This allows users in plain scripts / REPL sessions to consume the library without thinking about asyncio.

Examples:

::

from haclient import SyncHAClient

with SyncHAClient.from_url("http://localhost:8123", token=TOKEN) as ha:
    light = ha.light("kitchen")
    light.set_brightness(200)

SyncHAClient

Synchronous counterpart of HAClient.

Parameters:

Name Type Description Default
config ConnectionConfig

Resolved connection settings.

required
Source code in src/haclient/sync.py
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
class SyncHAClient:
    """Synchronous counterpart of `HAClient`.

    Parameters
    ----------
    config : ConnectionConfig
        Resolved connection settings.
    """

    def __init__(
        self,
        config: ConnectionConfig,
        **kwargs: Any,
    ) -> None:
        self._loop_thread = _LoopThread()

        async def _build() -> HAClient:
            return HAClient(config, **kwargs)

        self._client: HAClient = self._loop_thread.submit(_build())

    @classmethod
    def from_url(
        cls,
        base_url: str,
        *,
        token: str,
        ws_url: str | None = None,
        reconnect: bool = True,
        ping_interval: float = 30.0,
        request_timeout: float = 30.0,
        verify_ssl: bool = True,
        service_policy: ServicePolicy = "auto",
        domains: list[str] | None = None,
        load_plugins: bool = True,
    ) -> SyncHAClient:
        """Build a `SyncHAClient` from a base URL and token.

        Parameters
        ----------
        base_url : str
            Home Assistant base URL.
        token : str
            Long-lived access token.
        ws_url : str or None, optional
            Explicit WebSocket URL (derived from *base_url* when omitted).
        reconnect : bool, optional
            Whether the WebSocket should reconnect automatically.
        ping_interval : float, optional
            Seconds between WebSocket keepalive pings.
        request_timeout : float, optional
            Default timeout for individual requests.
        verify_ssl : bool, optional
            Verify TLS certificates.
        service_policy : ServicePolicy, optional
            Default service-call routing policy.
        domains : list of str or None, optional
            Restrict loaded domains. ``None`` loads all registered
            domains.
        load_plugins : bool, optional
            Discover third-party plugins via the ``haclient.domains``
            entry-point group.

        Returns
        -------
        SyncHAClient
            The configured sync client (not yet connected).
        """
        config = ConnectionConfig.from_url(
            base_url,
            token,
            ws_url=ws_url,
            reconnect=reconnect,
            ping_interval=ping_interval,
            request_timeout=request_timeout,
            verify_ssl=verify_ssl,
            service_policy=service_policy,
        )
        return cls(config, domains=domains, load_plugins=load_plugins)

    @property
    def client(self) -> HAClient:
        """Return the underlying `HAClient` instance."""
        return self._client

    def connect(self) -> None:
        """Connect the underlying client."""
        self._loop_thread.submit(self._client.connect())

    def close(self) -> None:
        """Close the underlying client and stop the background loop."""
        try:
            self._loop_thread.submit(self._client.close())
        finally:
            self._loop_thread.stop()

    def __enter__(self) -> SyncHAClient:
        """Enter the sync context manager by calling `connect`."""
        self.connect()
        return self

    def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
        """Exit the sync context manager by calling `close`."""
        self.close()

    def refresh_all(self) -> None:
        """Refresh all registered entities synchronously."""
        self._loop_thread.submit(self._client.state.refresh_all())

    def on_reconnect(
        self, handler: Callable[[], Awaitable[None] | None]
    ) -> Callable[[], Awaitable[None] | None]:
        """Register a reconnect listener.

        Parameters
        ----------
        handler : callable
            Sync or async zero-argument callable invoked after the
            WebSocket reconnects.

        Returns
        -------
        callable
            The same *handler*, returned so the method can be used as a
            decorator.
        """
        return self._client.on_reconnect(handler)

    def on_disconnect(
        self, handler: Callable[[], Awaitable[None] | None]
    ) -> Callable[[], Awaitable[None] | None]:
        """Register a disconnect listener.

        Parameters
        ----------
        handler : callable
            Sync or async zero-argument callable invoked when the
            WebSocket connection drops.

        Returns
        -------
        callable
            The same *handler*, returned so the method can be used as a
            decorator.
        """
        return self._client.on_disconnect(handler)

    # -- Domain accessors --

    def domain(self, name: str) -> _SyncDomainAccessor:
        """Return a sync accessor for *name*.

        Parameters
        ----------
        name : str
            HA domain name (e.g. ``"light"`` or a third-party domain).

        Returns
        -------
        _SyncDomainAccessor
            Blocking accessor wrapping the underlying async accessor.
        """
        return _SyncDomainAccessor(self._client.domain(name), self._loop_thread)

    def __getattr__(self, name: str) -> _SyncDomainAccessor:
        """Return a sync domain accessor for any registered domain.

        Enables ``ha.light("kitchen")``, ``ha.scene.create(...)``, etc.
        identically to `HAClient.__getattr__`, but wrapping everything
        in blocking proxies.

        Parameters
        ----------
        name : str
            The domain or accessor name.

        Returns
        -------
        _SyncDomainAccessor
            Blocking accessor wrapping the underlying async accessor.

        Raises
        ------
        AttributeError
            If *name* does not match any active domain.
        """
        # Delegate to HAClient.__getattr__ which returns a DomainAccessor
        # or raises AttributeError.
        accessor = getattr(self._client, name)
        return _SyncDomainAccessor(accessor, self._loop_thread)

client property

client: HAClient

Return the underlying HAClient instance.

from_url classmethod

from_url(base_url: str, *, token: str, ws_url: str | None = None, reconnect: bool = True, ping_interval: float = 30.0, request_timeout: float = 30.0, verify_ssl: bool = True, service_policy: ServicePolicy = 'auto', domains: list[str] | None = None, load_plugins: bool = True) -> SyncHAClient

Build a SyncHAClient from a base URL and token.

Parameters:

Name Type Description Default
base_url str

Home Assistant base URL.

required
token str

Long-lived access token.

required
ws_url str or None

Explicit WebSocket URL (derived from base_url when omitted).

None
reconnect bool

Whether the WebSocket should reconnect automatically.

True
ping_interval float

Seconds between WebSocket keepalive pings.

30.0
request_timeout float

Default timeout for individual requests.

30.0
verify_ssl bool

Verify TLS certificates.

True
service_policy ServicePolicy

Default service-call routing policy.

'auto'
domains list of str or None

Restrict loaded domains. None loads all registered domains.

None
load_plugins bool

Discover third-party plugins via the haclient.domains entry-point group.

True

Returns:

Type Description
SyncHAClient

The configured sync client (not yet connected).

Source code in src/haclient/sync.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
@classmethod
def from_url(
    cls,
    base_url: str,
    *,
    token: str,
    ws_url: str | None = None,
    reconnect: bool = True,
    ping_interval: float = 30.0,
    request_timeout: float = 30.0,
    verify_ssl: bool = True,
    service_policy: ServicePolicy = "auto",
    domains: list[str] | None = None,
    load_plugins: bool = True,
) -> SyncHAClient:
    """Build a `SyncHAClient` from a base URL and token.

    Parameters
    ----------
    base_url : str
        Home Assistant base URL.
    token : str
        Long-lived access token.
    ws_url : str or None, optional
        Explicit WebSocket URL (derived from *base_url* when omitted).
    reconnect : bool, optional
        Whether the WebSocket should reconnect automatically.
    ping_interval : float, optional
        Seconds between WebSocket keepalive pings.
    request_timeout : float, optional
        Default timeout for individual requests.
    verify_ssl : bool, optional
        Verify TLS certificates.
    service_policy : ServicePolicy, optional
        Default service-call routing policy.
    domains : list of str or None, optional
        Restrict loaded domains. ``None`` loads all registered
        domains.
    load_plugins : bool, optional
        Discover third-party plugins via the ``haclient.domains``
        entry-point group.

    Returns
    -------
    SyncHAClient
        The configured sync client (not yet connected).
    """
    config = ConnectionConfig.from_url(
        base_url,
        token,
        ws_url=ws_url,
        reconnect=reconnect,
        ping_interval=ping_interval,
        request_timeout=request_timeout,
        verify_ssl=verify_ssl,
        service_policy=service_policy,
    )
    return cls(config, domains=domains, load_plugins=load_plugins)

connect

connect() -> None

Connect the underlying client.

Source code in src/haclient/sync.py
263
264
265
def connect(self) -> None:
    """Connect the underlying client."""
    self._loop_thread.submit(self._client.connect())

close

close() -> None

Close the underlying client and stop the background loop.

Source code in src/haclient/sync.py
267
268
269
270
271
272
def close(self) -> None:
    """Close the underlying client and stop the background loop."""
    try:
        self._loop_thread.submit(self._client.close())
    finally:
        self._loop_thread.stop()

__enter__

__enter__() -> SyncHAClient

Enter the sync context manager by calling connect.

Source code in src/haclient/sync.py
274
275
276
277
def __enter__(self) -> SyncHAClient:
    """Enter the sync context manager by calling `connect`."""
    self.connect()
    return self

__exit__

__exit__(exc_type: Any, exc: Any, tb: Any) -> None

Exit the sync context manager by calling close.

Source code in src/haclient/sync.py
279
280
281
def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
    """Exit the sync context manager by calling `close`."""
    self.close()

refresh_all

refresh_all() -> None

Refresh all registered entities synchronously.

Source code in src/haclient/sync.py
283
284
285
def refresh_all(self) -> None:
    """Refresh all registered entities synchronously."""
    self._loop_thread.submit(self._client.state.refresh_all())

on_reconnect

on_reconnect(handler: Callable[[], Awaitable[None] | None]) -> Callable[[], Awaitable[None] | None]

Register a reconnect listener.

Parameters:

Name Type Description Default
handler callable

Sync or async zero-argument callable invoked after the WebSocket reconnects.

required

Returns:

Type Description
callable

The same handler, returned so the method can be used as a decorator.

Source code in src/haclient/sync.py
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
def on_reconnect(
    self, handler: Callable[[], Awaitable[None] | None]
) -> Callable[[], Awaitable[None] | None]:
    """Register a reconnect listener.

    Parameters
    ----------
    handler : callable
        Sync or async zero-argument callable invoked after the
        WebSocket reconnects.

    Returns
    -------
    callable
        The same *handler*, returned so the method can be used as a
        decorator.
    """
    return self._client.on_reconnect(handler)

on_disconnect

on_disconnect(handler: Callable[[], Awaitable[None] | None]) -> Callable[[], Awaitable[None] | None]

Register a disconnect listener.

Parameters:

Name Type Description Default
handler callable

Sync or async zero-argument callable invoked when the WebSocket connection drops.

required

Returns:

Type Description
callable

The same handler, returned so the method can be used as a decorator.

Source code in src/haclient/sync.py
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
def on_disconnect(
    self, handler: Callable[[], Awaitable[None] | None]
) -> Callable[[], Awaitable[None] | None]:
    """Register a disconnect listener.

    Parameters
    ----------
    handler : callable
        Sync or async zero-argument callable invoked when the
        WebSocket connection drops.

    Returns
    -------
    callable
        The same *handler*, returned so the method can be used as a
        decorator.
    """
    return self._client.on_disconnect(handler)

domain

domain(name: str) -> _SyncDomainAccessor

Return a sync accessor for name.

Parameters:

Name Type Description Default
name str

HA domain name (e.g. "light" or a third-party domain).

required

Returns:

Type Description
_SyncDomainAccessor

Blocking accessor wrapping the underlying async accessor.

Source code in src/haclient/sync.py
327
328
329
330
331
332
333
334
335
336
337
338
339
340
def domain(self, name: str) -> _SyncDomainAccessor:
    """Return a sync accessor for *name*.

    Parameters
    ----------
    name : str
        HA domain name (e.g. ``"light"`` or a third-party domain).

    Returns
    -------
    _SyncDomainAccessor
        Blocking accessor wrapping the underlying async accessor.
    """
    return _SyncDomainAccessor(self._client.domain(name), self._loop_thread)

__getattr__

__getattr__(name: str) -> _SyncDomainAccessor

Return a sync domain accessor for any registered domain.

Enables ha.light("kitchen"), ha.scene.create(...), etc. identically to HAClient.__getattr__, but wrapping everything in blocking proxies.

Parameters:

Name Type Description Default
name str

The domain or accessor name.

required

Returns:

Type Description
_SyncDomainAccessor

Blocking accessor wrapping the underlying async accessor.

Raises:

Type Description
AttributeError

If name does not match any active domain.

Source code in src/haclient/sync.py
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
def __getattr__(self, name: str) -> _SyncDomainAccessor:
    """Return a sync domain accessor for any registered domain.

    Enables ``ha.light("kitchen")``, ``ha.scene.create(...)``, etc.
    identically to `HAClient.__getattr__`, but wrapping everything
    in blocking proxies.

    Parameters
    ----------
    name : str
        The domain or accessor name.

    Returns
    -------
    _SyncDomainAccessor
        Blocking accessor wrapping the underlying async accessor.

    Raises
    ------
    AttributeError
        If *name* does not match any active domain.
    """
    # Delegate to HAClient.__getattr__ which returns a DomainAccessor
    # or raises AttributeError.
    accessor = getattr(self._client, name)
    return _SyncDomainAccessor(accessor, self._loop_thread)