diff --git a/slixmpp/plugins/xep_0030/disco.py b/slixmpp/plugins/xep_0030/disco.py index 9c4c5269..6aeadc84 100644 --- a/slixmpp/plugins/xep_0030/disco.py +++ b/slixmpp/plugins/xep_0030/disco.py @@ -6,13 +6,13 @@ import asyncio import logging - +from asyncio import Future from typing import Optional, Callable from slixmpp import Iq from slixmpp import future_wrapper from slixmpp.plugins import BasePlugin -from slixmpp.xmlstream.handler import Callback +from slixmpp.xmlstream.handler import Callback, CoroutineCallback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin, JID from slixmpp.plugins.xep_0030 import stanza, DiscoInfo, DiscoItems @@ -91,12 +91,12 @@ class XEP_0030(BasePlugin): Start the XEP-0030 plugin. """ self.xmpp.register_handler( - Callback('Disco Info', + CoroutineCallback('Disco Info', StanzaPath('iq/disco_info'), self._handle_disco_info)) self.xmpp.register_handler( - Callback('Disco Items', + CoroutineCallback('Disco Items', StanzaPath('iq/disco_items'), self._handle_disco_items)) @@ -228,10 +228,13 @@ class XEP_0030(BasePlugin): self.api.restore_default(op, jid, node) def supports(self, jid=None, node=None, feature=None, local=False, - cached=True, ifrom=None): + cached=True, ifrom=None) -> Future: """ Check if a JID supports a given feature. + .. versionchanged:: 1.8.0 + This function now returns a Future. + Return values: :param True: The feature is supported :param False: The feature is not listed as supported @@ -259,10 +262,13 @@ class XEP_0030(BasePlugin): return self.api['supports'](jid, node, ifrom, data) def has_identity(self, jid=None, node=None, category=None, itype=None, - lang=None, local=False, cached=True, ifrom=None): + lang=None, local=False, cached=True, ifrom=None) -> Future: """ Check if a JID provides a given identity. + .. versionchanged:: 1.8.0 + This function now returns a Future. + Return values: :param True: The identity is provided :param False: The identity is not listed @@ -324,8 +330,7 @@ class XEP_0030(BasePlugin): callback(results) return results - @future_wrapper - def get_info(self, jid=None, node=None, local=None, + async def get_info(self, jid=None, node=None, local=None, cached=None, **kwargs): """ Retrieve the disco#info results from a given JID/node combination. @@ -338,6 +343,9 @@ class XEP_0030(BasePlugin): If requesting items from a local JID/node, then only a DiscoInfo stanza will be returned. Otherwise, an Iq stanza will be returned. + .. versionchanged:: 1.8.0 + This function is now a coroutine. + :param jid: Request info from this JID. :param node: The particular node to query. :param local: If true, then the query is for a JID/node @@ -369,18 +377,21 @@ class XEP_0030(BasePlugin): if local: log.debug("Looking up local disco#info data " + \ "for %s, node %s.", jid, node) - info = self.api['get_info'](jid, node, - kwargs.get('ifrom', None), - kwargs) + info = await self.api['get_info']( + jid, node, kwargs.get('ifrom', None), + kwargs + ) info = self._fix_default_info(info) return self._wrap(kwargs.get('ifrom', None), jid, info) if cached: log.debug("Looking up cached disco#info data " + \ "for %s, node %s.", jid, node) - info = self.api['get_cached_info'](jid, node, - kwargs.get('ifrom', None), - kwargs) + info = await self.api['get_cached_info']( + jid, node, + kwargs.get('ifrom', None), + kwargs + ) if info is not None: return self._wrap(kwargs.get('ifrom', None), jid, info) @@ -390,21 +401,24 @@ class XEP_0030(BasePlugin): iq['to'] = jid iq['type'] = 'get' iq['disco_info']['node'] = node if node else '' - return iq.send(timeout=kwargs.get('timeout', None), + return await iq.send(timeout=kwargs.get('timeout', None), callback=kwargs.get('callback', None), timeout_callback=kwargs.get('timeout_callback', None)) - def set_info(self, jid=None, node=None, info=None): + def set_info(self, jid=None, node=None, info=None) -> Future: """ Set the disco#info data for a JID/node based on an existing disco#info stanza. + + .. versionchanged:: 1.8.0 + This function now returns a Future. + """ if isinstance(info, Iq): info = info['disco_info'] - self.api['set_info'](jid, node, None, info) + return self.api['set_info'](jid, node, None, info) - @future_wrapper - def get_items(self, jid=None, node=None, local=False, **kwargs): + async def get_items(self, jid=None, node=None, local=False, **kwargs): """ Retrieve the disco#items results from a given JID/node combination. @@ -416,6 +430,9 @@ class XEP_0030(BasePlugin): If requesting items from a local JID/node, then only a DiscoItems stanza will be returned. Otherwise, an Iq stanza will be returned. + .. versionchanged:: 1.8.0 + This function is now a coroutine. + :param jid: Request info from this JID. :param node: The particular node to query. :param local: If true, then the query is for a JID/node @@ -428,7 +445,7 @@ class XEP_0030(BasePlugin): Otherwise the parameter is ignored. """ if local or local is None and jid is None: - items = self.api['get_items'](jid, node, + items = await self.api['get_items'](jid, node, kwargs.get('ifrom', None), kwargs) return self._wrap(kwargs.get('ifrom', None), jid, items) @@ -440,43 +457,52 @@ class XEP_0030(BasePlugin): iq['type'] = 'get' iq['disco_items']['node'] = node if node else '' if kwargs.get('iterator', False) and self.xmpp['xep_0059']: - raise NotImplementedError("XEP 0059 has not yet been fixed") return self.xmpp['xep_0059'].iterate(iq, 'disco_items') else: - return iq.send(timeout=kwargs.get('timeout', None), + return await iq.send(timeout=kwargs.get('timeout', None), callback=kwargs.get('callback', None), timeout_callback=kwargs.get('timeout_callback', None)) - def set_items(self, jid=None, node=None, **kwargs): + def set_items(self, jid=None, node=None, **kwargs) -> Future: """ Set or replace all items for the specified JID/node combination. The given items must be in a list or set where each item is a tuple of the form: (jid, node, name). + + .. versionchanged:: 1.8.0 + This function now returns a Future. + :param jid: The JID to modify. :param node: Optional node to modify. :param items: A series of items in tuple format. """ - self.api['set_items'](jid, node, None, kwargs) + return self.api['set_items'](jid, node, None, kwargs) - def del_items(self, jid=None, node=None, **kwargs): + def del_items(self, jid=None, node=None, **kwargs) -> Future: """ Remove all items from the given JID/node combination. + .. versionchanged:: 1.8.0 + This function now returns a Future. + Arguments: :param jid: The JID to modify. :param node: Optional node to modify. """ - self.api['del_items'](jid, node, None, kwargs) + return self.api['del_items'](jid, node, None, kwargs) - def add_item(self, jid='', name='', node=None, subnode='', ijid=None): + def add_item(self, jid='', name='', node=None, subnode='', ijid=None) -> Future: """ Add a new item element to the given JID/node combination. Each item is required to have a JID, but may also specify a node value to reference non-addressable entities. + .. versionchanged:: 1.8.0 + This function now returns a Future. + :param jid: The JID for the item. :param name: Optional name for the item. :param node: The node to modify. @@ -488,9 +514,9 @@ class XEP_0030(BasePlugin): kwargs = {'ijid': jid, 'name': name, 'inode': subnode} - self.api['add_item'](ijid, node, None, kwargs) + return self.api['add_item'](ijid, node, None, kwargs) - def del_item(self, jid=None, node=None, **kwargs): + def del_item(self, jid=None, node=None, **kwargs) -> Future: """ Remove a single item from the given JID/node combination. @@ -499,10 +525,10 @@ class XEP_0030(BasePlugin): :param ijid: The item's JID. :param inode: The item's node. """ - self.api['del_item'](jid, node, None, kwargs) + return self.api['del_item'](jid, node, None, kwargs) def add_identity(self, category='', itype='', name='', - node=None, jid=None, lang=None): + node=None, jid=None, lang=None) -> Future: """ Add a new identity to the given JID/node combination. @@ -514,6 +540,9 @@ class XEP_0030(BasePlugin): category/type/xml:lang pairs are allowed so long as the names are different. A category and type is always required. + .. versionchanged:: 1.8.0 + This function now returns a Future. + :param category: The identity's category. :param itype: The identity's type. :param name: Optional name for the identity. @@ -525,24 +554,31 @@ class XEP_0030(BasePlugin): 'itype': itype, 'name': name, 'lang': lang} - self.api['add_identity'](jid, node, None, kwargs) + return self.api['add_identity'](jid, node, None, kwargs) def add_feature(self, feature: str, node: Optional[str] = None, - jid: Optional[JID] = None): + jid: Optional[JID] = None) -> Future: """ Add a feature to a JID/node combination. + .. versionchanged:: 1.8.0 + This function now returns a Future. + :param feature: The namespace of the supported feature. :param node: The node to modify. :param jid: The JID to modify. """ kwargs = {'feature': feature} - self.api['add_feature'](jid, node, None, kwargs) + return self.api['add_feature'](jid, node, None, kwargs) - def del_identity(self, jid: Optional[JID] = None, node: Optional[str] = None, **kwargs): + def del_identity(self, jid: Optional[JID] = None, + node: Optional[str] = None, **kwargs) -> Future: """ Remove an identity from the given JID/node combination. + .. versionchanged:: 1.8.0 + This function now returns a Future. + :param jid: The JID to modify. :param node: The node to modify. :param category: The identity's category. @@ -550,67 +586,82 @@ class XEP_0030(BasePlugin): :param name: Optional, human readable name for the identity. :param lang: Optional, the identity's xml:lang value. """ - self.api['del_identity'](jid, node, None, kwargs) + return self.api['del_identity'](jid, node, None, kwargs) - def del_feature(self, jid=None, node=None, **kwargs): + def del_feature(self, jid=None, node=None, **kwargs) -> Future: """ Remove a feature from a given JID/node combination. + .. versionchanged:: 1.8.0 + This function now returns a Future. + :param jid: The JID to modify. :param node: The node to modify. :param feature: The feature's namespace. """ - self.api['del_feature'](jid, node, None, kwargs) + return self.api['del_feature'](jid, node, None, kwargs) - def set_identities(self, jid=None, node=None, **kwargs): + def set_identities(self, jid=None, node=None, **kwargs) -> Future: """ Add or replace all identities for the given JID/node combination. The identities must be in a set where each identity is a tuple of the form: (category, type, lang, name) + .. versionchanged:: 1.8.0 + This function now returns a Future. + :param jid: The JID to modify. :param node: The node to modify. :param identities: A set of identities in tuple form. :param lang: Optional, xml:lang value. """ - self.api['set_identities'](jid, node, None, kwargs) + return self.api['set_identities'](jid, node, None, kwargs) - def del_identities(self, jid=None, node=None, **kwargs): + def del_identities(self, jid=None, node=None, **kwargs) -> Future: """ Remove all identities for a JID/node combination. If a language is specified, only identities using that language will be removed. + .. versionchanged:: 1.8.0 + This function now returns a Future. + :param jid: The JID to modify. :param node: The node to modify. :param lang: Optional. If given, only remove identities using this xml:lang value. """ - self.api['del_identities'](jid, node, None, kwargs) + return self.api['del_identities'](jid, node, None, kwargs) - def set_features(self, jid=None, node=None, **kwargs): + def set_features(self, jid=None, node=None, **kwargs) -> Future: """ Add or replace the set of supported features for a JID/node combination. + .. versionchanged:: 1.8.0 + This function now returns a Future. + :param jid: The JID to modify. :param node: The node to modify. :param features: The new set of supported features. """ - self.api['set_features'](jid, node, None, kwargs) + return self.api['set_features'](jid, node, None, kwargs) - def del_features(self, jid=None, node=None, **kwargs): + def del_features(self, jid=None, node=None, **kwargs) -> Future: """ Remove all features from a JID/node combination. + .. versionchanged:: 1.8.0 + This function now returns a Future. + :param jid: The JID to modify. :param node: The node to modify. """ - self.api['del_features'](jid, node, None, kwargs) + return self.api['del_features'](jid, node, None, kwargs) - def _run_node_handler(self, htype, jid, node=None, ifrom=None, data=None): + async def _run_node_handler(self, htype, jid, node=None, ifrom=None, data=None): """ Execute the most specific node handler for the given JID/node combination. @@ -623,9 +674,9 @@ class XEP_0030(BasePlugin): if not data: data = {} - return self.api[htype](jid, node, ifrom, data) + return await self.api[htype](jid, node, ifrom, data) - def _handle_disco_info(self, iq): + async def _handle_disco_info(self, iq): """ Process an incoming disco#info stanza. If it is a get request, find and return the appropriate identities @@ -637,10 +688,10 @@ class XEP_0030(BasePlugin): if iq['type'] == 'get': log.debug("Received disco info query from " + \ "<%s> to <%s>.", iq['from'], iq['to']) - info = self.api['get_info'](iq['to'], - iq['disco_info']['node'], - iq['from'], - iq) + info = await self.api['get_info'](iq['to'], + iq['disco_info']['node'], + iq['from'], + iq) if isinstance(info, Iq): info['id'] = iq['id'] info.send() @@ -662,13 +713,13 @@ class XEP_0030(BasePlugin): ito = iq['to'].full else: ito = None - self.api['cache_info'](iq['from'], - iq['disco_info']['node'], - ito, - iq) + await self.api['cache_info'](iq['from'], + iq['disco_info']['node'], + ito, + iq) self.xmpp.event('disco_info', iq) - def _handle_disco_items(self, iq): + async def _handle_disco_items(self, iq): """ Process an incoming disco#items stanza. If it is a get request, find and return the appropriate items. If it @@ -679,10 +730,10 @@ class XEP_0030(BasePlugin): if iq['type'] == 'get': log.debug("Received disco items query from " + \ "<%s> to <%s>.", iq['from'], iq['to']) - items = self.api['get_items'](iq['to'], - iq['disco_items']['node'], - iq['from'], - iq) + items = await self.api['get_items'](iq['to'], + iq['disco_items']['node'], + iq['from'], + iq) if isinstance(items, Iq): items.send() else: diff --git a/slixmpp/plugins/xep_0030/static.py b/slixmpp/plugins/xep_0030/static.py index 1b5ff2d8..1ae34148 100644 --- a/slixmpp/plugins/xep_0030/static.py +++ b/slixmpp/plugins/xep_0030/static.py @@ -109,7 +109,7 @@ class StaticDisco(object): # the requester's JID, except for cached results. To do that, # register a custom node handler. - def supports(self, jid, node, ifrom, data): + async def supports(self, jid, node, ifrom, data): """ Check if a JID supports a given feature. @@ -137,8 +137,8 @@ class StaticDisco(object): return False try: - info = self.disco.get_info(jid=jid, node=node, - ifrom=ifrom, **data) + info = await self.disco.get_info(jid=jid, node=node, + ifrom=ifrom, **data) info = self.disco._wrap(ifrom, jid, info, True) features = info['disco_info']['features'] return feature in features @@ -147,7 +147,7 @@ class StaticDisco(object): except IqTimeout: return None - def has_identity(self, jid, node, ifrom, data): + async def has_identity(self, jid, node, ifrom, data): """ Check if a JID has a given identity. @@ -176,8 +176,8 @@ class StaticDisco(object): 'cached': data.get('cached', True)} try: - info = self.disco.get_info(jid=jid, node=node, - ifrom=ifrom, **data) + info = await self.disco.get_info(jid=jid, node=node, + ifrom=ifrom, **data) info = self.disco._wrap(ifrom, jid, info, True) trunc = lambda i: (i[0], i[1], i[2]) return identity in map(trunc, info['disco_info']['identities']) diff --git a/tests/test_stream_xep_0030.py b/tests/test_stream_xep_0030.py index d1ad9087..8cba8280 100644 --- a/tests/test_stream_xep_0030.py +++ b/tests/test_stream_xep_0030.py @@ -1,5 +1,5 @@ +import asyncio import time -import threading import unittest from slixmpp.test import SlixTest @@ -288,7 +288,9 @@ class TestStreamDisco(SlixTest): self.xmpp.add_event_handler('disco_info', handle_disco_info) - self.xmpp['xep_0030'].get_info('user@localhost', 'foo') + + self.xmpp.wrap(self.xmpp['xep_0030'].get_info('user@localhost', 'foo')) + self.wait_() self.send(""" @@ -483,7 +485,8 @@ class TestStreamDisco(SlixTest): self.xmpp.add_event_handler('disco_items', handle_disco_items) - self.xmpp['xep_0030'].get_items('user@localhost', 'foo') + self.xmpp.wrap(self.xmpp['xep_0030'].get_items('user@localhost', 'foo')) + self.wait_() self.send("""