From d3cb1ee2a0ea535d108c02b45899662c6a7fd0df Mon Sep 17 00:00:00 2001 From: Thomas Hahn Date: Sat, 22 Feb 2025 21:21:07 +0100 Subject: [PATCH] Refactor async initialization methods and update README examples --- README.md | 40 ++++++------- bin/homematicip_cli_async.py | 2 +- src/homematicip/async_home.py | 6 +- .../cli/hmip_generate_auth_token.py | 2 +- .../connection/connection_context.py | 59 ++++++++++--------- .../connection/connection_url_resolver.py | 33 ++++++++++- src/homematicip/home.py | 3 + tests/test_home.py | 2 +- 8 files changed, 90 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 33bf19b9..8ee50a17 100644 --- a/README.md +++ b/README.md @@ -260,26 +260,26 @@ It’s also possible to use push notifications based on a websocket connection: ```python # Example function to display incoming events. - def print_events(event_list): - for event in event_list: - print("EventType: {} Data: {}".format(event["eventType"], event["data"])) - - - # Initialise the API. - config = homematicip.find_and_load_config_file() - home = Home() - home.set_auth_token(config.auth_token) - home.init(config.access_point) - - # Add function to handle events and start the connection. - home.onEvent += print_events - home.enable_events() - - try: - while True: - time.sleep(1) - except KeyboardInterrupt: - print("Interrupt.") +def print_events(event_list): + for event in event_list: + print("EventType: {} Data: {}".format(event["eventType"], event["data"])) + + +# Initialise the API. +config = homematicip.find_and_load_config_file() +home = Home() +home.set_auth_token(config.auth_token) +home.init(config.access_point) + +# Add function to handle events and start the connection. +home.onEvent += print_events +home.enable_events() + +try: + while True: + time.sleep(1) +except KeyboardInterrupt: + print("Interrupt.") ``` ## Pathes for config.ini diff --git a/bin/homematicip_cli_async.py b/bin/homematicip_cli_async.py index a49615a8..188bd480 100644 --- a/bin/homematicip_cli_async.py +++ b/bin/homematicip_cli_async.py @@ -31,7 +31,7 @@ async def get_home(loop): home = AsyncHome(loop) home.set_auth_token(_config.auth_token) - await home.init(_config.access_point) + await home.init_async(_config.access_point) return home diff --git a/src/homematicip/async_home.py b/src/homematicip/async_home.py index 8809d379..d29fc140 100644 --- a/src/homematicip/async_home.py +++ b/src/homematicip/async_home.py @@ -1,4 +1,3 @@ - from typing import List, Callable from homematicip.EventHook import * @@ -74,14 +73,15 @@ def __init__(self, connection=None): self.rules = [] self.functionalHomes = [] - def init(self, access_point_id, auth_token: str | None = None, lookup=True, use_rate_limiting=True): + async def init_async(self, access_point_id, auth_token: str | None = None, lookup=True, use_rate_limiting=True): """Initializes the home with the given access point id and auth token :param access_point_id: the access point id :param auth_token: the auth token :param lookup: if set to true, the urls will be looked up :param use_rate_limiting: if set to true, the connection will be rate limited """ - self._connection_context = ConnectionContextBuilder.build_context(accesspoint_id=access_point_id, auth_token=auth_token) + self._connection_context = await ConnectionContextBuilder.build_context_async(accesspoint_id=access_point_id, + auth_token=auth_token) self._connection = ConnectionFactory.create_connection(self._connection_context, use_rate_limiting) def init_with_context(self, context: ConnectionContext, use_rate_limiting=True): diff --git a/src/homematicip/cli/hmip_generate_auth_token.py b/src/homematicip/cli/hmip_generate_auth_token.py index a2f3e7fa..6e8a2ef9 100644 --- a/src/homematicip/cli/hmip_generate_auth_token.py +++ b/src/homematicip/cli/hmip_generate_auth_token.py @@ -21,7 +21,7 @@ async def run_auth(access_point: str = None, devicename: str = None, pin: str = continue break - context = ConnectionContextBuilder.build_context(access_point) + context = await ConnectionContextBuilder.build_context_async(access_point) connection = RestConnection(context, log_status_exceptions=False) auth = homematicip.auth.Auth(connection, context.client_auth_token, access_point) diff --git a/src/homematicip/connection/connection_context.py b/src/homematicip/connection/connection_context.py index e7d1f56a..a0391ecf 100644 --- a/src/homematicip/connection/connection_context.py +++ b/src/homematicip/connection/connection_context.py @@ -8,11 +8,11 @@ class ConnectionContextBuilder: @classmethod - def build_context(cls, accesspoint_id: str, - lookup_url: str = "https://lookup.homematic.com:48335/getHost", - auth_token: str = None, - enforce_ssl: bool = True, - ssl_ctx=None): + async def build_context_async(cls, accesspoint_id: str, + lookup_url: str = "https://lookup.homematic.com:48335/getHost", + auth_token: str = None, + enforce_ssl: bool = True, + ssl_ctx=None): """ Create a new connection context and lookup urls @@ -30,36 +30,24 @@ def build_context(cls, accesspoint_id: str, ctx.enforce_ssl = enforce_ssl cc = ClientCharacteristicsBuilder.get(accesspoint_id) - ctx.rest_url, ctx.websocket_url = ConnectionUrlResolver().lookup_urls(cc, lookup_url, enforce_ssl, ssl_ctx) + ctx.rest_url, ctx.websocket_url = await ConnectionUrlResolver().lookup_urls_async(cc, lookup_url, enforce_ssl, + ssl_ctx) if auth_token is not None: ctx.auth_token = auth_token return ctx - -@dataclass -class ConnectionContext: - auth_token: str = None - client_auth_token: str = None - - websocket_url: str = "ws://localhost:8765" - rest_url: str = None - accesspoint_id: str = None - - enforce_ssl: bool = True - ssl_ctx = None - @classmethod - def create(cls, access_point_id: str, - lookup_url: str = "https://lookup.homematic.com:48335/getHost", - auth_token: str = None, - enforce_ssl: bool = True, - ssl_ctx=None): + def build_context(cls, accesspoint_id: str, + lookup_url: str = "https://lookup.homematic.com:48335/getHost", + auth_token: str = None, + enforce_ssl: bool = True, + ssl_ctx=None): """ - Create a new connection context. + Create a new connection context and lookup urls - :param access_point_id: Access point id + :param accesspoint_id: Access point id :param lookup_url: Url to lookup the connection urls :param auth_token: The Auth Token if exists. If no one is provided None will be used :param enforce_ssl: Disable ssl verification by setting enforce_ssl to False @@ -67,15 +55,28 @@ def create(cls, access_point_id: str, :return: a new ConnectionContext """ ctx = ConnectionContext() - ctx.accesspoint_id = access_point_id - ctx.client_auth_token = ClientTokenBuilder.build_client_token(access_point_id) + ctx.accesspoint_id = accesspoint_id + ctx.client_auth_token = ClientTokenBuilder.build_client_token(accesspoint_id) ctx.ssl_ctx = ssl_ctx ctx.enforce_ssl = enforce_ssl - cc = ClientCharacteristicsBuilder.get(access_point_id) + cc = ClientCharacteristicsBuilder.get(accesspoint_id) ctx.rest_url, ctx.websocket_url = ConnectionUrlResolver().lookup_urls(cc, lookup_url, enforce_ssl, ssl_ctx) if auth_token is not None: ctx.auth_token = auth_token return ctx + + +@dataclass +class ConnectionContext: + auth_token: str = None + client_auth_token: str = None + + websocket_url: str = "ws://localhost:8765" + rest_url: str = None + accesspoint_id: str = None + + enforce_ssl: bool = True + ssl_ctx = None diff --git a/src/homematicip/connection/connection_url_resolver.py b/src/homematicip/connection/connection_url_resolver.py index 1daebf32..b6aeacc0 100644 --- a/src/homematicip/connection/connection_url_resolver.py +++ b/src/homematicip/connection/connection_url_resolver.py @@ -6,12 +6,41 @@ class ConnectionUrlResolver: """Lookup rest and websocket urls.""" + @staticmethod + async def lookup_urls_async( + client_characteristics: dict, + lookup_url: str, + enforce_ssl: bool = True, + ssl_context: SSLContext = None, + ) -> tuple[str, str]: + """Lookup urls async. + + :param client_characteristics: The client characteristics + :param lookup_url: The lookup url + :param enforce_ssl: Disable ssl verification by setting enforce_ssl to False + :param ssl_context: The ssl context + + :return: The rest and websocket url as tuple + """ + verify = ConnectionUrlResolver._get_verify(enforce_ssl, ssl_context) + + async with httpx.AsyncClient(verify=verify) as client: + result = await client.post(lookup_url, json=client_characteristics) + result.raise_for_status() + + js = result.json() + + rest_url = js["urlREST"] + websocket_url = js["urlWebSocket"] + + return rest_url, websocket_url + @staticmethod def lookup_urls( client_characteristics: dict, lookup_url: str, enforce_ssl: bool = True, - ssl_context = None, + ssl_context=None, ) -> tuple[str, str]: """Lookup urls. @@ -40,4 +69,4 @@ def _get_verify(enforce_ssl: bool, ssl_context): if enforce_ssl: return enforce_ssl - return True \ No newline at end of file + return True diff --git a/src/homematicip/home.py b/src/homematicip/home.py index eb49d476..3f997ccd 100644 --- a/src/homematicip/home.py +++ b/src/homematicip/home.py @@ -8,6 +8,9 @@ class Home(AsyncHome): """this class represents the 'Home' of the homematic ip""" + def init(self, access_point_id, auth_token: str | None = None, lookup=True, use_rate_limiting=True): + return self._run_non_async(self.init_async, access_point_id, auth_token, lookup, use_rate_limiting) + def activate_absence_permanent(self): return self._run_non_async(self.activate_absence_permanent_async) diff --git a/tests/test_home.py b/tests/test_home.py index 34a64711..ac4492aa 100644 --- a/tests/test_home.py +++ b/tests/test_home.py @@ -19,7 +19,7 @@ def test_init(): context = ConnectionContext(auth_token="auth_token", accesspoint_id="access_point_id") - with patch('homematicip.connection.connection_context.ConnectionContextBuilder.build_context', + with patch('homematicip.connection.connection_context.ConnectionContextBuilder.build_context_async', return_value=context) as mock_create: home = Home() home.init('access_point_id', 'auth_token')