44import os
55import logging
66import random
7- import json
87import collections
9- import functools
108import inspect
119import signal
12- import concurrent .futures
13- from typing import Optional , Callable
10+ from typing import Optional , Callable , DefaultDict
1411from ssl import SSLContext
1512
1613# ThirdParty Imports
@@ -69,13 +66,12 @@ class RTMClient(object):
6966 Example:
7067 ```python
7168 import os
72- import slack
69+ from slack import RTMClient
7370
74- @slack. RTMClient.run_on(event=' message' )
71+ @RTMClient.run_on(event=" message" )
7572 def say_hello(**payload):
7673 data = payload['data']
7774 web_client = payload['web_client']
78- rtm_client = payload['rtm_client']
7975 if 'Hello' in data['text']:
8076 channel_id = data['channel']
8177 thread_ts = data['ts']
@@ -88,7 +84,7 @@ def say_hello(**payload):
8884 )
8985
9086 slack_token = os.environ["SLACK_API_TOKEN"]
91- rtm_client = slack. RTMClient(token=slack_token)
87+ rtm_client = RTMClient(token=slack_token)
9288 rtm_client.start()
9389 ```
9490
@@ -102,7 +98,7 @@ def say_hello(**payload):
10298 removed at anytime.
10399 """
104100
105- _callbacks = collections .defaultdict (list )
101+ _callbacks : DefaultDict = collections .defaultdict (list )
106102
107103 def __init__ (
108104 self ,
@@ -141,11 +137,7 @@ def run_on(*, event: str):
141137 """A decorator to store and link a callback to an event."""
142138
143139 def decorator (callback ):
144- @functools .wraps (callback )
145- def decorator_wrapper ():
146- RTMClient .on (event = event , callback = callback )
147-
148- return decorator_wrapper ()
140+ RTMClient .on (event = event , callback = callback )
149141
150142 return decorator
151143
@@ -196,7 +188,7 @@ def start(self) -> asyncio.Future:
196188
197189 future = asyncio .ensure_future (self ._connect_and_read (), loop = self ._event_loop )
198190
199- if self .run_async or self . _event_loop . is_running () :
191+ if self .run_async :
200192 return future
201193
202194 return self ._event_loop .run_until_complete (future )
@@ -231,17 +223,19 @@ def send_over_websocket(self, *, payload: dict):
231223 Raises:
232224 SlackClientNotConnectedError: Websocket connection is closed.
233225 """
226+ return asyncio .ensure_future (self ._send_json (payload ))
227+
228+ async def _send_json (self , payload ):
234229 if self ._websocket is None or self ._event_loop is None :
235230 raise client_err .SlackClientNotConnectedError (
236231 "Websocket connection is closed."
237232 )
238233 if "id" not in payload :
239234 payload ["id" ] = self ._next_msg_id ()
240- asyncio .ensure_future (
241- self ._websocket .send_str (json .dumps (payload )), loop = self ._event_loop
242- )
243235
244- def ping (self ):
236+ return await self ._websocket .send_json (payload )
237+
238+ async def ping (self ):
245239 """Sends a ping message over the websocket to Slack.
246240
247241 Not all web browsers support the WebSocket ping spec,
@@ -251,9 +245,9 @@ def ping(self):
251245 SlackClientNotConnectedError: Websocket connection is closed.
252246 """
253247 payload = {"id" : self ._next_msg_id (), "type" : "ping" }
254- self .send_over_websocket (payload = payload )
248+ await self ._send_json (payload = payload )
255249
256- def typing (self , * , channel : str ):
250+ async def typing (self , * , channel : str ):
257251 """Sends a typing indicator to the specified channel.
258252
259253 This indicates that this app is currently
@@ -266,7 +260,7 @@ def typing(self, *, channel: str):
266260 SlackClientNotConnectedError: Websocket connection is closed.
267261 """
268262 payload = {"id" : self ._next_msg_id (), "type" : "typing" , "channel" : channel }
269- self .send_over_websocket (payload = payload )
263+ await self ._send_json (payload = payload )
270264
271265 @staticmethod
272266 def _validate_callback (callback ):
@@ -307,9 +301,9 @@ def _next_msg_id(self):
307301 return self ._last_message_id
308302
309303 async def _connect_and_read (self ):
310- """Retreives and connects to Slack's RTM API.
304+ """Retreives the WS url and connects to Slack's RTM API.
311305
312- Makes an authenticated call to Slack's RTM API to retrieve
306+ Makes an authenticated call to Slack's Web API to retrieve
313307 a websocket URL. Then connects to the message server and
314308 reads event messages as they come in.
315309
@@ -338,15 +332,15 @@ async def _connect_and_read(self):
338332 ) as websocket :
339333 self ._logger .debug ("The Websocket connection has been opened." )
340334 self ._websocket = websocket
341- self ._dispatch_event (event = "open" , data = data )
335+ await self ._dispatch_event (event = "open" , data = data )
342336 await self ._read_messages ()
343337 except (
344338 client_err .SlackClientNotConnectedError ,
345339 client_err .SlackApiError ,
346340 # TODO: Catch websocket exceptions thrown by aiohttp.
347341 ) as exception :
348342 self ._logger .debug (str (exception ))
349- self ._dispatch_event (event = "error" , data = exception )
343+ await self ._dispatch_event (event = "error" , data = exception )
350344 if self .auto_reconnect and not self ._stopped :
351345 await self ._wait_exponentially (exception )
352346 continue
@@ -366,11 +360,11 @@ async def _read_messages(self):
366360 if message .type == aiohttp .WSMsgType .TEXT :
367361 payload = message .json ()
368362 event = payload .pop ("type" , "Unknown" )
369- self ._dispatch_event (event , data = payload )
363+ await self ._dispatch_event (event , data = payload )
370364 elif message .type == aiohttp .WSMsgType .ERROR :
371365 break
372366
373- def _dispatch_event (self , event , data = None ):
367+ async def _dispatch_event (self , event , data = None ):
374368 """Dispatches the event and executes any associated callbacks.
375369
376370 Note: To prevent the app from crashing due to callback errors. We
@@ -399,52 +393,19 @@ def _dispatch_event(self, event, data=None):
399393 # Don't run callbacks if client was stopped unless they're close/error callbacks.
400394 break
401395
402- if self .run_async :
403- self ._execute_callback_async (callback , data )
396+ if inspect .iscoroutinefunction (callback ):
397+ await callback (
398+ rtm_client = self , web_client = self ._web_client , data = data
399+ )
404400 else :
405- self . _execute_callback ( callback , data )
401+ callback ( rtm_client = self , web_client = self . _web_client , data = data )
406402 except Exception as err :
407403 name = callback .__name__
408404 module = callback .__module__
409405 msg = f"When calling '#{ name } ()' in the '{ module } ' module the following error was raised: { err } "
410406 self ._logger .error (msg )
411407 raise
412408
413- def _execute_callback_async (self , callback , data ):
414- """Execute the callback asynchronously.
415-
416- If the callback is not a coroutine, convert it.
417-
418- Note: The WebClient passed into the callback is running in "async" mode.
419- This means all responses will be futures.
420- """
421- if asyncio .iscoroutine (callback ):
422- asyncio .ensure_future (
423- callback (rtm_client = self , web_client = self ._web_client , data = data )
424- )
425- else :
426- asyncio .ensure_future (
427- asyncio .coroutine (callback )(
428- rtm_client = self , web_client = self ._web_client , data = data
429- )
430- )
431-
432- def _execute_callback (self , callback , data ):
433- """Execute the callback in another thread. Wait for and return the results."""
434- web_client = WebClient (
435- token = self .token , base_url = self .base_url , ssl = self .ssl , proxy = self .proxy
436- )
437- with concurrent .futures .ThreadPoolExecutor (max_workers = 1 ) as executor :
438- # Execute the callback on a separate thread,
439- future = executor .submit (
440- callback , rtm_client = self , web_client = web_client , data = data
441- )
442-
443- while future .running ():
444- pass
445-
446- future .result ()
447-
448409 async def _retreive_websocket_info (self ):
449410 """Retreives the WebSocket info from Slack.
450411
@@ -491,7 +452,7 @@ async def _wait_exponentially(self, exception, max_wait_time=300):
491452 """Wait exponentially longer for each connection attempt.
492453
493454 Calculate the number of seconds to wait and then add
494- a random number of milliseconds to avoid coincendental
455+ a random number of milliseconds to avoid coincidental
495456 synchronized client retries. Wait up to the maximium amount
496457 of wait time specified via 'max_wait_time'. However,
497458 if Slack returned how long to wait use that.
@@ -512,4 +473,6 @@ def _close_websocket(self):
512473 if callable (close_method ):
513474 asyncio .ensure_future (close_method (), loop = self ._event_loop )
514475 self ._websocket = None
515- self ._dispatch_event (event = "close" )
476+ asyncio .ensure_future (
477+ self ._dispatch_event (event = "close" ), loop = self ._event_loop
478+ )
0 commit comments