I am attempting to wrap V2 of the Slack API into a class so that I can keep information about my bot encapsulated. Here is one of their example snippets:
import slack
slack_token = os.environ["SLACK_API_TOKEN"]
rtmclient = slack.RTMClient(token=slack_token)
@slack.RTMClient.run_on(event='message')
def say_hello(**payload):
data = payload['data']
if 'Hello' in data['text']:
channel_id = data['channel']
thread_ts = data['ts']
user = data['user']
webclient = payload['web_client']
webclient.chat_postMessage(
channel=channel_id,
text="Hi <@{}>!".format(user),
thread_ts=thread_ts
)
rtmclient.start()
My understanding here is that this say_hello
function is being passed into the slack object because of the decorator, so if I were to wrap this into a class, that function isn't really sitting inside my class. How do I wrap the say_hello
function for it to be able to call methods and reference properties that belonged to an instance of my class?
Answer
You're correct in observing that the say_hello
function in the example is outside the scope of a class. The decorator @slack.RTMClient.run_on(event='message')
essentially registers the function as an event handler for the Slack real-time messaging client, and it's not naturally tied to any instance of a class.
To encapsulate the bot information in a class and still use the Slack API with instance methods and properties, you need to ensure that your event handler method is part of the class and has access to the class instance (self
). Here's how you can do it:
Steps to wrap the Slack API into a class:
-
Move the event handler function (
say_hello
) into a class method: This way, the method will have access to the instance of the class viaself
. -
Pass the instance (
self
) into the decorator: Since the decorator expects a regular function, you need to use a technique to bind the class method to the instance so that the method can access the class's properties. -
Use
slack.RTMClient.run_on
within a class: You can either register the function directly from within the class or use theself
reference to call methods on the instance.
Example:
import os
import slack
class SlackBot:
def __init__(self, token):
self.token = token
self.rtmclient = slack.RTMClient(token=self.token)
# Register the event handler within the class
self._register_event_handlers()
def _register_event_handlers(self):
# Using a decorator method to register event handlers inside the class
@self.rtmclient.run_on(event='message')
def say_hello(**payload):
self.handle_message(payload)
def handle_message(self, payload):
data = payload['data']
if 'Hello' in data['text']:
channel_id = data['channel']
thread_ts = data['ts']
user = data['user']
webclient = payload['web_client']
webclient.chat_postMessage(
channel=channel_id,
text=f"Hi <@{user}>!",
thread_ts=thread_ts
)
def start(self):
# Start the RTM client
self.rtmclient.start()
# Usage
slack_token = os.environ["SLACK_API_TOKEN"]
bot = SlackBot(slack_token)
bot.start()
Key changes:
-
Class structure: The
SlackBot
class contains the token, RTM client, and methods to handle events. The bot token is passed to the constructor during initialization. -
_register_event_handlers
method: This method registers the event handler (say_hello
) with@self.rtmclient.run_on(event='message')
. This ensures the method is correctly tied to the class instance and has access to theself
context. -
handle_message
method: The logic for handling themessage
event is now moved to a class method calledhandle_message
, which makes it easier to reference instance properties and maintain encapsulation. -
Start the bot: The
start
method callsrtmclient.start()
to begin the RTM client loop, keeping the bot running.
How it works:
-
Decorator Usage: The
@self.rtmclient.run_on(event='message')
decorator is applied to thesay_hello
method inside_register_event_handlers()
. The method is still an instance method, so it has access to the class properties (self.token
, etc.). -
Accessing Class Methods and Properties: In the event handler (
say_hello
), we callself.handle_message(payload)
, which is a method of theSlackBot
class and can use any instance variables or methods.
Alternative: Use functools.partial
for more flexibility
In case you prefer to keep the decorator usage a bit cleaner, you can use functools.partial
to pass the instance (self
) into the decorator:
from functools import partial
import slack
class SlackBot:
def __init__(self, token):
self.token = token
self.rtmclient = slack.RTMClient(token=self.token)
# Register event handlers using partial
self._register_event_handlers()
def _register_event_handlers(self):
# Use partial to bind self to the event handler
say_hello = partial(self.say_hello)
# Register the method as an event handler
self.rtmclient.run_on(event='message')(say_hello)
def say_hello(self, **payload):
self.handle_message(payload)
def handle_message(self, payload):
data = payload['data']
if 'Hello' in data['text']:
channel_id = data['channel']
thread_ts = data['ts']
user = data['user']
webclient = payload['web_client']
webclient.chat_postMessage(
channel=channel_id,
text=f"Hi <@{user}>!",
thread_ts=thread_ts
)
def start(self):
self.rtmclient.start()
# Usage
slack_token = os.environ["SLACK_API_TOKEN"]
bot = SlackBot(slack_token)
bot.start()
In this approach, partial(self.say_hello)
creates a version of the say_hello
method with the instance bound to it. This is then passed to the run_on
decorator, making it possible to call self
inside the handler.
Both approaches should work for your goal of encapsulating the Slack bot functionality inside a class while keeping the event handling intact.