11""" Python wrapper for the Tahoma API """
22import urllib .parse
3- from typing import Any , List , Optional
3+ from typing import Any , Dict , List , Optional , Union
44
55import aiohttp
66import humps
7+ from aiohttp import ClientResponse
78
89from tahoma_api .exceptions import BadCredentialsException , TooManyRequestsException
910from tahoma_api .models import Command , CommandMode , Device , State
1011
12+ JSON = Union [Dict [str , Any ], List [Dict [str , Any ]]]
13+
1114API_URL = "https://tahomalink.com/enduser-mobile-web/enduserAPI/" # /doc for API doc
1215
1316
@@ -41,7 +44,7 @@ async def login(self) -> bool:
4144 Caller must provide one of [userId+userPassword, userId+ssoToken, accessToken, jwt]
4245 """
4346 payload = {"userId" : self .username , "userPassword" : self .password }
44- response = await self .__do_http_request ( "POST" , "login" , payload )
47+ response = await self .__post ( "login" , payload )
4548
4649 if response .get ("success" ):
4750 self .__roles = response .get ("roles" )
@@ -57,9 +60,8 @@ async def get_devices(self, refresh: bool = False) -> List[Device]:
5760 if self .devices and not refresh :
5861 return self .devices
5962
60- response = await self .__do_http_request ("GET" , "setup/devices" )
61-
62- devices = [Device (** d ) for d in response ]
63+ response = await self .__get ("setup/devices" )
64+ devices = [Device (** d ) for d in humps .decamelize (response )]
6365 self .devices = devices
6466
6567 return devices
@@ -68,10 +70,10 @@ async def get_state(self, deviceurl: str) -> List[State]:
6870 """
6971 Retrieve states of requested device
7072 """
71- response = await self .__do_http_request (
72- "GET" , f"setup/devices/{ urllib .parse .quote_plus (deviceurl )} /states"
73+ response = await self .__get (
74+ f"setup/devices/{ urllib .parse .quote_plus (deviceurl )} /states"
7375 )
74- state = [State (** s ) for s in response ]
76+ state = [State (** s ) for s in humps . decamelize ( response ) ]
7577
7678 return state
7779
@@ -85,7 +87,7 @@ async def register_event_listener(self) -> str:
8587 timeout : listening sessions are expected to call the /events/{listenerId}/fetch
8688 API on a regular basis.
8789 """
88- response = await self .__do_http_request ( "POST" , "events/register" )
90+ response = await self .__post ( "events/register" )
8991 listener_id = response .get ("id" )
9092
9193 return listener_id
@@ -97,7 +99,7 @@ async def fetch_event_listener(self, listener_id: str) -> List[Any]:
9799 Per-session rate-limit : 1 calls per 1 SECONDS period for this particular
98100 operation (polling)
99101 """
100- response = await self .__do_http_request ( "POST" , f"events/{ listener_id } /fetch" )
102+ response = await self .__post ( f"events/{ listener_id } /fetch" )
101103
102104 return response
103105
@@ -121,58 +123,49 @@ async def execute_action_group(
121123 if mode in supported_modes :
122124 endpoint = f"{ endpoint } /{ mode } "
123125
124- response = await self .__do_http_request ( "POST" , endpoint , payload )
126+ response = await self .__post ( endpoint , payload )
125127
126128 return response
127129
128130 async def get_current_execution (self , exec_id : str ) -> List [Any ]:
129131 """ Get an action group execution currently running """
130- response = await self .__do_http_request ( "GET" , f"/exec/current/{ exec_id } " )
132+ response = await self .__get ( f"/exec/current/{ exec_id } " )
131133 # TODO Strongly type executions
132134
133135 return response
134136
135137 async def get_current_executions (self ) -> List [Any ]:
136138 """ Get all action groups executions currently running """
137- response = await self .__do_http_request ( "GET" , "/exec/current" )
139+ response = await self .__get ( "/exec/current" )
138140 # TODO Strongly type executions
139141
140142 return response
141143
142- async def __do_http_request (
143- self , method : str , endpoint : str , payload : Optional [Any ] = None
144- ) -> Any :
145- """Make a request to the TaHoma API"""
146- supported_methods = ["GET" , "POST" ]
147- result = response = None
148-
149- if method not in supported_methods :
150- raise Exception
151-
152- if method == "GET" :
153- async with self .session .get (f"{ self .api_url } { endpoint } " ) as response :
154- result = await response .json ()
155-
156- if method == "POST" :
157- async with self .session .post (
158- f"{ self .api_url } { endpoint } " , data = payload
159- ) as response :
160- result = await response .json ()
161-
162- if result is None or response is None :
163- return # TODO throw error
164-
165- # TODO replace by our own library
166- result = humps .decamelize (result )
167-
144+ async def __get (self , endpoint : str ) -> Any :
145+ """ Make a GET request to the TaHoma API """
146+ async with self .session .get (f"{ self .api_url } { endpoint } " ) as response :
147+ await self .check_response (response )
148+ return await response .json ()
149+
150+ async def __post (self , endpoint : str , payload : Optional [JSON ] = None ,) -> Any :
151+ """ Make a POST request to the TaHoma API """
152+ async with self .session .post (
153+ f"{ self .api_url } { endpoint } " , data = payload
154+ ) as response :
155+ await self .check_response (response )
156+ return await response .json ()
157+
158+ @staticmethod
159+ async def check_response (response : ClientResponse ) -> None :
160+ """ Check the response returned by the TaHoma API"""
168161 if response .status == 200 :
169- return result
170-
162+ return
171163 # 401
172164 # {'errorCode': 'AUTHENTICATION_ERROR',
173165 # 'error': 'Too many requests, try again later : login with xxx@xxx.tld'}
174166 # 'error': 'Bad credentials'}
175167 # 'error': 'Your setup cannot be accessed through this application'}
168+ result = await response .json ()
176169 if response .status == 401 :
177170 if result .get ("errorCode" ) == "AUTHENTICATION_ERROR" :
178171 message = result .get ("error" )
0 commit comments