XEP-0030: API changes

- ``supports``, ``has_identity``, ``get_info``, ``get_items`` are now coroutines
- ``set_info````set_items``, ``del_items``, ``add_item``, ``add_identity``,
  ``del_identity``, ``set_identities``, ``del_identities``, ``add_feature``,
  ``del_feature``, ``set_feature``, ``set_features``, ``del_features``
  now return a Future

also fix has_identity and supports which have been broken in forever
This commit is contained in:
mathieui 2021-02-14 12:06:05 +01:00
parent 7772e26a8c
commit 13de36baa1
3 changed files with 125 additions and 71 deletions

View file

@ -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,
info = await self.api['get_cached_info'](
jid, node,
kwargs.get('ifrom', None),
kwargs)
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,7 +688,7 @@ 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'],
info = await self.api['get_info'](iq['to'],
iq['disco_info']['node'],
iq['from'],
iq)
@ -662,13 +713,13 @@ class XEP_0030(BasePlugin):
ito = iq['to'].full
else:
ito = None
self.api['cache_info'](iq['from'],
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,7 +730,7 @@ 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'],
items = await self.api['get_items'](iq['to'],
iq['disco_items']['node'],
iq['from'],
iq)

View file

@ -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,7 +137,7 @@ class StaticDisco(object):
return False
try:
info = self.disco.get_info(jid=jid, node=node,
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']
@ -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,7 +176,7 @@ class StaticDisco(object):
'cached': data.get('cached', True)}
try:
info = self.disco.get_info(jid=jid, node=node,
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])

View file

@ -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("""
<iq type="get" to="user@localhost" id="1">
@ -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("""
<iq type="get" to="user@localhost" id="1">