From 3cdec464a550b775d8c251f37b863a6e2212c5d5 Mon Sep 17 00:00:00 2001 From: mathieui Date: Fri, 26 Feb 2021 00:08:20 +0100 Subject: [PATCH] docs: document the "internal API" with reference & howto --- docs/api/api.rst | 88 ++++++++++++++++++++++++++++++++++ docs/api/index.rst | 1 + docs/howto/index.rst | 1 + docs/howto/internal_api.rst | 94 +++++++++++++++++++++++++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 docs/api/api.rst create mode 100644 docs/howto/internal_api.rst diff --git a/docs/api/api.rst b/docs/api/api.rst new file mode 100644 index 00000000..55642ef6 --- /dev/null +++ b/docs/api/api.rst @@ -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 + diff --git a/docs/api/index.rst b/docs/api/index.rst index aefa0f56..e9aa6264 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -14,3 +14,4 @@ API Reference xmlstream/matcher xmlstream/xmlstream xmlstream/tostring + api diff --git a/docs/howto/index.rst b/docs/howto/index.rst index b05dc499..e4dee4d7 100644 --- a/docs/howto/index.rst +++ b/docs/howto/index.rst @@ -6,6 +6,7 @@ Tutorials, FAQs, and How To Guides stanzas create_plugin + internal_api features sasl handlersmatchers diff --git a/docs/howto/internal_api.rst b/docs/howto/internal_api.rst new file mode 100644 index 00000000..003225c4 --- /dev/null +++ b/docs/howto/internal_api.rst @@ -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.