docs: document the "internal API" with reference & howto
This commit is contained in:
parent
9927e69435
commit
3cdec464a5
4 changed files with 184 additions and 0 deletions
88
docs/api/api.rst
Normal file
88
docs/api/api.rst
Normal 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
|
||||||
|
|
|
@ -14,3 +14,4 @@ API Reference
|
||||||
xmlstream/matcher
|
xmlstream/matcher
|
||||||
xmlstream/xmlstream
|
xmlstream/xmlstream
|
||||||
xmlstream/tostring
|
xmlstream/tostring
|
||||||
|
api
|
||||||
|
|
|
@ -6,6 +6,7 @@ Tutorials, FAQs, and How To Guides
|
||||||
|
|
||||||
stanzas
|
stanzas
|
||||||
create_plugin
|
create_plugin
|
||||||
|
internal_api
|
||||||
features
|
features
|
||||||
sasl
|
sasl
|
||||||
handlersmatchers
|
handlersmatchers
|
||||||
|
|
94
docs/howto/internal_api.rst
Normal file
94
docs/howto/internal_api.rst
Normal 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 that’s 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.
|
Loading…
Reference in a new issue