66from types import TracebackType
77from typing import Any , Dict , List , Optional , Type , Union
88
9+ import backoff
910import humps
1011from aiohttp import ClientResponse , ClientSession
1112
2122API_URL = "https://tahomalink.com/enduser-mobile-web/enduserAPI/" # /doc for API doc
2223
2324
25+ async def relogin (invocation : Dict [str , Any ]) -> None :
26+ await invocation ["args" ][0 ].login ()
27+
28+
2429class TahomaClient :
2530 """ Interface class for the Tahoma API """
2631
@@ -64,11 +69,11 @@ async def __aexit__(
6469 async def close (self ) -> None :
6570 """Close the session."""
6671 if self .event_listener_id :
67- await self .unregister_event_listener (self . event_listener_id )
72+ await self .unregister_event_listener ()
6873
6974 await self .session .close ()
7075
71- async def login (self ) -> bool :
76+ async def login (self , register_event_listener : Optional [ bool ] = True ) -> bool :
7277 """
7378 Authenticate and create an API session allowing access to the other operations.
7479 Caller must provide one of [userId+userPassword, userId+ssoToken, accessToken, jwt]
@@ -77,10 +82,9 @@ async def login(self) -> bool:
7782 response = await self .__post ("login" , data = payload )
7883
7984 if response .get ("success" ):
80- self . __roles = response . get ( "roles" )
81-
85+ if register_event_listener :
86+ await self . register_event_listener ()
8287 return True
83-
8488 return False
8589
8690 async def get_devices (self , refresh : bool = False ) -> List [Device ]:
@@ -96,6 +100,9 @@ async def get_devices(self, refresh: bool = False) -> List[Device]:
96100
97101 return devices
98102
103+ @backoff .on_exception (
104+ backoff .expo , NotAuthenticatedException , max_tries = 2 , on_backoff = relogin
105+ )
99106 async def get_state (self , deviceurl : str ) -> List [State ]:
100107 """
101108 Retrieve states of requested device
@@ -123,40 +130,49 @@ async def register_event_listener(self) -> str:
123130
124131 return listener_id
125132
126- async def fetch_event_listener (self , listener_id : str ) -> List [Event ]:
133+ async def fetch_events (self ) -> List [Event ]:
127134 """
128135 Fetch new events from a registered event listener. Fetched events are removed
129136 from the listener buffer. Return an empty response if no event is available.
130137 Per-session rate-limit : 1 calls per 1 SECONDS period for this particular
131138 operation (polling)
132139 """
133- response = await self .__post (f"events/{ listener_id } /fetch" )
140+ response = await self .__post (f"events/{ self . event_listener_id } /fetch" )
134141 events = [Event (** e ) for e in humps .decamelize (response )]
135142
136143 return events
137144
138- async def unregister_event_listener (self , listener_id : str ) -> None :
145+ async def unregister_event_listener (self ) -> None :
139146 """
140147 Unregister an event listener.
141148 API response status is always 200, even on unknown listener ids.
142149 """
143- await self .__post (f"events/{ listener_id } /unregister" )
150+ await self .__post (f"events/{ self . event_listener_id } /unregister" )
144151 self .event_listener_id = None
145152
153+ @backoff .on_exception (
154+ backoff .expo , NotAuthenticatedException , max_tries = 2 , on_backoff = relogin
155+ )
146156 async def get_current_execution (self , exec_id : str ) -> Execution :
147157 """ Get an action group execution currently running """
148158 response = await self .__get (f"exec/current/{ exec_id } " )
149159 execution = Execution (** humps .decamelize (response ))
150160
151161 return execution
152162
163+ @backoff .on_exception (
164+ backoff .expo , NotAuthenticatedException , max_tries = 2 , on_backoff = relogin
165+ )
153166 async def get_current_executions (self ) -> List [Execution ]:
154167 """ Get all action groups executions currently running """
155168 response = await self .__get ("exec/current" )
156169 executions = [Execution (** e ) for e in humps .decamelize (response )]
157170
158171 return executions
159172
173+ @backoff .on_exception (
174+ backoff .expo , NotAuthenticatedException , max_tries = 2 , on_backoff = relogin
175+ )
160176 async def execute_command (
161177 self ,
162178 device_url : str ,
@@ -168,10 +184,16 @@ async def execute_command(
168184 command = Command (command )
169185 return await self .execute_commands (device_url , [command ], label )
170186
187+ @backoff .on_exception (
188+ backoff .expo , NotAuthenticatedException , max_tries = 2 , on_backoff = relogin
189+ )
171190 async def cancel_command (self , exec_id : str ) -> None :
172191 """ Cancel a running setup-level execution """
173192 await self .__delete (f"/exec/current/setup/{ exec_id } " )
174193
194+ @backoff .on_exception (
195+ backoff .expo , NotAuthenticatedException , max_tries = 2 , on_backoff = relogin
196+ )
175197 async def execute_commands (
176198 self ,
177199 device_url : str ,
@@ -186,11 +208,17 @@ async def execute_commands(
186208 response = await self .__post ("exec/apply" , payload )
187209 return response ["execId" ]
188210
211+ @backoff .on_exception (
212+ backoff .expo , NotAuthenticatedException , max_tries = 2 , on_backoff = relogin
213+ )
189214 async def get_scenarios (self ) -> List [Scenario ]:
190215 """ List the scenarios """
191216 response = await self .__get ("actionGroups" )
192217 return [Scenario (** scenario ) for scenario in response ]
193218
219+ @backoff .on_exception (
220+ backoff .expo , NotAuthenticatedException , max_tries = 2 , on_backoff = relogin
221+ )
194222 async def execute_scenario (self , oid : str ) -> str :
195223 """ Execute a scenario """
196224 response = await self .__post (f"exec/{ oid } " )
0 commit comments