Использование декораторов с Python Slack API - PullRequest
0 голосов
/ 15 января 2019

Я пытался создать простой декоратор для использования с Slack API, чтобы сделать дальнейшее кодирование, чтобы позже улучшить функциональность, сделать ее проще и более организованной (скажем, в будущем я хотел включить новую функцию / команду, которую я хотел бот делать)

Но это не работает, как я ожидал. Позвольте мне объяснить, вот мой код (я оставил большие куски кода, такие как создание экземпляров, токен и т. Д., Чтобы кратко ответить на вопрос и проиллюстрировать основные моменты). Он включает в себя простую функцию, которая должна выводить сообщение в Slack канал:

class Bot(object):
    def __init__(self): 
        # Bot's user ID in Slack: value is assigned after the bot starts up
        self.starterbot_id = None
        # Declare constants
        self.RTM_READ_DELAY = 1 # 1 second delay between reading from RTM
        self.EXAMPLE_COMMAND = "do"
        self.MENTION_REGEX = "^<@(|[WU].+?)>(.*)"

    def startup(self):
        #Startup the client
        if slack_client.rtm_connect(with_team_state=False):
            print("Slack Bot connected and running!")
            # Read bot's user ID by calling Web API method `auth.test`
            self.starterbot_id = slack_client.api_call("auth.test")["user_id"]
            while True:
                command, channel = self.parse_bot_commands(slack_client.rtm_read())
                if command: 
                    response = self.command(command)
                    self.handle_command(response, channel)
                time.sleep(self.RTM_READ_DELAY)
        else: print("Connection failed. Exception traceback printed above.")

    def handle_command(self, response, channel):
        """
            Executes bot command if the command is known
        """
        # Default response is help text for the user
        default_response = "Not sure what you mean. Try *{}*.".format(self.EXAMPLE_COMMAND)

        # Sends the response back to the channel
        slack_client.api_call("chat.postMessage", channel=channel, text=response or default_response)

    def command(self, func):
        """
            Simple wrapper for bot commands
        """
        def wrapper_command():
            func()
        return wrapper_command

'''USAGE:'''
bot = Bot()
bot.startup()

@bot.command
def greet():
    response = "Hi there I am CodeMonkey"
    return response

Я искал команду greet(), чтобы бот выводил “Hi there I am CodeMonkey”

Бот ответил, но не приветствием. Вместо этого он возвращает объект. Вот ответ, который я получаю от бота в канале:

CodeMonkey APP [11:40 PM]
<function Bot.command.<locals>.wrapper_command at 0x1083281e0>

Я все еще учусь использовать декораторы и чувствую, что они сделают кодирование моего Slack Bot намного проще и организованнее, но что я делаю не так?

1 Ответ

0 голосов
/ 15 января 2019

Ну, ответ, который вы получите, - это именно то, что можно ожидать от этого:

                response = self.command(command)
                self.handle_command(response, channel)

и это:

    def command(self, func):
        """
            Simple wrapper for bot commands
        """
        def wrapper_command():
            func()
        return wrapper_command

Вы вызываете self.command(...) и передаете результат в self.handle_command(), self.command() возвращает функцию, поэтому self.handle_command() получает этот вопрос в качестве параметра response.

Вы все еще не объяснили, что ожидали command(), когда будете использовать его в качестве декоратора, но я предполагаю, что вы хотите использовать декорированную функцию в качестве обработчика для данной «команды» (параметр, а не функция - подсказка: используйте разные имена для разных вещей), т.е. self.command('greet') должен вызывать функцию greet.

Самое первое, что вам нужно, - это использовать различные методы для выполнения команд и их регистрации - поэтому мы назовем эти методы execute_command и register_command - и пока мы будем работать, мы переименуем "handle_command" "to" send_response ", потому что это то, что он действительно делает:

def startup(self):
    #Startup the client
    if slack_client.rtm_connect(with_team_state=False):
        print("Slack Bot connected and running!")
        # Read bot's user ID by calling Web API method `auth.test`
        self.starterbot_id = slack_client.api_call("auth.test")["user_id"]
        while True:
            command, channel = self.parse_bot_commands(slack_client.rtm_read())
            if command: 
                response = self.execute_command(command)
                self.send_response(response, channel)
            time.sleep(self.RTM_READ_DELAY)
    else: 
        print("Connection failed. Exception traceback printed above.")


def execute_command(self, command):
    """
        Executes bot command if the command is known
    """
    # mock it for the moment
    return "this is a mockup"  

def send_response(self, response, channel):
    # Default response is help text for the user
    default_response = "Not sure what you mean. Try *{}*.".format(self.EXAMPLE_COMMAND)

    # Sends the response back to the channel
    slack_client.api_call("chat.postMessage", channel=channel, text=response or default_response)

def register_command(self, func):
    """
        TODO
    """

Теперь для декоратора. Обертывание функций в другие функции, которые будут выполнять «вокруг» декорированной, является распространенным случаем для декораторов, но это не означает, что это обязательно. технически «декоратор» - это просто «функция более высокого порядка»: функция, которая принимает функцию в качестве аргумента и возвращает функцию в качестве результата. На самом деле, синтаксис @decorator является только синтаксическим сахаром, поэтому

@decorate
def func(): 
    pass

это просто причудливый способ написать

def func(): 
    pass

func = decorate(func)

и как только вы поймете это, вы также поймете, что здесь нет абсолютно ничего волшебного.

Теперь вернемся к нашему register_command декоратору. Мы хотим сохранить аргумент (функцию), чтобы мы могли получить его по его имени. Это легко сделать с помощью dict и func.__name__:

 def __init__(self, ...):
     # init code here

     self._commands = {}


def register_command(self, func):
    self._commands[func.__name__] = func
    # and that's all - no wrapper needed here
    return func

Так что теперь вы можете использовать

@bot.register_command:
def greet():
    return "Hello"

, а затем bot._commands должен содержать 'greet': <function greet @...>.

Теперь для метода execute_command - это довольно просто: ищите self._commands, если что-то найдено, вызывайте его и возвращайте ответ, иначе возвращайте ответ по умолчанию:

def execute_command(self, command):
    handler = self._commands.get(command)
    if handler:
        return handler()

    # default:
    return "Not sure what you mean. Try *{}*.".format(self.EXAMPLE_COMMAND)

и теперь мы можем упростить send_response(), поскольку execute_command позаботится о предоставлении ответа по умолчанию:

def send_response(self, response, channel):
    # Sends the response back to the channel
    slack_client.api_call("chat.postMessage", channel=channel, text=response)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...