Utilities¶
IMMP comes with a number of common idioms and features provided by built-in hooks, which can be used by your own classes to avoid reimplementing common bot tasks.
Data schema validation¶
The included plugs make use of an internal schema validator to ensure all API responses contain the expected fields. These effectively act as assertions on the existence of all required fields, before starting to parse the object.
In the most basic case, prepare a Schema
representing the data structure, and call it on
each API response of that type before using it. For plug and hook configuration, attach it to the
schema
attribute to have incoming config validated at creation.
Storing data¶
Some hooks may need to store their own information. The hook config is read-only – any changes are not persisted because the config must be provided at startup. For data encountered during the app’s lifetime, a database may be required.
The DatabaseHook
resource provides generic database access using the Peewee ORM. Define
your models as subclasses of BaseModel
, then at startup, obtain the database connection
DatabaseHook.db
and call Database.create_tables()
with a list of all your models.
From there, you can use Peewee’s model methods to query and manage records.
Adding commands¶
The CommandHook
provides an easy way to register commands that interact with a custom
hook. Each command method should be decorated using command()
, and should accept a
Message
argument followed by any arguments expected from the user. Keyword arguments are
not supported, though optional (with default values) and variable (varargs) parameters may be used.
Note
This replaces the class-level attribute with a Command
instance. This instance is
callable, allowing you to use the underlying method like any other, though introspection may
give surprising results if you rely on it being a traditional method.
Dynamic commands¶
You may need to make commands available based on config or state. For simple cases, you can make a
command conditional by setting the test
field to a method returning True
if the command
is currently valid.
For more complex hooks, you may instead need to generate new commands based on config. This is
where dynamic commands come into play. An unnamed command is effectively a template – it won’t
itself be included in command sets, but you can provide qualified instances of it inside
DynamicCommands.commands()
using BaseCommand.complete()
.
Identities and access¶
If your hook interacts with a third-party service, with users from connected plugs mapping to
external users within the service, consider implementing IdentityProvider
to make this
information available to other hooks like Sync.
You can also use permissions from your service to enforce and restrict user access to channels by
integrating AccessPredicate
into your hook, to be used with Access.
Incoming HTTP requests¶
Some networks may only support listening for messages by subscribing to a webhook. You may also
need to provide some web-based UI for certain features. You can use the WebHook
resource hook to spin up a local aiohttp.web
server, and bind URL paths to it from your plug
or hook.
Initial setup is abstracted by the WebContext
context – an instance of this can be
obtained using WebHook.context()
. With this, you can configure new routes into your code
like a native aiohttp
application.
Note
Routes are named in aiohttp
using the module path as their prefix. The helper method
WebContext.url_for()
provides path resolution without providing the prefix each time.
Jinja2 templates¶
If a template name is given to WebContext.route()
, the request handler method will be
wrapped by aiohttp_jinja2
, meaning it should return a dict
to act as the template
context. See WebContext.env
for the variables provided by default.