11import json
22import logging
33import os
4- import time
54from threading import Thread
5+ import time
66from typing import Callable , Dict , Optional , Union
77
88from oauthlib .oauth2 import TokenExpiredError
99from requests import Response
10+ from requests .adapters import HTTPAdapter , Retry
11+ from requests .exceptions import RetryError
1012from requests_oauthlib import OAuth2Session
1113
1214from .sseclient import SSEClient
1618ENDPOINT_TOKEN = "/security/oauth/token"
1719ENDPOINT_APPLIANCES = "/api/homeappliances"
1820TIMEOUT_S = 120
21+ TOTAL_RETRIES = 1
22+
1923
2024LOGGER = logging .getLogger ("homeconnect" )
2125
@@ -52,6 +56,9 @@ def __init__(
5256 token = token ,
5357 token_updater = token_updater ,
5458 )
59+ self .retry = Retry (TOTAL_RETRIES , status_forcelist = [429 ])
60+ self ._oauth .mount ("https://" , HTTPAdapter (max_retries = self .retry ))
61+ self ._oauth .mount ("http://" , HTTPAdapter (max_retries = self .retry ))
5562
5663 def refresh_tokens (self ) -> Dict [str , Union [str , int ]]:
5764 """Refresh and return new tokens."""
@@ -77,6 +84,9 @@ def request(self, method: str, path: str, **kwargs) -> Response:
7784 self ._oauth .token = self .refresh_tokens ()
7885
7986 return getattr (self ._oauth , method )(url , ** kwargs )
87+ except RetryError as e :
88+ LOGGER .warning ("Retry failed: %s" , e )
89+ return e .response
8090
8191 def get (self , endpoint ):
8292 """Get data as dictionary from an endpoint."""
@@ -128,8 +138,7 @@ def delete(self, endpoint):
128138 return res
129139
130140 def get_appliances (self ):
131- """Return a list of `HomeConnectAppliance` instances for all
132- appliances."""
141+ """Return a list of `HomeConnectAppliance` instances for all appliances."""
133142
134143 appliances = {}
135144
@@ -150,7 +159,7 @@ def get_appliances(self):
150159 def get_authurl (self ):
151160 """Get the URL needed for the authorization code grant flow."""
152161 authorization_url , _ = self ._oauth .authorization_url (
153- f"{ self .host } / { ENDPOINT_AUTHORIZE } "
162+ f"{ self .host } { ENDPOINT_AUTHORIZE } "
154163 )
155164 return authorization_url
156165
@@ -184,15 +193,16 @@ def handle_event(self, event, appliance):
184193 """Handle a new event.
185194
186195 Updates the status with the event data and executes any callback
187- function."""
196+ function.
197+ """
188198 event_data = json .loads (event .data )
189199 items = event_data .get ("items" )
190200 if items is not None :
191201 data_dict = self .json2dict (items )
192202 else :
193203 data_dict = {event_data .pop ("key" ): event_data }
194204
195- if event .event == "NOTIFY" or event . event == "STATUS" :
205+ if event .event in ( "NOTIFY" , "STATUS" , "EVENT" ) :
196206 appliance .status .update (data_dict )
197207
198208 elif event .event == "CONNECTED" :
@@ -208,8 +218,11 @@ def handle_event(self, event, appliance):
208218
209219 @staticmethod
210220 def json2dict (lst ):
211- """Turn a list of dictionaries where one key is called 'key'
212- into a dictionary with the value of 'key' as key."""
221+ """Convert JSON to dictionary.
222+
223+ Turn a list of dictionaries where one key is called 'key'
224+ into a dictionary with the value of 'key' as key.
225+ """
213226 return {d .pop ("key" ): d for d in lst }
214227
215228
@@ -237,14 +250,23 @@ def token_dump(self, token):
237250 json .dump (token , f )
238251
239252 def token_load (self ):
240- """Load the token from the cache if exists it and is not expired,
241- otherwise return None."""
253+ """Load the token from the cache if exists and not expired."""
242254 if not os .path .exists (self .token_cache ):
243255 return None
244256 with open (self .token_cache , "r" ) as f :
245257 token = json .load (f )
246258 now = int (time .time ())
247259 token ["expires_in" ] = token .get ("expires_at" , now - 1 ) - now
260+ self ._oauth = OAuth2Session (
261+ client_id = self .client_id ,
262+ redirect_uri = self .redirect_uri ,
263+ auto_refresh_kwargs = {
264+ "client_id" : self .client_id ,
265+ "client_secret" : self .client_secret ,
266+ },
267+ token = token ,
268+ token_updater = self .token_updater ,
269+ )
248270 return token
249271
250272 def token_expired (self , token ):
@@ -253,11 +275,10 @@ def token_expired(self, token):
253275 return token ["expires_at" ] - now < 60
254276
255277 def get_token (self , authorization_response ):
256- """Get the token given the redirect URL obtained from the
257- authorization."""
278+ """Get the token given the redirect URL obtained from the authorization."""
258279 LOGGER .info ("Fetching token ..." )
259280 token = self ._oauth .fetch_token (
260- f"{ self .host } / { ENDPOINT_TOKEN } " ,
281+ f"{ self .host } { ENDPOINT_TOKEN } " ,
261282 authorization_response = authorization_response ,
262283 client_secret = self .client_secret ,
263284 )
@@ -291,7 +312,10 @@ def __init__(
291312 self .event_callback = None
292313
293314 def __repr__ (self ):
294- return "HomeConnectAppliance(hc, haId='{}', vib='{}', brand='{}', type='{}', name='{}', enumber='{}', connected={})" .format (
315+ return (
316+ "HomeConnectAppliance(hc, haId='{}', vib='{}', brand='{}'"
317+ ", type='{}', name='{}', enumber='{}', connected={})"
318+ ).format (
295319 self .haId ,
296320 self .vib ,
297321 self .brand ,
@@ -302,16 +326,19 @@ def __repr__(self):
302326 )
303327
304328 def listen_events (self , callback = None ):
305- """Register event callback method"""
329+ """Register event callback method. """
306330 self .event_callback = callback
307331
308332 if not self .hc .listening_events :
309333 self .hc .listen_events ()
310334
311335 @staticmethod
312336 def json2dict (lst ):
313- """Turn a list of dictionaries where one key is called 'key'
314- into a dictionary with the value of 'key' as key."""
337+ """Convert JSON to dictionary.
338+
339+ Turn a list of dictionaries where one key is called 'key'
340+ into a dictionary with the value of 'key' as key.
341+ """
315342 return {d .pop ("key" ): d for d in lst }
316343
317344 def get (self , endpoint ):
0 commit comments