diff options
60 files changed, 1789 insertions, 358 deletions
@@ -12,3 +12,4 @@ slixmpp.egg-info/ *~ .baboon/ .DS_STORE +.idea/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..22e3abf1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: python +python: + - "2.6" + - "2.7" + - "3.2" + - "3.3" + - "3.4" +install: + - "pip install ." +script: testall.py @@ -8,7 +8,6 @@ Slixmpp's goals is to only rewrite the core of the library (the low level socket handling, the timers, the events dispatching) in order to remove all threads. - Documentation and Testing ------------------------- Documentation can be found both inline in the code, and as a Sphinx project in ``/docs``. diff --git a/docs/guide_xep_0030.rst b/docs/guide_xep_0030.rst index 674b9eb5..70f92b0c 100644 --- a/docs/guide_xep_0030.rst +++ b/docs/guide_xep_0030.rst @@ -161,8 +161,8 @@ item itself, and the JID and node that will own the item. In this case, the owning JID and node are provided with the parameters ``ijid`` and ``node``. -Peforming Disco Queries ------------------------ +Performing Disco Queries +------------------------ The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs and their nodes for disco information. Since these methods are wrappers for sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()`` @@ -172,11 +172,10 @@ the `XEP-0059 <http://xmpp.org/extensions/xep-0059.html>`_ plug-in. .. code-block:: python - info = self['xep_0030'].get_info(jid='foo@example.com', - node='bar', - ifrom='baz@mycomponent.example.com', - block=True, - timeout=30) + info = yield from self['xep_0030'].get_info(jid='foo@example.com', + node='bar', + ifrom='baz@mycomponent.example.com', + timeout=30) items = self['xep_0030'].get_info(jid='foo@example.com', node='bar', diff --git a/examples/IoT_TestDevice.py b/examples/IoT_TestDevice.py index 5b72994c..b9546017 100755 --- a/examples/IoT_TestDevice.py +++ b/examples/IoT_TestDevice.py @@ -160,9 +160,9 @@ if __name__ == '__main__': myDevice = TheDevice(args.nodeid); # myDevice._add_field(name="Relay", typename="numeric", unit="Bool"); - myDevice._add_field(name="Temperature", typename="numeric", unit="C"); + myDevice._add_field(name="Temperature", typename="numeric", unit="C") myDevice._set_momentary_timestamp("2013-03-07T16:24:30") - myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}); + myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}) xmpp['xep_0323'].register_node(nodeId=args.nodeid, device=myDevice, commTimeout=10); xmpp.beClientOrServer(server=True) diff --git a/examples/disco_browser.py b/examples/disco_browser.py index 54f6c933..a9e8711f 100755 --- a/examples/disco_browser.py +++ b/examples/disco_browser.py @@ -76,10 +76,6 @@ class Disco(slixmpp.ClientXMPP): try: if self.get in self.info_types: - # By using block=True, the result stanza will be - # returned. Execution will block until the reply is - # received. Non-blocking options would be to listen - # for the disco_info event, or passing a handler # function using the callback parameter. info = yield from self['xep_0030'].get_info(jid=self.target_jid, node=self.target_node) diff --git a/examples/http_over_xmpp.py b/examples/http_over_xmpp.py new file mode 100644 index 00000000..73e4a612 --- /dev/null +++ b/examples/http_over_xmpp.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" + Slixmpp: The Slick XMPP Library + Implementation of HTTP over XMPP transport + http://xmpp.org/extensions/xep-0332.html + Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com + This file is part of slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp import ClientXMPP + +from optparse import OptionParser +import logging +import getpass + + +class HTTPOverXMPPClient(ClientXMPP): + def __init__(self, jid, password): + ClientXMPP.__init__(self, jid, password) + self.register_plugin('xep_0332') # HTTP over XMPP Transport + self.add_event_handler( + 'session_start', self.session_start, threaded=True + ) + self.add_event_handler('http_request', self.http_request_received) + self.add_event_handler('http_response', self.http_response_received) + + def http_request_received(self, iq): + pass + + def http_response_received(self, iq): + print('HTTP Response Received : %s' % iq) + print('From : %s' % iq['from']) + print('To : %s' % iq['to']) + print('Type : %s' % iq['type']) + print('Headers : %s' % iq['resp']['headers']) + print('Code : %s' % iq['resp']['code']) + print('Message : %s' % iq['resp']['message']) + print('Data : %s' % iq['resp']['data']) + + def session_start(self, event): + # TODO: Fill in the blanks + self['xep_0332'].send_request( + to='?', method='?', resource='?', headers={} + ) + self.disconnect() + + +if __name__ == '__main__': + + # + # NOTE: To run this example, fill up the blanks in session_start() and + # use the following command. + # + # ./http_over_xmpp.py -J <jid> -P <pwd> -i <ip> -p <port> [-v] + # + + parser = OptionParser() + + # Output verbosity options. + parser.add_option( + '-v', '--verbose', help='set logging to DEBUG', action='store_const', + dest='loglevel', const=logging.DEBUG, default=logging.ERROR + ) + + # JID and password options. + parser.add_option('-J', '--jid', dest='jid', help='JID') + parser.add_option('-P', '--password', dest='password', help='Password') + + # XMPP server ip and port options. + parser.add_option( + '-i', '--ipaddr', dest='ipaddr', + help='IP Address of the XMPP server', default=None + ) + parser.add_option( + '-p', '--port', dest='port', + help='Port of the XMPP server', default=None + ) + + opts, args = parser.parse_args() + + # Setup logging. + logging.basicConfig(level=opts.loglevel, + format='%(levelname)-8s %(message)s') + + if opts.jid is None: + opts.jid = input('Username: ') + if opts.password is None: + opts.password = getpass.getpass('Password: ') + + xmpp = HTTPOverXMPPClient(opts.jid, opts.password) + xmpp.connect() + xmpp.process() + diff --git a/examples/migrate_roster.py b/examples/migrate_roster.py index e6c66f2e..d599b10c 100755 --- a/examples/migrate_roster.py +++ b/examples/migrate_roster.py @@ -100,7 +100,7 @@ def on_session2(event): new_xmpp.update_roster(jid, name = item['name'], groups = item['groups']) - new_xmpp.disconnect() + new_xmpp.disconnect() new_xmpp.add_event_handler('session_start', on_session2) new_xmpp.connect() diff --git a/examples/roster_browser.py b/examples/roster_browser.py index 4d07de11..eb92ad2a 100755 --- a/examples/roster_browser.py +++ b/examples/roster_browser.py @@ -59,7 +59,7 @@ class RosterBrowser(slixmpp.ClientXMPP): self.get_roster(callback=callback) yield from future except IqError as err: - print('Error: %' % err.iq['error']['condition']) + print('Error: %s' % err.iq['error']['condition']) except IqTimeout: print('Error: Request timed out') self.send_presence() diff --git a/slixmpp/basexmpp.py b/slixmpp/basexmpp.py index 80699319..83741bd7 100644 --- a/slixmpp/basexmpp.py +++ b/slixmpp/basexmpp.py @@ -22,7 +22,6 @@ from slixmpp.exceptions import IqError, IqTimeout from slixmpp.stanza import Message, Presence, Iq, StreamError from slixmpp.stanza.roster import Roster from slixmpp.stanza.nick import Nick -from slixmpp.stanza.htmlim import HTMLIM from slixmpp.xmlstream import XMLStream, JID from slixmpp.xmlstream import ET, register_stanza_plugin @@ -46,8 +45,8 @@ class BaseXMPP(XMLStream): is used during initialization. """ - def __init__(self, jid='', default_ns='jabber:client'): - XMLStream.__init__(self) + def __init__(self, jid='', default_ns='jabber:client', **kwargs): + XMLStream.__init__(self, **kwargs) self.default_ns = default_ns self.stream_ns = 'http://etherx.jabber.org/streams' @@ -221,7 +220,7 @@ class BaseXMPP(XMLStream): self.plugin[name].post_init() self.plugin[name].post_inited = True - def register_plugin(self, plugin, pconfig={}, module=None): + def register_plugin(self, plugin, pconfig=None, module=None): """Register and configure a plugin for use in this stream. :param plugin: The name of the plugin class. Plugin names must diff --git a/slixmpp/clientxmpp.py b/slixmpp/clientxmpp.py index d1fd65a9..40d20333 100644 --- a/slixmpp/clientxmpp.py +++ b/slixmpp/clientxmpp.py @@ -50,7 +50,6 @@ class ClientXMPP(BaseXMPP): :param jid: The JID of the XMPP user account. :param password: The password for the XMPP user account. - :param ssl: **Deprecated.** :param plugin_config: A dictionary of plugin configurations. :param plugin_whitelist: A list of approved plugins that will be loaded when calling @@ -58,9 +57,15 @@ class ClientXMPP(BaseXMPP): :param escape_quotes: **Deprecated.** """ - def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[], - escape_quotes=True, sasl_mech=None, lang='en'): - BaseXMPP.__init__(self, jid, 'jabber:client') + def __init__(self, jid, password, plugin_config=None, + plugin_whitelist=None, escape_quotes=True, sasl_mech=None, + lang='en', **kwargs): + if not plugin_whitelist: + plugin_whitelist = [] + if not plugin_config: + plugin_config = {} + + BaseXMPP.__init__(self, jid, 'jabber:client', **kwargs) self.escape_quotes = escape_quotes self.plugin_config = plugin_config diff --git a/slixmpp/componentxmpp.py b/slixmpp/componentxmpp.py index 68669c06..868798d1 100644 --- a/slixmpp/componentxmpp.py +++ b/slixmpp/componentxmpp.py @@ -46,8 +46,13 @@ class ComponentXMPP(BaseXMPP): Defaults to ``False``. """ - def __init__(self, jid, secret, host=None, port=None, - plugin_config={}, plugin_whitelist=[], use_jc_ns=False): + def __init__(self, jid, secret, host=None, port=None, plugin_config=None, plugin_whitelist=None, use_jc_ns=False): + + if not plugin_whitelist: + plugin_whitelist = [] + if not plugin_config: + plugin_config = {} + if use_jc_ns: default_ns = 'jabber:client' else: diff --git a/slixmpp/features/feature_mechanisms/mechanisms.py b/slixmpp/features/feature_mechanisms/mechanisms.py index 5f947cfa..8e507afc 100644 --- a/slixmpp/features/feature_mechanisms/mechanisms.py +++ b/slixmpp/features/feature_mechanisms/mechanisms.py @@ -190,14 +190,14 @@ class FeatureMechanisms(BasePlugin): except sasl.SASLCancelled: self.attempted_mechs.add(self.mech.name) self._send_auth() - except sasl.SASLFailed: - self.attempted_mechs.add(self.mech.name) - self._send_auth() except sasl.SASLMutualAuthFailed: log.error("Mutual authentication failed! " + \ "A security breach is possible.") self.attempted_mechs.add(self.mech.name) self.xmpp.disconnect() + except sasl.SASLFailed: + self.attempted_mechs.add(self.mech.name) + self._send_auth() else: resp.send() @@ -210,13 +210,13 @@ class FeatureMechanisms(BasePlugin): resp['value'] = self.mech.process(stanza['value']) except sasl.SASLCancelled: self.stanza.Abort(self.xmpp).send() - except sasl.SASLFailed: - self.stanza.Abort(self.xmpp).send() except sasl.SASLMutualAuthFailed: log.error("Mutual authentication failed! " + \ "A security breach is possible.") self.attempted_mechs.add(self.mech.name) self.xmpp.disconnect() + except sasl.SASLFailed: + self.stanza.Abort(self.xmpp).send() else: if resp.get_value() == '': resp.del_value() diff --git a/slixmpp/plugins/__init__.py b/slixmpp/plugins/__init__.py index cf24caed..d28cf281 100644 --- a/slixmpp/plugins/__init__.py +++ b/slixmpp/plugins/__init__.py @@ -47,6 +47,7 @@ __all__ = [ 'xep_0108', # User Activity 'xep_0115', # Entity Capabilities 'xep_0118', # User Tune + 'xep_0122', # Data Forms Validation 'xep_0128', # Extended Service Discovery 'xep_0131', # Standard Headers and Internet Metadata 'xep_0133', # Service Administration @@ -83,4 +84,5 @@ __all__ = [ 'xep_0319', # Last User Interaction in Presence 'xep_0323', # IoT Systems Sensor Data 'xep_0325', # IoT Systems Control + 'xep_0332', # HTTP Over XMPP Transport ] diff --git a/slixmpp/plugins/google/auth/stanza.py b/slixmpp/plugins/google/auth/stanza.py new file mode 100644 index 00000000..c5c693ee --- /dev/null +++ b/slixmpp/plugins/google/auth/stanza.py @@ -0,0 +1,47 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout + This file is part of slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import ElementBase, ET + + +class GoogleAuth(ElementBase): + name = 'auth' + namespace = 'http://www.google.com/talk/protocol/auth' + plugin_attrib = 'google' + interfaces = set(['client_uses_full_bind_result', 'service']) + + discovery_attr= '{%s}client-uses-full-bind-result' % namespace + service_attr= '{%s}service' % namespace + + def setup(self, xml): + """Don't create XML for the plugin.""" + self.xml = ET.Element('') + + def get_client_uses_full_bind_result(self): + return self.parent()._get_attr(self.discovery_attr) == 'true' + + def set_client_uses_full_bind_result(self, value): + if value in (True, 'true'): + self.parent()._set_attr(self.discovery_attr, 'true') + else: + self.parent()._del_attr(self.discovery_attr) + + def del_client_uses_full_bind_result(self): + self.parent()._del_attr(self.discovery_attr) + + def get_service(self): + return self.parent()._get_attr(self.service_attr, '') + + def set_service(self, value): + if value: + self.parent()._set_attr(self.service_attr, value) + else: + self.parent()._del_attr(self.service_attr) + + def del_service(self): + self.parent()._del_attr(self.service_attr) diff --git a/slixmpp/plugins/google/gmail/notifications.py b/slixmpp/plugins/google/gmail/notifications.py new file mode 100644 index 00000000..e6785ccb --- /dev/null +++ b/slixmpp/plugins/google/gmail/notifications.py @@ -0,0 +1,90 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout + This file is part of slixmpp. + + See the file LICENSE for copying permission. +""" + +import logging + +from slixmpp.stanza import Iq +from slixmpp.xmlstream.handler import Callback +from slixmpp.xmlstream.matcher import MatchXPath +from slixmpp.xmlstream import register_stanza_plugin +from slixmpp.plugins import BasePlugin +from slixmpp.plugins.google.gmail import stanza + + +log = logging.getLogger(__name__) + + +class Gmail(BasePlugin): + + """ + Google: Gmail Notifications + + Also see <https://developers.google.com/talk/jep_extensions/gmail>. + """ + + name = 'gmail' + description = 'Google: Gmail Notifications' + dependencies = set() + stanza = stanza + + def plugin_init(self): + register_stanza_plugin(Iq, stanza.GmailQuery) + register_stanza_plugin(Iq, stanza.MailBox) + register_stanza_plugin(Iq, stanza.NewMail) + + self.xmpp.register_handler( + Callback('Gmail New Mail', + MatchXPath('{%s}iq/{%s}%s' % ( + self.xmpp.default_ns, + stanza.NewMail.namespace, + stanza.NewMail.name)), + self._handle_new_mail)) + + self._last_result_time = None + self._last_result_tid = None + + def plugin_end(self): + self.xmpp.remove_handler('Gmail New Mail') + + def _handle_new_mail(self, iq): + log.info('Gmail: New email!') + iq.reply().send() + self.xmpp.event('gmail_notification') + + def check(self, timeout=None, callback=None): + last_time = self._last_result_time + last_tid = self._last_result_tid + + callback = lambda iq: self._update_last_results(iq, callback) + + return self.search(newer_time=last_time, + newer_tid=last_tid, + timeout=timeout, + callback=callback) + + def _update_last_results(self, iq, callback=None): + self._last_result_time = iq['gmail_messages']['result_time'] + threads = iq['gmail_messages']['threads'] + if threads: + self._last_result_tid = threads[0]['tid'] + if callback: + callback(iq) + + def search(self, query=None, newer_time=None, newer_tid=None, + timeout=None, callback=None): + if not query: + log.info('Gmail: Checking for new email') + else: + log.info('Gmail: Searching for emails matching: "%s"', query) + iq = self.xmpp.Iq() + iq['type'] = 'get' + iq['to'] = self.xmpp.boundjid.bare + iq['gmail']['search'] = query + iq['gmail']['newer_than_time'] = newer_time + iq['gmail']['newer_than_tid'] = newer_tid + return iq.send(timeout=timeout, callback=callback) diff --git a/slixmpp/plugins/google/nosave/stanza.py b/slixmpp/plugins/google/nosave/stanza.py new file mode 100644 index 00000000..b060a486 --- /dev/null +++ b/slixmpp/plugins/google/nosave/stanza.py @@ -0,0 +1,59 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout + This file is part of slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.jid import JID +from slixmpp.xmlstream import ElementBase, register_stanza_plugin + + +class NoSave(ElementBase): + name = 'x' + namespace = 'google:nosave' + plugin_attrib = 'google_nosave' + interfaces = set(['value']) + + def get_value(self): + return self._get_attr('value', '') == 'enabled' + + def set_value(self, value): + self._set_attr('value', 'enabled' if value else 'disabled') + + +class NoSaveQuery(ElementBase): + name = 'query' + namespace = 'google:nosave' + plugin_attrib = 'google_nosave' + interfaces = set() + + +class Item(ElementBase): + name = 'item' + namespace = 'google:nosave' + plugin_attrib = 'item' + plugin_multi_attrib = 'items' + interfaces = set(['jid', 'source', 'value']) + + def get_value(self): + return self._get_attr('value', '') == 'enabled' + + def set_value(self, value): + self._set_attr('value', 'enabled' if value else 'disabled') + + def get_jid(self): + return JID(self._get_attr('jid', '')) + + def set_jid(self, value): + self._set_attr('jid', str(value)) + + def get_source(self): + return JID(self._get_attr('source', '')) + + def set_source(self, value): + self._set_attr('source', str(value)) + + +register_stanza_plugin(NoSaveQuery, Item) diff --git a/slixmpp/plugins/google/settings/settings.py b/slixmpp/plugins/google/settings/settings.py new file mode 100644 index 00000000..84a8dfa9 --- /dev/null +++ b/slixmpp/plugins/google/settings/settings.py @@ -0,0 +1,63 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout + This file is part of slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.stanza import Iq +from slixmpp.xmlstream.handler import Callback +from slixmpp.xmlstream.matcher import StanzaPath +from slixmpp.xmlstream import register_stanza_plugin +from slixmpp.plugins import BasePlugin +from slixmpp.plugins.google.settings import stanza + + +class GoogleSettings(BasePlugin): + + """ + Google: Gmail Notifications + + Also see <https://developers.google.com/talk/jep_extensions/usersettings>. + """ + + name = 'google_settings' + description = 'Google: User Settings' + dependencies = set() + stanza = stanza + + def plugin_init(self): + register_stanza_plugin(Iq, stanza.UserSettings) + + self.xmpp.register_handler( + Callback('Google Settings', + StanzaPath('iq@type=set/google_settings'), + self._handle_settings_change)) + + def plugin_end(self): + self.xmpp.remove_handler('Google Settings') + + def get(self, timeout=None, callback=None): + iq = self.xmpp.Iq() + iq['type'] = 'get' + iq.enable('google_settings') + return iq.send(timeout=timeout, callback=callback) + + def update(self, settings, timeout=None, callback=None): + iq = self.xmpp.Iq() + iq['type'] = 'set' + iq.enable('google_settings') + + for setting, value in settings.items(): + iq['google_settings'][setting] = value + + return iq.send(timeout=timeout, callback=callback) + + def _handle_settings_change(self, iq): + reply = self.xmpp.Iq() + reply['type'] = 'result' + reply['id'] = iq['id'] + reply['to'] = iq['from'] + reply.send() + self.xmpp.event('google_settings_change', iq) diff --git a/slixmpp/plugins/xep_0004/stanza/field.py b/slixmpp/plugins/xep_0004/stanza/field.py index ceddcd0e..42f1210b 100644 --- a/slixmpp/plugins/xep_0004/stanza/field.py +++ b/slixmpp/plugins/xep_0004/stanza/field.py @@ -13,8 +13,9 @@ class FormField(ElementBase): namespace = 'jabber:x:data' name = 'field' plugin_attrib = 'field' + plugin_multi_attrib = 'fields' interfaces = set(('answer', 'desc', 'required', 'value', - 'options', 'label', 'type', 'var')) + 'label', 'type', 'var')) sub_interfaces = set(('desc',)) plugin_tag_map = {} plugin_attrib_map = {} @@ -165,6 +166,7 @@ class FieldOption(ElementBase): plugin_attrib = 'option' interfaces = set(('label', 'value')) sub_interfaces = set(('value',)) + plugin_multi_attrib = 'options' FormField.addOption = FormField.add_option diff --git a/slixmpp/plugins/xep_0004/stanza/form.py b/slixmpp/plugins/xep_0004/stanza/form.py index 2f617e39..151e2ef1 100644 --- a/slixmpp/plugins/xep_0004/stanza/form.py +++ b/slixmpp/plugins/xep_0004/stanza/form.py @@ -10,6 +10,7 @@ import copy import logging from collections import OrderedDict +from slixmpp.thirdparty import OrderedSet from slixmpp.xmlstream import ElementBase, ET from slixmpp.plugins.xep_0004.stanza import FormField @@ -22,8 +23,7 @@ class Form(ElementBase): namespace = 'jabber:x:data' name = 'x' plugin_attrib = 'form' - interfaces = set(('fields', 'instructions', 'items', - 'reported', 'title', 'type', 'values')) + interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', )) sub_interfaces = set(('title',)) form_types = set(('cancel', 'form', 'result', 'submit')) @@ -43,12 +43,12 @@ class Form(ElementBase): @property def field(self): - return self['fields'] + return self.get_fields() def set_type(self, ftype): self._set_attr('type', ftype) if ftype == 'submit': - fields = self['fields'] + fields = self.get_fields() for var in fields: field = fields[var] del field['type'] @@ -74,7 +74,8 @@ class Form(ElementBase): field['desc'] = desc field['required'] = required if options is not None: - field['options'] = options + for option in options: + field.add_option(**option) else: del field['type'] self.append(field) @@ -151,7 +152,6 @@ class Form(ElementBase): return fields def get_instructions(self): - instructions = '' instsXML = self.xml.findall('{%s}instructions' % self.namespace) return "\n".join([instXML.text for instXML in instsXML]) @@ -170,7 +170,7 @@ class Form(ElementBase): def get_reported(self): fields = OrderedDict() xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, - FormField.namespace)) + FormField.namespace)) for field in xml: field = FormField(xml=field) fields[field['var']] = field @@ -178,7 +178,7 @@ class Form(ElementBase): def get_values(self): values = OrderedDict() - fields = self['fields'] + fields = self.get_fields() for var in fields: values[var] = fields[var]['value'] return values @@ -195,7 +195,14 @@ class Form(ElementBase): fields = fields.items() for var, field in fields: field['var'] = var - self.add_field(**field) + self.add_field( + var=field.get('var'), + label=field.get('label'), + desc=field.get('desc'), + required=field.get('required'), + value=field.get('value'), + options=field.get('options'), + type=field.get('type')) def set_instructions(self, instructions): del self['instructions'] @@ -213,17 +220,33 @@ class Form(ElementBase): self.add_item(item) def set_reported(self, reported): + """ + This either needs a dictionary of dictionaries or a dictionary of form fields. + :param reported: + :return: + """ for var in reported: field = reported[var] - field['var'] = var - self.add_reported(var, **field) + + if isinstance(field, dict): + self.add_reported(**field) + else: + reported = self.xml.find('{%s}reported' % self.namespace) + if reported is None: + reported = ET.Element('{%s}reported' % self.namespace) + self.xml.append(reported) + + fieldXML = ET.Element('{%s}field' % FormField.namespace) + reported.append(fieldXML) + new_field = FormField(xml=fieldXML) + new_field.values = field.values def set_values(self, values): - fields = self['fields'] + fields = self.get_fields() for field in values: - if field not in fields: + if field not in self.get_fields(): fields[field] = self.add_field(var=field) - fields[field]['value'] = values[field] + self.get_fields()[field]['value'] = values[field] def merge(self, other): new = copy.copy(self) diff --git a/slixmpp/plugins/xep_0009/remote.py b/slixmpp/plugins/xep_0009/remote.py index b7612c03..9675c88d 100644 --- a/slixmpp/plugins/xep_0009/remote.py +++ b/slixmpp/plugins/xep_0009/remote.py @@ -6,7 +6,7 @@ See the file LICENSE for copying permission. """ -from binding import py2xml, xml2py, xml2fault, fault2xml +from slixmpp.plugins.xep_0009.binding import py2xml, xml2py, xml2fault, fault2xml from threading import RLock import abc import inspect @@ -18,6 +18,38 @@ import traceback log = logging.getLogger(__name__) +def _isstr(obj): + return isinstance(obj, str) + + +# Class decorator to declare a metaclass to a class in a way compatible with Python 2 and 3. +# This decorator is copied from 'six' (https://bitbucket.org/gutworth/six): +# +# Copyright (c) 2010-2015 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +def _add_metaclass(metaclass): + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + def _intercept(method, name, public): def _resolver(instance, *args, **kwargs): log.debug("Locally calling %s.%s with arguments %s.", instance.FQN(), method.__name__, args) @@ -68,7 +100,7 @@ def remote(function_argument, public = True): if hasattr(function_argument, '__call__'): return _intercept(function_argument, None, public) else: - if not isinstance(function_argument, basestring): + if not _isstr(function_argument): if not isinstance(function_argument, bool): raise Exception('Expected an RPC method name or visibility modifier!') else: @@ -222,12 +254,11 @@ class TimeoutException(Exception): pass +@_add_metaclass(abc.ABCMeta) class Callback(object): ''' A base class for callback handlers. ''' - __metaclass__ = abc.ABCMeta - @abc.abstractproperty def set_value(self, value): @@ -291,7 +322,7 @@ class Future(Callback): self._event.set() - +@_add_metaclass(abc.ABCMeta) class Endpoint(object): ''' The Endpoint class is an abstract base class for all objects @@ -303,8 +334,6 @@ class Endpoint(object): which specifies which object an RPC call refers to. It is the first part in a RPC method name '<fqn>.<method>'. ''' - __metaclass__ = abc.ABCMeta - def __init__(self, session, target_jid): ''' @@ -491,7 +520,7 @@ class RemoteSession(object): def _find_key(self, dict, value): """return the key of dictionary dic given the value""" - search = [k for k, v in dict.iteritems() if v == value] + search = [k for k, v in dict.items() if v == value] if len(search) == 0: return None else: @@ -547,7 +576,7 @@ class RemoteSession(object): result = handler_cls(*args, **kwargs) Endpoint.__init__(result, self, self._client.boundjid.full) method_dict = result.get_methods() - for method_name, method in method_dict.iteritems(): + for method_name, method in method_dict.items(): #!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name) self._register_call(result.FQN(), method, method_name) self._register_acl(result.FQN(), acl) @@ -569,11 +598,11 @@ class RemoteSession(object): self._register_callback(pid, callback) iq.send() - def close(self): + def close(self, wait=False): ''' Closes this session. ''' - self._client.disconnect(False) + self._client.disconnect(wait=wait) self._session_close_callback() def _on_jabber_rpc_method_call(self, iq): @@ -697,7 +726,8 @@ class Remote(object): if(client.boundjid.bare in cls._sessions): raise RemoteException("There already is a session associated with these credentials!") else: - cls._sessions[client.boundjid.bare] = client; + cls._sessions[client.boundjid.bare] = client + def _session_close_callback(): with Remote._lock: del cls._sessions[client.boundjid.bare] diff --git a/slixmpp/plugins/xep_0009/rpc.py b/slixmpp/plugins/xep_0009/rpc.py index 786b1d2f..3ce156cf 100644 --- a/slixmpp/plugins/xep_0009/rpc.py +++ b/slixmpp/plugins/xep_0009/rpc.py @@ -220,3 +220,4 @@ class XEP_0009(BasePlugin): def _extract_method(self, stanza): xml = ET.fromstring("%s" % stanza) return xml.find("./methodCall/methodName").text + diff --git a/slixmpp/plugins/xep_0030/disco.py b/slixmpp/plugins/xep_0030/disco.py index f368bc12..e6286b92 100644 --- a/slixmpp/plugins/xep_0030/disco.py +++ b/slixmpp/plugins/xep_0030/disco.py @@ -609,7 +609,7 @@ class XEP_0030(BasePlugin): """ self.api['del_features'](jid, node, None, kwargs) - def _run_node_handler(self, htype, jid, node=None, ifrom=None, data={}): + 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. @@ -620,6 +620,9 @@ class XEP_0030(BasePlugin): node -- The node requested. data -- Optional, custom data to pass to the handler. """ + if not data: + data = {} + return self.api[htype](jid, node, ifrom, data) def _handle_disco_info(self, iq): diff --git a/slixmpp/plugins/xep_0045.py b/slixmpp/plugins/xep_0045.py index 66bd863c..f6f48891 100644 --- a/slixmpp/plugins/xep_0045.py +++ b/slixmpp/plugins/xep_0045.py @@ -403,6 +403,16 @@ class XEP_0045(BasePlugin): return None return self.rooms[room].keys() + def getUsersByAffiliation(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') + item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation': affiliation}) + query.append(item) + iq = cls.xmpp.Iq(sto=room, sfrom=ifrom, stype='get') + iq.append(query) + return iq.send() + xep_0045 = XEP_0045 register_plugin(XEP_0045) diff --git a/slixmpp/plugins/xep_0050/adhoc.py b/slixmpp/plugins/xep_0050/adhoc.py index 2ecbfea6..fa6017d5 100644 --- a/slixmpp/plugins/xep_0050/adhoc.py +++ b/slixmpp/plugins/xep_0050/adhoc.py @@ -94,7 +94,7 @@ class XEP_0050(BasePlugin): self._handle_command)) register_stanza_plugin(Iq, Command) - register_stanza_plugin(Command, Form) + register_stanza_plugin(Command, Form, iterable=True) self.xmpp.add_event_handler('command_execute', self._handle_command_start) @@ -415,12 +415,26 @@ class XEP_0050(BasePlugin): del self.sessions[sessionid] + payload = session['payload'] + if payload is None: + payload = [] + if not isinstance(payload, list): + payload = [payload] + + for item in payload: + register_stanza_plugin(Command, item.__class__, iterable=True) + iq = iq.reply() + iq['command']['node'] = node iq['command']['sessionid'] = sessionid iq['command']['actions'] = [] iq['command']['status'] = 'completed' iq['command']['notes'] = session['notes'] + + for item in payload: + iq['command'].append(item) + iq.send() else: raise XMPPError('item-not-found') diff --git a/slixmpp/plugins/xep_0054/stanza.py b/slixmpp/plugins/xep_0054/stanza.py index 13b36320..48a41432 100644 --- a/slixmpp/plugins/xep_0054/stanza.py +++ b/slixmpp/plugins/xep_0054/stanza.py @@ -128,7 +128,8 @@ class Telephone(ElementBase): def setup(self, xml=None): super(Telephone, self).setup(xml=xml) - self._set_sub_text('NUMBER', '', keep=True) + ## this blanks out numbers received from server + ##self._set_sub_text('NUMBER', '', keep=True) def set_number(self, value): self._set_sub_text('NUMBER', value, keep=True) diff --git a/slixmpp/plugins/xep_0065/proxy.py b/slixmpp/plugins/xep_0065/proxy.py index 3e75b710..c5d358dd 100644 --- a/slixmpp/plugins/xep_0065/proxy.py +++ b/slixmpp/plugins/xep_0065/proxy.py @@ -251,7 +251,6 @@ class XEP_0065(BasePlugin): host : The hostname or the IP of the proxy. <str> port : The port of the proxy. <str> or <int> """ - factory = lambda: Socks5Protocol(dest, 0, self.xmpp.event) return self.xmpp.loop.create_connection(factory, proxy, proxy_port) diff --git a/slixmpp/plugins/xep_0096/file_transfer.py b/slixmpp/plugins/xep_0096/file_transfer.py index 462e7c19..3c09a5b5 100644 --- a/slixmpp/plugins/xep_0096/file_transfer.py +++ b/slixmpp/plugins/xep_0096/file_transfer.py @@ -47,6 +47,7 @@ class XEP_0096(BasePlugin): data['size'] = size data['date'] = date data['desc'] = desc + data['hash'] = hash if allow_ranged: data.enable('range') diff --git a/slixmpp/plugins/xep_0122/__init__.py b/slixmpp/plugins/xep_0122/__init__.py new file mode 100644 index 00000000..76ca80b2 --- /dev/null +++ b/slixmpp/plugins/xep_0122/__init__.py @@ -0,0 +1,11 @@ + +from slixmpp.plugins.base import register_plugin +from slixmpp.plugins.xep_0122.stanza import FormValidation +from slixmpp.plugins.xep_0122.data_validation import XEP_0122 + + +register_plugin(XEP_0122) + + +# Retain some backwards compatibility +xep_0122 = XEP_0122 diff --git a/slixmpp/plugins/xep_0122/data_validation.py b/slixmpp/plugins/xep_0122/data_validation.py new file mode 100644 index 00000000..6129db51 --- /dev/null +++ b/slixmpp/plugins/xep_0122/data_validation.py @@ -0,0 +1,19 @@ +from slixmpp.xmlstream import register_stanza_plugin +from slixmpp.plugins import BasePlugin +from slixmpp.plugins.xep_0004 import stanza +from slixmpp.plugins.xep_0004.stanza import FormField +from slixmpp.plugins.xep_0122.stanza import FormValidation + + +class XEP_0122(BasePlugin): + """ + XEP-0122: Data Forms + """ + + name = 'xep_0122' + description = 'XEP-0122: Data Forms Validation' + dependencies = set(['xep_0004']) + stanza = stanza + + def plugin_init(self): + register_stanza_plugin(FormField, FormValidation) diff --git a/slixmpp/plugins/xep_0122/stanza.py b/slixmpp/plugins/xep_0122/stanza.py new file mode 100644 index 00000000..9f1c423d --- /dev/null +++ b/slixmpp/plugins/xep_0122/stanza.py @@ -0,0 +1,93 @@ +from slixmpp.xmlstream import ElementBase, ET + + +class FormValidation(ElementBase): + """ + Validation values for form fields. + + Example: + + <field var='evt.date' type='text-single' label='Event Date/Time'> + <validate xmlns='http://jabber.org/protocol/xdata-validate' + datatype='xs:dateTime'/> + <value>2003-10-06T11:22:00-07:00</value> + </field> + + Questions: + Should this look at the datatype value and convert the range values as appropriate? + Should this stanza provide a pass/fail for a value from the field, or convert field value to datatype? + """ + + namespace = 'http://jabber.org/protocol/xdata-validate' + name = 'validate' + plugin_attrib = 'validate' + interfaces = {'datatype', 'basic', 'open', 'range', 'regex', } + sub_interfaces = {'basic', 'open', 'range', 'regex', } + plugin_attrib_map = {} + plugin_tag_map = {} + + def _add_field(self, name): + self.remove_all() + item_xml = ET.Element('{%s}%s' % (self.namespace, name)) + self.xml.append(item_xml) + return item_xml + + def set_basic(self, value): + if value: + self._add_field('basic') + else: + del self['basic'] + + def set_open(self, value): + if value: + self._add_field('open') + else: + del self['open'] + + def set_regex(self, regex): + if regex: + _regex = self._add_field('regex') + _regex.text = regex + else: + del self['regex'] + + def set_range(self, value, minimum=None, maximum=None): + if value: + _range = self._add_field('range') + _range.attrib['min'] = str(minimum) + _range.attrib['max'] = str(maximum) + else: + del self['range'] + + def remove_all(self, except_tag=None): + for a in self.sub_interfaces: + if a != except_tag: + del self[a] + + def get_basic(self): + present = self.xml.find('{%s}basic' % self.namespace) + return present is not None + + def get_open(self): + present = self.xml.find('{%s}open' % self.namespace) + return present is not None + + def get_regex(self): + present = self.xml.find('{%s}regex' % self.namespace) + if present is not None: + return present.text + + return False + + def get_range(self): + present = self.xml.find('{%s}range' % self.namespace) + if present is not None: + attributes = present.attrib + return_value = dict() + if 'min' in attributes: + return_value['minimum'] = attributes['min'] + if 'max' in attributes: + return_value['maximum'] = attributes['max'] + return return_value + + return False diff --git a/slixmpp/plugins/xep_0138.py b/slixmpp/plugins/xep_0138.py new file mode 100644 index 00000000..049060cf --- /dev/null +++ b/slixmpp/plugins/xep_0138.py @@ -0,0 +1,145 @@ +""" + slixmpp: The Slick XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of slixmpp. + + See the file LICENSE for copying permission. +""" + +import logging +import zlib + + +from slixmpp.stanza import StreamFeatures +from slixmpp.xmlstream import RestartStream, register_stanza_plugin, ElementBase, StanzaBase +from slixmpp.xmlstream.matcher import * +from slixmpp.xmlstream.handler import * +from slixmpp.plugins import BasePlugin, register_plugin + +log = logging.getLogger(__name__) + + +class Compression(ElementBase): + name = 'compression' + namespace = 'http://jabber.org/features/compress' + interfaces = set(('methods',)) + plugin_attrib = 'compression' + plugin_tag_map = {} + plugin_attrib_map = {} + + def get_methods(self): + methods = [] + for method in self.xml.findall('{%s}method' % self.namespace): + methods.append(method.text) + return methods + + +class Compress(StanzaBase): + name = 'compress' + namespace = 'http://jabber.org/protocol/compress' + interfaces = set(('method',)) + sub_interfaces = interfaces + plugin_attrib = 'compress' + plugin_tag_map = {} + plugin_attrib_map = {} + + def setup(self, xml): + StanzaBase.setup(self, xml) + self.xml.tag = self.tag_name() + + +class Compressed(StanzaBase): + name = 'compressed' + namespace = 'http://jabber.org/protocol/compress' + interfaces = set() + plugin_tag_map = {} + plugin_attrib_map = {} + + def setup(self, xml): + StanzaBase.setup(self, xml) + self.xml.tag = self.tag_name() + + + + +class ZlibSocket(object): + + def __init__(self, socketobj): + self.__socket = socketobj + self.compressor = zlib.compressobj() + self.decompressor = zlib.decompressobj(zlib.MAX_WBITS) + + def __getattr__(self, name): + return getattr(self.__socket, name) + + def send(self, data): + sentlen = len(data) + data = self.compressor.compress(data) + data += self.compressor.flush(zlib.Z_SYNC_FLUSH) + log.debug(b'>>> (compressed)' + (data.encode("hex"))) + #return self.__socket.send(data) + sentactuallen = self.__socket.send(data) + assert(sentactuallen == len(data)) + + return sentlen + + def recv(self, *args, **kwargs): + data = self.__socket.recv(*args, **kwargs) + log.debug(b'<<< (compressed)' + data.encode("hex")) + return self.decompressor.decompress(self.decompressor.unconsumed_tail + data) + + +class XEP_0138(BasePlugin): + """ + XEP-0138: Compression + """ + name = "xep_0138" + description = "XEP-0138: Compression" + dependencies = set(["xep_0030"]) + + def plugin_init(self): + self.xep = '0138' + self.description = 'Stream Compression (Generic)' + + self.compression_methods = {'zlib': True} + + register_stanza_plugin(StreamFeatures, Compression) + self.xmpp.register_stanza(Compress) + self.xmpp.register_stanza(Compressed) + + self.xmpp.register_handler( + Callback('Compressed', + StanzaPath('compressed'), + self._handle_compressed, + instream=True)) + + self.xmpp.register_feature('compression', + self._handle_compression, + restart=True, + order=self.config.get('order', 5)) + + def register_compression_method(self, name, handler): + self.compression_methods[name] = handler + + def _handle_compression(self, features): + for method in features['compression']['methods']: + if method in self.compression_methods: + log.info('Attempting to use %s compression' % method) + c = Compress(self.xmpp) + c['method'] = method + c.send(now=True) + return True + return False + + def _handle_compressed(self, stanza): + self.xmpp.features.add('compression') + log.debug('Stream Compressed!') + compressed_socket = ZlibSocket(self.xmpp.socket) + self.xmpp.set_socket(compressed_socket) + raise RestartStream() + + def _handle_failure(self, stanza): + pass + +xep_0138 = XEP_0138 +register_plugin(XEP_0138) diff --git a/slixmpp/plugins/xep_0202/time.py b/slixmpp/plugins/xep_0202/time.py index fbf6b4f0..185200fc 100644 --- a/slixmpp/plugins/xep_0202/time.py +++ b/slixmpp/plugins/xep_0202/time.py @@ -96,3 +96,4 @@ class XEP_0202(BasePlugin): iq['from'] = ifrom iq.enable('entity_time') return iq.send(**iqargs) + diff --git a/slixmpp/plugins/xep_0323/device.py b/slixmpp/plugins/xep_0323/device.py index b4142003..994fc5ce 100644 --- a/slixmpp/plugins/xep_0323/device.py +++ b/slixmpp/plugins/xep_0323/device.py @@ -21,7 +21,10 @@ class Device(object): request_fields """ - def __init__(self, nodeId, fields={}): + def __init__(self, nodeId, fields=None): + if not fields: + fields = {} + self.nodeId = nodeId self.fields = fields # see fields described below # {'type':'numeric', diff --git a/slixmpp/plugins/xep_0323/sensordata.py b/slixmpp/plugins/xep_0323/sensordata.py index 21afb55a..c88deee9 100644 --- a/slixmpp/plugins/xep_0323/sensordata.py +++ b/slixmpp/plugins/xep_0323/sensordata.py @@ -22,7 +22,6 @@ from slixmpp.plugins.base import BasePlugin from slixmpp.plugins.xep_0323 import stanza from slixmpp.plugins.xep_0323.stanza import Sensordata - log = logging.getLogger(__name__) @@ -108,7 +107,6 @@ class XEP_0323(BasePlugin): default_config = { 'threaded': True -# 'session_db': None } def plugin_init(self): @@ -161,11 +159,11 @@ class XEP_0323(BasePlugin): self.last_seqnr = 0 self.seqnr_lock = Lock() - ## For testning only + ## For testing only self.test_authenticated_from = "" def post_init(self): - """ Init complete. Register our features in Serivce discovery. """ + """ Init complete. Register our features in Service discovery. """ BasePlugin.post_init(self) self.xmpp['xep_0030'].add_feature(Sensordata.namespace) self.xmpp['xep_0030'].set_items(node=Sensordata.namespace, items=tuple()) @@ -301,8 +299,6 @@ class XEP_0323(BasePlugin): self.sessions[session]["commTimers"] = {} self.sessions[session]["nodeDone"] = {} - #print("added session: " + str(self.sessions)) - iq = iq.reply() iq['accepted']['seqnr'] = seqnr if not request_delay_sec is None: @@ -319,10 +315,8 @@ class XEP_0323(BasePlugin): return if self.threaded: - #print("starting thread") tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields, req_flags)) tr_req.start() - #print("started thread") else: self._threaded_node_request(session, process_fields, req_flags) @@ -349,7 +343,6 @@ class XEP_0323(BasePlugin): for node in self.sessions[session]["node_list"]: timer = TimerReset(self.nodes[node]['commTimeout'], self._event_comm_timeout, args=(session, node)) self.sessions[session]["commTimers"][node] = timer - #print("Starting timer " + str(timer) + ", timeout: " + str(self.nodes[node]['commTimeout'])) timer.start() self.nodes[node]['device'].request_fields(process_fields, flags=flags, session=session, callback=self._device_field_request_callback) @@ -377,7 +370,6 @@ class XEP_0323(BasePlugin): msg['failure']['done'] = 'true' msg.send() # The session is complete, delete it - #print("del session " + session + " due to timeout") del self.sessions[session] def _event_delayed_req(self, session, process_fields, req_flags): @@ -404,7 +396,7 @@ class XEP_0323(BasePlugin): def _all_nodes_done(self, session): """ - Checks wheter all devices are done replying to the readout. + Checks whether all devices are done replying to the readout. Arguments: session -- The request session id @@ -448,7 +440,7 @@ class XEP_0323(BasePlugin): Error details when a request failed. """ if not session in self.sessions: - # This can happend if a session was deleted, like in a cancellation. Just drop the data. + # This can happen if a session was deleted, like in a cancellation. Just drop the data. return if result == "error": @@ -467,7 +459,6 @@ class XEP_0323(BasePlugin): if (self._all_nodes_done(session)): msg['failure']['done'] = 'true' # The session is complete, delete it - # print("del session " + session + " due to error") del self.sessions[session] msg.send() else: @@ -491,11 +482,10 @@ class XEP_0323(BasePlugin): if result == "done": self.sessions[session]["commTimers"][nodeId].cancel() self.sessions[session]["nodeDone"][nodeId] = True - msg['fields']['done'] = 'true' if (self._all_nodes_done(session)): # The session is complete, delete it - # print("del session " + session + " due to complete") del self.sessions[session] + msg['fields']['done'] = 'true' else: # Restart comm timer self.sessions[session]["commTimers"][nodeId].reset() @@ -531,19 +521,19 @@ class XEP_0323(BasePlugin): iq['rejected']['error'] = "Cancel request received, no matching request is active." iq.send() - # ================================================================= + # ================================================================= # Client side (data retriever) API def request_data(self, from_jid, to_jid, callback, nodeIds=None, fields=None, flags=None): """ - Called on the client side to initiade a data readout. + Called on the client side to initiate a data readout. Composes a message with the request and sends it to the device(s). Does not block, the callback will be called when data is available. Arguments: from_jid -- The jid of the requester to_jid -- The jid of the device(s) - callback -- The callback function to call when data is availble. + callback -- The callback function to call when data is available. The callback function must support the following arguments: @@ -636,7 +626,7 @@ class XEP_0323(BasePlugin): def _get_new_seqnr(self): """ Returns a unique sequence number (unique across threads) """ self.seqnr_lock.acquire() - self.last_seqnr = self.last_seqnr + 1 + self.last_seqnr += 1 self.seqnr_lock.release() return str(self.last_seqnr) @@ -664,7 +654,6 @@ class XEP_0323(BasePlugin): Received Iq with cancelled - this is a cancel confirm. Delete the session. """ - #print("Got cancelled") seqnr = iq['cancelled']['seqnr'] callback = self.sessions[seqnr]["callback"] callback(from_jid=iq['from'], result="cancelled") @@ -673,7 +662,7 @@ class XEP_0323(BasePlugin): def _handle_event_fields(self, msg): """ - Received Msg with fields - this is a data reponse to a request. + Received Msg with fields - this is a data response to a request. If this is the last data block, issue a "done" callback. """ seqnr = msg['fields']['seqnr'] diff --git a/slixmpp/plugins/xep_0323/timerreset.py b/slixmpp/plugins/xep_0323/timerreset.py index baa80d41..616380e7 100644 --- a/slixmpp/plugins/xep_0323/timerreset.py +++ b/slixmpp/plugins/xep_0323/timerreset.py @@ -23,7 +23,12 @@ class _TimerReset(Thread): t.cancel() # stop the timer's action if it's still waiting """ - def __init__(self, interval, function, args=[], kwargs={}): + def __init__(self, interval, function, args=None, kwargs=None): + if not kwargs: + kwargs = {} + if not args: + args = [] + Thread.__init__(self) self.interval = interval self.function = function diff --git a/slixmpp/plugins/xep_0325/control.py b/slixmpp/plugins/xep_0325/control.py index 0c6837f6..9a493b02 100644 --- a/slixmpp/plugins/xep_0325/control.py +++ b/slixmpp/plugins/xep_0325/control.py @@ -223,7 +223,6 @@ class XEP_0325(BasePlugin): error_msg = "Access denied" # Nodes - process_nodes = [] if len(iq['set']['nodes']) > 0: for n in iq['set']['nodes']: if not n['nodeId'] in self.nodes: @@ -286,7 +285,6 @@ class XEP_0325(BasePlugin): req_ok = True # Nodes - process_nodes = [] if len(msg['set']['nodes']) > 0: for n in msg['set']['nodes']: if not n['nodeId'] in self.nodes: @@ -548,4 +546,3 @@ class XEP_0325(BasePlugin): callback = self.sessions[seqnr]["callback"] callback(from_jid=from_jid, result=result, nodeIds=nodeIds, fields=fields, error_msg=error_msg) - diff --git a/slixmpp/plugins/xep_0332/__init__.py b/slixmpp/plugins/xep_0332/__init__.py new file mode 100644 index 00000000..8bf6b369 --- /dev/null +++ b/slixmpp/plugins/xep_0332/__init__.py @@ -0,0 +1,17 @@ +""" + Slixmpp: The Slick XMPP Library + Implementation of HTTP over XMPP transport + http://xmpp.org/extensions/xep-0332.html + Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com + This file is part of slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.plugins.base import register_plugin + +from slixmpp.plugins.xep_0332 import stanza +from slixmpp.plugins.xep_0332.http import XEP_0332 + + +register_plugin(XEP_0332) diff --git a/slixmpp/plugins/xep_0332/http.py b/slixmpp/plugins/xep_0332/http.py new file mode 100644 index 00000000..7ad14dc8 --- /dev/null +++ b/slixmpp/plugins/xep_0332/http.py @@ -0,0 +1,159 @@ +""" + Slixmpp: The Slick XMPP Library + Implementation of HTTP over XMPP transport + http://xmpp.org/extensions/xep-0332.html + Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com + This file is part of slixmpp. + + See the file LICENSE for copying permission. +""" + +import logging + +from slixmpp import Iq + +from slixmpp.xmlstream import register_stanza_plugin +from slixmpp.xmlstream.handler import Callback +from slixmpp.xmlstream.matcher import StanzaPath + +from slixmpp.plugins.base import BasePlugin +from slixmpp.plugins.xep_0332.stanza import ( + HTTPRequest, HTTPResponse, HTTPData +) +from slixmpp.plugins.xep_0131.stanza import Headers + + +log = logging.getLogger(__name__) + + +class XEP_0332(BasePlugin): + """ + XEP-0332: HTTP over XMPP transport + """ + + name = 'xep_0332' + description = 'XEP-0332: HTTP over XMPP transport' + + #: xep_0047 not included. + #: xep_0001, 0137 and 0166 are missing + dependencies = set(['xep_0030', 'xep_0131']) + + #: TODO: Do we really need to mention the supported_headers?! + default_config = { + 'supported_headers': set([ + 'Content-Length', 'Transfer-Encoding', 'DateTime', + 'Accept-Charset', 'Location', 'Content-ID', 'Description', + 'Content-Language', 'Content-Transfer-Encoding', 'Timestamp', + 'Expires', 'User-Agent', 'Host', 'Proxy-Authorization', 'Date', + 'WWW-Authenticate', 'Accept-Encoding', 'Server', 'Error-Info', + 'Identifier', 'Content-Location', 'Content-Encoding', 'Distribute', + 'Accept', 'Proxy-Authenticate', 'ETag', 'Expect', 'Content-Type' + ]) + } + + def plugin_init(self): + self.xmpp.register_handler( + Callback( + 'HTTP Request', + StanzaPath('iq/http-req'), + self._handle_request + ) + ) + self.xmpp.register_handler( + Callback( + 'HTTP Response', + StanzaPath('iq/http-resp'), + self._handle_response + ) + ) + register_stanza_plugin(Iq, HTTPRequest, iterable=True) + register_stanza_plugin(Iq, HTTPResponse, iterable=True) + register_stanza_plugin(HTTPRequest, Headers, iterable=True) + register_stanza_plugin(HTTPRequest, HTTPData, iterable=True) + register_stanza_plugin(HTTPResponse, Headers, iterable=True) + register_stanza_plugin(HTTPResponse, HTTPData, iterable=True) + # TODO: Should we register any api's here? self.api.register() + + def plugin_end(self): + self.xmpp.remove_handler('HTTP Request') + self.xmpp.remove_handler('HTTP Response') + self.xmpp['xep_0030'].del_feature('urn:xmpp:http') + for header in self.supported_headers: + self.xmpp['xep_0030'].del_feature( + feature='%s#%s' % (Headers.namespace, header) + ) + + def session_bind(self, jid): + self.xmpp['xep_0030'].add_feature('urn:xmpp:http') + for header in self.supported_headers: + self.xmpp['xep_0030'].add_feature( + '%s#%s' % (Headers.namespace, header) + ) + # TODO: Do we need to add the supported headers to xep_0131? + # self.xmpp['xep_0131'].supported_headers.add(header) + + def _handle_request(self, iq): + self.xmpp.event('http_request', iq) + + def _handle_response(self, iq): + self.xmpp.event('http_response', iq) + + def send_request(self, to=None, method=None, resource=None, headers=None, + data=None, **kwargs): + iq = self.xmpp.Iq() + iq['from'] = self.xmpp.boundjid + iq['to'] = to + iq['type'] = 'set' + iq['http-req']['headers'] = headers + iq['http-req']['method'] = method + iq['http-req']['resource'] = resource + iq['http-req']['version'] = '1.1' # TODO: set this implicitly + if 'id' in kwargs: + iq['id'] = kwargs["id"] + if data is not None: + iq['http-req']['data'] = data + return iq.send( + timeout=kwargs.get('timeout', None), + block=kwargs.get('block', True), + callback=kwargs.get('callback', None), + timeout_callback=kwargs.get('timeout_callback', None) + ) + + def send_response(self, to=None, code=None, message=None, headers=None, + data=None, **kwargs): + iq = self.xmpp.Iq() + iq['from'] = self.xmpp.boundjid + iq['to'] = to + iq['type'] = 'result' + iq['http-resp']['headers'] = headers + iq['http-resp']['code'] = code + iq['http-resp']['message'] = message + iq['http-resp']['version'] = '1.1' # TODO: set this implicitly + if 'id' in kwargs: + iq['id'] = kwargs["id"] + if data is not None: + iq['http-resp']['data'] = data + return iq.send( + timeout=kwargs.get('timeout', None), + block=kwargs.get('block', True), + callback=kwargs.get('callback', None), + timeout_callback=kwargs.get('timeout_callback', None) + ) + + def send_error(self, to=None, ecode='500', etype='wait', + econd='internal-server-error', **kwargs): + iq = self.xmpp.Iq() + iq['from'] = self.xmpp.boundjid + iq['to'] = to + iq['type'] = 'error' + iq['error']['code'] = ecode + iq['error']['type'] = etype + iq['error']['condition'] = econd + if 'id' in kwargs: + iq['id'] = kwargs["id"] + return iq.send( + timeout=kwargs.get('timeout', None), + block=kwargs.get('block', True), + callback=kwargs.get('callback', None), + timeout_callback=kwargs.get('timeout_callback', None) + ) diff --git a/slixmpp/plugins/xep_0332/stanza/__init__.py b/slixmpp/plugins/xep_0332/stanza/__init__.py new file mode 100644 index 00000000..f98375c6 --- /dev/null +++ b/slixmpp/plugins/xep_0332/stanza/__init__.py @@ -0,0 +1,13 @@ +""" + Slixmpp: The Slick XMPP Library + Implementation of HTTP over XMPP transport + http://xmpp.org/extensions/xep-0332.html + Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com + This file is part of slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.plugins.xep_0332.stanza.request import HTTPRequest +from slixmpp.plugins.xep_0332.stanza.response import HTTPResponse +from slixmpp.plugins.xep_0332.stanza.data import HTTPData diff --git a/slixmpp/plugins/xep_0332/stanza/data.py b/slixmpp/plugins/xep_0332/stanza/data.py new file mode 100644 index 00000000..a19c94f5 --- /dev/null +++ b/slixmpp/plugins/xep_0332/stanza/data.py @@ -0,0 +1,30 @@ +""" + Slixmpp: The Slick XMPP Library + Implementation of HTTP over XMPP transport + http://xmpp.org/extensions/xep-0332.html + Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com + This file is part of slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import ElementBase + + +class HTTPData(ElementBase): + """ + The data element. + """ + name = 'data' + namespace = 'urn:xmpp:http' + interfaces = set(['data']) + plugin_attrib = 'data' + is_extension = True + + def get_data(self, encoding='text'): + data = self._get_sub_text(encoding, None) + return str(data) if data is not None else data + + def set_data(self, data, encoding='text'): + self._set_sub_text(encoding, text=data) + diff --git a/slixmpp/plugins/xep_0332/stanza/request.py b/slixmpp/plugins/xep_0332/stanza/request.py new file mode 100644 index 00000000..e3e46361 --- /dev/null +++ b/slixmpp/plugins/xep_0332/stanza/request.py @@ -0,0 +1,71 @@ +""" + slixmpp: The Slick XMPP Library + Implementation of HTTP over XMPP transport + http://xmpp.org/extensions/xep-0332.html + Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com + This file is part of slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import ElementBase + + +class HTTPRequest(ElementBase): + + """ + All HTTP communication is done using the `Request`/`Response` paradigm. + Each HTTP Request is made sending an `iq` stanza containing a `req` + element to the server. Each `iq` stanza sent is of type `set`. + + Examples: + <iq type='set' from='a@b.com/browser' to='x@y.com' id='1'> + <req xmlns='urn:xmpp:http' + method='GET' + resource='/api/users' + version='1.1'> + <headers xmlns='http://jabber.org/protocol/shim'> + <header name='Host'>b.com</header> + </headers> + </req> + </iq> + + <iq type='set' from='a@b.com/browser' to='x@y.com' id='2'> + <req xmlns='urn:xmpp:http' + method='PUT' + resource='/api/users' + version='1.1'> + <headers xmlns='http://jabber.org/protocol/shim'> + <header name='Host'>b.com</header> + <header name='Content-Type'>text/html</header> + <header name='Content-Length'>...</header> + </headers> + <data> + <text>...</text> + </data> + </req> + </iq> + """ + + name = 'request' + namespace = 'urn:xmpp:http' + interfaces = set(['method', 'resource', 'version']) + plugin_attrib = 'http-req' + + def get_method(self): + return self._get_attr('method', None) + + def set_method(self, method): + self._set_attr('method', method) + + def get_resource(self): + return self._get_attr('resource', None) + + def set_resource(self, resource): + self._set_attr('resource', resource) + + def get_version(self): + return self._get_attr('version', None) + + def set_version(self, version='1.1'): + self._set_attr('version', version) diff --git a/slixmpp/plugins/xep_0332/stanza/response.py b/slixmpp/plugins/xep_0332/stanza/response.py new file mode 100644 index 00000000..a0b8fe34 --- /dev/null +++ b/slixmpp/plugins/xep_0332/stanza/response.py @@ -0,0 +1,66 @@ +""" + Slixmpp: The Slick XMPP Library + Implementation of HTTP over XMPP transport + http://xmpp.org/extensions/xep-0332.html + Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com + This file is part of slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.xmlstream import ElementBase + + +class HTTPResponse(ElementBase): + + """ + When the HTTP Server responds, it does so by sending an `iq` stanza + response (type=`result`) back to the client containing the `resp` element. + Since response are asynchronous, and since multiple requests may be active + at the same time, responses may be returned in a different order than the + in which the original requests were made. + + Examples: + <iq type='result' + from='httpserver@clayster.com' + to='httpclient@clayster.com/browser' id='2'> + <resp xmlns='urn:xmpp:http' + version='1.1' + statusCode='200' + statusMessage='OK'> + <headers xmlns='http://jabber.org/protocol/shim'> + <header name='Date'>Fri, 03 May 2013 16:39:54GMT-4</header> + <header name='Server'>Clayster</header> + <header name='Content-Type'>text/turtle</header> + <header name='Content-Length'>...</header> + <header name='Connection'>Close</header> + </headers> + <data> + <text> + ... + </text> + </data> + </resp> + </iq> + """ + + name = 'response' + namespace = 'urn:xmpp:http' + interfaces = set(['code', 'message', 'version']) + plugin_attrib = 'http-resp' + + def get_code(self): + code = self._get_attr('statusCode', None) + return int(code) if code is not None else code + + def set_code(self, code): + self._set_attr('statusCode', str(code)) + + def get_message(self): + return self._get_attr('statusMessage', '') + + def set_message(self, message): + self._set_attr('statusMessage', message) + + def set_version(self, version='1.1'): + self._set_attr('version', version) diff --git a/slixmpp/roster/single.py b/slixmpp/roster/single.py index a37e3eb7..62fbca41 100644 --- a/slixmpp/roster/single.py +++ b/slixmpp/roster/single.py @@ -254,6 +254,9 @@ class RosterNode(object): callback -- Optional reference to a stream handler function. Will be executed when the roster is received. """ + if not groups: + groups = [] + self[jid]['name'] = name self[jid]['groups'] = groups self[jid].save() diff --git a/slixmpp/stanza/atom.py b/slixmpp/stanza/atom.py index 2c105685..ccded724 100644 --- a/slixmpp/stanza/atom.py +++ b/slixmpp/stanza/atom.py @@ -6,8 +6,7 @@ See the file LICENSE for copying permission. """ -from slixmpp.xmlstream import ElementBase - +from slixmpp.xmlstream import ElementBase, register_stanza_plugin class AtomEntry(ElementBase): @@ -22,5 +21,23 @@ class AtomEntry(ElementBase): namespace = 'http://www.w3.org/2005/Atom' name = 'entry' plugin_attrib = 'entry' - interfaces = set(('title', 'summary')) - sub_interfaces = set(('title', 'summary')) + interfaces = set(('title', 'summary', 'id', 'published', 'updated')) + sub_interfaces = set(('title', 'summary', 'id', 'published', + 'updated')) + +class AtomAuthor(ElementBase): + + """ + An Atom author. + + Stanza Interface: + name -- The printable author name + uri -- The bare jid of the author + """ + + name = 'author' + plugin_attrib = 'author' + interfaces = set(('name', 'uri')) + sub_interfaces = set(('name', 'uri')) + +register_stanza_plugin(AtomEntry, AtomAuthor) diff --git a/slixmpp/stanza/rootstanza.py b/slixmpp/stanza/rootstanza.py index ff139382..a6dd958e 100644 --- a/slixmpp/stanza/rootstanza.py +++ b/slixmpp/stanza/rootstanza.py @@ -60,7 +60,9 @@ class RootStanza(StanzaBase): reply.send() elif isinstance(e, XMPPError): # We raised this deliberately + keep_id = self['id'] reply = self.reply(clear=e.clear) + reply['id'] = keep_id reply['error']['condition'] = e.condition reply['error']['text'] = e.text reply['error']['type'] = e.etype @@ -72,7 +74,9 @@ class RootStanza(StanzaBase): reply.send() else: # We probably didn't raise this on purpose, so send an error stanza + keep_id = self['id'] reply = self.reply() + reply['id'] = keep_id reply['error']['condition'] = 'undefined-condition' reply['error']['text'] = "Slixmpp got into trouble." reply['error']['type'] = 'cancel' diff --git a/slixmpp/test/slixtest.py b/slixmpp/test/slixtest.py index 19ef9ae6..f66cf6be 100644 --- a/slixmpp/test/slixtest.py +++ b/slixmpp/test/slixtest.py @@ -319,6 +319,9 @@ class SlixTest(unittest.TestCase): plugins -- List of plugins to register. By default, all plugins are loaded. """ + if not plugin_config: + plugin_config = {} + if mode == 'client': self.xmpp = ClientXMPP(jid, password, sasl_mech=sasl_mech, @@ -402,8 +405,7 @@ class SlixTest(unittest.TestCase): parts.append('xmlns="%s"' % default_ns) return header % ' '.join(parts) - def recv(self, data, defaults=[], method='exact', - use_values=True, timeout=1): + def recv(self, data, defaults=None, method='exact', use_values=True, timeout=1): """ Pass data to the dummy XMPP client as if it came from an XMPP server. diff --git a/slixmpp/thirdparty/__init__.py b/slixmpp/thirdparty/__init__.py index fe1056e6..d950f4f9 100644 --- a/slixmpp/thirdparty/__init__.py +++ b/slixmpp/thirdparty/__init__.py @@ -4,3 +4,4 @@ except: from slixmpp.thirdparty.gnupg import GPG from slixmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso +from slixmpp.thirdparty.orderedset import OrderedSet diff --git a/slixmpp/thirdparty/orderedset.py b/slixmpp/thirdparty/orderedset.py new file mode 100644 index 00000000..f6642db3 --- /dev/null +++ b/slixmpp/thirdparty/orderedset.py @@ -0,0 +1,89 @@ +# Copyright (c) 2009 Raymond Hettinger +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +import collections + +class OrderedSet(collections.MutableSet): + + def __init__(self, iterable=None): + self.end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.map = {} # key --> [key, prev, next] + if iterable is not None: + self |= iterable + + def __len__(self): + return len(self.map) + + def __contains__(self, key): + return key in self.map + + def add(self, key): + if key not in self.map: + end = self.end + curr = end[1] + curr[2] = end[1] = self.map[key] = [key, curr, end] + + def discard(self, key): + if key in self.map: + key, prev, next = self.map.pop(key) + prev[2] = next + next[1] = prev + + def __iter__(self): + end = self.end + curr = end[2] + while curr is not end: + yield curr[0] + curr = curr[2] + + def __reversed__(self): + end = self.end + curr = end[1] + while curr is not end: + yield curr[0] + curr = curr[1] + + def pop(self, last=True): + if not self: + raise KeyError('set is empty') + key = self.end[1][0] if last else self.end[2][0] + self.discard(key) + return key + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, list(self)) + + def __eq__(self, other): + if isinstance(other, OrderedSet): + return len(self) == len(other) and list(self) == list(other) + return set(self) == set(other) + + +if __name__ == '__main__': + s = OrderedSet('abracadaba') + t = OrderedSet('simsalabim') + print(s | t) + print(s & t) + print(s - t)
\ No newline at end of file diff --git a/slixmpp/xmlstream/cert.py b/slixmpp/xmlstream/cert.py index 71146f36..d357b326 100644 --- a/slixmpp/xmlstream/cert.py +++ b/slixmpp/xmlstream/cert.py @@ -181,4 +181,4 @@ def verify(expected, raw_cert): return True raise CertificateError( - 'Could not match certficate against hostname: %s' % expected) + 'Could not match certificate against hostname: %s' % expected) diff --git a/slixmpp/xmlstream/stanzabase.py b/slixmpp/xmlstream/stanzabase.py index 10c29782..1ddee825 100644 --- a/slixmpp/xmlstream/stanzabase.py +++ b/slixmpp/xmlstream/stanzabase.py @@ -558,10 +558,13 @@ class ElementBase(object): .. versionadded:: 1.0-Beta1 """ - values = {} + values = OrderedDict() values['lang'] = self['lang'] for interface in self.interfaces: - values[interface] = self[interface] + if isinstance(self[interface], JID): + values[interface] = self[interface].jid + else: + values[interface] = self[interface] if interface in self.lang_interfaces: values['%s|*' % interface] = self['%s|*' % interface] for plugin, stanza in self.plugins.items(): @@ -672,6 +675,8 @@ class ElementBase(object): if lang and attrib in self.lang_interfaces: kwargs['lang'] = lang + kwargs = OrderedDict(kwargs) + if attrib == 'substanzas': return self.iterables elif attrib in self.interfaces or attrib == 'lang': @@ -748,6 +753,8 @@ class ElementBase(object): if lang and attrib in self.lang_interfaces: kwargs['lang'] = lang + kwargs = OrderedDict(kwargs) + if attrib in self.interfaces or attrib == 'lang': if value is not None: set_method = "set_%s" % attrib.lower() @@ -834,6 +841,8 @@ class ElementBase(object): if lang and attrib in self.lang_interfaces: kwargs['lang'] = lang + kwargs = OrderedDict(kwargs) + if attrib in self.interfaces or attrib == 'lang': del_method = "del_%s" % attrib.lower() del_method2 = "del%s" % attrib.title() diff --git a/tests/test_stanza_element.py b/tests/test_stanza_element.py index eb5791fc..0c49f201 100644 --- a/tests/test_stanza_element.py +++ b/tests/test_stanza_element.py @@ -385,7 +385,7 @@ class TestElementBase(SlixTest): interfaces = set(('bar', 'baz')) def setBar(self, value): - self._set_sub_text("path/to/only/bar", value); + self._set_sub_text("path/to/only/bar", value) def getBar(self): return self._get_sub_text("path/to/only/bar") @@ -394,7 +394,7 @@ class TestElementBase(SlixTest): self._del_sub("path/to/only/bar") def setBaz(self, value): - self._set_sub_text("path/to/just/baz", value); + self._set_sub_text("path/to/just/baz", value) def getBaz(self): return self._get_sub_text("path/to/just/baz") diff --git a/tests/test_stanza_xep_0004.py b/tests/test_stanza_xep_0004.py index b95b64ca..7b01b575 100644 --- a/tests/test_stanza_xep_0004.py +++ b/tests/test_stanza_xep_0004.py @@ -11,8 +11,8 @@ class TestDataForms(SlixTest): def setUp(self): register_stanza_plugin(Message, xep_0004.Form) - register_stanza_plugin(xep_0004.Form, xep_0004.FormField) - register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption) + register_stanza_plugin(xep_0004.Form, xep_0004.FormField, iterable=True) + register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption, iterable=True) def testMultipleInstructions(self): """Testing using multiple instructions elements in a data form.""" @@ -68,7 +68,7 @@ class TestDataForms(SlixTest): 'value': 'cool'}, {'label': 'Urgh!', 'value': 'urgh'}]} - form['fields'] = fields + form.set_fields(fields) self.check(msg, """ @@ -141,13 +141,13 @@ class TestDataForms(SlixTest): 'value': 'cool'}, {'label': 'Urgh!', 'value': 'urgh'}]} - form['fields'] = fields + form.set_fields(fields) form['type'] = 'submit' - form['values'] = {'f1': 'username', + form.set_values({'f1': 'username', 'f2': 'hunter2', 'f3': 'A long\nmultiline\nmessage', - 'f4': 'cool'} + 'f4': 'cool'}) self.check(form, """ <x xmlns="jabber:x:data" type="submit"> @@ -189,7 +189,7 @@ class TestDataForms(SlixTest): 'value': 'cool'}, {'label': 'Urgh!', 'value': 'urgh'}]} - form['fields'] = fields + form.set_fields(fields) form['type'] = 'cancel' @@ -197,5 +197,52 @@ class TestDataForms(SlixTest): <x xmlns="jabber:x:data" type="cancel" /> """) + def testReported(self): + msg = self.Message() + form = msg['form'] + form['type'] = 'result' + + form.add_reported(var='f1', ftype='text-single', label='Username') + + form.add_item({'f1': 'username@example.org'}) + + self.check(msg, """ + <message> + <x xmlns="jabber:x:data" type="result"> + <reported> + <field var="f1" type="text-single" label="Username" /> + </reported> + <item> + <field var="f1"> + <value>username@example.org</value> + </field> + </item> + </x> + </message> + """) + + def testSetReported(self): + msg = self.Message() + form = msg['form'] + form['type'] = 'result' + + reported = {'f1': { + 'var': 'f1', + 'type': 'text-single', + 'label': 'Username' + }} + + form.set_reported(reported) + + self.check(msg, """ + <message> + <x xmlns="jabber:x:data" type="result"> + <reported> + <field var="f1" type="text-single" label="Username" /> + </reported> + </x> + </message> + """) + suite = unittest.TestLoader().loadTestsFromTestCase(TestDataForms) diff --git a/tests/test_stanza_xep_0122.py b/tests/test_stanza_xep_0122.py new file mode 100644 index 00000000..0c032c05 --- /dev/null +++ b/tests/test_stanza_xep_0122.py @@ -0,0 +1,189 @@ +import unittest + +from slixmpp import Message +from slixmpp.test import SlixTest +import slixmpp.plugins.xep_0004 as xep_0004 +import slixmpp.plugins.xep_0122 as xep_0122 +from slixmpp.xmlstream import register_stanza_plugin + + +class TestDataForms(SlixTest): + + def setUp(self): + register_stanza_plugin(Message, xep_0004.Form) + register_stanza_plugin(xep_0004.Form, xep_0004.FormField, iterable=True) + register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption, iterable=True) + register_stanza_plugin(xep_0004.FormField, xep_0122.FormValidation) + + def test_basic_validation(self): + """Testing basic validation setting and getting.""" + msg = self.Message() + form = msg['form'] + field = form.add_field(var='f1', + ftype='text-single', + label='Text', + desc='A text field', + required=True, + value='Some text!') + + validation = field['validate'] + validation['datatype'] = 'xs:string' + validation.set_basic(True) + + self.check(msg, """ + <message> + <x xmlns="jabber:x:data" type="form"> + <field var="f1" type="text-single" label="Text"> + <desc>A text field</desc> + <required /> + <value>Some text!</value> + <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="xs:string"> + <basic/> + </validate> + </field> + </x> + </message> + """) + + self.assertTrue(validation.get_basic()) + self.assertFalse(validation.get_open()) + self.assertFalse(validation.get_range()) + self.assertFalse(validation.get_regex()) + + def test_open_validation(self): + """Testing open validation setting and getting.""" + msg = self.Message() + form = msg['form'] + field = form.add_field(var='f1', + ftype='text-single', + label='Text', + desc='A text field', + required=True, + value='Some text!') + + validation = field['validate'] + validation.set_open(True) + + self.check(msg, """ + <message> + <x xmlns="jabber:x:data" type="form"> + <field var="f1" type="text-single" label="Text"> + <desc>A text field</desc> + <required /> + <value>Some text!</value> + <validate xmlns="http://jabber.org/protocol/xdata-validate"> + <open /> + </validate> + </field> + </x> + </message> + """) + + self.assertFalse(validation.get_basic()) + self.assertTrue(validation.get_open()) + self.assertFalse(validation.get_range()) + self.assertFalse(validation.get_regex()) + + def test_regex_validation(self): + """Testing regex validation setting and getting.""" + msg = self.Message() + form = msg['form'] + field = form.add_field(var='f1', + ftype='text-single', + label='Text', + desc='A text field', + required=True, + value='Some text!') + + regex_value = '[0-9]+' + + validation = field['validate'] + validation.set_regex(regex_value) + + self.check(msg, """ + <message> + <x xmlns="jabber:x:data" type="form"> + <field var="f1" type="text-single" label="Text"> + <desc>A text field</desc> + <required /> + <value>Some text!</value> + <validate xmlns="http://jabber.org/protocol/xdata-validate"> + <regex>[0-9]+</regex> + </validate> + </field> + </x> + </message> + """) + + self.assertFalse(validation.get_basic()) + self.assertFalse(validation.get_open()) + self.assertFalse(validation.get_range()) + self.assertTrue(validation.get_regex()) + + self.assertEqual(regex_value, validation.get_regex()) + + def test_range_validation(self): + """Testing range validation setting and getting.""" + msg = self.Message() + form = msg['form'] + field = form.add_field(var='f1', + ftype='text-single', + label='Text', + desc='A text field', + required=True, + value='Some text!') + + validation = field['validate'] + validation.set_range(True, minimum=0, maximum=10) + + self.check(msg, """ + <message> + <x xmlns="jabber:x:data" type="form"> + <field var="f1" type="text-single" label="Text"> + <desc>A text field</desc> + <required /> + <value>Some text!</value> + <validate xmlns="http://jabber.org/protocol/xdata-validate"> + <range min="0" max="10" /> + </validate> + </field> + </x> + </message> + """) + + self.assertDictEqual(dict(minimum=str(0), maximum=str(10)), validation.get_range()) + + def test_reported_field_validation(self): + """ + Testing adding validation to the field when it's stored in the reported. + :return: + """ + msg = self.Message() + form = msg['form'] + field = form.add_reported(var='f1', ftype='text-single', label='Text') + validation = field['validate'] + validation.set_basic(True) + + form.add_item({'f1': 'Some text!'}) + + self.check(msg, """ + <message> + <x xmlns="jabber:x:data" type="form"> + <reported> + <field var="f1" type="text-single" label="Text"> + <validate xmlns="http://jabber.org/protocol/xdata-validate"> + <basic /> + </validate> + </field> + </reported> + <item> + <field var="f1"> + <value>Some text!</value> + </field> + </item> + </x> + </message> + """) + + +suite = unittest.TestLoader().loadTestsFromTestCase(TestDataForms) diff --git a/tests/test_stanza_xep_0323.py b/tests/test_stanza_xep_0323.py index 95fb138b..991481d7 100644 --- a/tests/test_stanza_xep_0323.py +++ b/tests/test_stanza_xep_0323.py @@ -7,7 +7,6 @@ namespace='sn' class TestSensorDataStanzas(SlixTest): - def setUp(self): pass #register_stanza_plugin(Iq, xep_0323.stanza.Request) @@ -59,8 +58,8 @@ class TestSensorDataStanzas(SlixTest): iq['req']['momentary'] = 'true' - iq['req'].add_node("Device02", "Source02", "CacheType"); - iq['req'].add_node("Device44"); + iq['req'].add_node("Device02", "Source02", "CacheType") + iq['req'].add_node("Device44") self.check(iq,""" <iq type='get' @@ -75,7 +74,7 @@ class TestSensorDataStanzas(SlixTest): """ ) - iq['req'].del_node("Device02"); + iq['req'].del_node("Device02") self.check(iq,""" <iq type='get' @@ -89,7 +88,7 @@ class TestSensorDataStanzas(SlixTest): """ ) - iq['req'].del_nodes(); + iq['req'].del_nodes() self.check(iq,""" <iq type='get' @@ -115,8 +114,8 @@ class TestSensorDataStanzas(SlixTest): iq['req']['momentary'] = 'true' - iq['req'].add_field("Top temperature"); - iq['req'].add_field("Bottom temperature"); + iq['req'].add_field("Top temperature") + iq['req'].add_field("Bottom temperature") self.check(iq,""" <iq type='get' @@ -237,12 +236,12 @@ class TestSensorDataStanzas(SlixTest): msg['to'] = 'master@clayster.com/amr' msg['fields']['seqnr'] = '1' - node = msg['fields'].add_node("Device02"); - ts = node.add_timestamp("2013-03-07T16:24:30"); + node = msg['fields'].add_node("Device02") + ts = node.add_timestamp("2013-03-07T16:24:30") - data = ts.add_data(typename="numeric", name="Temperature", value="-12.42", unit='K'); - data['momentary'] = 'true'; - data['automaticReadout'] = 'true'; + data = ts.add_data(typename="numeric", name="Temperature", value="-12.42", unit='K') + data['momentary'] = 'true' + data['automaticReadout'] = 'true' self.check(msg,""" <message from='device@clayster.com' @@ -258,10 +257,9 @@ class TestSensorDataStanzas(SlixTest): """ ) - node = msg['fields'].add_node("EmptyDevice"); - node = msg['fields'].add_node("Device04"); - ts = node.add_timestamp("EmptyTimestamp"); - + node = msg['fields'].add_node("EmptyDevice") + node = msg['fields'].add_node("Device04") + ts = node.add_timestamp("EmptyTimestamp") self.check(msg,""" <message from='device@clayster.com' @@ -281,32 +279,32 @@ class TestSensorDataStanzas(SlixTest): """ ) - node = msg['fields'].add_node("Device77"); - ts = node.add_timestamp("2013-05-03T12:00:01"); - data = ts.add_data(typename="numeric", name="Temperature", value="-12.42", unit='K'); - data['historicalDay'] = 'true'; - data = ts.add_data(typename="numeric", name="Speed", value="312.42", unit='km/h'); - data['historicalWeek'] = 'false'; - data = ts.add_data(typename="string", name="Temperature name", value="Bottom oil"); - data['historicalMonth'] = 'true'; - data = ts.add_data(typename="string", name="Speed name", value="Top speed"); - data['historicalQuarter'] = 'false'; - data = ts.add_data(typename="dateTime", name="T1", value="1979-01-01T00:00:00"); - data['historicalYear'] = 'true'; - data = ts.add_data(typename="dateTime", name="T2", value="2000-01-01T01:02:03"); - data['historicalOther'] = 'false'; - data = ts.add_data(typename="timeSpan", name="TS1", value="P5Y"); - data['missing'] = 'true'; - data = ts.add_data(typename="timeSpan", name="TS2", value="PT2M1S"); - data['manualEstimate'] = 'false'; - data = ts.add_data(typename="enum", name="top color", value="red", dataType="string"); - data['invoiced'] = 'true'; - data = ts.add_data(typename="enum", name="bottom color", value="black", dataType="string"); - data['powerFailure'] = 'false'; - data = ts.add_data(typename="boolean", name="Temperature real", value="false"); - data['historicalDay'] = 'true'; - data = ts.add_data(typename="boolean", name="Speed real", value="true"); - data['historicalWeek'] = 'false'; + node = msg['fields'].add_node("Device77") + ts = node.add_timestamp("2013-05-03T12:00:01") + data = ts.add_data(typename="numeric", name="Temperature", value="-12.42", unit='K') + data['historicalDay'] = 'true' + data = ts.add_data(typename="numeric", name="Speed", value="312.42", unit='km/h') + data['historicalWeek'] = 'false' + data = ts.add_data(typename="string", name="Temperature name", value="Bottom oil") + data['historicalMonth'] = 'true' + data = ts.add_data(typename="string", name="Speed name", value="Top speed") + data['historicalQuarter'] = 'false' + data = ts.add_data(typename="dateTime", name="T1", value="1979-01-01T00:00:00") + data['historicalYear'] = 'true' + data = ts.add_data(typename="dateTime", name="T2", value="2000-01-01T01:02:03") + data['historicalOther'] = 'false' + data = ts.add_data(typename="timeSpan", name="TS1", value="P5Y") + data['missing'] = 'true' + data = ts.add_data(typename="timeSpan", name="TS2", value="PT2M1S") + data['manualEstimate'] = 'false' + data = ts.add_data(typename="enum", name="top color", value="red", dataType="string") + data['invoiced'] = 'true' + data = ts.add_data(typename="enum", name="bottom color", value="black", dataType="string") + data['powerFailure'] = 'false' + data = ts.add_data(typename="boolean", name="Temperature real", value="false") + data['historicalDay'] = 'true' + data = ts.add_data(typename="boolean", name="Speed real", value="true") + data['historicalWeek'] = 'false' self.check(msg,""" <message from='device@clayster.com' @@ -344,19 +342,17 @@ class TestSensorDataStanzas(SlixTest): def testTimestamp(self): - msg = self.Message(); + msg = self.Message() msg['from'] = 'device@clayster.com' msg['to'] = 'master@clayster.com/amr' msg['fields']['seqnr'] = '1' - node = msg['fields'].add_node("Device02"); - node = msg['fields'].add_node("Device03"); - - ts = node.add_timestamp("2013-03-07T16:24:30"); - ts = node.add_timestamp("2013-03-07T16:24:31"); - + node = msg['fields'].add_node("Device02") + node = msg['fields'].add_node("Device03") + ts = node.add_timestamp("2013-03-07T16:24:30") + ts = node.add_timestamp("2013-03-07T16:24:31") self.check(msg,""" <message from='device@clayster.com' diff --git a/tests/test_stanza_xep_0325.py b/tests/test_stanza_xep_0325.py index 75a45907..3e388ac3 100644 --- a/tests/test_stanza_xep_0325.py +++ b/tests/test_stanza_xep_0325.py @@ -16,7 +16,6 @@ namespace='sn' class TestControlStanzas(SlixTest): - def setUp(self): pass @@ -29,8 +28,8 @@ class TestControlStanzas(SlixTest): iq['from'] = 'master@clayster.com/amr' iq['to'] = 'device@clayster.com' iq['id'] = '1' - iq['set'].add_node("Device02", "Source02", "MyCacheType"); - iq['set'].add_node("Device15"); + iq['set'].add_node("Device02", "Source02", "MyCacheType") + iq['set'].add_node("Device15") iq['set'].add_data("Tjohej", "boolean", "true") self.check(iq,""" @@ -47,7 +46,7 @@ class TestControlStanzas(SlixTest): """ ) - iq['set'].del_node("Device02"); + iq['set'].del_node("Device02") self.check(iq,""" <iq type='set' @@ -62,7 +61,7 @@ class TestControlStanzas(SlixTest): """ ) - iq['set'].del_nodes(); + iq['set'].del_nodes() self.check(iq,""" <iq type='set' @@ -84,8 +83,8 @@ class TestControlStanzas(SlixTest): msg = self.Message() msg['from'] = 'master@clayster.com/amr' msg['to'] = 'device@clayster.com' - msg['set'].add_node("Device02"); - msg['set'].add_node("Device15"); + msg['set'].add_node("Device02") + msg['set'].add_node("Device15") msg['set'].add_data("Tjohej", "boolean", "true") self.check(msg,""" @@ -111,7 +110,7 @@ class TestControlStanzas(SlixTest): iq['from'] = 'master@clayster.com/amr' iq['to'] = 'device@clayster.com' iq['id'] = '8' - iq['setResponse']['responseCode'] = "OK"; + iq['setResponse']['responseCode'] = "OK" self.check(iq,""" <iq type='result' @@ -128,10 +127,9 @@ class TestControlStanzas(SlixTest): iq['from'] = 'master@clayster.com/amr' iq['to'] = 'device@clayster.com' iq['id'] = '9' - iq['setResponse']['responseCode'] = "OtherError"; - iq['setResponse']['error']['var'] = "Output"; - iq['setResponse']['error']['text'] = "Test of other error.!"; - + iq['setResponse']['responseCode'] = "OtherError" + iq['setResponse']['error']['var'] = "Output" + iq['setResponse']['error']['text'] = "Test of other error.!" self.check(iq,""" <iq type='error' @@ -150,11 +148,10 @@ class TestControlStanzas(SlixTest): iq['from'] = 'master@clayster.com/amr' iq['to'] = 'device@clayster.com' iq['id'] = '9' - iq['setResponse']['responseCode'] = "NotFound"; - iq['setResponse'].add_node("Device17", "Source09"); - iq['setResponse'].add_node("Device18", "Source09"); - iq['setResponse'].add_data("Tjohopp"); - + iq['setResponse']['responseCode'] = "NotFound" + iq['setResponse'].add_node("Device17", "Source09") + iq['setResponse'].add_node("Device18", "Source09") + iq['setResponse'].add_data("Tjohopp") self.check(iq,""" <iq type='error' @@ -179,38 +176,38 @@ class TestControlStanzas(SlixTest): iq['from'] = 'master@clayster.com/amr' iq['to'] = 'device@clayster.com' iq['id'] = '1' - iq['set'].add_node("Device02", "Source02", "MyCacheType"); - iq['set'].add_node("Device15"); + iq['set'].add_node("Device02", "Source02", "MyCacheType") + iq['set'].add_node("Device15") - iq['set'].add_data("Tjohej", "boolean", "true"); - iq['set'].add_data("Tjohej2", "boolean", "false"); + iq['set'].add_data("Tjohej", "boolean", "true") + iq['set'].add_data("Tjohej2", "boolean", "false") - iq['set'].add_data("TjohejC", "color", "FF00FF"); - iq['set'].add_data("TjohejC2", "color", "00FF00"); + iq['set'].add_data("TjohejC", "color", "FF00FF") + iq['set'].add_data("TjohejC2", "color", "00FF00") - iq['set'].add_data("TjohejS", "string", "String1"); - iq['set'].add_data("TjohejS2", "string", "String2"); + iq['set'].add_data("TjohejS", "string", "String1") + iq['set'].add_data("TjohejS2", "string", "String2") - iq['set'].add_data("TjohejDate", "date", "2012-01-01"); - iq['set'].add_data("TjohejDate2", "date", "1900-12-03"); + iq['set'].add_data("TjohejDate", "date", "2012-01-01") + iq['set'].add_data("TjohejDate2", "date", "1900-12-03") - iq['set'].add_data("TjohejDateT4", "dateTime", "1900-12-03 12:30"); - iq['set'].add_data("TjohejDateT2", "dateTime", "1900-12-03 11:22"); + iq['set'].add_data("TjohejDateT4", "dateTime", "1900-12-03 12:30") + iq['set'].add_data("TjohejDateT2", "dateTime", "1900-12-03 11:22") - iq['set'].add_data("TjohejDouble2", "double", "200.22"); - iq['set'].add_data("TjohejDouble3", "double", "-12232131.3333"); + iq['set'].add_data("TjohejDouble2", "double", "200.22") + iq['set'].add_data("TjohejDouble3", "double", "-12232131.3333") - iq['set'].add_data("TjohejDur", "duration", "P5Y"); - iq['set'].add_data("TjohejDur2", "duration", "PT2M1S"); + iq['set'].add_data("TjohejDur", "duration", "P5Y") + iq['set'].add_data("TjohejDur2", "duration", "PT2M1S") - iq['set'].add_data("TjohejInt", "int", "1"); - iq['set'].add_data("TjohejInt2", "int", "-42"); + iq['set'].add_data("TjohejInt", "int", "1") + iq['set'].add_data("TjohejInt2", "int", "-42") - iq['set'].add_data("TjohejLong", "long", "123456789098"); - iq['set'].add_data("TjohejLong2", "long", "-90983243827489374"); + iq['set'].add_data("TjohejLong", "long", "123456789098") + iq['set'].add_data("TjohejLong2", "long", "-90983243827489374") - iq['set'].add_data("TjohejTime", "time", "23:59"); - iq['set'].add_data("TjohejTime2", "time", "12:00"); + iq['set'].add_data("TjohejTime", "time", "23:59") + iq['set'].add_data("TjohejTime2", "time", "12:00") self.check(iq,""" <iq type='set' diff --git a/tests/test_stream_xep_0050.py b/tests/test_stream_xep_0050.py index 530dba9f..6ebf88a9 100644 --- a/tests/test_stream_xep_0050.py +++ b/tests/test_stream_xep_0050.py @@ -119,7 +119,8 @@ class TestAdHocCommands(SlixTest): def handle_command(iq, session): def handle_form(form, session): - results.append(form['values']['foo']) + results.append(form.get_values()['foo']) + session['payload'] = None form = self.xmpp['xep_0004'].makeForm('form') form.addField(var='foo', ftype='text-single', label='Foo') @@ -191,10 +192,11 @@ class TestAdHocCommands(SlixTest): def handle_command(iq, session): def handle_step2(form, session): - results.append(form['values']['bar']) + results.append(form.get_values()['bar']) + session['payload'] = None def handle_step1(form, session): - results.append(form['values']['foo']) + results.append(form.get_values()['foo']) form = self.xmpp['xep_0004'].makeForm('form') form.addField(var='bar', ftype='text-single', label='Bar') @@ -426,7 +428,8 @@ class TestAdHocCommands(SlixTest): def handle_form(forms, session): for form in forms: - results.append(form['values']['FORM_TYPE']) + results.append(form.get_values()['FORM_TYPE']) + session['payload'] = None form1 = self.xmpp['xep_0004'].makeForm('form') form1.addField(var='FORM_TYPE', ftype='hidden', value='form_1') diff --git a/tests/test_stream_xep_0323.py b/tests/test_stream_xep_0323.py index f92c4553..42230e1f 100644 --- a/tests/test_stream_xep_0323.py +++ b/tests/test_stream_xep_0323.py @@ -19,7 +19,7 @@ class TestStreamSensorData(SlixTest): pass def _time_now(self): - return datetime.datetime.now().replace(microsecond=0).isoformat(); + return datetime.datetime.now().replace(microsecond=0).isoformat() def tearDown(self): self.stream_close() @@ -29,12 +29,12 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - myDevice = Device("Device22"); - myDevice._add_field(name="Temperature", typename="numeric", unit="°C"); + myDevice = Device("Device22") + myDevice._add_field(name="Temperature", typename="numeric", unit="°C") myDevice._set_momentary_timestamp("2013-03-07T16:24:30") - myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}); + myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}) - self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5); + self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5) self.recv(""" <iq type='get' @@ -73,7 +73,7 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - self.xmpp['xep_0323']._set_authenticated("darth@deathstar.com"); + self.xmpp['xep_0323']._set_authenticated("darth@deathstar.com") self.recv(""" <iq type='get' @@ -101,8 +101,8 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - myDevice = Device("Device44"); - self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5); + myDevice = Device("Device44") + self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5) print("."), @@ -157,11 +157,11 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - myDevice = Device("Device44"); - myDevice._add_field(name='Voltage', typename="numeric", unit="V"); - myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"}); + myDevice = Device("Device44") + myDevice._add_field(name='Voltage', typename="numeric", unit="V") + myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"}) - self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5); + self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5) print("."), @@ -236,15 +236,15 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - myDevice = Device("Device44"); - myDevice._add_field(name='Voltage', typename="numeric", unit="V"); - myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"}); - myDevice._add_field(name='Current', typename="numeric", unit="A"); - myDevice._add_field(name='Height', typename="string"); - myDevice._add_field_timestamp_data(name="Voltage", value="230.6", timestamp="2000-01-01T01:01:02"); - myDevice._add_field_timestamp_data(name="Height", value="115 m", timestamp="2000-01-01T01:01:02", flags={"invoiced": "true"}); + myDevice = Device("Device44") + myDevice._add_field(name='Voltage', typename="numeric", unit="V") + myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"}) + myDevice._add_field(name='Current', typename="numeric", unit="A") + myDevice._add_field(name='Height', typename="string") + myDevice._add_field_timestamp_data(name="Voltage", value="230.6", timestamp="2000-01-01T01:01:02") + myDevice._add_field_timestamp_data(name="Height", value="115 m", timestamp="2000-01-01T01:01:02", flags={"invoiced": "true"}) - self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5); + self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5) print("."), @@ -308,15 +308,15 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - myDevice = Device("Device44"); - myDevice._add_field(name='Voltage', typename="numeric", unit="V"); - myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"}); - myDevice._add_field(name='Current', typename="numeric", unit="A"); - myDevice._add_field(name='Height', typename="string"); - myDevice._add_field_timestamp_data(name="Voltage", value="230.6", timestamp="2000-01-01T01:01:02"); - myDevice._add_field_timestamp_data(name="Height", value="115 m", timestamp="2000-01-01T01:01:02", flags={"invoiced": "true"}); + myDevice = Device("Device44") + myDevice._add_field(name='Voltage', typename="numeric", unit="V") + myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"}) + myDevice._add_field(name='Current', typename="numeric", unit="A") + myDevice._add_field(name='Height', typename="string") + myDevice._add_field_timestamp_data(name="Voltage", value="230.6", timestamp="2000-01-01T01:01:02") + myDevice._add_field_timestamp_data(name="Height", value="115 m", timestamp="2000-01-01T01:01:02", flags={"invoiced": "true"}) - self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5); + self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5) print("."), @@ -379,7 +379,7 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", callback=None); + self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", callback=None) self.send(""" <iq type='get' @@ -390,7 +390,7 @@ class TestStreamSensorData(SlixTest): </iq> """) - self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=None); + self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=None) self.send(""" <iq type='get' @@ -404,7 +404,7 @@ class TestStreamSensorData(SlixTest): </iq> """) - self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", fields=['Temperature', 'Voltage'], callback=None); + self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", fields=['Temperature', 'Voltage'], callback=None) self.send(""" <iq type='get' @@ -424,13 +424,13 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - results = []; + results = [] def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None): if (result == "rejected") and (error_msg == "Invalid device Device22"): - results.append("rejected"); + results.append("rejected") - self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=my_callback); + self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=my_callback) self.send(""" <iq type='get' @@ -456,7 +456,7 @@ class TestStreamSensorData(SlixTest): """) self.failUnless(results == ["rejected"], - "Rejected callback was not properly executed"); + "Rejected callback was not properly executed") def testRequestAcceptedAPI(self): @@ -464,12 +464,12 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - results = []; + results = [] def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None): - results.append(result); + results.append(result) - self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=my_callback); + self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=my_callback) self.send(""" <iq type='get' @@ -493,7 +493,7 @@ class TestStreamSensorData(SlixTest): """) self.failUnless(results == ["accepted"], - "Accepted callback was not properly executed"); + "Accepted callback was not properly executed") def testRequestFieldsAPI(self): @@ -501,17 +501,17 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - results = []; - callback_data = {}; + results = [] + callback_data = {} def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None): - results.append(result); + results.append(result) if result == "fields": - callback_data["nodeId"] = nodeId; - callback_data["timestamp"] = timestamp; - callback_data["error_msg"] = error_msg; + callback_data["nodeId"] = nodeId + callback_data["timestamp"] = timestamp + callback_data["error_msg"] = error_msg for f in fields: - callback_data["field_" + f['name']] = f; + callback_data["field_" + f['name']] = f self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", @@ -560,24 +560,24 @@ class TestStreamSensorData(SlixTest): </message> """) - self.failUnlessEqual(results, ["accepted","fields","done"]); + self.failUnlessEqual(results, ["accepted","fields","done"]) # self.assertIn("nodeId", callback_data); self.assertTrue("nodeId" in callback_data) - self.failUnlessEqual(callback_data["nodeId"], "Device33"); + self.failUnlessEqual(callback_data["nodeId"], "Device33") # self.assertIn("timestamp", callback_data); - self.assertTrue("timestamp" in callback_data); - self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02"); + self.assertTrue("timestamp" in callback_data) + self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02") #self.assertIn("field_Voltage", callback_data); - self.assertTrue("field_Voltage" in callback_data); - self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}}); + self.assertTrue("field_Voltage" in callback_data) + self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}}) #self.assertIn("field_TestBool", callback_data); - self.assertTrue("field_TestBool" in callback_data); - self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" }); + self.assertTrue("field_TestBool" in callback_data) + self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" }) def testServiceDiscoveryClient(self): self.stream_start(mode='client', plugins=['xep_0030', - 'xep_0323']); + 'xep_0323']) self.recv(""" <iq type='get' @@ -602,7 +602,7 @@ class TestStreamSensorData(SlixTest): def testServiceDiscoveryComponent(self): self.stream_start(mode='component', plugins=['xep_0030', - 'xep_0323']); + 'xep_0323']) self.recv(""" <iq type='get' @@ -631,21 +631,20 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - results = []; - callback_data = {}; + results = [] + callback_data = {} def my_callback(from_jid, result, nodeId=None, timestamp=None, error_msg=None): - results.append(result); + results.append(result) if result == "failure": - callback_data["nodeId"] = nodeId; - callback_data["timestamp"] = timestamp; - callback_data["error_msg"] = error_msg; + callback_data["nodeId"] = nodeId + callback_data["timestamp"] = timestamp + callback_data["error_msg"] = error_msg self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33'], callback=my_callback) - self.send(""" <iq type='get' from='tester@localhost' @@ -677,26 +676,26 @@ class TestStreamSensorData(SlixTest): self.failUnlessEqual(results, ["accepted","failure"]); # self.assertIn("nodeId", callback_data); - self.assertTrue("nodeId" in callback_data); - self.failUnlessEqual(callback_data["nodeId"], "Device33"); + self.assertTrue("nodeId" in callback_data) + self.failUnlessEqual(callback_data["nodeId"], "Device33") # self.assertIn("timestamp", callback_data); - self.assertTrue("timestamp" in callback_data); - self.failUnlessEqual(callback_data["timestamp"], "2013-03-07T17:13:30"); + self.assertTrue("timestamp" in callback_data) + self.failUnlessEqual(callback_data["timestamp"], "2013-03-07T17:13:30") # self.assertIn("error_msg", callback_data); - self.assertTrue("error_msg" in callback_data); - self.failUnlessEqual(callback_data["error_msg"], "Timeout."); + self.assertTrue("error_msg" in callback_data) + self.failUnlessEqual(callback_data["error_msg"], "Timeout.") def testDelayedRequest(self): self.stream_start(mode='component', plugins=['xep_0030', 'xep_0323']) - myDevice = Device("Device22"); - myDevice._add_field(name="Temperature", typename="numeric", unit="°C"); + myDevice = Device("Device22") + myDevice._add_field(name="Temperature", typename="numeric", unit="°C") myDevice._set_momentary_timestamp("2013-03-07T16:24:30") - myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}); + myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}) - self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5); + self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5) dtnow = datetime.datetime.now() ts_2sec = datetime.timedelta(0,2) @@ -748,12 +747,12 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - myDevice = Device("Device22"); - myDevice._add_field(name="Temperature", typename="numeric", unit="°C"); + myDevice = Device("Device22") + myDevice._add_field(name="Temperature", typename="numeric", unit="°C") myDevice._set_momentary_timestamp("2013-03-07T16:24:30") - myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}); + myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}) - self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5); + self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5) dtnow = datetime.datetime.now() ts_2sec = datetime.timedelta(0,2) @@ -809,13 +808,13 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - myDevice = Device("Device44"); - myDevice._add_field(name='Voltage', typename="numeric", unit="V"); - myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"}); - myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"}); - myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"}); + myDevice = Device("Device44") + myDevice._add_field(name='Voltage', typename="numeric", unit="V") + myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"}) + myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"}) + myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"}) - self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5); + self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5) print("."), @@ -879,13 +878,13 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - myDevice = Device("Device44"); - myDevice._add_field(name='Voltage', typename="numeric", unit="V"); - myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"}); - myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"}); - myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"}); + myDevice = Device("Device44") + myDevice._add_field(name='Voltage', typename="numeric", unit="V") + myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"}) + myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"}) + myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"}) - self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5); + self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5) print("."), @@ -949,13 +948,13 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - myDevice = Device("Device44"); - myDevice._add_field(name='Voltage', typename="numeric", unit="V"); - myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"}); - myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"}); - myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"}); + myDevice = Device("Device44") + myDevice._add_field(name='Voltage', typename="numeric", unit="V") + myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"}) + myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"}) + myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"}) - self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5); + self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5) print("."), @@ -1005,17 +1004,17 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - results = []; - callback_data = {}; + results = [] + callback_data = {} def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None): - results.append(result); + results.append(result) if result == "fields": - callback_data["nodeId"] = nodeId; - callback_data["timestamp"] = timestamp; - callback_data["error_msg"] = error_msg; + callback_data["nodeId"] = nodeId + callback_data["timestamp"] = timestamp + callback_data["error_msg"] = error_msg for f in fields: - callback_data["field_" + f['name']] = f; + callback_data["field_" + f['name']] = f self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", @@ -1073,17 +1072,17 @@ class TestStreamSensorData(SlixTest): self.failUnlessEqual(results, ["queued","started","fields","done"]); # self.assertIn("nodeId", callback_data); - self.assertTrue("nodeId" in callback_data); - self.failUnlessEqual(callback_data["nodeId"], "Device33"); + self.assertTrue("nodeId" in callback_data) + self.failUnlessEqual(callback_data["nodeId"], "Device33") # self.assertIn("timestamp", callback_data); - self.assertTrue("timestamp" in callback_data); - self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02"); + self.assertTrue("timestamp" in callback_data) + self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02") # self.assertIn("field_Voltage", callback_data); - self.assertTrue("field_Voltage" in callback_data); - self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}}); + self.assertTrue("field_Voltage" in callback_data) + self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}}) # self.assertIn("field_TestBool", callback_data); - self.assertTrue("field_TestBool" in callback_data); - self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" }); + self.assertTrue("field_TestBool" in callback_data) + self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" }) def testRequestFieldsCancelAPI(self): @@ -1092,12 +1091,12 @@ class TestStreamSensorData(SlixTest): plugins=['xep_0030', 'xep_0323']) - results = []; + results = [] def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None): - results.append(result); + results.append(result) - session = self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33'], callback=my_callback); + session = self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33'], callback=my_callback) self.send(""" <iq type='get' @@ -1119,7 +1118,7 @@ class TestStreamSensorData(SlixTest): </iq> """) - self.xmpp['xep_0323'].cancel_request(session=session); + self.xmpp['xep_0323'].cancel_request(session=session) self.send(""" <iq type='get' @@ -1139,19 +1138,19 @@ class TestStreamSensorData(SlixTest): </iq> """) - self.failUnlessEqual(results, ["accepted","cancelled"]); + self.failUnlessEqual(results, ["accepted","cancelled"]) def testDelayedRequestCancel(self): self.stream_start(mode='component', plugins=['xep_0030', 'xep_0323']) - myDevice = Device("Device22"); - myDevice._add_field(name="Temperature", typename="numeric", unit="°C"); + myDevice = Device("Device22") + myDevice._add_field(name="Temperature", typename="numeric", unit="°C") myDevice._set_momentary_timestamp("2013-03-07T16:24:30") - myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}); + myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}) - self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5); + self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5) dtnow = datetime.datetime.now() ts_2sec = datetime.timedelta(0,2) diff --git a/tests/test_stream_xep_0325.py b/tests/test_stream_xep_0325.py index d27ee5a7..600bfa64 100644 --- a/tests/test_stream_xep_0325.py +++ b/tests/test_stream_xep_0325.py @@ -28,7 +28,7 @@ class TestStreamControl(SlixTest): pass def _time_now(self): - return datetime.datetime.now().replace(microsecond=0).isoformat(); + return datetime.datetime.now().replace(microsecond=0).isoformat() def tearDown(self): self.stream_close() @@ -38,10 +38,10 @@ class TestStreamControl(SlixTest): plugins=['xep_0030', 'xep_0325']) - myDevice = Device("Device22"); - myDevice._add_control_field(name="Temperature", typename="int", value="15"); + myDevice = Device("Device22") + myDevice._add_control_field(name="Temperature", typename="int", value="15") - self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5); + self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5) self.recv(""" <iq type='set' @@ -63,23 +63,23 @@ class TestStreamControl(SlixTest): </iq> """) - self.assertEqual(myDevice._get_field_value("Temperature"), "17"); + self.assertEqual(myDevice._get_field_value("Temperature"), "17") def testRequestSetMulti(self): self.stream_start(mode='component', plugins=['xep_0030', 'xep_0325']) - myDevice = Device("Device22"); - myDevice._add_control_field(name="Temperature", typename="int", value="15"); - myDevice._add_control_field(name="Startup", typename="date", value="2013-01-03"); + myDevice = Device("Device22") + myDevice._add_control_field(name="Temperature", typename="int", value="15") + myDevice._add_control_field(name="Startup", typename="date", value="2013-01-03") - myDevice2 = Device("Device23"); - myDevice2._add_control_field(name="Temperature", typename="int", value="19"); - myDevice2._add_control_field(name="Startup", typename="date", value="2013-01-09"); + myDevice2 = Device("Device23") + myDevice2._add_control_field(name="Temperature", typename="int", value="19") + myDevice2._add_control_field(name="Startup", typename="date", value="2013-01-09") - self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5); - self.xmpp['xep_0325'].register_node(nodeId="Device23", device=myDevice2, commTimeout=0.5); + self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5) + self.xmpp['xep_0325'].register_node(nodeId="Device23", device=myDevice2, commTimeout=0.5) self.recv(""" <iq type='set' @@ -102,8 +102,8 @@ class TestStreamControl(SlixTest): </iq> """) - self.assertEqual(myDevice._get_field_value("Temperature"), "17"); - self.assertEqual(myDevice2._get_field_value("Temperature"), "19"); + self.assertEqual(myDevice._get_field_value("Temperature"), "17") + self.assertEqual(myDevice2._get_field_value("Temperature"), "19") self.recv(""" <iq type='set' @@ -128,20 +128,20 @@ class TestStreamControl(SlixTest): </iq> """) - self.assertEqual(myDevice._get_field_value("Temperature"), "20"); - self.assertEqual(myDevice2._get_field_value("Temperature"), "20"); - self.assertEqual(myDevice._get_field_value("Startup"), "2013-02-01"); - self.assertEqual(myDevice2._get_field_value("Startup"), "2013-02-01"); + self.assertEqual(myDevice._get_field_value("Temperature"), "20") + self.assertEqual(myDevice2._get_field_value("Temperature"), "20") + self.assertEqual(myDevice._get_field_value("Startup"), "2013-02-01") + self.assertEqual(myDevice2._get_field_value("Startup"), "2013-02-01") def testRequestSetFail(self): self.stream_start(mode='component', plugins=['xep_0030', 'xep_0325']) - myDevice = Device("Device23"); - myDevice._add_control_field(name="Temperature", typename="int", value="15"); + myDevice = Device("Device23") + myDevice._add_control_field(name="Temperature", typename="int", value="15") - self.xmpp['xep_0325'].register_node(nodeId="Device23", device=myDevice, commTimeout=0.5); + self.xmpp['xep_0325'].register_node(nodeId="Device23", device=myDevice, commTimeout=0.5) self.recv(""" <iq type='set' @@ -166,18 +166,18 @@ class TestStreamControl(SlixTest): </iq> """) - self.assertEqual(myDevice._get_field_value("Temperature"), "15"); - self.assertFalse(myDevice.has_control_field("Voltage", "int")); + self.assertEqual(myDevice._get_field_value("Temperature"), "15") + self.assertFalse(myDevice.has_control_field("Voltage", "int")) def testDirectSetOk(self): self.stream_start(mode='component', plugins=['xep_0030', 'xep_0325']) - myDevice = Device("Device22"); - myDevice._add_control_field(name="Temperature", typename="int", value="15"); + myDevice = Device("Device22") + myDevice._add_control_field(name="Temperature", typename="int", value="15") - self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5); + self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5) self.recv(""" <message @@ -191,17 +191,17 @@ class TestStreamControl(SlixTest): time.sleep(0.5) - self.assertEqual(myDevice._get_field_value("Temperature"), "17"); + self.assertEqual(myDevice._get_field_value("Temperature"), "17") def testDirectSetFail(self): self.stream_start(mode='component', plugins=['xep_0030', 'xep_0325']) - myDevice = Device("Device22"); - myDevice._add_control_field(name="Temperature", typename="int", value="15"); + myDevice = Device("Device22") + myDevice._add_control_field(name="Temperature", typename="int", value="15") - self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5); + self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5) self.recv(""" <message @@ -223,16 +223,16 @@ class TestStreamControl(SlixTest): plugins=['xep_0030', 'xep_0325']) - results = []; + results = [] def my_callback(from_jid, result, nodeIds=None, fields=None, error_msg=None): - results.append(result); + results.append(result) fields = [] fields.append(("Temperature", "double", "20.5")) fields.append(("TemperatureAlarmSetting", "string", "High")) - self.xmpp['xep_0325'].set_request(from_jid="tester@localhost", to_jid="you@google.com", fields=fields, nodeIds=['Device33', 'Device22'], callback=my_callback); + self.xmpp['xep_0325'].set_request(from_jid="tester@localhost", to_jid="you@google.com", fields=fields, nodeIds=['Device33', 'Device22'], callback=my_callback) self.send(""" <iq type='set' @@ -265,16 +265,16 @@ class TestStreamControl(SlixTest): plugins=['xep_0030', 'xep_0325']) - results = []; + results = [] def my_callback(from_jid, result, nodeIds=None, fields=None, error_msg=None): - results.append(result); + results.append(result) fields = [] fields.append(("Temperature", "double", "20.5")) fields.append(("TemperatureAlarmSetting", "string", "High")) - self.xmpp['xep_0325'].set_request(from_jid="tester@localhost", to_jid="you@google.com", fields=fields, nodeIds=['Device33', 'Device22'], callback=my_callback); + self.xmpp['xep_0325'].set_request(from_jid="tester@localhost", to_jid="you@google.com", fields=fields, nodeIds=['Device33', 'Device22'], callback=my_callback) self.send(""" <iq type='set' @@ -301,12 +301,12 @@ class TestStreamControl(SlixTest): </iq> """) - self.assertEqual(results, ["OtherError"]); + self.assertEqual(results, ["OtherError"]) def testServiceDiscoveryClient(self): self.stream_start(mode='client', plugins=['xep_0030', - 'xep_0325']); + 'xep_0325']) self.recv(""" <iq type='get' @@ -331,7 +331,7 @@ class TestStreamControl(SlixTest): def testServiceDiscoveryComponent(self): self.stream_start(mode='component', plugins=['xep_0030', - 'xep_0325']); + 'xep_0325']) self.recv(""" <iq type='get' @@ -1,5 +1,5 @@ [tox] -envlist = py26,py27,py31,py32,py33 +envlist = py34 [testenv] deps = nose commands = nosetests --where=tests --exclude=live -i slixtest.py |