ekonda / kutana

The library for developing systems for messengers and social networks
MIT License
72 stars 17 forks source link

Принцип работы плагинов #36

Closed AndreiDrang closed 5 years ago

AndreiDrang commented 5 years ago

На сколько я понимаю, сейчас принцип работы ядра при выборе плагина примерно следующий - проходится по всем плагинам, пока не найдёт плагин с нужной командой, если нашёл - выполнил, если не нашёл - не выполнил ничего.

Expected Behavior

Как насчёт, такого варианта работы:

  1. Перед стартом регистрировать все имеющиеся плагины в отдельной моделе в созданной юзером БД(указывать в моделе название плагина + команды + ещё что-нибудь)
  2. При поступлении команды от юзера в ядро - обращаться к БД и искать там подходящую команду(при помощи LIKE/ILIKE всяких), ну а дальше действовать, как и сейчас - вначале вызывать те плагины, которые помечены для предварительного вызова, а потом уже тот, для которого пришла команда.

Actual Behavior

Я так понимаю, для этого потребуется немного переработать ядро.

Context

Столкнулся с проблемой, когда хотел для плагинов добавить просто свои декораторы, оказалось что перед тем как добраться до нужного плагина, вызываются все мои декораторы, которые находятся у предыдущих плагинов. И, на сколько я знаю, подобный принцип регистрации плагинов в ядре присутствует у многих систем(в которых реализована модульность), к примеру Odoo.

P.S. Если в реализации потребуется помощь - готов заняться этим.

Спасибо.

michaelkryukov commented 5 years ago

С какой проблемы вы столкнулись? Текущая система используется для того, чтобы полученные события можно было обрабатывать - не только подбирать по тексту, но и проверять какие-либо критерии, модифицировать события для последующих обработчиков и т.д.

AndreiDrang commented 5 years ago

Окей.

  1. У меня есть список товарищей, которые забанены и не могут пользоваться ботом. Как мне реализовать, что бы их команды не выполнялись(в том числе и в общем чате, в который бот добавлен)?
  2. У меня на некоторые плагины есть таймаут на вызов. Как мне реализовать его?

Естественно, это хотелось бы делать, с наименьшим количеством костылей, поэтому я выбрал путь создания декораторов, но не вышло.

Возможно есть способ задать какой-то дополнительный "предобработчик" для плагинов, в котором можно вызывать проверки и в зависимости от результата обращаться к плагинам или нет?

Я не думаю, что система, в которой получится реализовать регистрацию плагинов не сможет быть гибкой, ведь дам всё так же можно будет задать дополнительные параметры(прикреплённые документы, переменные и т.д.)

michaelkryukov commented 5 years ago

Я не вижу никаких проблем. Можно сделать отдельный плагин, который будет проверять событие - заблокирован его инициатор или нет и т.д. Например, плагин из примера "prefix.py" делает именно это, но проверяет не отправителя сообщений, а наличие префикса.

С таймаутами немного интереснее. Можно добавить декоратор для функций-обработчиков, который будет проверять время и т.д. Так-же можно и создать отдельный плагин, который будет отмечать время использования команд и предотвращать использование без кулдауна, но это действительно будет достаточно костыльно.

В итоге, во-первых, предложенная модель наоборот никак не решает вопросы, которые подняты в этом вопросе. Во-вторых, использование декораторов - логичное и эргономичное решение, которому ничего, в общем-то не мешает (на сколько я могу судить).

AndreiDrang commented 5 years ago

С блокировкой Вас понял, действительно, можно создать плагин на подобии префикса и всё на этом.

предложенная модель наоборот никак не решает вопросы

Почему же? Бот вызывает нужный плагин, на плагине висит декоратор, в котором происходит заданная магия и тут либо плагин срабатывает, либо нет. И другие плагины не трогаются.

Я привёл два самых свежих примера, возможно были ещё вопросы, но из них как-то выбирались.

Ладно, спасибо за ответы.

michaelkryukov commented 5 years ago

Декораторы сейчас возможны в полной мере, не важно, от куда и как был передан контроль обработчику, вы, как разработчик, всегда можете добавить свой код перед или после обработки. Это справедливо для любого проекта на python вообще и ваше предложение рассказывает о БД, которая не относится к поднятому вопросу.

В версии kutana 2.0.0 будет использоваться сигнатура функции-обработчика, чтобы передавать нужные аргументы - поэтому там надо будет поработать над надёжным получением ожидаемых аргументов. Но для версии <2.0.0 никаких проблем даже теоретически нет. С самого начала этот разговор ведётся так, будто то, о чём вы говорите невозможно, когда никаких препятсвий нет.

Легко сделать декоратор, который будет проверять что-то перед основных обработчиком. Этот декоратор может как прекратить обработку события, так и пропустить только конкретный обработчик, на котором он висит.

AndreiDrang commented 5 years ago

вы, как разработчик, всегда можете добавить свой код перед или после обработки

Не хотелось лезть в ядро и переделывать его под себя.

С самого начала этот разговор ведётся так, будто то, о чём вы говорите невозможно, когда никаких препятсвий нет.

Потому как я искал различные способы решения вопросов, и простого, не костыльного, не нашёл.

ваше предложение рассказывает о БД, которая не относится к поднятому вопросу

На мой взгляд, это взаимосвязано, т.к. столкнулся стем, что мой декоратор, перед плагином обрабатывается n-раз и по этой причине логика с тайм-аутами не работала как надо, к примеру.

michaelkryukov commented 5 years ago

Я не говорю, что надо лезть в ядро. Функции-обработчики - это просто функции. В python не состовляет труда сделать декоратор. Вот пример работающего декоратора для реализации кулдауна без костылей, без изменения ядра.

from kutana import Plugin
import time

plugin = Plugin(name="Echo")

COOLDOWNS = {}

def with_cooldown(cooldown_name, cooldown=10):
    def decorator(coro):
        async def wrapper(*args, **kwargs):
            now = time.time()

            if now - COOLDOWNS.get(cooldown_name, 0) < cooldown:
                await args[2].reply(
                    "Cooldown left: {}s".format(
                        10 - now + COOLDOWNS[cooldown_name]
                    )
                )

                return

            COOLDOWNS[cooldown_name] = now

            return await coro(*args, **kwargs)

        return wrapper

    return decorator

@plugin.on_startswith_text("echo")
@with_cooldown("cooldown_name", 10)
async def on_echo(message, attachments, env):
    await env.reply("{}".format(env.body))

Пример работы: image

michaelkryukov commented 5 years ago

Думаю мне стоит подчеркнуть, что для того, чтобы декораторы работали на версии 2.0.0, необходимо будет использовать functools.wraps.

AndreiDrang commented 5 years ago

Я примерно такой же декоратор и писал. Только у меня ещё учитывается время последней активности юзера(хранится в БД). И обновляется, после запроса(успешного или нет). Пришлось разносить логику этой проверки по разным плагинам/декораторам.

Вы не переживайте, все вопросы которые я задал - я решил/реализовал. Просто рассчитывал на более простое(на мой взгляд) и легковесное(для бота) решение.

michaelkryukov commented 5 years ago

Декоратор прямолинейно проверяет - прошло время или нет, и если нет - не выполняет обработчик. Можно добавить в этот код запрос в БД и проверку ответа функции. Как можно упросить подобный подход?

Для реализации своей системы плагинов можно работать напрямую с Executor - создать свои обработчики прямо для событий. Обработчики будут получать события и окружение, а вы сможете релизовать любой подход для поиска ответов на запросы пользователей.