api.py 7.26 KB
Newer Older
louiz’'s avatar
louiz’ committed
1
from slixmpp.xmlstream import JID
2 3 4 5 6 7 8


class APIWrapper(object):

    def __init__(self, api, name):
        self.api = api
        self.name = name
9 10
        if name not in self.api.settings:
            self.api.settings[name] = {}
11 12 13 14 15 16 17 18

    def __getattr__(self, attr):
        """Curry API management commands with the API name."""
        if attr == 'name':
            return self.name
        elif attr == 'settings':
            return self.api.settings[self.name]
        elif attr == 'register':
Lance Stout's avatar
Lance Stout committed
19
            def partial(handler, op, jid=None, node=None, default=False):
20
                register = getattr(self.api, attr)
21
                return register(handler, self.name, op, jid, node, default)
Lance Stout's avatar
Lance Stout committed
22
            return partial
23
        elif attr == 'register_default':
Lance Stout's avatar
Lance Stout committed
24
            def partial(handler, op, jid=None, node=None):
25
                return getattr(self.api, attr)(handler, self.name, op)
Lance Stout's avatar
Lance Stout committed
26
            return partial
27
        elif attr in ('run', 'restore_default', 'unregister'):
Lance Stout's avatar
Lance Stout committed
28
            def partial(*args, **kwargs):
29
                return getattr(self.api, attr)(self.name, *args, **kwargs)
Lance Stout's avatar
Lance Stout committed
30
            return partial
31 32 33
        return None

    def __getitem__(self, attr):
Lance Stout's avatar
Lance Stout committed
34
        def partial(jid=None, node=None, ifrom=None, args=None):
35
            return self.api.run(self.name, attr, jid, node, ifrom, args)
Lance Stout's avatar
Lance Stout committed
36
        return partial
37 38 39 40 41 42 43 44


class APIRegistry(object):

    def __init__(self, xmpp):
        self._handlers = {}
        self._handler_defaults = {}
        self.xmpp = xmpp
Lance Stout's avatar
Lance Stout committed
45
        self.settings = {}
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101

    def _setup(self, ctype, op):
        """Initialize the API callback dictionaries.

        :param string ctype: The name of the API to initialize.
        :param string op: The API operation to initialize.
        """
        if ctype not in self.settings:
            self.settings[ctype] = {}
        if ctype not in self._handler_defaults:
            self._handler_defaults[ctype] = {}
        if ctype not in self._handlers:
            self._handlers[ctype] = {}
        if op not in self._handlers[ctype]:
            self._handlers[ctype][op] = {'global': None,
                                         'jid': {},
                                         'node': {}}

    def wrap(self, ctype):
        """Return a wrapper object that targets a specific API."""
        return APIWrapper(self, ctype)

    def purge(self, ctype):
        """Remove all information for a given API."""
        del self.settings[ctype]
        del self._handler_defaults[ctype]
        del self._handlers[ctype]

    def run(self, ctype, op, jid=None, node=None, ifrom=None, args=None):
        """Execute an API callback, based on specificity.

        The API callback that is executed is chosen based on the combination
        of the provided JID and node:

        JID   | node  | Handler
        ==============================
        Given | Given | Node handler
        Given | None  | JID handler
        None  | None  | Global handler

        A node handler is responsible for servicing a single node at a single
        JID, while a JID handler may respond for any node at a given JID, and
        the global handler will answer to any JID+node combination.

        Handlers should check that the JID ``ifrom`` is authorized to perform
        the desired action.

        :param string ctype: The name of the API to use.
        :param string op: The API operation to perform.
        :param JID jid: Optionally provide specific JID.
        :param string node: Optionally provide specific node.
        :param JID ifrom: Optionally provide the requesting JID.
        :param tuple args: Optional positional arguments to the handler.
        """
        self._setup(ctype, op)

102
        if not jid:
103
            jid = self.xmpp.boundjid
104
        elif jid and not isinstance(jid, JID):
105
            jid = JID(jid)
106 107
        elif jid == JID(''):
            jid = self.xmpp.boundjid
108 109 110 111 112 113 114 115 116 117

        if node is None:
            node = ''

        if self.xmpp.is_component:
            if self.settings[ctype].get('component_bare', False):
                jid = jid.bare
            else:
                jid = jid.full
        else:
118
            if self.settings[ctype].get('client_bare', False):
119 120 121 122
                jid = jid.bare
            else:
                jid = jid.full

123 124
        jid = JID(jid)

125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
        handler = self._handlers[ctype][op]['node'].get((jid, node), None)
        if handler is None:
            handler = self._handlers[ctype][op]['jid'].get(jid, None)
        if handler is None:
            handler = self._handlers[ctype][op].get('global', None)

        if handler:
            try:
                return handler(jid, node, ifrom, args)
            except TypeError:
                # To preserve backward compatibility, drop the ifrom
                # parameter for existing handlers that don't understand it.
                return handler(jid, node, args)

    def register(self, handler, ctype, op, jid=None, node=None, default=False):
        """Register an API callback, with JID+node specificity.

        The API callback can later be executed based on the
Lance Stout's avatar
Lance Stout committed
143 144
        specificity of the provided JID+node combination.

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
        See :meth:`~ApiRegistry.run` for more details.

        :param string ctype: The name of the API to use.
        :param string op: The API operation to perform.
        :param JID jid: Optionally provide specific JID.
        :param string node: Optionally provide specific node.
        """
        self._setup(ctype, op)
        if jid is None and node is None:
            if handler is None:
                handler = self._handler_defaults[op]
            self._handlers[ctype][op]['global'] = handler
        elif jid is not None and node is None:
            self._handlers[ctype][op]['jid'][jid] = handler
        else:
            self._handlers[ctype][op]['node'][(jid, node)] = handler

162 163 164
        if default:
            self.register_default(handler, ctype, op)

165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
    def register_default(self, handler, ctype, op):
        """Register a default, global handler for an operation.

        :param func handler: The default, global handler for the operation.
        :param string ctype: The name of the API to modify.
        :param string op: The API operation to use.
        """
        self._setup(ctype, op)
        self._handler_defaults[ctype][op] = handler

    def unregister(self, ctype, op, jid=None, node=None):
        """Remove an API callback.

        The API callback chosen for removal is based on the
        specificity of the provided JID+node combination.

        See :meth:`~ApiRegistry.run` for more details.

        :param string ctype: The name of the API to use.
        :param string op: The API operation to perform.
        :param JID jid: Optionally provide specific JID.
        :param string node: Optionally provide specific node.
        """
        self._setup(ctype, op)
        self.register(None, ctype, op, jid, node)

    def restore_default(self, ctype, op, jid=None, node=None):
        """Reset an API callback to use a default handler.

        :param string ctype: The name of the API to use.
        :param string op: The API operation to perform.
        :param JID jid: Optionally provide specific JID.
        :param string node: Optionally provide specific node.
        """
        self.unregister(ctype, op, jid, node)
        self.register(self._handler_defaults[ctype][op], ctype, op, jid, node)