api.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. from sleekxmpp.xmlstream import JID
  2. class APIWrapper(object):
  3. def __init__(self, api, name):
  4. self.api = api
  5. self.name = name
  6. if name not in self.api.settings:
  7. self.api.settings[name] = {}
  8. def __getattr__(self, attr):
  9. """Curry API management commands with the API name."""
  10. if attr == 'name':
  11. return self.name
  12. elif attr == 'settings':
  13. return self.api.settings[self.name]
  14. elif attr == 'register':
  15. def partial(handler, op, jid=None, node=None, default=False):
  16. register = getattr(self.api, attr)
  17. return register(handler, self.name, op, jid, node, default)
  18. return partial
  19. elif attr == 'register_default':
  20. def partial(handler, op, jid=None, node=None):
  21. return getattr(self.api, attr)(handler, self.name, op)
  22. return partial
  23. elif attr in ('run', 'restore_default', 'unregister'):
  24. def partial(*args, **kwargs):
  25. return getattr(self.api, attr)(self.name, *args, **kwargs)
  26. return partial
  27. return None
  28. def __getitem__(self, attr):
  29. def partial(jid=None, node=None, ifrom=None, args=None):
  30. return self.api.run(self.name, attr, jid, node, ifrom, args)
  31. return partial
  32. class APIRegistry(object):
  33. def __init__(self, xmpp):
  34. self._handlers = {}
  35. self._handler_defaults = {}
  36. self.xmpp = xmpp
  37. self.settings = {}
  38. def _setup(self, ctype, op):
  39. """Initialize the API callback dictionaries.
  40. :param string ctype: The name of the API to initialize.
  41. :param string op: The API operation to initialize.
  42. """
  43. if ctype not in self.settings:
  44. self.settings[ctype] = {}
  45. if ctype not in self._handler_defaults:
  46. self._handler_defaults[ctype] = {}
  47. if ctype not in self._handlers:
  48. self._handlers[ctype] = {}
  49. if op not in self._handlers[ctype]:
  50. self._handlers[ctype][op] = {'global': None,
  51. 'jid': {},
  52. 'node': {}}
  53. def wrap(self, ctype):
  54. """Return a wrapper object that targets a specific API."""
  55. return APIWrapper(self, ctype)
  56. def purge(self, ctype):
  57. """Remove all information for a given API."""
  58. del self.settings[ctype]
  59. del self._handler_defaults[ctype]
  60. del self._handlers[ctype]
  61. def run(self, ctype, op, jid=None, node=None, ifrom=None, args=None):
  62. """Execute an API callback, based on specificity.
  63. The API callback that is executed is chosen based on the combination
  64. of the provided JID and node:
  65. JID | node | Handler
  66. ==============================
  67. Given | Given | Node handler
  68. Given | None | JID handler
  69. None | None | Global handler
  70. A node handler is responsible for servicing a single node at a single
  71. JID, while a JID handler may respond for any node at a given JID, and
  72. the global handler will answer to any JID+node combination.
  73. Handlers should check that the JID ``ifrom`` is authorized to perform
  74. the desired action.
  75. :param string ctype: The name of the API to use.
  76. :param string op: The API operation to perform.
  77. :param JID jid: Optionally provide specific JID.
  78. :param string node: Optionally provide specific node.
  79. :param JID ifrom: Optionally provide the requesting JID.
  80. :param tuple args: Optional positional arguments to the handler.
  81. """
  82. self._setup(ctype, op)
  83. if not jid:
  84. jid = self.xmpp.boundjid
  85. elif jid and not isinstance(jid, JID):
  86. jid = JID(jid)
  87. elif jid == JID(''):
  88. jid = self.xmpp.boundjid
  89. if node is None:
  90. node = ''
  91. if self.xmpp.is_component:
  92. if self.settings[ctype].get('component_bare', False):
  93. jid = jid.bare
  94. else:
  95. jid = jid.full
  96. else:
  97. if self.settings[ctype].get('client_bare', False):
  98. jid = jid.bare
  99. else:
  100. jid = jid.full
  101. jid = JID(jid)
  102. handler = self._handlers[ctype][op]['node'].get((jid, node), None)
  103. if handler is None:
  104. handler = self._handlers[ctype][op]['jid'].get(jid, None)
  105. if handler is None:
  106. handler = self._handlers[ctype][op].get('global', None)
  107. if handler:
  108. try:
  109. return handler(jid, node, ifrom, args)
  110. except TypeError:
  111. # To preserve backward compatibility, drop the ifrom
  112. # parameter for existing handlers that don't understand it.
  113. return handler(jid, node, args)
  114. def register(self, handler, ctype, op, jid=None, node=None, default=False):
  115. """Register an API callback, with JID+node specificity.
  116. The API callback can later be executed based on the
  117. specificity of the provided JID+node combination.
  118. See :meth:`~ApiRegistry.run` for more details.
  119. :param string ctype: The name of the API to use.
  120. :param string op: The API operation to perform.
  121. :param JID jid: Optionally provide specific JID.
  122. :param string node: Optionally provide specific node.
  123. """
  124. self._setup(ctype, op)
  125. if jid is None and node is None:
  126. if handler is None:
  127. handler = self._handler_defaults[op]
  128. self._handlers[ctype][op]['global'] = handler
  129. elif jid is not None and node is None:
  130. self._handlers[ctype][op]['jid'][jid] = handler
  131. else:
  132. self._handlers[ctype][op]['node'][(jid, node)] = handler
  133. if default:
  134. self.register_default(handler, ctype, op)
  135. def register_default(self, handler, ctype, op):
  136. """Register a default, global handler for an operation.
  137. :param func handler: The default, global handler for the operation.
  138. :param string ctype: The name of the API to modify.
  139. :param string op: The API operation to use.
  140. """
  141. self._setup(ctype, op)
  142. self._handler_defaults[ctype][op] = handler
  143. def unregister(self, ctype, op, jid=None, node=None):
  144. """Remove an API callback.
  145. The API callback chosen for removal is based on the
  146. specificity of the provided JID+node combination.
  147. See :meth:`~ApiRegistry.run` for more details.
  148. :param string ctype: The name of the API to use.
  149. :param string op: The API operation to perform.
  150. :param JID jid: Optionally provide specific JID.
  151. :param string node: Optionally provide specific node.
  152. """
  153. self._setup(ctype, op)
  154. self.register(None, ctype, op, jid, node)
  155. def restore_default(self, ctype, op, jid=None, node=None):
  156. """Reset an API callback to use a default handler.
  157. :param string ctype: The name of the API to use.
  158. :param string op: The API operation to perform.
  159. :param JID jid: Optionally provide specific JID.
  160. :param string node: Optionally provide specific node.
  161. """
  162. self.unregister(ctype, op, jid, node)
  163. self.register(self._handler_defaults[ctype][op], ctype, op, jid, node)