diff options
author | Lance Stout <lancestout@gmail.com> | 2012-03-30 23:02:48 -0700 |
---|---|---|
committer | Lance Stout <lancestout@gmail.com> | 2012-04-06 15:09:25 -0400 |
commit | 488f7ed88691d9f7fa756a28702f6bee43d6a260 (patch) | |
tree | 62d59aef1051cd3ae0d068499e371efd5a6c6fdc /sleekxmpp/api.py | |
parent | 51e5aee8308e42a89b7c0ab83ec53e2abea9767f (diff) | |
download | slixmpp-488f7ed88691d9f7fa756a28702f6bee43d6a260.tar.gz slixmpp-488f7ed88691d9f7fa756a28702f6bee43d6a260.tar.bz2 slixmpp-488f7ed88691d9f7fa756a28702f6bee43d6a260.tar.xz slixmpp-488f7ed88691d9f7fa756a28702f6bee43d6a260.zip |
Begin experiment with a centralized API callback registry.
The API registry generalizes the node handler system from the xep_0030
plugin so that other plugins can use it.
Diffstat (limited to 'sleekxmpp/api.py')
-rw-r--r-- | sleekxmpp/api.py | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/sleekxmpp/api.py b/sleekxmpp/api.py new file mode 100644 index 00000000..9d8ec201 --- /dev/null +++ b/sleekxmpp/api.py @@ -0,0 +1,191 @@ +from sleekxmpp.xmlstream import JID + + +class APIWrapper(object): + + def __init__(self, api, name): + self.api = api + self.name = name + + 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': + def curried_handler(handler, op, jid=None, node=None): + register = getattr(self.api, attr) + return register(handler, self.name, op, jid, node) + return curried_handler + elif attr == 'register_default': + def curried_handler(handler, op, jid=None, node=None): + return getattr(self.api, attr)(handler, self.name, op) + return curried_handler + elif attr in ('run', 'restore_default', 'unregister'): + def curried_handler(*args, **kwargs): + return getattr(self.api, attr)(self.name, *args, **kwargs) + return curried_handler + return None + + def __getitem__(self, attr): + def curried_handler(jid=None, node=None, ifrom=None, args=None): + return self.api.run(self.name, attr, jid, node, ifrom, args) + return curried_handler + + +class APIRegistry(object): + + def __init__(self, xmpp): + self._handlers = {} + self._handler_defaults = {} + self.xmpp = xmpp + self.settings = {} + + 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) + + if jid in (None, ''): + jid = self.xmpp.boundjid + if jid and not isinstance(jid, JID): + jid = JID(jid) + + 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: + if self.settings[ctype].get('client_bare', True): + jid = jid.bare + else: + jid = jid.full + + 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 + 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) + 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 + + 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) |