Compare commits

...

20 Commits

Author SHA1 Message Date
mathieui
36824379c3 slixmpp 1.2.1
Fix a few bugs along with the testsuite, and remove the asyncio loop
monkeypatch hack.
2016-10-05 20:32:32 +02:00
mathieui
a0a37c19ff Remove monkeypatching hack on the event loop
This allowed us to schedule events in-order later in the event loop, but
was detrimental to using other event loops and debugging.
2016-10-05 20:19:07 +02:00
mathieui
1b5fe57a5e Fix XEP-0060 tests 2016-10-04 21:21:55 +02:00
mathieui
5da31db0c7 Fix stanza accessors case in tests
They were using deprecated (and-removed) style.
2016-10-04 21:15:01 +02:00
mathieui
f8cea760b6 Fix the gmail_notify plugin 2016-10-04 21:10:10 +02:00
mathieui
5ef01ecdd1 Fix XEP-0033
Re-add relevant stanza methods, broken in 7cd1cf32ae
2016-10-04 19:47:11 +02:00
mathieui
62aafe0ee7 Attrib property has been removed 2016-10-04 19:43:45 +02:00
mathieui
cf3f36ac52 Set unset part of a JID to empty string instead of None
it breaks assumptions on the type of the value
2016-10-04 19:42:05 +02:00
mathieui
b88d2ecd77 Add more checks in the XEP-0060 stanza building
Try to not append slixmpp stanzas to ElementTree objects.
2016-10-04 19:31:49 +02:00
mathieui
e691850a2b Fix XEP-0128
Broken since 125336aeee due to unforeseen consequences of a variable
removal.
2016-10-04 19:26:03 +02:00
mathieui
d4bff8dee6 Fix XEP-0009
Broken since 3a9b45e4f because of an overzealous cleanup.
2016-10-04 19:23:21 +02:00
mathieui
187c350805 Update for slixmpp 1.2 2016-10-02 17:36:14 +02:00
mathieui
96d1c26f90 Add a fallback if the lang we want is not available
Previously, trying to get a text node with a lang which is different
from the one we specified would return nothing, which means e.g. a
message would be ignored because its body is of lang 'fr' when we setup
slixmpp to prefer 'en'. We want to return something when there is an
available, valid content in a different language.
2016-10-02 17:12:47 +02:00
mathieui
46a90749f8 Fix uses of super() in the codebase
Fix #3165, we don’t need to use the long form to get the superobject in
our supported python versions.
2016-09-30 21:25:36 +02:00
mathieui
0c63a4bbda Fix #3226 (unicity of scheduled event names)
Thanks tchiroux for raising the issue and providing the fix as well.
2016-09-30 20:59:31 +02:00
mathieui
e4696e0471 Merge branch 'doc_fixes' of https://github.com/SamWhited/slixmpp 2016-09-30 20:53:36 +02:00
Sam Whited
8217dc5239 Minor documentation fixes 2016-09-30 13:49:04 -05:00
mathieui
2586abc0d3 Fix xep-0050 stanza
broken in 3a9b45e4f2
2016-09-20 20:51:21 +02:00
Emmanuel Gil Peyrot
28f84ab3d9 ElementBase: Remove support for TitleCase methods.
This gains about 1/8th of the time spent in __getitem__.
2016-09-21 01:31:53 +09:00
Emmanuel Gil Peyrot
813b45aded XEP-0045: Remove support for old-style {get,set,del}TitleCase methods. 2016-09-21 01:28:24 +09:00
29 changed files with 160 additions and 180 deletions

View File

@@ -12,8 +12,8 @@ Building
--------
Slixmpp can make use of cython to improve performance on critical modules.
To do that, cython3 is necessary along with libidn headers. Otherwise,
no compilation is needed. Building is done by running setup.py::
To do that, **cython3** is necessary along with **libidn** headers.
Otherwise, no compilation is needed. Building is done by running setup.py::
python3 setup.py build_ext --inplace
@@ -108,6 +108,11 @@ Slixmpp Credits
**Contributors:**
- Emmanuel Gil Peyrot (`Link mauve <xmpp:linkmauve@linkmauve.fr?message>`_)
- Sam Whited (`Sam Whited <mailto:sam@samwhited.com>`_)
- Dan Sully (`Dan Sully <mailto:daniel@electricalrain.com>`_)
- Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_)
- Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
- Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_)
Credits (SleekXMPP)
-------------------

View File

@@ -70,7 +70,7 @@ as well.
class EchoBot(slixmpp.ClientXMPP):
def __init__(self, jid, password):
super(EchoBot, self).__init__(jid, password)
super().__init__(jid, password)
Handling Session Start
~~~~~~~~~~~~~~~~~~~~~~
@@ -83,7 +83,7 @@ started. To do that, we will register an event handler for the :term:`session_st
.. code-block:: python
def __init__(self, jid, password):
super(EchoBot, self).__init__(jid, password)
super().__init__(jid, password)
self.add_event_handler('session_start', self.start)
@@ -153,7 +153,7 @@ whenever a messsage is received.
.. code-block:: python
def __init__(self, jid, password):
super(EchoBot, self).__init__(jid, password)
super().__init__(jid, password)
self.add_event_handler('session_start', self.start)
self.add_event_handler('message', self.message)

View File

@@ -63,13 +63,13 @@ has been established:
def start(self, event):
self.get_roster()
self.send_presence()
self.plugin['xep_0045'].joinMUC(self.room,
self.nick,
wait=True)
self.plugin['xep_0045'].join_muc(self.room,
self.nick,
wait=True)
Note that as in :ref:`echobot`, we need to include send an initial presence and request
the roster. Next, we want to join the group chat, so we call the
``joinMUC`` method of the MUC plugin.
``join_muc`` method of the MUC plugin.
.. note::

View File

@@ -24,7 +24,7 @@ for the JID that will receive our message, and the string content of the message
class SendMsgBot(slixmpp.ClientXMPP):
def __init__(self, jid, password, recipient, msg):
super(SendMsgBot, self).__init__(jid, password)
super().__init__(jid, password)
self.recipient = recipient
self.msg = msg

View File

@@ -67,11 +67,11 @@ class MUCBot(slixmpp.ClientXMPP):
"""
self.get_roster()
self.send_presence()
self.plugin['xep_0045'].joinMUC(self.room,
self.nick,
# If a room password is needed, use:
# password=the_room_password,
wait=True)
self.plugin['xep_0045'].join_muc(self.room,
self.nick,
# If a room password is needed, use:
# password=the_room_password,
wait=True)
def muc_message(self, msg):
"""

View File

@@ -15,7 +15,7 @@ class PubsubClient(slixmpp.ClientXMPP):
def __init__(self, jid, password, server,
node=None, action='nodes', data=''):
super(PubsubClient, self).__init__(jid, password)
super().__init__(jid, password)
self.register_plugin('xep_0030')
self.register_plugin('xep_0059')

View File

@@ -14,7 +14,7 @@ from slixmpp.xmlstream.handler import Callback
class PubsubEvents(slixmpp.ClientXMPP):
def __init__(self, jid, password):
super(PubsubEvents, self).__init__(jid, password)
super().__init__(jid, password)
self.register_plugin('xep_0030')
self.register_plugin('xep_0059')

View File

@@ -22,7 +22,7 @@ from slixmpp import ClientXMPP
class LocationBot(ClientXMPP):
def __init__(self, jid, password):
super(LocationBot, self).__init__(jid, password)
super().__init__(jid, password)
self.add_event_handler('session_start', self.start)
self.add_event_handler('user_location_publish',

View File

@@ -17,7 +17,7 @@ from slixmpp import ClientXMPP
class TuneBot(ClientXMPP):
def __init__(self, jid, password):
super(TuneBot, self).__init__(jid, password)
super().__init__(jid, password)
# Check for the current song every 5 seconds.
self.schedule('Check Current Tune', 5, self._update_tune, repeat=True)

View File

@@ -141,8 +141,11 @@ class ClientXMPP(BaseXMPP):
will be used.
:param address: A tuple containing the server's host and port.
:param use_tls: Indicates if TLS should be used for the
connection. Defaults to ``True``.
:param force_starttls: Indicates that negotiation should be aborted
if the server does not advertise support for
STARTTLS. Defaults to ``True``.
:param disable_starttls: Disables TLS for the connection.
Defaults to ``False``.
:param use_ssl: Indicates if the older SSL connection method
should be used. Defaults to ``False``.
"""

View File

@@ -77,7 +77,7 @@ class IqTimeout(XMPPError):
"""
def __init__(self, iq):
super(IqTimeout, self).__init__(
super().__init__(
condition='remote-server-timeout',
etype='cancel')
@@ -94,7 +94,7 @@ class IqError(XMPPError):
"""
def __init__(self, iq):
super(IqError, self).__init__(
super().__init__(
condition=iq['error']['condition'],
text=iq['error']['text'],
etype=iq['error']['type'])

View File

@@ -79,7 +79,7 @@ def _validate_node(node):
:returns: The local portion of a JID, as validated by nodeprep.
"""
if node is None:
return None
return ''
try:
node = nodeprep(node)
@@ -160,7 +160,7 @@ def _validate_resource(resource):
:returns: The local portion of a JID, as validated by resourceprep.
"""
if resource is None:
return None
return ''
try:
resource = resourceprep(resource)

View File

@@ -308,7 +308,7 @@ class BasePlugin(object):
if key in self.default_config:
self.config[key] = value
else:
super(BasePlugin, self).__setattr__(key, value)
super().__setattr__(key, value)
def _init(self):
"""Initialize plugin state, such as registering event handlers.

View File

@@ -23,13 +23,13 @@ class GmailQuery(ElementBase):
plugin_attrib = 'gmail'
interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search'))
def getSearch(self):
def get_search(self):
return self['q']
def setSearch(self, search):
def set_search(self, search):
self['q'] = search
def delSearch(self):
def del_search(self):
del self['q']
@@ -40,17 +40,17 @@ class MailBox(ElementBase):
interfaces = set(('result-time', 'total-matched', 'total-estimate',
'url', 'threads', 'matched', 'estimate'))
def getThreads(self):
def get_threads(self):
threads = []
for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace,
MailThread.name)):
threads.append(MailThread(xml=threadXML, parent=None))
return threads
def getMatched(self):
def get_matched(self):
return self['total-matched']
def getEstimate(self):
def get_estimate(self):
return self['total-estimate'] == '1'
@@ -62,7 +62,7 @@ class MailThread(ElementBase):
'senders', 'url', 'labels', 'subject', 'snippet'))
sub_interfaces = set(('labels', 'subject', 'snippet'))
def getSenders(self):
def get_senders(self):
senders = []
sendersXML = self.xml.find('{%s}senders' % self.namespace)
if sendersXML is not None:
@@ -77,10 +77,10 @@ class MailSender(ElementBase):
plugin_attrib = 'sender'
interfaces = set(('address', 'name', 'originator', 'unread'))
def getOriginator(self):
def get_originator(self):
return self.xml.attrib.get('originator', '0') == '1'
def getUnread(self):
def get_unread(self):
return self.xml.attrib.get('unread', '0') == '1'

View File

@@ -92,13 +92,13 @@ def _py2xml(*args):
def xml2py(params):
namespace = 'jabber:iq:rpc'
vals = []
for param in params.xml.findall('{%s}param' % namespace):
for param in params.findall('{%s}param' % namespace):
vals.append(_xml2py(param.find('{%s}value' % namespace)))
return vals
def _xml2py(value):
namespace = 'jabber:iq:rpc'
find_value = value.xml.find
find_value = value.find
if find_value('{%s}nil' % namespace) is not None:
return None
if find_value('{%s}i4' % namespace) is not None:

View File

@@ -117,9 +117,12 @@ for atype in ('all', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'):
setattr(Addresses, "set_%s" % atype, set_multi)
setattr(Addresses, "del_%s" % atype, del_multi)
# To retain backwards compatibility:
if atype == 'all':
Addresses.interfaces.add('addresses')
setattr(Addresses, "get_addresses", get_multi)
setattr(Addresses, "set_addresses", set_multi)
setattr(Addresses, "del_addresses", del_multi)
register_stanza_plugin(Addresses, Address, iterable=True)

View File

@@ -29,82 +29,82 @@ class MUCPresence(ElementBase):
affiliations = set(('', ))
roles = set(('', ))
def getXMLItem(self):
def get_xml_item(self):
item = self.xml.find('{http://jabber.org/protocol/muc#user}item')
if item is None:
item = ET.Element('{http://jabber.org/protocol/muc#user}item')
self.xml.append(item)
return item
def getAffiliation(self):
def get_affiliation(self):
#TODO if no affilation, set it to the default and return default
item = self.getXMLItem()
item = self.get_xml_item()
return item.get('affiliation', '')
def setAffiliation(self, value):
item = self.getXMLItem()
def set_affiliation(self, value):
item = self.get_xml_item()
#TODO check for valid affiliation
item.attrib['affiliation'] = value
return self
def delAffiliation(self):
item = self.getXMLItem()
def del_affiliation(self):
item = self.get_xml_item()
#TODO set default affiliation
if 'affiliation' in item.attrib: del item.attrib['affiliation']
return self
def getJid(self):
item = self.getXMLItem()
def get_jid(self):
item = self.get_xml_item()
return JID(item.get('jid', ''))
def setJid(self, value):
item = self.getXMLItem()
def set_jid(self, value):
item = self.get_xml_item()
if not isinstance(value, str):
value = str(value)
item.attrib['jid'] = value
return self
def delJid(self):
item = self.getXMLItem()
def del_jid(self):
item = self.get_xml_item()
if 'jid' in item.attrib: del item.attrib['jid']
return self
def getRole(self):
item = self.getXMLItem()
def get_role(self):
item = self.get_xml_item()
#TODO get default role, set default role if none
return item.get('role', '')
def setRole(self, value):
item = self.getXMLItem()
def set_role(self, value):
item = self.get_xml_item()
#TODO check for valid role
item.attrib['role'] = value
return self
def delRole(self):
item = self.getXMLItem()
def del_role(self):
item = self.get_xml_item()
#TODO set default role
if 'role' in item.attrib: del item.attrib['role']
return self
def getNick(self):
def get_nick(self):
return self.parent()['from'].resource
def getRoom(self):
def get_room(self):
return self.parent()['from'].bare
def setNick(self, value):
def set_nick(self, value):
log.warning("Cannot set nick through mucpresence plugin.")
return self
def setRoom(self, value):
def set_room(self, value):
log.warning("Cannot set room through mucpresence plugin.")
return self
def delNick(self):
def del_nick(self):
log.warning("Cannot delete nick through mucpresence plugin.")
return self
def delRoom(self):
def del_room(self):
log.warning("Cannot delete room through mucpresence plugin.")
return self
@@ -121,7 +121,7 @@ class XEP_0045(BasePlugin):
def plugin_init(self):
self.rooms = {}
self.ourNicks = {}
self.our_nicks = {}
self.xep = '0045'
# load MUC support in presence stanzas
register_stanza_plugin(Presence, MUCPresence)
@@ -201,22 +201,22 @@ class XEP_0045(BasePlugin):
"""
self.xmpp.event('groupchat_subject', msg)
def jidInRoom(self, room, jid):
def jid_in_room(self, room, jid):
for nick in self.rooms[room]:
entry = self.rooms[room][nick]
if entry is not None and entry['jid'].full == jid:
return True
return False
def getNick(self, room, jid):
def get_nick(self, room, jid):
for nick in self.rooms[room]:
entry = self.rooms[room][nick]
if entry is not None and entry['jid'].full == jid:
return nick
def configureRoom(self, room, form=None, ifrom=None):
def configure_room(self, room, form=None, ifrom=None):
if form is None:
form = self.getRoomConfig(room, ifrom=ifrom)
form = self.get_room_config(room, ifrom=ifrom)
iq = self.xmpp.make_iq_set()
iq['to'] = room
if ifrom is not None:
@@ -234,7 +234,7 @@ class XEP_0045(BasePlugin):
return False
return True
def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None):
def join_muc(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None):
""" Join the specified room, requesting 'maxhistory' lines of history.
"""
stanza = self.xmpp.make_presence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow, pfrom=pfrom)
@@ -258,7 +258,7 @@ class XEP_0045(BasePlugin):
expect = ET.Element("{%s}presence" % self.xmpp.default_ns, {'from':"%s/%s" % (room, nick)})
self.xmpp.send(stanza, expect)
self.rooms[room] = {}
self.ourNicks[room] = nick
self.our_nicks[room] = nick
def destroy(self, room, reason='', altroom = '', ifrom=None):
iq = self.xmpp.make_iq_set()
@@ -283,7 +283,7 @@ class XEP_0045(BasePlugin):
return False
return True
def setAffiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None):
def set_affiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None):
""" Change room affiliation."""
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
raise TypeError
@@ -305,7 +305,7 @@ class XEP_0045(BasePlugin):
return False
return True
def setRole(self, room, nick, role):
def set_role(self, room, nick, role):
""" Change role property of a nick in a room.
Typically, roles are temporary (they last only as long as you are in the
room), whereas affiliations are permanent (they last across groupchat
@@ -337,7 +337,7 @@ class XEP_0045(BasePlugin):
msg.append(x)
self.xmpp.send(msg)
def leaveMUC(self, room, nick, msg='', pfrom=None):
def leave_muc(self, room, nick, msg='', pfrom=None):
""" Leave the specified room.
"""
if msg:
@@ -346,7 +346,7 @@ class XEP_0045(BasePlugin):
self.xmpp.send_presence(pshow='unavailable', pto="%s/%s" % (room, nick), pfrom=pfrom)
del self.rooms[room]
def getRoomConfig(self, room, ifrom=''):
def get_room_config(self, room, ifrom=''):
iq = self.xmpp.make_iq_get('http://jabber.org/protocol/muc#owner')
iq['to'] = room
iq['from'] = ifrom
@@ -362,7 +362,7 @@ class XEP_0045(BasePlugin):
raise ValueError
return self.xmpp.plugin['xep_0004'].build_form(form)
def cancelConfig(self, room, ifrom=None):
def cancel_config(self, room, ifrom=None):
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
x = ET.Element('{jabber:x:data}x', type='cancel')
query.append(x)
@@ -371,7 +371,7 @@ class XEP_0045(BasePlugin):
iq['from'] = ifrom
iq.send()
def setRoomConfig(self, room, config, ifrom=''):
def set_room_config(self, room, config, ifrom=''):
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
config['type'] = 'submit'
query.append(config)
@@ -380,31 +380,31 @@ class XEP_0045(BasePlugin):
iq['from'] = ifrom
iq.send()
def getJoinedRooms(self):
def get_joined_rooms(self):
return self.rooms.keys()
def getOurJidInRoom(self, roomJid):
def get_our_jid_in_room(self, room_jid):
""" Return the jid we're using in a room.
"""
return "%s/%s" % (roomJid, self.ourNicks[roomJid])
return "%s/%s" % (room_jid, self.our_nicks[room_jid])
def getJidProperty(self, room, nick, jidProperty):
def get_jid_property(self, room, nick, jid_property):
""" Get the property of a nick in a room, such as its 'jid' or 'affiliation'
If not found, return None.
"""
if room in self.rooms and nick in self.rooms[room] and jidProperty in self.rooms[room][nick]:
return self.rooms[room][nick][jidProperty]
if room in self.rooms and nick in self.rooms[room] and jid_property in self.rooms[room][nick]:
return self.rooms[room][nick][jid_property]
else:
return None
def getRoster(self, room):
def get_roster(self, room):
""" Get the list of nicks in a room.
"""
if room not in self.rooms.keys():
return None
return self.rooms[room].keys()
def getUsersByAffiliation(cls, room, affiliation='member', ifrom=None):
def get_users_by_affiliation(cls, room, affiliation='member', ifrom=None):
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
raise TypeError
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
@@ -415,5 +415,4 @@ class XEP_0045(BasePlugin):
return iq.send()
xep_0045 = XEP_0045
register_plugin(XEP_0045)

View File

@@ -59,7 +59,7 @@ class XEP_0048(BasePlugin):
for conf in bookmarks['conferences']:
if conf['autojoin']:
log.debug('Auto joining %s as %s', conf['jid'], conf['nick'])
self.xmpp['xep_0045'].joinMUC(conf['jid'], conf['nick'],
self.xmpp['xep_0045'].join_muc(conf['jid'], conf['nick'],
password=conf['password'])
def set_bookmarks(self, bookmarks, method=None, **iqargs):

View File

@@ -136,7 +136,7 @@ class Command(ElementBase):
('error', 'The command ran, but had errors')]
"""
notes = []
notes_xml = self.findall('{%s}note' % self.namespace)
notes_xml = self.xml.findall('{%s}note' % self.namespace)
for note in notes_xml:
notes.append((note.attrib.get('type', 'info'),
note.text))
@@ -167,7 +167,7 @@ class Command(ElementBase):
"""
Remove all notes associated with the command result.
"""
notes_xml = self.findall('{%s}note' % self.namespace)
notes_xml = self.xml.findall('{%s}note' % self.namespace)
for note in notes_xml:
self.xml.remove(note)

View File

@@ -127,7 +127,7 @@ class Telephone(ElementBase):
'ISDN', 'PCS', 'PREF'])
def setup(self, xml=None):
super(Telephone, self).setup(xml=xml)
super().setup(xml=xml)
## this blanks out numbers received from server
##self._set_sub_text('NUMBER', '', keep=True)

View File

@@ -206,7 +206,10 @@ class Options(ElementBase):
return form
def set_options(self, value):
self.xml.append(value)
if isinstance(value, ElementBase):
self.xml.append(value.xml)
else:
self.xml.append(value)
return self
def del_options(self):
@@ -238,7 +241,10 @@ class PublishOptions(ElementBase):
if value is None:
self.del_publish_options()
else:
self.xml.append(value)
if isinstance(value, ElementBase):
self.xml.append(value.xml)
else:
self.xml.append(value)
return self
def del_publish_options(self):

View File

@@ -38,9 +38,8 @@ class StaticExtendedDisco(object):
The data parameter may provide:
data -- Either a single data form, or a list of data forms.
"""
with self.static.lock:
self.del_extended_info(jid, node, ifrom, data)
self.add_extended_info(jid, node, ifrom, data)
self.del_extended_info(jid, node, ifrom, data)
self.add_extended_info(jid, node, ifrom, data)
def add_extended_info(self, jid, node, ifrom, data):
"""
@@ -49,16 +48,15 @@ class StaticExtendedDisco(object):
The data parameter may provide:
data -- Either a single data form, or a list of data forms.
"""
with self.static.lock:
self.static.add_node(jid, node)
self.static.add_node(jid, node)
forms = data.get('data', [])
if not isinstance(forms, list):
forms = [forms]
forms = data.get('data', [])
if not isinstance(forms, list):
forms = [forms]
info = self.static.get_node(jid, node)['info']
for form in forms:
info.append(form)
info = self.static.get_node(jid, node)['info']
for form in forms:
info.append(form)
def del_extended_info(self, jid, node, ifrom, data):
"""
@@ -66,8 +64,7 @@ class StaticExtendedDisco(object):
The data parameter is not used.
"""
with self.static.lock:
if self.static.node_exists(jid, node):
info = self.static.get_node(jid, node)['info']
for form in info['substanza']:
info.xml.remove(form.xml)
if self.static.node_exists(jid, node):
info = self.static.get_node(jid, node)['info']
for form in info['substanza']:
info.xml.remove(form.xml)

View File

@@ -9,5 +9,5 @@
# We don't want to have to import the entire library
# just to get the version info for setup.py
__version__ = '1.1'
__version_info__ = (1, 1)
__version__ = '1.2.1'
__version_info__ = (1, 2, 1)

View File

@@ -1,38 +1,10 @@
"""
A module that monkey patches the standard asyncio module to add an
idle_call() method to the main loop. This method is used to execute a
callback whenever the loop is not busy handling anything else. This means
that it is a callback with lower priority than IO, timer, or even
call_soon() ones. These callback are called only once each.
asyncio-related utilities
"""
import asyncio
from asyncio import events
from functools import wraps
import collections
def idle_call(self, callback):
if asyncio.iscoroutinefunction(callback):
raise TypeError("coroutines cannot be used with idle_call()")
handle = events.Handle(callback, [], self)
self._idle.append(handle)
def my_run_once(self):
if self._idle:
self._ready.append(events.Handle(lambda: None, (), self))
real_run_once(self)
if self._idle:
handle = self._idle.popleft()
handle._run()
cls = asyncio.get_event_loop().__class__
cls._idle = collections.deque()
cls.idle_call = idle_call
real_run_once = cls._run_once
cls._run_once = my_run_once
def future_wrapper(func):
"""
Make sure the result of a function call is an asyncio.Future()

View File

@@ -668,9 +668,6 @@ class ElementBase(object):
if hasattr(self, get_method):
return getattr(self, get_method)(**kwargs)
get_method2 = "get%s" % attrib.title()
if hasattr(self, get_method2):
return getattr(self, get_method2)(**kwargs)
else:
if attrib in self.sub_interfaces:
return self._get_sub_text(attrib, lang=lang)
@@ -733,7 +730,6 @@ class ElementBase(object):
if attrib in self.interfaces or attrib == 'lang':
if value is not None:
set_method = "set_%s" % attrib.lower()
set_method2 = "set%s" % attrib.title()
if self.plugin_overrides:
name = self.plugin_overrides.get(set_method, None)
@@ -746,8 +742,6 @@ class ElementBase(object):
if hasattr(self, set_method):
getattr(self, set_method)(value, **kwargs)
elif hasattr(self, set_method2):
getattr(self, set_method2)(value, **kwargs)
else:
if attrib in self.sub_interfaces:
if lang == '*':
@@ -820,7 +814,6 @@ class ElementBase(object):
if attrib in self.interfaces or attrib == 'lang':
del_method = "del_%s" % attrib.lower()
del_method2 = "del%s" % attrib.title()
if self.plugin_overrides:
name = self.plugin_overrides.get(del_method, None)
@@ -833,8 +826,6 @@ class ElementBase(object):
if hasattr(self, del_method):
getattr(self, del_method)(**kwargs)
elif hasattr(self, del_method2):
getattr(self, del_method2)(**kwargs)
else:
if attrib in self.sub_interfaces:
return self._del_sub(attrib, lang=lang)
@@ -916,11 +907,17 @@ class ElementBase(object):
stanzas = self.xml.findall(name)
if not stanzas:
return default
result = None
for stanza in stanzas:
if stanza.attrib.get('{%s}lang' % XML_NS, default_lang) == lang:
if stanza.text is None:
return default
return stanza.text
result = stanza.text
break
if stanza.text:
result = stanza.text
if result is not None:
return result
return default
def _get_all_sub_text(self, name, default='', lang=None):

View File

@@ -256,9 +256,9 @@ class XMLStream(asyncio.BaseProtocol):
TODO fix the comment
:param force_starttls: If True, the connection will be aborted if
the server does not initiate a STARTTLS
negociation. If None, the connection will be
negotiation. If None, the connection will be
upgraded to TLS only if the server initiate
the STARTTLS negociation, otherwise it will
the STARTTLS negotiation, otherwise it will
connect in clear. If False it will never
upgrade to TLS, even if the server provides
it. Use this for example if youre on
@@ -380,7 +380,7 @@ class XMLStream(asyncio.BaseProtocol):
elif self.xml_depth == 1:
# A stanza is an XML element that is a direct child of
# the root element, hence the check of depth == 1
self.loop.idle_call(functools.partial(self.__spawn_event, xml))
self._spawn_event(xml)
if self.xml_root is not None:
# Keep the root element empty of children to
# save on memory use.
@@ -760,6 +760,9 @@ class XMLStream(asyncio.BaseProtocol):
:param repeat: Flag indicating if the scheduled event should
be reset and repeat after executing.
"""
if name in self.scheduled_events:
raise ValueError(
"There is already a scheduled event of name: %s" % name)
if seconds is None:
seconds = RESPONSE_TIMEOUT
cb = functools.partial(callback, *args, **kwargs)
@@ -769,7 +772,6 @@ class XMLStream(asyncio.BaseProtocol):
else:
handle = self.loop.call_later(seconds, self._execute_and_unschedule,
name, cb)
# Save that handle, so we can just cancel this scheduled event by
# canceling scheduled_events[name]
self.scheduled_events[name] = handle
@@ -891,7 +893,7 @@ class XMLStream(asyncio.BaseProtocol):
stanza['lang'] = self.peer_default_lang
return stanza
def __spawn_event(self, xml):
def _spawn_event(self, xml):
"""
Analyze incoming XML stanzas and convert them into stanza
objects if applicable and queue stream events to be processed

View File

@@ -142,7 +142,7 @@ class TestElementBase(SlixTest):
interfaces = set(('bar', 'baz', 'qux'))
sub_interfaces = set(('baz',))
def getQux(self):
def get_qux(self):
return 'qux'
class TestStanzaPlugin(ElementBase):
@@ -188,7 +188,7 @@ class TestElementBase(SlixTest):
interfaces = set(('bar', 'baz', 'qux'))
sub_interfaces = set(('baz',))
def setQux(self, value):
def set_qux(self, value):
pass
class TestStanzaPlugin(ElementBase):
@@ -222,7 +222,7 @@ class TestElementBase(SlixTest):
interfaces = set(('bar', 'baz', 'qux'))
sub_interfaces = set(('bar',))
def delQux(self):
def del_qux(self):
pass
class TestStanzaPlugin(ElementBase):
@@ -300,14 +300,14 @@ class TestElementBase(SlixTest):
namespace = "foo"
interfaces = set(('bar',))
def setBar(self, value):
def set_bar(self, value):
wrapper = ET.Element("{foo}wrapper")
bar = ET.Element("{foo}bar")
bar.text = value
wrapper.append(bar)
self.xml.append(wrapper)
def getBar(self):
def get_bar(self):
return self._get_sub_text("wrapper/bar", default="not found")
stanza = TestStanza()
@@ -333,16 +333,16 @@ class TestElementBase(SlixTest):
namespace = "foo"
interfaces = set(('bar', 'baz'))
def setBaz(self, value):
def set_baz(self, value):
self._set_sub_text("wrapper/baz", text=value)
def getBaz(self):
def get_baz(self):
return self._get_sub_text("wrapper/baz")
def setBar(self, value):
def set_bar(self, value):
self._set_sub_text("wrapper/bar", text=value)
def getBar(self):
def get_bar(self):
return self._get_sub_text("wrapper/bar")
stanza = TestStanza()
@@ -384,22 +384,22 @@ class TestElementBase(SlixTest):
namespace = "foo"
interfaces = set(('bar', 'baz'))
def setBar(self, value):
def set_bar(self, value):
self._set_sub_text("path/to/only/bar", value)
def getBar(self):
def get_bar(self):
return self._get_sub_text("path/to/only/bar")
def delBar(self):
def del_bar(self):
self._del_sub("path/to/only/bar")
def setBaz(self, value):
def set_baz(self, value):
self._set_sub_text("path/to/just/baz", value)
def getBaz(self):
def get_baz(self):
return self._get_sub_text("path/to/just/baz")
def delBaz(self):
def del_baz(self):
self._del_sub("path/to/just/baz")
stanza = TestStanza()
@@ -466,10 +466,10 @@ class TestElementBase(SlixTest):
interfaces = set(('bar','baz', 'qux'))
sub_interfaces = set(('qux',))
def setQux(self, value):
def set_qux(self, value):
self._set_sub_text('qux', text=value)
def getQux(self):
def get_qux(self):
return self._get_sub_text('qux')
class TestStanzaPlugin(ElementBase):

View File

@@ -20,12 +20,6 @@ class TestMessageStanzas(SlixTest):
msg = msg.reply()
self.failUnless(str(msg['to']) == 'room@someservice.someserver.tld')
def testAttribProperty(self):
"Test attrib property returning self"
msg = self.Message()
msg.attrib.attrib.attrib['to'] = 'usr@server.tld'
self.failUnless(str(msg['to']) == 'usr@server.tld')
def testHTMLPlugin(self):
"Test message/html/body stanza"
msg = self.Message()

View File

@@ -136,11 +136,11 @@ class TestPubsubStanzas(SlixTest):
iq = self.Iq()
iq['pubsub_owner']['default']
iq['pubsub_owner']['default']['node'] = 'mynode'
iq['pubsub_owner']['default']['form'].addField('pubsub#title',
iq['pubsub_owner']['default']['form'].add_field('pubsub#title',
ftype='text-single',
value='This thing is awesome')
self.check(iq, """
<iq id="0">
<iq id="0">
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
<default node="mynode">
<x xmlns="jabber:x:data" type="form">
@@ -161,7 +161,8 @@ class TestPubsubStanzas(SlixTest):
iq['pubsub']['subscribe']['options']['node'] = 'cheese'
iq['pubsub']['subscribe']['options']['jid'] = 'fritzy@netflint.net/slixmpp'
form = xep_0004.Form()
form.addField('pubsub#title', ftype='text-single', value='this thing is awesome')
form['type'] = 'submit'
form.add_field('pubsub#title', ftype='text-single', value='this thing is awesome')
iq['pubsub']['subscribe']['options']['options'] = form
self.check(iq, """
<iq id="0">
@@ -201,6 +202,7 @@ class TestPubsubStanzas(SlixTest):
iq['pubsub']['publish'].append(item)
iq['pubsub']['publish'].append(item2)
form = xep_0004.Form()
form['type'] = 'submit'
form.addField('pubsub#description', ftype='text-single', value='this thing is awesome')
iq['pubsub']['publish_options'] = form
@@ -253,7 +255,7 @@ class TestPubsubStanzas(SlixTest):
pub = iq['pubsub']
pub['create']['node'] = 'testnode2'
pub['configure']['form']['type'] = 'submit'
pub['configure']['form'].setFields([
pub['configure']['form'].set_fields([
('FORM_TYPE', {'type': 'hidden',
'value': 'http://jabber.org/protocol/pubsub#node_config'}),
('pubsub#node_type', {'type': 'list-single',