docs: document the "internal API" with reference & howto

This commit is contained in:
mathieui 2021-02-26 00:08:20 +01:00
parent 9927e69435
commit 3cdec464a5
4 changed files with 184 additions and 0 deletions

88
docs/api/api.rst Normal file
View file

@ -0,0 +1,88 @@
.. _internal-api:
Internal "API"
==============
Slixmpp has a generic API registry that can be used by its plugins to allow
access control, redefinition of behaviour, without having to inherit from the
plugin or do more dark magic.
The idea is that each api call can be replaced, most of them use a form
of in-memory storage that can be, for example, replaced with database
or file-based storaged.
Each plugin is assigned an API proxy bound to itself, but only a few make use
of it.
See also :ref:`api-simple-tuto`.
Description of a generic API call
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: python
def get_toto(jid, node, ifrom, args):
return 'toto'
self.xmpp.plugin['xep_XXXX'].api.register(handler, 'get_toto')
Each API call will receive 4 parameters (which can be ``None`` if data
is not relevant to the operation), which are ``jid`` (``Optional[JID]``),
``node`` (``Optional[str]``), ``ifrom`` (``Optional[JID]``), and ``args``
(``Any``).
- ``jid``, if relevant, represents the JID targeted by that operation
- ``node``, if relevant is an arbitrary string, but was thought for, e.g.,
a pubsub or disco node.
- ``ifrom``, if relevant, is the JID the event is coming from.
- ``args`` is the event-specific data passed on by the plugin, often a dict
of arguments (can be None as well).
.. note::
Since 1.8.0, API calls can be coroutines.
Handler hierarchy
~~~~~~~~~~~~~~~~~
The ``self.api.register()`` signature is as follows:
.. code-block:: python
def register(handler, op, jid=None, node=None, dedfault=False):
pass
As you can see, :meth:`~.APIRegistry.register` takes an additional ctype
parameter, but the :class:`~.APIWrapper` takes care of that for us (in most
cases, it is the name of the XEP plugin, such as ``'xep_0XXX'``).
When you register a handler, you register it for an ``op``, for **operation**.
For example, ``get_vcard``.
``handler`` and ``op`` are the only two required parameters (and in many cases,
all you will ever need). You can, however, go further and register handlers
for specific values of the ``jid`` and ``node`` parameters of the calls.
The priority of the execution of handlers is as follows:
- Check if a handler for both values of ``node`` and ``jid`` has been defined
- If not found, check if a handler for this value of ``jid`` has been defined
- If not found, check if a handler for this value of ``node`` has been defined
- If still not found, get the global handler (no parameter registered)
Raw documentation
~~~~~~~~~~~~~~~~~
This documentation is provided for reference, but :meth:`~.APIRegistry.register`
should be all you need.
.. module:: slixmpp.api
.. autoclass:: APIRegistry
:members:
.. autoclass:: APIWrapper

View file

@ -14,3 +14,4 @@ API Reference
xmlstream/matcher
xmlstream/xmlstream
xmlstream/tostring
api

View file

@ -6,6 +6,7 @@ Tutorials, FAQs, and How To Guides
stanzas
create_plugin
internal_api
features
sasl
handlersmatchers

View file

@ -0,0 +1,94 @@
.. _api-simple-tuto:
Flexible internal API usage
===========================
The :ref:`internal-api` in slixmpp is used to override behavior or simply
to override the default, in-memory storage backend with something persistent.
We will use the XEP-0231 (Bits of Binary) plugin as an example here to show
very basic functionality. Its API reference is in the plugin documentation:
:ref:`api-0231`.
Let us assume we want to keep each bit of binary in a file named with its
content-id, with all metadata.
First, we have to load the plugin:
.. code-block:: python
from slixmpp import ClientXMPP
xmpp = ClientXMPP(...)
xmpp.register_plugin('xep_0231')
This enables the default, in-memory storage.
We have 3 methods to override to provide similar functionality and keep things
coherent.
Here is a class implementing very basic file storage for BoB:
.. code-block:: python
from slixmpp.plugins.xep_0231 import BitsOfBinary
from os import makedirs, remove
from os.path import join, exists
import base64
import json
class BobLoader:
def __init__(self, directory):
makedirs(directory, exist_ok=True)
self.dir = directory
def set_bob(self, jid=None, node=None, ifrom=None, args=None):
payload = {
'data': base64.b64encode(args['data']).decode(),
'type': args['type'],
'cid': args['cid'],
'max_age': args['max_age']
}
with open(join(self.dir, args['cid']), 'w') as fd:
fd.write(json.dumps(payload))
def get_bob(self, jid=None, node=None, ifrom=None, args=None):
with open(join(self.dir, args), 'r') as fd:
payload = json.loads(fd.read())
bob = BitsOfBinary()
bob['data'] = base64.b64decode(payload['data'])
bob['type'] = payload['type']
bob['max_age'] = payload['max_age']
bob['cid'] = payload['cid']
return bob
def del_bob(self, jid=None, node=None, ifrom=None, args=None):
path = join(self.dir, args)
if exists(path):
remove(path)
Now we need to replace the default handler with ours:
.. code-block:: python
bobhandler = BobLoader('/tmp/bobcache')
xmpp.plugin['xep_0231'].api.register(bobhandler.set_bob, 'set_bob')
xmpp.plugin['xep_0231'].api.register(bobhandler.get_bob, 'get_bob')
xmpp.plugin['xep_0231'].api.register(bobhandler.del_bob, 'del_bob')
And thats it, the BoB storage is now made of JSON files living in a
directory (``/tmp/bobcache`` here).
To check that everything works, you can do the following:
.. code-block:: python
cid = await xmpp.plugin['xep_0231'].set_bob(b'coucou', 'text/plain')
# A new bob file should appear
content = await xmpp.plugin['xep_0231'].get_bob(cid=cid)
assert content['bob']['data'] == b'coucou'
A file should have been created in that directory.