Skip to content

Plugins

A program that grows past one file wants its features in separate ones. The obstacle is that the decorators are methods on a client, so a file written to be imported has no app to decorate with and should not have to be handed one to be readable.

So the intent is recorded on the function and attached later.

# plugins/greet.py
from sunnygram import filters, plugins

@plugins.on_message(filters.command("hello"))
async def greet(client, message):
    await message.reply("Hello yourself")
# main.py
from sunnygram import Client

app = Client("my.session", api_id=API_ID, api_hash=API_HASH)
app.load_plugins("plugins")
app.run()

Every module in the package is imported, and every function in it carrying one of the decorators is registered. The package needs an __init__.py, so that a traceback from inside a plugin says which plugin it came from.

The decorators

The same twenty-one the client has, with the same names, taking the same filters and the same group:

@plugins.on_message(filters.text)
@plugins.on_callback_query(filters.data("yes"))
@plugins.on_inline_query()
@plugins.on_edited(group=1)

Stacking two of them on one function registers it twice, once per kind, which is usually what someone stacking them meant.

The decorator is @plugins.on_message(...) instead of a method on the client. Hanging these off the class would mean each of the twenty-one has to accept a filter where the client belongs, and giving that up is not worth what it costs in what your editor can tell you.

What it will not do quietly

A plugin that fails to import raises. It is not skipped with a warning. A feature that is silently absent looks exactly like a program with nothing to do, and that is the fault class this library refuses everywhere.

A package with no handlers in it says so. Not an error, since a package of helpers is a reasonable thing to point at, but it is nearly always the decorators having been left off, and nothing else in the program would ever mention it.

Loading twice registers twice. load_plugins returns how many handlers it registered, and calling it twice on the same package gives you each handler twice, which means every message answered twice by code that appears once in the source. The count is there to be read.

Choosing what loads

app.load_plugins("plugins", exclude=("weather",))
app.load_plugins("plugins", include=("greet", "echo"))

Names are modules without the package in front. A module whose name starts with _ is never loaded, which is where shared code between plugins goes.