diff options
73 files changed, 2408 insertions, 1213 deletions
@@ -12,3 +12,4 @@ sleekxmpp.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 @@ -3,7 +3,7 @@ SleekXMPP SleekXMPP is an MIT licensed XMPP library for Python 2.6/3.1+, and is featured in examples in -`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_ +`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_ by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived here from reading the Definitive Guide, please see the notes on updating the examples to the latest version of SleekXMPP. @@ -52,7 +52,7 @@ The latest source code for SleekXMPP may be found on `Github Installing DNSPython ---------------------- +-------------------- If you are using Python3 and wish to use dnspython, you will have to checkout and install the ``python3`` branch:: @@ -144,7 +144,7 @@ SleekXMPP projects:: if __name__ == '__main__': - # Ideally use optparse or argparse to get JID, + # Ideally use optparse or argparse to get JID, # password, and log level. logging.basicConfig(level=logging.DEBUG, @@ -158,15 +158,15 @@ SleekXMPP projects:: Credits ------- **Main Author:** Nathan Fritz - `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_, + `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_, `@fritzy <http://twitter.com/fritzy>`_ Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP - <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a former member of + <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a former member of the XMPP Council. **Co-Author:** Lance Stout - `lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_, + `lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_, `@lancestout <http://twitter.com/lancestout>`_ **Contributors:** diff --git a/docs/guide_xep_0030.rst b/docs/guide_xep_0030.rst index cb8d7d25..857f7ff1 100644 --- a/docs/guide_xep_0030.rst +++ b/docs/guide_xep_0030.rst @@ -161,7 +161,7 @@ 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 diff --git a/examples/IoT_TestDevice.py b/examples/IoT_TestDevice.py index 8105aaff..b85a0b7c 100755 --- a/examples/IoT_TestDevice.py +++ b/examples/IoT_TestDevice.py @@ -11,18 +11,10 @@ See the file LICENSE for copying permission. """ -import os -import sys -# This can be used when you are in a test environment and need to make paths right -sys.path=['/Users/jocke/Dropbox/06_dev/SleekXMPP']+sys.path - +import getpass import logging -import unittest -import distutils.core -import datetime +import sys -from glob import glob -from os.path import splitext, basename, join as pjoin from optparse import OptionParser from urllib import urlopen @@ -39,8 +31,6 @@ else: from sleekxmpp.plugins.xep_0323.device import Device -#from sleekxmpp.exceptions import IqError, IqTimeout - class IoT_TestDevice(sleekxmpp.ClientXMPP): """ @@ -179,13 +169,13 @@ if __name__ == '__main__': # node=opts.nodeid, # jid=xmpp.boundjid.full) - myDevice = TheDevice(opts.nodeid); + myDevice = TheDevice(opts.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=opts.nodeid, device=myDevice, commTimeout=10); + xmpp['xep_0323'].register_node(nodeId=opts.nodeid, device=myDevice, commTimeout=10) xmpp.beClientOrServer(server=True) while not(xmpp.testForRelease()): xmpp.connect() diff --git a/examples/disco_browser.py b/examples/disco_browser.py index aeb4fb5e..78626e7c 100755 --- a/examples/disco_browser.py +++ b/examples/disco_browser.py @@ -94,7 +94,7 @@ class Disco(sleekxmpp.ClientXMPP): info = self['xep_0030'].get_info(jid=self.target_jid, node=self.target_node, block=True) - elif self.get in self.items_types: + if self.get in self.items_types: # The same applies from above. Listen for the # disco_items event or pass a callback function # if you need to process a non-blocking request. diff --git a/examples/http_over_xmpp.py b/examples/http_over_xmpp.py new file mode 100644 index 00000000..a2fbf664 --- /dev/null +++ b/examples/http_over_xmpp.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" + SleekXMPP: The Sleek 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 SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp 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 : ', iq + print 'From : ', iq['from'] + print 'To : ', iq['to'] + print 'Type : ', iq['type'] + print 'Headers : ', iq['resp']['headers'] + print 'Code : ', iq['resp']['code'] + print 'Message : ', iq['resp']['message'] + print 'Data : ', 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 = raw_input('Username: ') + if opts.password is None: + opts.password = getpass.getpass('Password: ') + + xmpp = HTTPOverXMPPClient(opts.jid, opts.password) + if xmpp.connect((opts.ipaddr, int(opts.port))): + print 'Connected!' + xmpp.process(block=True) + else: + print 'Not connected!' + print 'Goodbye....' + diff --git a/examples/migrate_roster.py b/examples/migrate_roster.py index 797e4f44..9f679523 100755 --- a/examples/migrate_roster.py +++ b/examples/migrate_roster.py @@ -113,7 +113,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) if new_xmpp.connect(): diff --git a/examples/roster_browser.py b/examples/roster_browser.py index 485ac941..a16de24c 100755 --- a/examples/roster_browser.py +++ b/examples/roster_browser.py @@ -68,7 +68,7 @@ class RosterBrowser(sleekxmpp.ClientXMPP): try: self.get_roster() 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/examples/set_avatar.py b/examples/set_avatar.py index cae93c99..08e0b664 100755 --- a/examples/set_avatar.py +++ b/examples/set_avatar.py @@ -63,7 +63,7 @@ class AvatarSetter(sleekxmpp.ClientXMPP): avatar_file = None try: - avatar_file = open(os.path.expanduser(self.filepath)) + avatar_file = open(os.path.expanduser(self.filepath), 'rb') except IOError: print('Could not find file: %s' % self.filepath) return self.disconnect() @@ -93,6 +93,7 @@ packages = [ 'sleekxmpp', 'sleekxmpp/plugins/xep_0108', 'sleekxmpp/plugins/xep_0115', 'sleekxmpp/plugins/xep_0118', + 'sleekxmpp/plugins/xep_0122', 'sleekxmpp/plugins/xep_0128', 'sleekxmpp/plugins/xep_0131', 'sleekxmpp/plugins/xep_0152', @@ -123,6 +124,8 @@ packages = [ 'sleekxmpp', 'sleekxmpp/plugins/xep_0323/stanza', 'sleekxmpp/plugins/xep_0325', 'sleekxmpp/plugins/xep_0325/stanza', + 'sleekxmpp/plugins/xep_0332', + 'sleekxmpp/plugins/xep_0332/stanza', 'sleekxmpp/plugins/google', 'sleekxmpp/plugins/google/gmail', 'sleekxmpp/plugins/google/auth', diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index bf0ae4df..cb72b9bd 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -25,7 +25,6 @@ from sleekxmpp.exceptions import IqError, IqTimeout from sleekxmpp.stanza import Message, Presence, Iq, StreamError from sleekxmpp.stanza.roster import Roster from sleekxmpp.stanza.nick import Nick -from sleekxmpp.stanza.htmlim import HTMLIM from sleekxmpp.xmlstream import XMLStream, JID from sleekxmpp.xmlstream import ET, register_stanza_plugin @@ -56,8 +55,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' @@ -245,7 +244,7 @@ class BaseXMPP(XMLStream): self.plugin[name].post_inited = True return XMLStream.process(self, *args, **kwargs) - 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/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index f837c0f2..31a5a70b 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -52,7 +52,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 @@ -60,9 +59,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/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py index bac455e2..4b229a6f 100644 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -49,8 +49,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/sleekxmpp/features/feature_mechanisms/mechanisms.py b/sleekxmpp/features/feature_mechanisms/mechanisms.py index 17ad5ed0..1d8f8798 100644 --- a/sleekxmpp/features/feature_mechanisms/mechanisms.py +++ b/sleekxmpp/features/feature_mechanisms/mechanisms.py @@ -187,14 +187,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(now=True) @@ -207,13 +207,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/sleekxmpp/jid.py b/sleekxmpp/jid.py index ac5ba30d..754a3d3a 100644 --- a/sleekxmpp/jid.py +++ b/sleekxmpp/jid.py @@ -72,19 +72,18 @@ JID_CACHE_LOCK = threading.Lock() JID_CACHE_MAX_SIZE = 1024 def _cache(key, parts, locked): - JID_CACHE[key] = (parts, locked) - if len(JID_CACHE) > JID_CACHE_MAX_SIZE: - with JID_CACHE_LOCK: - while len(JID_CACHE) > JID_CACHE_MAX_SIZE: - found = None - for key, item in JID_CACHE.items(): - if not item[1]: # if not locked - found = key - break - if not found: # more than MAX_SIZE locked - # warn? + with JID_CACHE_LOCK: + JID_CACHE[key] = (parts, locked) + while len(JID_CACHE) > JID_CACHE_MAX_SIZE: + found = None + for key, item in JID_CACHE.items(): + if not item[1]: # if not locked + found = key break - del JID_CACHE[found] + if not found: # more than MAX_SIZE locked + # warn? + break + del JID_CACHE[found] # pylint: disable=c0103 #: The nodeprep profile of stringprep used to validate the local, @@ -529,10 +528,6 @@ class JID(object): return self._jid[0] or '' @property - def bare(self): - return _format_jid(self._jid[0], self._jid[1]) - - @property def server(self): return self._jid[1] or '' @@ -556,7 +551,6 @@ class JID(object): def bare(self): return _format_jid(self._jid[0], self._jid[1]) - @resource.setter def resource(self, value): self._jid = JID(self, resource=value)._jid diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py index 951f31eb..f501687b 100644 --- a/sleekxmpp/plugins/__init__.py +++ b/sleekxmpp/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/sleekxmpp/plugins/google/auth/stanza.py b/sleekxmpp/plugins/google/auth/stanza.py index 49c5cba7..2d13f85a 100644 --- a/sleekxmpp/plugins/google/auth/stanza.py +++ b/sleekxmpp/plugins/google/auth/stanza.py @@ -24,7 +24,7 @@ class GoogleAuth(ElementBase): print('setting up google extension') def get_client_uses_full_bind_result(self): - return self.parent()._get_attr(self.disovery_attr) == 'true' + return self.parent()._get_attr(self.discovery_attr) == 'true' def set_client_uses_full_bind_result(self, value): print('>>>', value) diff --git a/sleekxmpp/plugins/google/gmail/notifications.py b/sleekxmpp/plugins/google/gmail/notifications.py index 509a95fd..e65b2ca7 100644 --- a/sleekxmpp/plugins/google/gmail/notifications.py +++ b/sleekxmpp/plugins/google/gmail/notifications.py @@ -74,8 +74,8 @@ class Gmail(BasePlugin): return resp def _update_last_results(self, iq, callback=None): - self._last_result_time = data['gmail_messages']['result_time'] - threads = data['gmail_messages']['threads'] + 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: diff --git a/sleekxmpp/plugins/google/nosave/stanza.py b/sleekxmpp/plugins/google/nosave/stanza.py index d8701322..791d4b0c 100644 --- a/sleekxmpp/plugins/google/nosave/stanza.py +++ b/sleekxmpp/plugins/google/nosave/stanza.py @@ -52,7 +52,7 @@ class Item(ElementBase): def get_source(self): return JID(self._get_attr('source', '')) - def set_source(self): + def set_source(self, value): self._set_attr('source', str(value)) diff --git a/sleekxmpp/plugins/google/settings/settings.py b/sleekxmpp/plugins/google/settings/settings.py index 7122ff56..591956fc 100644 --- a/sleekxmpp/plugins/google/settings/settings.py +++ b/sleekxmpp/plugins/google/settings/settings.py @@ -6,8 +6,6 @@ See the file LICENSE for copying permission. """ -import logging - from sleekxmpp.stanza import Iq from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.matcher import StanzaPath diff --git a/sleekxmpp/plugins/xep_0004/stanza/field.py b/sleekxmpp/plugins/xep_0004/stanza/field.py index 51f85995..73e48758 100644 --- a/sleekxmpp/plugins/xep_0004/stanza/field.py +++ b/sleekxmpp/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/sleekxmpp/plugins/xep_0004/stanza/form.py b/sleekxmpp/plugins/xep_0004/stanza/form.py index bbd8540f..3dcc7821 100644 --- a/sleekxmpp/plugins/xep_0004/stanza/form.py +++ b/sleekxmpp/plugins/xep_0004/stanza/form.py @@ -9,7 +9,7 @@ import copy import logging -from sleekxmpp.thirdparty import OrderedDict +from sleekxmpp.thirdparty import OrderedDict, OrderedSet from sleekxmpp.xmlstream import ElementBase, ET from sleekxmpp.plugins.xep_0004.stanza import FormField @@ -22,8 +22,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 +42,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 +73,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 +151,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 +169,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 +177,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 +194,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 +219,33 @@ class Form(ElementBase): self.add_item(item) def set_reported(self, reported): + """ + This either needs a dictionary or 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/sleekxmpp/plugins/xep_0009/remote.py b/sleekxmpp/plugins/xep_0009/remote.py index 8c08e8f3..b02f587e 100644 --- a/sleekxmpp/plugins/xep_0009/remote.py +++ b/sleekxmpp/plugins/xep_0009/remote.py @@ -6,7 +6,7 @@ See the file LICENSE for copying permission. """ -from binding import py2xml, xml2py, xml2fault, fault2xml +from sleekxmpp.plugins.xep_0009.binding import py2xml, xml2py, xml2fault, fault2xml from threading import RLock import abc import inspect @@ -18,6 +18,45 @@ import traceback log = logging.getLogger(__name__) +# Define a function _isstr() to check if an object is a string in a way +# compatible with Python 2 and Python 3 (basestring does not exists in Python 3). +try: + basestring # This evaluation will throw an exception if basestring does not exists (Python 3). + def _isstr(obj): + return isinstance(obj, basestring) +except NameError: + 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 +107,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 +261,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 +329,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 +341,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 +527,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 +583,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 +605,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 +733,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/sleekxmpp/plugins/xep_0009/rpc.py b/sleekxmpp/plugins/xep_0009/rpc.py index 3378c650..6179355e 100644 --- a/sleekxmpp/plugins/xep_0009/rpc.py +++ b/sleekxmpp/plugins/xep_0009/rpc.py @@ -61,7 +61,7 @@ class XEP_0009(BasePlugin): iq.enable('rpc_query')
iq['rpc_query']['method_call']['method_name'] = pmethod
iq['rpc_query']['method_call']['params'] = params
- return iq;
+ return iq
def make_iq_method_response(self, pid, pto, params):
iq = self.xmpp.makeIqResult(pid)
@@ -93,7 +93,7 @@ class XEP_0009(BasePlugin): def _item_not_found(self, iq):
payload = iq.get_payload()
- iq.reply().error().set_payload(payload);
+ iq.reply().error().set_payload(payload)
iq['error']['code'] = '404'
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'item-not-found'
diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py index 8a397923..721f73f6 100644 --- a/sleekxmpp/plugins/xep_0030/disco.py +++ b/sleekxmpp/plugins/xep_0030/disco.py @@ -324,7 +324,7 @@ class XEP_0030(BasePlugin): callback -- Optional callback to execute when a reply is received instead of blocking and waiting for the reply. - timeout_callback -- Optional callback to execute when no result + timeout_callback -- Optional callback to execute when no result has been received in timeout seconds. """ if local is None: @@ -408,7 +408,7 @@ class XEP_0030(BasePlugin): iterator -- If True, return a result set iterator using the XEP-0059 plugin, if the plugin is loaded. Otherwise the parameter is ignored. - timeout_callback -- Optional callback to execute when no result + timeout_callback -- Optional callback to execute when no result has been received in timeout seconds. """ if local or local is None and jid is None: @@ -604,7 +604,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. @@ -615,6 +615,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/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py index ca5ed1ef..cc96d66e 100644 --- a/sleekxmpp/plugins/xep_0045.py +++ b/sleekxmpp/plugins/xep_0045.py @@ -397,6 +397,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/sleekxmpp/plugins/xep_0054/stanza.py b/sleekxmpp/plugins/xep_0054/stanza.py index 72da0b51..2d017d6e 100644 --- a/sleekxmpp/plugins/xep_0054/stanza.py +++ b/sleekxmpp/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/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index fdd9f97e..d890b57a 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -206,7 +206,7 @@ class XEP_0065(base_plugin): # Though this should not be neccessary remove the closed session anyway with self._sessions_lock: if sid in self._sessions: - log.warn(('SOCKS5 session with sid = "%s" was not ' + + log.warn(('SOCKS5 session with sid = "%s" was not ' + 'removed from _sessions by sock.close()') % sid) del self._sessions[sid] @@ -240,9 +240,9 @@ class XEP_0065(base_plugin): # The hostname MUST be SHA1(SID + Requester JID + Target JID) # where the output is hexadecimal-encoded (not binary). digest = sha1() - digest.update(sid) - digest.update(str(requester)) - digest.update(str(target)) + digest.update(sid.encode('utf-8')) + digest.update(str(requester).encode('utf-8')) + digest.update(str(target).encode('utf-8')) dest = digest.hexdigest() diff --git a/sleekxmpp/plugins/xep_0084/stanza.py b/sleekxmpp/plugins/xep_0084/stanza.py index 22f11b72..fd21e6f1 100644 --- a/sleekxmpp/plugins/xep_0084/stanza.py +++ b/sleekxmpp/plugins/xep_0084/stanza.py @@ -8,7 +8,7 @@ from base64 import b64encode, b64decode -from sleekxmpp.util import bytes +from sleekxmpp.util import bytes as sbytes from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin @@ -20,12 +20,15 @@ class Data(ElementBase): def get_value(self): if self.xml.text: - return b64decode(bytes(self.xml.text)) + return b64decode(sbytes(self.xml.text)) return '' def set_value(self, value): if value: - self.xml.text = b64encode(bytes(value)) + self.xml.text = b64encode(sbytes(value)) + # Python3 base64 encoded is bytes and needs to be decoded to string + if isinstance(self.xml.text, bytes): + self.xml.text = self.xml.text.decode() else: self.xml.text = '' diff --git a/sleekxmpp/plugins/xep_0096/file_transfer.py b/sleekxmpp/plugins/xep_0096/file_transfer.py index 6873c7f5..52ba2f27 100644 --- a/sleekxmpp/plugins/xep_0096/file_transfer.py +++ b/sleekxmpp/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/sleekxmpp/plugins/xep_0122/__init__.py b/sleekxmpp/plugins/xep_0122/__init__.py new file mode 100644 index 00000000..4b3e9483 --- /dev/null +++ b/sleekxmpp/plugins/xep_0122/__init__.py @@ -0,0 +1,11 @@ + +from sleekxmpp.plugins.base import register_plugin +from sleekxmpp.plugins.xep_0122.stanza import FormValidation +from sleekxmpp.plugins.xep_0122.data_validation import XEP_0122 + + +register_plugin(XEP_0122) + + +# Retain some backwards compatibility +xep_0122 = XEP_0122 diff --git a/sleekxmpp/plugins/xep_0122/data_validation.py b/sleekxmpp/plugins/xep_0122/data_validation.py new file mode 100644 index 00000000..ec2cdfcc --- /dev/null +++ b/sleekxmpp/plugins/xep_0122/data_validation.py @@ -0,0 +1,19 @@ +from sleekxmpp.xmlstream import register_stanza_plugin +from sleekxmpp.plugins import BasePlugin +from sleekxmpp.plugins.xep_0004 import stanza +from sleekxmpp.plugins.xep_0004.stanza import FormField +from sleekxmpp.plugins.xep_0122.stanza import FormValidation + + +class XEP_0122(BasePlugin): + """ + XEP-0004: 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/sleekxmpp/plugins/xep_0122/stanza.py b/sleekxmpp/plugins/xep_0122/stanza.py new file mode 100644 index 00000000..bc3c177a --- /dev/null +++ b/sleekxmpp/plugins/xep_0122/stanza.py @@ -0,0 +1,94 @@ + +from sleekxmpp.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/sleekxmpp/plugins/xep_0138.py b/sleekxmpp/plugins/xep_0138.py new file mode 100644 index 00000000..c5d8f06f --- /dev/null +++ b/sleekxmpp/plugins/xep_0138.py @@ -0,0 +1,148 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import logging +import socket +import zlib + +from sleekxmpp.thirdparty.suelta.util import bytes + + +from sleekxmpp.stanza import StreamFeatures +from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin, ElementBase, StanzaBase +from sleekxmpp.xmlstream.matcher import * +from sleekxmpp.xmlstream.handler import * +from sleekxmpp.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/sleekxmpp/plugins/xep_0202/time.py b/sleekxmpp/plugins/xep_0202/time.py index d5b3af37..4e48eae8 100644 --- a/sleekxmpp/plugins/xep_0202/time.py +++ b/sleekxmpp/plugins/xep_0202/time.py @@ -72,9 +72,10 @@ class XEP_0202(BasePlugin): Arguments:
iq -- The Iq time request stanza.
"""
- iq.reply()
- iq['entity_time']['time'] = self.local_time(iq['to'])
- iq.send()
+ if iq['type'] == 'get':
+ iq.reply()
+ iq['entity_time']['time'] = self.local_time(iq['to'])
+ iq.send()
def get_entity_time(self, to, ifrom=None, **iqargs):
"""
diff --git a/sleekxmpp/plugins/xep_0257/stanza.py b/sleekxmpp/plugins/xep_0257/stanza.py index 17e20136..c3c41db2 100644 --- a/sleekxmpp/plugins/xep_0257/stanza.py +++ b/sleekxmpp/plugins/xep_0257/stanza.py @@ -10,7 +10,7 @@ from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin class Certs(ElementBase): - name = 'query' + name = 'items' namespace = 'urn:xmpp:saslcert:1' plugin_attrib = 'sasl_certs' interfaces = set() diff --git a/sleekxmpp/plugins/xep_0323/device.py b/sleekxmpp/plugins/xep_0323/device.py index 0bc20327..80e6fd95 100644 --- a/sleekxmpp/plugins/xep_0323/device.py +++ b/sleekxmpp/plugins/xep_0323/device.py @@ -13,15 +13,18 @@ import logging class Device(object): """ - Example implementation of a device readout object. + Example implementation of a device readout object. Is registered in the XEP_0323.register_node call - The device object may be any custom implementation to support + The device object may be any custom implementation to support specific devices, but it must implement the functions: has_field 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', @@ -38,19 +41,19 @@ class Device(object): Returns true if the supplied field name exists in this device. Arguments: - field -- The field name + field -- The field name """ if field in self.fields.keys(): - return True; - return False; - + return True + return False + def refresh(self, fields): """ override method to do the refresh work refresh values from hardware or other """ pass - + def request_fields(self, fields, flags, session, callback): """ @@ -65,7 +68,7 @@ class Device(object): Formatted as a dictionary like { "flag name": "flag value" ... } session -- Session id, only used in the callback as identifier callback -- Callback function to call when data is available. - + The callback function must support the following arguments: session -- Session id, as supplied in the request_fields call @@ -73,11 +76,11 @@ class Device(object): result -- The current result status of the readout. Valid values are: "error" - Readout failed. "fields" - Contains readout data. - "done" - Indicates that the readout is complete. May contain + "done" - Indicates that the readout is complete. May contain readout data. - timestamp_block -- [optional] Only applies when result != "error" + timestamp_block -- [optional] Only applies when result != "error" The readout data. Structured as a dictionary: - { + { timestamp: timestamp for this datablock, fields: list of field dictionary (one per readout field). readout field dictionary format: @@ -89,10 +92,10 @@ class Device(object): dataType: The datatype of the field. Only applies to type enum. flags: [optional] data classifier flags for the field, e.g. momentary Formatted as a dictionary like { "flag name": "flag value" ... } - } + } } error_msg -- [optional] Only applies when result == "error". - Error details when a request failed. + Error details when a request failed. """ logging.debug("request_fields called looking for fields %s",fields) @@ -101,10 +104,10 @@ class Device(object): for f in fields: if f not in self.fields.keys(): self._send_reject(session, callback) - return False; + return False else: # Request all fields - fields = self.fields.keys(); + fields = self.fields.keys() # Refresh data from device @@ -114,27 +117,27 @@ class Device(object): if "momentary" in flags and flags['momentary'] == "true" or \ "all" in flags and flags['all'] == "true": - ts_block = {}; - timestamp = ""; + ts_block = {} + timestamp = "" if len(self.momentary_timestamp) > 0: - timestamp = self.momentary_timestamp; + timestamp = self.momentary_timestamp else: - timestamp = self._get_timestamp(); + timestamp = self._get_timestamp() - field_block = []; + field_block = [] for f in self.momentary_data: if f in fields: - field_block.append({"name": f, - "type": self.fields[f]["type"], + field_block.append({"name": f, + "type": self.fields[f]["type"], "unit": self.fields[f]["unit"], "dataType": self.fields[f]["dataType"], - "value": self.momentary_data[f]["value"], - "flags": self.momentary_data[f]["flags"]}); - ts_block["timestamp"] = timestamp; - ts_block["fields"] = field_block; + "value": self.momentary_data[f]["value"], + "flags": self.momentary_data[f]["flags"]}) + ts_block["timestamp"] = timestamp + ts_block["fields"] = field_block - callback(session, result="done", nodeId=self.nodeId, timestamp_block=ts_block); + callback(session, result="done", nodeId=self.nodeId, timestamp_block=ts_block) return from_flag = self._datetime_flag_parser(flags, 'from') @@ -142,36 +145,36 @@ class Device(object): for ts in sorted(self.timestamp_data.keys()): tsdt = datetime.datetime.strptime(ts, "%Y-%m-%dT%H:%M:%S") - if not from_flag is None: - if tsdt < from_flag: + if not from_flag is None: + if tsdt < from_flag: #print (str(tsdt) + " < " + str(from_flag)) continue - if not to_flag is None: - if tsdt > to_flag: + if not to_flag is None: + if tsdt > to_flag: #print (str(tsdt) + " > " + str(to_flag)) continue - - ts_block = {}; - field_block = []; + + ts_block = {} + field_block = [] for f in self.timestamp_data[ts]: if f in fields: - field_block.append({"name": f, - "type": self.fields[f]["type"], + field_block.append({"name": f, + "type": self.fields[f]["type"], "unit": self.fields[f]["unit"], "dataType": self.fields[f]["dataType"], - "value": self.timestamp_data[ts][f]["value"], - "flags": self.timestamp_data[ts][f]["flags"]}); + "value": self.timestamp_data[ts][f]["value"], + "flags": self.timestamp_data[ts][f]["flags"]}) - ts_block["timestamp"] = ts; - ts_block["fields"] = field_block; - callback(session, result="fields", nodeId=self.nodeId, timestamp_block=ts_block); - callback(session, result="done", nodeId=self.nodeId, timestamp_block=None); + ts_block["timestamp"] = ts + ts_block["fields"] = field_block + callback(session, result="fields", nodeId=self.nodeId, timestamp_block=ts_block) + callback(session, result="done", nodeId=self.nodeId, timestamp_block=None) def _datetime_flag_parser(self, flags, flagname): if not flagname in flags: return None - + dt = None try: dt = datetime.datetime.strptime(flags[flagname], "%Y-%m-%dT%H:%M:%S") @@ -195,7 +198,7 @@ class Device(object): session -- Session id, see definition in request_fields function callback -- Callback function, see definition in request_fields function """ - callback(session, result="error", nodeId=self.nodeId, timestamp_block=None, error_msg="Reject"); + callback(session, result="error", nodeId=self.nodeId, timestamp_block=None, error_msg="Reject") def _add_field(self, name, typename, unit=None, dataType=None): """ @@ -207,7 +210,7 @@ class Device(object): unit -- [optional] only applies to "numeric". Unit for the field. dataType -- [optional] only applies to "enum". Datatype for the field. """ - self.fields[name] = {"type": typename, "unit": unit, "dataType": dataType}; + self.fields[name] = {"type": typename, "unit": unit, "dataType": dataType} def _add_field_timestamp_data(self, name, timestamp, value, flags=None): """ @@ -221,12 +224,12 @@ class Device(object): Formatted as a dictionary like { "flag name": "flag value" ... } """ if not name in self.fields.keys(): - return False; + return False if not timestamp in self.timestamp_data: - self.timestamp_data[timestamp] = {}; + self.timestamp_data[timestamp] = {} - self.timestamp_data[timestamp][name] = {"value": value, "flags": flags}; - return True; + self.timestamp_data[timestamp][name] = {"value": value, "flags": flags} + return True def _add_field_momentary_data(self, name, value, flags=None): """ @@ -239,17 +242,17 @@ class Device(object): Formatted as a dictionary like { "flag name": "flag value" ... } """ if name not in self.fields: - return False; + return False if flags is None: - flags = {}; - + flags = {} + flags["momentary"] = "true" - self.momentary_data[name] = {"value": value, "flags": flags}; - return True; + self.momentary_data[name] = {"value": value, "flags": flags} + return True def _set_momentary_timestamp(self, timestamp): """ This function is only for unit testing to produce predictable results. """ - self.momentary_timestamp = timestamp; + self.momentary_timestamp = timestamp diff --git a/sleekxmpp/plugins/xep_0323/sensordata.py b/sleekxmpp/plugins/xep_0323/sensordata.py index 2e2f2470..a3d4cf34 100644 --- a/sleekxmpp/plugins/xep_0323/sensordata.py +++ b/sleekxmpp/plugins/xep_0323/sensordata.py @@ -15,7 +15,6 @@ from threading import Thread, Lock, Timer from sleekxmpp.plugins.xep_0323.timerreset import TimerReset -from sleekxmpp.xmlstream import JID from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.matcher import StanzaPath from sleekxmpp.plugins.base import BasePlugin @@ -29,12 +28,12 @@ log = logging.getLogger(__name__) class XEP_0323(BasePlugin): """ - XEP-0323: IoT Sensor Data + XEP-0323: IoT Sensor Data This XEP provides the underlying architecture, basic operations and data structures for sensor data communication over XMPP networks. It includes - a hardware abstraction model, removing any technical detail implemented + a hardware abstraction model, removing any technical detail implemented in underlying technologies. Also see <http://xmpp.org/extensions/xep-0323.html> @@ -55,10 +54,10 @@ class XEP_0323(BasePlugin): Sensordata Event:Rejected -- Received a reject from sensor for a request Sensordata Event:Cancelled -- Received a cancel confirm from sensor Sensordata Event:Fields -- Received fields from sensor for a request - This may be triggered multiple times since + This may be triggered multiple times since the sensor can split up its response in multiple messages. - Sensordata Event:Failure -- Received a failure indication from sensor + Sensordata Event:Failure -- Received a failure indication from sensor for a request. Typically a comm timeout. Attributes: @@ -69,7 +68,7 @@ class XEP_0323(BasePlugin): relevant to a request's session. This dictionary is used both by the client and sensor side. On client side, seqnr is used as key, while on sensor side, a session_id is used - as key. This ensures that the two will not collide, so + as key. This ensures that the two will not collide, so one instance can be both client and sensor. Sensor side ----------- @@ -89,12 +88,12 @@ class XEP_0323(BasePlugin): Sensor side ----------- - register_node -- Register a sensor as available from this XMPP + register_node -- Register a sensor as available from this XMPP instance. Client side ----------- - request_data -- Initiates a request for data from one or more + request_data -- Initiates a request for data from one or more sensors. Non-blocking, a callback function will be called when data is available. @@ -102,13 +101,12 @@ class XEP_0323(BasePlugin): name = 'xep_0323' description = 'XEP-0323 Internet of Things - Sensor Data' - dependencies = set(['xep_0030']) + dependencies = set(['xep_0030']) stanza = stanza default_config = { 'threaded': True -# 'session_db': None } def plugin_init(self): @@ -155,17 +153,17 @@ class XEP_0323(BasePlugin): self._handle_event_started)) # Server side dicts - self.nodes = {}; - self.sessions = {}; + self.nodes = {} + self.sessions = {} - self.last_seqnr = 0; - self.seqnr_lock = Lock(); + 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()) @@ -182,7 +180,7 @@ class XEP_0323(BasePlugin): def plugin_end(self): """ Stop the XEP-0323 plugin """ - self.sessions.clear(); + self.sessions.clear() self.xmpp.remove_handler('Sensordata Event:Req') self.xmpp.remove_handler('Sensordata Event:Accepted') self.xmpp.remove_handler('Sensordata Event:Rejected') @@ -198,9 +196,9 @@ class XEP_0323(BasePlugin): def register_node(self, nodeId, device, commTimeout, sourceId=None, cacheType=None): """ Register a sensor/device as available for serving of data through this XMPP - instance. + instance. - The device object may by any custom implementation to support + The device object may by any custom implementation to support specific devices, but it must implement the functions: has_field request_fields @@ -212,25 +210,25 @@ class XEP_0323(BasePlugin): commTimeout -- Time in seconds to wait between each callback from device during a data readout. Float. sourceId -- [optional] identifying the data source controlling the device - cacheType -- [optional] narrowing down the search to a specific kind of node + cacheType -- [optional] narrowing down the search to a specific kind of node """ - self.nodes[nodeId] = {"device": device, + self.nodes[nodeId] = {"device": device, "commTimeout": commTimeout, - "sourceId": sourceId, - "cacheType": cacheType}; + "sourceId": sourceId, + "cacheType": cacheType} def _set_authenticated(self, auth=''): """ Internal testing function """ - self.test_authenticated_from = auth; + self.test_authenticated_from = auth def _handle_event_req(self, iq): """ Event handler for reception of an Iq with req - this is a request. - Verifies that + Verifies that - all the requested nodes are available - - at least one of the requested fields is available from at least + - at least one of the requested fields is available from at least one of the nodes If the request passes verification, an accept response is sent, and @@ -238,42 +236,42 @@ class XEP_0323(BasePlugin): If the verification fails, a reject message is sent. """ - seqnr = iq['req']['seqnr']; - error_msg = ''; - req_ok = True; + seqnr = iq['req']['seqnr'] + error_msg = '' + req_ok = True # Authentication if len(self.test_authenticated_from) > 0 and not iq['from'] == self.test_authenticated_from: # Invalid authentication - req_ok = False; - error_msg = "Access denied"; + req_ok = False + error_msg = "Access denied" # Nodes - process_nodes = []; + process_nodes = [] if len(iq['req']['nodes']) > 0: for n in iq['req']['nodes']: if not n['nodeId'] in self.nodes: - req_ok = False; - error_msg = "Invalid nodeId " + n['nodeId']; - process_nodes = [n['nodeId'] for n in iq['req']['nodes']]; + req_ok = False + error_msg = "Invalid nodeId " + n['nodeId'] + process_nodes = [n['nodeId'] for n in iq['req']['nodes']] else: - process_nodes = self.nodes.keys(); + process_nodes = self.nodes.keys() # Fields - if we just find one we are happy, otherwise we reject - process_fields = []; + process_fields = [] if len(iq['req']['fields']) > 0: found = False for f in iq['req']['fields']: for node in self.nodes: if self.nodes[node]["device"].has_field(f['name']): - found = True; - break; + found = True + break if not found: - req_ok = False; - error_msg = "Invalid field " + f['name']; - process_fields = [f['name'] for n in iq['req']['fields']]; + req_ok = False + error_msg = "Invalid field " + f['name'] + process_fields = [f['name'] for n in iq['req']['fields']] - req_flags = iq['req']._get_flags(); + req_flags = iq['req']._get_flags() request_delay_sec = None if 'when' in req_flags: @@ -283,7 +281,7 @@ class XEP_0323(BasePlugin): try: dt = datetime.datetime.strptime(req_flags['when'], "%Y-%m-%dT%H:%M:%S") except ValueError: - req_ok = False; + req_ok = False error_msg = "Invalid datetime in 'when' flag, please use ISO format (i.e. 2013-04-05T15:00:03)." if not dt is None: @@ -292,51 +290,47 @@ class XEP_0323(BasePlugin): dtdiff = dt - dtnow request_delay_sec = dtdiff.seconds + dtdiff.days * 24 * 3600 if request_delay_sec <= 0: - req_ok = False; - error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past. Current time: " + dtnow.isoformat(); + req_ok = False + error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past. Current time: " + dtnow.isoformat() if req_ok: - session = self._new_session(); - self.sessions[session] = {"from": iq['from'], "to": iq['to'], "seqnr": seqnr}; - self.sessions[session]["commTimers"] = {}; - self.sessions[session]["nodeDone"] = {}; + session = self._new_session() + self.sessions[session] = {"from": iq['from'], "to": iq['to'], "seqnr": seqnr} + self.sessions[session]["commTimers"] = {} + self.sessions[session]["nodeDone"] = {} - #print("added session: " + str(self.sessions)) - - iq.reply(); - iq['accepted']['seqnr'] = seqnr; + iq.reply() + iq['accepted']['seqnr'] = seqnr if not request_delay_sec is None: iq['accepted']['queued'] = "true" - iq.send(block=False); + iq.send(block=False) - self.sessions[session]["node_list"] = process_nodes; + self.sessions[session]["node_list"] = process_nodes if not request_delay_sec is None: # Delay request to requested time timer = Timer(request_delay_sec, self._event_delayed_req, args=(session, process_fields, req_flags)) - self.sessions[session]["commTimers"]["delaytimer"] = timer; - timer.start(); + self.sessions[session]["commTimers"]["delaytimer"] = timer + timer.start() 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); + self._threaded_node_request(session, process_fields, req_flags) else: - iq.reply(); - iq['type'] = 'error'; - iq['rejected']['seqnr'] = seqnr; - iq['rejected']['error'] = error_msg; - iq.send(block=False); + iq.reply() + iq['type'] = 'error' + iq['rejected']['seqnr'] = seqnr + iq['rejected']['error'] = error_msg + iq.send(block=False) def _threaded_node_request(self, session, process_fields, flags): - """ + """ Helper function to handle the device readouts in a separate thread. - + Arguments: session -- The request session id process_fields -- The fields to request from the devices @@ -344,41 +338,39 @@ class XEP_0323(BasePlugin): Formatted as a dictionary like { "flag name": "flag value" ... } """ for node in self.sessions[session]["node_list"]: - self.sessions[session]["nodeDone"][node] = False; + self.sessions[session]["nodeDone"][node] = False 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); + timer = TimerReset(self.nodes[node]['commTimeout'], self._event_comm_timeout, args=(session, node)) + self.sessions[session]["commTimers"][node] = timer + timer.start() + self.nodes[node]['device'].request_fields(process_fields, flags=flags, session=session, callback=self._device_field_request_callback) def _event_comm_timeout(self, session, nodeId): - """ + """ Triggered if any of the readout operations timeout. Sends a failure message back to the client, stops communicating with the failing device. - + Arguments: session -- The request session id nodeId -- The id of the device which timed out """ - msg = self.xmpp.Message(); - msg['from'] = self.sessions[session]['to']; - msg['to'] = self.sessions[session]['from']; - msg['failure']['seqnr'] = self.sessions[session]['seqnr']; - msg['failure']['error']['text'] = "Timeout"; - msg['failure']['error']['nodeId'] = nodeId; - msg['failure']['error']['timestamp'] = datetime.datetime.now().replace(microsecond=0).isoformat(); + msg = self.xmpp.Message() + msg['from'] = self.sessions[session]['to'] + msg['to'] = self.sessions[session]['from'] + msg['failure']['seqnr'] = self.sessions[session]['seqnr'] + msg['failure']['error']['text'] = "Timeout" + msg['failure']['error']['nodeId'] = nodeId + msg['failure']['error']['timestamp'] = datetime.datetime.now().replace(microsecond=0).isoformat() # Drop communication with this device and check if we are done - self.sessions[session]["nodeDone"][nodeId] = True; + self.sessions[session]["nodeDone"][nodeId] = True if (self._all_nodes_done(session)): - msg['failure']['done'] = 'true'; - msg.send(); + msg['failure']['done'] = 'true' + msg.send() # The session is complete, delete it - #print("del session " + session + " due to timeout") - del self.sessions[session]; + del self.sessions[session] def _event_delayed_req(self, session, process_fields, req_flags): """ @@ -390,47 +382,47 @@ class XEP_0323(BasePlugin): flags -- [optional] flags to pass to the devices, e.g. momentary Formatted as a dictionary like { "flag name": "flag value" ... } """ - msg = self.xmpp.Message(); - msg['from'] = self.sessions[session]['to']; - msg['to'] = self.sessions[session]['from']; - msg['started']['seqnr'] = self.sessions[session]['seqnr']; - msg.send(); + msg = self.xmpp.Message() + msg['from'] = self.sessions[session]['to'] + msg['to'] = self.sessions[session]['from'] + msg['started']['seqnr'] = self.sessions[session]['seqnr'] + msg.send() if self.threaded: tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields, req_flags)) tr_req.start() else: - self._threaded_node_request(session, process_fields, req_flags); + self._threaded_node_request(session, process_fields, req_flags) 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 """ for n in self.sessions[session]["nodeDone"]: if not self.sessions[session]["nodeDone"][n]: - return False; - return True; + return False + return True def _device_field_request_callback(self, session, nodeId, result, timestamp_block, error_msg=None): - """ + """ Callback function called by the devices when they have any additional data. - Composes a message with the data and sends it back to the client, and resets + Composes a message with the data and sends it back to the client, and resets the timeout timer for the device. - + Arguments: session -- The request session id nodeId -- The device id which initiated the callback result -- The current result status of the readout. Valid values are: "error" - Readout failed. "fields" - Contains readout data. - "done" - Indicates that the readout is complete. May contain + "done" - Indicates that the readout is complete. May contain readout data. - timestamp_block -- [optional] Only applies when result != "error" + timestamp_block -- [optional] Only applies when result != "error" The readout data. Structured as a dictionary: - { + { timestamp: timestamp for this datablock, fields: list of field dictionary (one per readout field). readout field dictionary format: @@ -442,109 +434,107 @@ class XEP_0323(BasePlugin): dataType: The datatype of the field. Only applies to type enum. flags: [optional] data classifier flags for the field, e.g. momentary Formatted as a dictionary like { "flag name": "flag value" ... } - } + } } error_msg -- [optional] Only applies when result == "error". 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": - self.sessions[session]["commTimers"][nodeId].cancel(); + self.sessions[session]["commTimers"][nodeId].cancel() - msg = self.xmpp.Message(); - msg['from'] = self.sessions[session]['to']; - msg['to'] = self.sessions[session]['from']; - msg['failure']['seqnr'] = self.sessions[session]['seqnr']; - msg['failure']['error']['text'] = error_msg; - msg['failure']['error']['nodeId'] = nodeId; - msg['failure']['error']['timestamp'] = datetime.datetime.now().replace(microsecond=0).isoformat(); + msg = self.xmpp.Message() + msg['from'] = self.sessions[session]['to'] + msg['to'] = self.sessions[session]['from'] + msg['failure']['seqnr'] = self.sessions[session]['seqnr'] + msg['failure']['error']['text'] = error_msg + msg['failure']['error']['nodeId'] = nodeId + msg['failure']['error']['timestamp'] = datetime.datetime.now().replace(microsecond=0).isoformat() # Drop communication with this device and check if we are done - self.sessions[session]["nodeDone"][nodeId] = True; + self.sessions[session]["nodeDone"][nodeId] = True if (self._all_nodes_done(session)): - msg['failure']['done'] = 'true'; + msg['failure']['done'] = 'true' # The session is complete, delete it - # print("del session " + session + " due to error") - del self.sessions[session]; - msg.send(); + del self.sessions[session] + msg.send() else: - msg = self.xmpp.Message(); - msg['from'] = self.sessions[session]['to']; - msg['to'] = self.sessions[session]['from']; - msg['fields']['seqnr'] = self.sessions[session]['seqnr']; + msg = self.xmpp.Message() + msg['from'] = self.sessions[session]['to'] + msg['to'] = self.sessions[session]['from'] + msg['fields']['seqnr'] = self.sessions[session]['seqnr'] if timestamp_block is not None and len(timestamp_block) > 0: - node = msg['fields'].add_node(nodeId); - ts = node.add_timestamp(timestamp_block["timestamp"]); + node = msg['fields'].add_node(nodeId) + ts = node.add_timestamp(timestamp_block["timestamp"]) for f in timestamp_block["fields"]: - data = ts.add_data( typename=f['type'], - name=f['name'], - value=f['value'], - unit=f['unit'], - dataType=f['dataType'], - flags=f['flags']); + data = ts.add_data( typename=f['type'], + name=f['name'], + value=f['value'], + unit=f['unit'], + dataType=f['dataType'], + flags=f['flags']) if result == "done": - self.sessions[session]["commTimers"][nodeId].cancel(); - self.sessions[session]["nodeDone"][nodeId] = True; - msg['fields']['done'] = 'true'; + self.sessions[session]["commTimers"][nodeId].cancel() + self.sessions[session]["nodeDone"][nodeId] = True if (self._all_nodes_done(session)): # The session is complete, delete it - # print("del session " + session + " due to complete") - del self.sessions[session]; + del self.sessions[session] + msg['fields']['done'] = 'true' else: # Restart comm timer - self.sessions[session]["commTimers"][nodeId].reset(); + self.sessions[session]["commTimers"][nodeId].reset() - msg.send(); + msg.send() def _handle_event_cancel(self, iq): - """ Received Iq with cancel - this is a cancel request. + """ Received Iq with cancel - this is a cancel request. Delete the session and confirm. """ - seqnr = iq['cancel']['seqnr']; + seqnr = iq['cancel']['seqnr'] # Find the session for s in self.sessions: if self.sessions[s]['from'] == iq['from'] and self.sessions[s]['to'] == iq['to'] and self.sessions[s]['seqnr'] == seqnr: # found it. Cancel all timers for n in self.sessions[s]["commTimers"]: - self.sessions[s]["commTimers"][n].cancel(); + self.sessions[s]["commTimers"][n].cancel() # Confirm - iq.reply(); - iq['type'] = 'result'; - iq['cancelled']['seqnr'] = seqnr; - iq.send(block=False); - + iq.reply() + iq['type'] = 'result' + iq['cancelled']['seqnr'] = seqnr + iq.send(block=False) + # Delete session del self.sessions[s] return # Could not find session, send reject - iq.reply(); - iq['type'] = 'error'; - iq['rejected']['seqnr'] = seqnr; - iq['rejected']['error'] = "Cancel request received, no matching request is active."; - iq.send(block=False); + iq.reply() + iq['type'] = 'error' + iq['rejected']['seqnr'] = seqnr + iq['rejected']['error'] = "Cancel request received, no matching request is active." + iq.send(block=False) - # ================================================================= + # ================================================================= # 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: from_jid -- The jid of the responding device(s) @@ -565,7 +555,7 @@ class XEP_0323(BasePlugin): The timestamp of data in this callback. One callback will only contain data from one timestamp. fields -- [optional] Mandatory when result == "fields". - List of field dictionaries representing the readout data. + List of field dictionaries representing the readout data. Dictionary format: { typename: The field type (numeric, boolean, dateTime, timeSpan, string, enum) @@ -575,11 +565,11 @@ class XEP_0323(BasePlugin): dataType: The datatype of the field. Only applies to type enum. flags: [optional] data classifier flags for the field, e.g. momentary. Formatted as a dictionary like { "flag name": "flag value" ... } - } + } error_msg -- [optional] Mandatory when result == "rejected" or "failure". - Details about why the request is rejected or failed. - "rejected" means that the request is stopped, but note that the + Details about why the request is rejected or failed. + "rejected" means that the request is stopped, but note that the request will continue even after a "failure". "failure" only means that communication was stopped to that specific device, other device(s) (if any) will continue their readout. @@ -593,131 +583,130 @@ class XEP_0323(BasePlugin): session -- Session identifier. Client can use this as a reference to cancel the request. """ - iq = self.xmpp.Iq(); - iq['from'] = from_jid; - iq['to'] = to_jid; - iq['type'] = "get"; - seqnr = self._get_new_seqnr(); - iq['id'] = seqnr; - iq['req']['seqnr'] = seqnr; + iq = self.xmpp.Iq() + iq['from'] = from_jid + iq['to'] = to_jid + iq['type'] = "get" + seqnr = self._get_new_seqnr() + iq['id'] = seqnr + iq['req']['seqnr'] = seqnr if nodeIds is not None: for nodeId in nodeIds: - iq['req'].add_node(nodeId); + iq['req'].add_node(nodeId) if fields is not None: for field in fields: - iq['req'].add_field(field); + iq['req'].add_field(field) - iq['req']._set_flags(flags); + iq['req']._set_flags(flags) - self.sessions[seqnr] = {"from": iq['from'], "to": iq['to'], "seqnr": seqnr, "callback": callback}; - iq.send(block=False); + self.sessions[seqnr] = {"from": iq['from'], "to": iq['to'], "seqnr": seqnr, "callback": callback} + iq.send(block=False) - return seqnr; + return seqnr def cancel_request(self, session): - """ + """ Called on the client side to cancel a request for data readout. Composes a message with the cancellation and sends it to the device(s). - Does not block, the callback will be called when cancellation is + Does not block, the callback will be called when cancellation is confirmed. - + Arguments: session -- The session id of the request to cancel """ seqnr = session - iq = self.xmpp.Iq(); + iq = self.xmpp.Iq() iq['from'] = self.sessions[seqnr]['from'] - iq['to'] = self.sessions[seqnr]['to']; - iq['type'] = "get"; - iq['id'] = seqnr; - iq['cancel']['seqnr'] = seqnr; - iq.send(block=False); + iq['to'] = self.sessions[seqnr]['to'] + iq['type'] = "get" + iq['id'] = seqnr + iq['cancel']['seqnr'] = seqnr + iq.send(block=False) 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.seqnr_lock.release(); - return str(self.last_seqnr); + self.seqnr_lock.acquire() + self.last_seqnr += 1 + self.seqnr_lock.release() + return str(self.last_seqnr) def _handle_event_accepted(self, iq): """ Received Iq with accepted - request was accepted """ - seqnr = iq['accepted']['seqnr']; + seqnr = iq['accepted']['seqnr'] result = "accepted" if iq['accepted']['queued'] == 'true': result = "queued" - callback = self.sessions[seqnr]["callback"]; - callback(from_jid=iq['from'], result=result); + callback = self.sessions[seqnr]["callback"] + callback(from_jid=iq['from'], result=result) def _handle_event_rejected(self, iq): - """ Received Iq with rejected - this is a reject. + """ Received Iq with rejected - this is a reject. Delete the session. """ - seqnr = iq['rejected']['seqnr']; - callback = self.sessions[seqnr]["callback"]; - callback(from_jid=iq['from'], result="rejected", error_msg=iq['rejected']['error']); + seqnr = iq['rejected']['seqnr'] + callback = self.sessions[seqnr]["callback"] + callback(from_jid=iq['from'], result="rejected", error_msg=iq['rejected']['error']) # Session terminated - del self.sessions[seqnr]; + del self.sessions[seqnr] def _handle_event_cancelled(self, iq): - """ - 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"); + Received Iq with cancelled - this is a cancel confirm. + Delete the session. + """ + seqnr = iq['cancelled']['seqnr'] + callback = self.sessions[seqnr]["callback"] + callback(from_jid=iq['from'], result="cancelled") # Session cancelled - del self.sessions[seqnr]; + del self.sessions[seqnr] 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']; - callback = self.sessions[seqnr]["callback"]; + seqnr = msg['fields']['seqnr'] + callback = self.sessions[seqnr]["callback"] for node in msg['fields']['nodes']: for ts in node['timestamps']: - fields = []; + fields = [] for d in ts['datas']: - field_block = {}; - field_block["name"] = d['name']; - field_block["typename"] = d._get_typename(); - field_block["value"] = d['value']; + field_block = {} + field_block["name"] = d['name'] + field_block["typename"] = d._get_typename() + field_block["value"] = d['value'] if not d['unit'] == "": field_block["unit"] = d['unit']; if not d['dataType'] == "": field_block["dataType"] = d['dataType']; - flags = d._get_flags(); + flags = d._get_flags() if not len(flags) == 0: - field_block["flags"] = flags; - fields.append(field_block); + field_block["flags"] = flags + fields.append(field_block) + + callback(from_jid=msg['from'], result="fields", nodeId=node['nodeId'], timestamp=ts['value'], fields=fields) - callback(from_jid=msg['from'], result="fields", nodeId=node['nodeId'], timestamp=ts['value'], fields=fields); - if msg['fields']['done'] == "true": - callback(from_jid=msg['from'], result="done"); + callback(from_jid=msg['from'], result="done") # Session done - del self.sessions[seqnr]; + del self.sessions[seqnr] def _handle_event_failure(self, msg): - """ + """ Received Msg with failure - our request failed - Delete the session. + Delete the session. """ - seqnr = msg['failure']['seqnr']; - callback = self.sessions[seqnr]["callback"]; - callback(from_jid=msg['from'], result="failure", nodeId=msg['failure']['error']['nodeId'], timestamp=msg['failure']['error']['timestamp'], error_msg=msg['failure']['error']['text']); + seqnr = msg['failure']['seqnr'] + callback = self.sessions[seqnr]["callback"] + callback(from_jid=msg['from'], result="failure", nodeId=msg['failure']['error']['nodeId'], timestamp=msg['failure']['error']['timestamp'], error_msg=msg['failure']['error']['text']) # Session failed - del self.sessions[seqnr]; + del self.sessions[seqnr] def _handle_event_started(self, msg): - """ - Received Msg with started - our request was queued and is now started. """ - seqnr = msg['started']['seqnr']; - callback = self.sessions[seqnr]["callback"]; - callback(from_jid=msg['from'], result="started"); - + Received Msg with started - our request was queued and is now started. + """ + seqnr = msg['started']['seqnr'] + callback = self.sessions[seqnr]["callback"] + callback(from_jid=msg['from'], result="started") + diff --git a/sleekxmpp/plugins/xep_0323/stanza/sensordata.py b/sleekxmpp/plugins/xep_0323/stanza/sensordata.py index a11c3e94..e8718161 100644 --- a/sleekxmpp/plugins/xep_0323/stanza/sensordata.py +++ b/sleekxmpp/plugins/xep_0323/stanza/sensordata.py @@ -20,14 +20,14 @@ class Sensordata(ElementBase): interfaces = set(tuple()) class FieldTypes(): - """ + """ All field types are optional booleans that default to False """ field_types = set([ 'momentary','peak','status','computed','identity','historicalSecond','historicalMinute','historicalHour', \ 'historicalDay','historicalWeek','historicalMonth','historicalQuarter','historicalYear','historicalOther']) class FieldStatus(): - """ + """ All field statuses are optional booleans that default to False """ field_status = set([ 'missing','automaticEstimate','manualEstimate','manualReadout','automaticReadout','timeOffset','warning','error', \ @@ -38,12 +38,12 @@ class Request(ElementBase): name = 'req' plugin_attrib = name interfaces = set(['seqnr','nodes','fields','serviceToken','deviceToken','userToken','from','to','when','historical','all']) - interfaces.update(FieldTypes.field_types); - _flags = set(['serviceToken','deviceToken','userToken','from','to','when','historical','all']); - _flags.update(FieldTypes.field_types); - + interfaces.update(FieldTypes.field_types) + _flags = set(['serviceToken','deviceToken','userToken','from','to','when','historical','all']) + _flags.update(FieldTypes.field_types) + def __init__(self, xml=None, parent=None): - ElementBase.__init__(self, xml, parent); + ElementBase.__init__(self, xml, parent) self._nodes = set() self._fields = set() @@ -64,27 +64,27 @@ class Request(ElementBase): def _get_flags(self): """ - Helper function for getting of flags. Returns all flags in - dictionary format: { "flag name": "flag value" ... } + Helper function for getting of flags. Returns all flags in + dictionary format: { "flag name": "flag value" ... } """ - flags = {}; + flags = {} for f in self._flags: if not self[f] == "": - flags[f] = self[f]; - return flags; + flags[f] = self[f] + return flags def _set_flags(self, flags): """ - Helper function for setting of flags. + Helper function for setting of flags. Arguments: - flags -- Flags in dictionary format: { "flag name": "flag value" ... } + flags -- Flags in dictionary format: { "flag name": "flag value" ... } """ for f in self._flags: if flags is not None and f in flags: - self[f] = flags[f]; + self[f] = flags[f] else: - self[f] = None; + self[f] = None def add_node(self, nodeId, sourceId=None, cacheType=None): """ @@ -94,7 +94,7 @@ class Request(ElementBase): Arguments: nodeId -- The ID for the node. sourceId -- [optional] identifying the data source controlling the device - cacheType -- [optional] narrowing down the search to a specific kind of node + cacheType -- [optional] narrowing down the search to a specific kind of node """ if nodeId not in self._nodes: self._nodes.add((nodeId)) @@ -269,7 +269,7 @@ class Error(ElementBase): :param value: string """ - self.xml.text = value; + self.xml.text = value return self def del_text(self): @@ -292,7 +292,7 @@ class Fields(ElementBase): interfaces = set(['seqnr','done','nodes']) def __init__(self, xml=None, parent=None): - ElementBase.__init__(self, xml, parent); + ElementBase.__init__(self, xml, parent) self._nodes = set() def setup(self, xml=None): @@ -318,7 +318,7 @@ class Fields(ElementBase): Arguments: nodeId -- The ID for the node. sourceId -- [optional] identifying the data source controlling the device - cacheType -- [optional] narrowing down the search to a specific kind of node + cacheType -- [optional] narrowing down the search to a specific kind of node """ if nodeId not in self._nodes: self._nodes.add((nodeId)) @@ -392,7 +392,7 @@ class FieldsNode(ElementBase): interfaces = set(['nodeId','sourceId','cacheType','timestamps']) def __init__(self, xml=None, parent=None): - ElementBase.__init__(self, xml, parent); + ElementBase.__init__(self, xml, parent) self._timestamps = set() def setup(self, xml=None): @@ -411,7 +411,7 @@ class FieldsNode(ElementBase): def add_timestamp(self, timestamp, substanzas=None): """ - Add a new timestamp element. + Add a new timestamp element. Arguments: timestamp -- The timestamp in ISO format. @@ -423,7 +423,7 @@ class FieldsNode(ElementBase): ts = Timestamp(parent=self) ts['value'] = timestamp if not substanzas is None: - ts.set_datas(substanzas); + ts.set_datas(substanzas) #print("add_timestamp with substanzas: " + str(substanzas)) self.iterables.append(ts) #print(str(id(self)) + " added_timestamp: " + str(id(ts))) @@ -485,7 +485,7 @@ class FieldsNode(ElementBase): self.iterables.remove(timestamp) class Field(ElementBase): - """ + """ Field element in response Timestamp. This is a base class, all instances of fields added to Timestamp must be of types: DataNumeric @@ -494,17 +494,17 @@ class Field(ElementBase): DataDateTime DataTimeSpan DataEnum - """ + """ namespace = 'urn:xmpp:iot:sensordata' name = 'field' plugin_attrib = name - interfaces = set(['name','module','stringIds']); - interfaces.update(FieldTypes.field_types); - interfaces.update(FieldStatus.field_status); + interfaces = set(['name','module','stringIds']) + interfaces.update(FieldTypes.field_types) + interfaces.update(FieldStatus.field_status) - _flags = set(); - _flags.update(FieldTypes.field_types); - _flags.update(FieldStatus.field_status); + _flags = set() + _flags.update(FieldTypes.field_types) + _flags.update(FieldStatus.field_status) def set_stringIds(self, value): """Verifies stringIds according to regexp from specification XMPP-0323. @@ -514,7 +514,7 @@ class Field(ElementBase): pattern = re.compile("^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$") if pattern.match(value) is not None: - self.xml.stringIds = value; + self.xml.stringIds = value else: # Bad content, add nothing pass @@ -523,30 +523,30 @@ class Field(ElementBase): def _get_flags(self): """ - Helper function for getting of flags. Returns all flags in - dictionary format: { "flag name": "flag value" ... } + Helper function for getting of flags. Returns all flags in + dictionary format: { "flag name": "flag value" ... } """ - flags = {}; + flags = {} for f in self._flags: if not self[f] == "": - flags[f] = self[f]; - return flags; + flags[f] = self[f] + return flags def _set_flags(self, flags): """ - Helper function for setting of flags. + Helper function for setting of flags. Arguments: - flags -- Flags in dictionary format: { "flag name": "flag value" ... } + flags -- Flags in dictionary format: { "flag name": "flag value" ... } """ for f in self._flags: if flags is not None and f in flags: - self[f] = flags[f]; + self[f] = flags[f] else: - self[f] = None; + self[f] = None def _get_typename(self): - return "invalid type, use subclasses!"; + return "invalid type, use subclasses!" class Timestamp(ElementBase): @@ -557,7 +557,7 @@ class Timestamp(ElementBase): interfaces = set(['value','datas']) def __init__(self, xml=None, parent=None): - ElementBase.__init__(self, xml, parent); + ElementBase.__init__(self, xml, parent) self._datas = set() def setup(self, xml=None): @@ -576,7 +576,7 @@ class Timestamp(ElementBase): def add_data(self, typename, name, value, module=None, stringIds=None, unit=None, dataType=None, flags=None): """ - Add a new data element. + Add a new data element. Arguments: typename -- The type of data element (numeric, string, boolean, dateTime, timeSpan or enum) @@ -587,29 +587,29 @@ class Timestamp(ElementBase): dataType -- [optional] The dataType. Only applicable for type enum """ if name not in self._datas: - dataObj = None; + dataObj = None if typename == "numeric": - dataObj = DataNumeric(parent=self); - dataObj['unit'] = unit; + dataObj = DataNumeric(parent=self) + dataObj['unit'] = unit elif typename == "string": - dataObj = DataString(parent=self); + dataObj = DataString(parent=self) elif typename == "boolean": - dataObj = DataBoolean(parent=self); + dataObj = DataBoolean(parent=self) elif typename == "dateTime": - dataObj = DataDateTime(parent=self); + dataObj = DataDateTime(parent=self) elif typename == "timeSpan": - dataObj = DataTimeSpan(parent=self); + dataObj = DataTimeSpan(parent=self) elif typename == "enum": - dataObj = DataEnum(parent=self); - dataObj['dataType'] = dataType; + dataObj = DataEnum(parent=self) + dataObj['dataType'] = dataType - dataObj['name'] = name; - dataObj['value'] = value; - dataObj['module'] = module; - dataObj['stringIds'] = stringIds; + dataObj['name'] = name + dataObj['value'] = value + dataObj['module'] = module + dataObj['stringIds'] = stringIds if flags is not None: - dataObj._set_flags(flags); + dataObj._set_flags(flags) self._datas.add(name) self.iterables.append(dataObj) @@ -661,87 +661,87 @@ class Timestamp(ElementBase): self.iterables.remove(data) class DataNumeric(Field): - """ - Field data of type numeric. - Note that the value is expressed as a string. + """ + Field data of type numeric. + Note that the value is expressed as a string. """ namespace = 'urn:xmpp:iot:sensordata' name = 'numeric' plugin_attrib = name - interfaces = set(['value', 'unit']); - interfaces.update(Field.interfaces); + interfaces = set(['value', 'unit']) + interfaces.update(Field.interfaces) def _get_typename(self): - return "numeric" + return "numeric" class DataString(Field): - """ - Field data of type string + """ + Field data of type string """ namespace = 'urn:xmpp:iot:sensordata' name = 'string' plugin_attrib = name - interfaces = set(['value']); - interfaces.update(Field.interfaces); + interfaces = set(['value']) + interfaces.update(Field.interfaces) def _get_typename(self): - return "string" + return "string" class DataBoolean(Field): - """ + """ Field data of type boolean. - Note that the value is expressed as a string. + Note that the value is expressed as a string. """ namespace = 'urn:xmpp:iot:sensordata' name = 'boolean' plugin_attrib = name - interfaces = set(['value']); - interfaces.update(Field.interfaces); + interfaces = set(['value']) + interfaces.update(Field.interfaces) def _get_typename(self): - return "boolean" + return "boolean" class DataDateTime(Field): - """ + """ Field data of type dateTime. - Note that the value is expressed as a string. + Note that the value is expressed as a string. """ namespace = 'urn:xmpp:iot:sensordata' name = 'dateTime' plugin_attrib = name - interfaces = set(['value']); - interfaces.update(Field.interfaces); + interfaces = set(['value']) + interfaces.update(Field.interfaces) def _get_typename(self): - return "dateTime" + return "dateTime" class DataTimeSpan(Field): - """ + """ Field data of type timeSpan. - Note that the value is expressed as a string. + Note that the value is expressed as a string. """ namespace = 'urn:xmpp:iot:sensordata' name = 'timeSpan' plugin_attrib = name - interfaces = set(['value']); - interfaces.update(Field.interfaces); + interfaces = set(['value']) + interfaces.update(Field.interfaces) def _get_typename(self): - return "timeSpan" + return "timeSpan" class DataEnum(Field): - """ + """ Field data of type enum. - Note that the value is expressed as a string. + Note that the value is expressed as a string. """ namespace = 'urn:xmpp:iot:sensordata' name = 'enum' plugin_attrib = name - interfaces = set(['value', 'dataType']); - interfaces.update(Field.interfaces); + interfaces = set(['value', 'dataType']) + interfaces.update(Field.interfaces) def _get_typename(self): - return "enum" + return "enum" class Done(ElementBase): """ Done element used to signal that all data has been transferred """ diff --git a/sleekxmpp/plugins/xep_0323/timerreset.py b/sleekxmpp/plugins/xep_0323/timerreset.py index 578f1efe..398b47c1 100644 --- a/sleekxmpp/plugins/xep_0323/timerreset.py +++ b/sleekxmpp/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/sleekxmpp/plugins/xep_0325/control.py b/sleekxmpp/plugins/xep_0325/control.py index e34eb2c2..11e7a045 100644 --- a/sleekxmpp/plugins/xep_0325/control.py +++ b/sleekxmpp/plugins/xep_0325/control.py @@ -12,7 +12,6 @@ import logging import time from threading import Thread, Timer, Lock -from sleekxmpp.xmlstream import JID from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.matcher import StanzaPath from sleekxmpp.plugins.base import BasePlugin @@ -26,16 +25,16 @@ log = logging.getLogger(__name__) class XEP_0325(BasePlugin): """ - XEP-0325: IoT Control + XEP-0325: IoT Control - Actuators are devices in sensor networks that can be controlled through - the network and act with the outside world. In sensor networks and - Internet of Things applications, actuators make it possible to automate - real-world processes. - This plugin implements a mechanism whereby actuators can be controlled - in XMPP-based sensor networks, making it possible to integrate sensors - and actuators of different brands, makes and models into larger + Actuators are devices in sensor networks that can be controlled through + the network and act with the outside world. In sensor networks and + Internet of Things applications, actuators make it possible to automate + real-world processes. + This plugin implements a mechanism whereby actuators can be controlled + in XMPP-based sensor networks, making it possible to integrate sensors + and actuators of different brands, makes and models into larger Internet of Things applications. Also see <http://xmpp.org/extensions/xep-0325.html> @@ -52,9 +51,9 @@ class XEP_0325(BasePlugin): Client side ----------- - Control Event:SetResponse -- Received a response to a + Control Event:SetResponse -- Received a response to a control request, type result - Control Event:SetResponseError -- Received a response to a + Control Event:SetResponseError -- Received a response to a control request, type error Attributes: @@ -65,7 +64,7 @@ class XEP_0325(BasePlugin): relevant to a request's session. This dictionary is used both by the client and sensor side. On client side, seqnr is used as key, while on sensor side, a session_id is used - as key. This ensures that the two will not collide, so + as key. This ensures that the two will not collide, so one instance can be both client and sensor. Sensor side ----------- @@ -85,15 +84,15 @@ class XEP_0325(BasePlugin): Sensor side ----------- - register_node -- Register a sensor as available from this XMPP + register_node -- Register a sensor as available from this XMPP instance. Client side ----------- - set_request -- Initiates a control request to modify data in + set_request -- Initiates a control request to modify data in sensor(s). Non-blocking, a callback function will be called when the sensor has responded. - set_command -- Initiates a control command to modify data in + set_command -- Initiates a control command to modify data in sensor(s). Non-blocking. The sensor(s) will not respond regardless of the result of the command, so no callback is made. @@ -102,7 +101,7 @@ class XEP_0325(BasePlugin): name = 'xep_0325' description = 'XEP-0325 Internet of Things - Control' - dependencies = set(['xep_0030']) + dependencies = set(['xep_0030']) stanza = stanza @@ -135,11 +134,11 @@ class XEP_0325(BasePlugin): self._handle_set_response)) # Server side dicts - self.nodes = {}; - self.sessions = {}; + self.nodes = {} + self.sessions = {} - self.last_seqnr = 0; - self.seqnr_lock = Lock(); + self.last_seqnr = 0 + self.seqnr_lock = Lock() ## For testning only self.test_authenticated_from = "" @@ -156,13 +155,13 @@ class XEP_0325(BasePlugin): def plugin_end(self): """ Stop the XEP-0325 plugin """ - self.sessions.clear(); + self.sessions.clear() self.xmpp.remove_handler('Control Event:DirectSet') self.xmpp.remove_handler('Control Event:SetReq') self.xmpp.remove_handler('Control Event:SetResponse') self.xmpp.remove_handler('Control Event:SetResponseError') self.xmpp['xep_0030'].del_feature(feature=Control.namespace) - self.xmpp['xep_0030'].set_items(node=Control.namespace, items=tuple()); + self.xmpp['xep_0030'].set_items(node=Control.namespace, items=tuple()) # ================================================================= @@ -170,10 +169,10 @@ class XEP_0325(BasePlugin): def register_node(self, nodeId, device, commTimeout, sourceId=None, cacheType=None): """ - Register a sensor/device as available for control requests/commands - through this XMPP instance. + Register a sensor/device as available for control requests/commands + through this XMPP instance. - The device object may by any custom implementation to support + The device object may by any custom implementation to support specific devices, but it must implement the functions: has_control_field set_control_fields @@ -185,30 +184,30 @@ class XEP_0325(BasePlugin): commTimeout -- Time in seconds to wait between each callback from device during a data readout. Float. sourceId -- [optional] identifying the data source controlling the device - cacheType -- [optional] narrowing down the search to a specific kind of node + cacheType -- [optional] narrowing down the search to a specific kind of node """ - self.nodes[nodeId] = {"device": device, + self.nodes[nodeId] = {"device": device, "commTimeout": commTimeout, - "sourceId": sourceId, - "cacheType": cacheType}; + "sourceId": sourceId, + "cacheType": cacheType} def _set_authenticated(self, auth=''): """ Internal testing function """ - self.test_authenticated_from = auth; + self.test_authenticated_from = auth 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.seqnr_lock.release(); - return str(self.last_seqnr); + self.seqnr_lock.acquire() + self.last_seqnr += 1 + self.seqnr_lock.release() + return str(self.last_seqnr) def _handle_set_req(self, iq): """ - Event handler for reception of an Iq with set req - this is a + Event handler for reception of an Iq with set req - this is a control request. - Verifies that + Verifies that - all the requested nodes are available (if no nodes are specified in the request, assume all nodes) - all the control fields are available from all requested nodes @@ -216,80 +215,79 @@ class XEP_0325(BasePlugin): If the request passes verification, the control request is passed to the devices (in a separate thread). - If the verification fails, a setResponse with error indication + If the verification fails, a setResponse with error indication is sent. """ - error_msg = ''; - req_ok = True; - missing_node = None; - missing_field = None; + error_msg = '' + req_ok = True + missing_node = None + missing_field = None # Authentication if len(self.test_authenticated_from) > 0 and not iq['from'] == self.test_authenticated_from: # Invalid authentication - req_ok = False; - error_msg = "Access denied"; + req_ok = False + 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: - req_ok = False; - missing_node = n['nodeId']; - error_msg = "Invalid nodeId " + n['nodeId']; - process_nodes = [n['nodeId'] for n in iq['set']['nodes']]; + req_ok = False + missing_node = n['nodeId'] + error_msg = "Invalid nodeId " + n['nodeId'] + process_nodes = [n['nodeId'] for n in iq['set']['nodes']] else: - process_nodes = self.nodes.keys(); + process_nodes = self.nodes.keys() # Fields - for control we need to find all in all devices, otherwise we reject - process_fields = []; + process_fields = [] if len(iq['set']['datas']) > 0: for f in iq['set']['datas']: for node in self.nodes: if not self.nodes[node]["device"].has_control_field(f['name'], f._get_typename()): - req_ok = False; - missing_field = f['name']; - error_msg = "Invalid field " + f['name']; - break; - process_fields = [(f['name'], f._get_typename(), f['value']) for f in iq['set']['datas']]; + req_ok = False + missing_field = f['name'] + error_msg = "Invalid field " + f['name'] + break + process_fields = [(f['name'], f._get_typename(), f['value']) for f in iq['set']['datas']] if req_ok: - session = self._new_session(); - self.sessions[session] = {"from": iq['from'], "to": iq['to'], "seqnr": iq['id']}; - self.sessions[session]["commTimers"] = {}; - self.sessions[session]["nodeDone"] = {}; + session = self._new_session() + self.sessions[session] = {"from": iq['from'], "to": iq['to'], "seqnr": iq['id']} + self.sessions[session]["commTimers"] = {} + self.sessions[session]["nodeDone"] = {} # Flag that a reply is exected when we are done - self.sessions[session]["reply"] = True; + self.sessions[session]["reply"] = True - self.sessions[session]["node_list"] = process_nodes; + self.sessions[session]["node_list"] = process_nodes if self.threaded: #print("starting thread") tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields)) tr_req.start() #print("started thread") else: - self._threaded_node_request(session, process_fields); + self._threaded_node_request(session, process_fields) else: - iq.reply(); - iq['type'] = 'error'; - iq['setResponse']['responseCode'] = "NotFound"; + iq.reply() + iq['type'] = 'error' + iq['setResponse']['responseCode'] = "NotFound" if missing_node is not None: - iq['setResponse'].add_node(missing_node); + iq['setResponse'].add_node(missing_node) if missing_field is not None: - iq['setResponse'].add_data(missing_field); - iq['setResponse']['error']['var'] = "Output"; - iq['setResponse']['error']['text'] = error_msg; - iq.send(block=False); + iq['setResponse'].add_data(missing_field) + iq['setResponse']['error']['var'] = "Output" + iq['setResponse']['error']['text'] = error_msg + iq.send(block=False) def _handle_direct_set(self, msg): """ - Event handler for reception of a Message with set command - this is a + Event handler for reception of a Message with set command - this is a direct control command. - Verifies that + Verifies that - all the requested nodes are available (if no nodes are specified in the request, assume all nodes) - all the control fields are available from all requested nodes @@ -299,73 +297,72 @@ class XEP_0325(BasePlugin): to the devices (in a separate thread). If the verification fails, do nothing. """ - req_ok = True; + 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: - req_ok = False; - error_msg = "Invalid nodeId " + n['nodeId']; - process_nodes = [n['nodeId'] for n in msg['set']['nodes']]; + req_ok = False + error_msg = "Invalid nodeId " + n['nodeId'] + process_nodes = [n['nodeId'] for n in msg['set']['nodes']] else: - process_nodes = self.nodes.keys(); + process_nodes = self.nodes.keys() # Fields - for control we need to find all in all devices, otherwise we reject - process_fields = []; + process_fields = [] if len(msg['set']['datas']) > 0: for f in msg['set']['datas']: for node in self.nodes: if not self.nodes[node]["device"].has_control_field(f['name'], f._get_typename()): - req_ok = False; - missing_field = f['name']; - error_msg = "Invalid field " + f['name']; - break; - process_fields = [(f['name'], f._get_typename(), f['value']) for f in msg['set']['datas']]; + req_ok = False + missing_field = f['name'] + error_msg = "Invalid field " + f['name'] + break + process_fields = [(f['name'], f._get_typename(), f['value']) for f in msg['set']['datas']] if req_ok: - session = self._new_session(); - self.sessions[session] = {"from": msg['from'], "to": msg['to']}; - self.sessions[session]["commTimers"] = {}; - self.sessions[session]["nodeDone"] = {}; - self.sessions[session]["reply"] = False; + session = self._new_session() + self.sessions[session] = {"from": msg['from'], "to": msg['to']} + self.sessions[session]["commTimers"] = {} + self.sessions[session]["nodeDone"] = {} + self.sessions[session]["reply"] = False - self.sessions[session]["node_list"] = process_nodes; + self.sessions[session]["node_list"] = process_nodes if self.threaded: #print("starting thread") tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields)) tr_req.start() #print("started thread") else: - self._threaded_node_request(session, process_fields); + self._threaded_node_request(session, process_fields) def _threaded_node_request(self, session, process_fields): - """ + """ Helper function to handle the device control in a separate thread. - + Arguments: session -- The request session id process_fields -- The fields to set in the devices. List of tuple format: (name, datatype, value) """ for node in self.sessions[session]["node_list"]: - self.sessions[session]["nodeDone"][node] = False; + self.sessions[session]["nodeDone"][node] = False for node in self.sessions[session]["node_list"]: - timer = Timer(self.nodes[node]['commTimeout'], self._event_comm_timeout, args=(session, node)); - self.sessions[session]["commTimers"][node] = timer; - timer.start(); - self.nodes[node]['device'].set_control_fields(process_fields, session=session, callback=self._device_set_command_callback); + timer = Timer(self.nodes[node]['commTimeout'], self._event_comm_timeout, args=(session, node)) + self.sessions[session]["commTimers"][node] = timer + timer.start() + self.nodes[node]['device'].set_control_fields(process_fields, session=session, callback=self._device_set_command_callback) def _event_comm_timeout(self, session, nodeId): - """ + """ Triggered if any of the control operations timeout. Stop communicating with the failing device. - If the control command was an Iq request, sends a failure - message back to the client. - + If the control command was an Iq request, sends a failure + message back to the client. + Arguments: session -- The request session id nodeId -- The id of the device which timed out @@ -373,51 +370,51 @@ class XEP_0325(BasePlugin): if self.sessions[session]["reply"]: # Reply is exected when we are done - iq = self.xmpp.Iq(); - iq['from'] = self.sessions[session]['to']; - iq['to'] = self.sessions[session]['from']; - iq['type'] = "error"; - iq['id'] = self.sessions[session]['seqnr']; - iq['setResponse']['responseCode'] = "OtherError"; - iq['setResponse'].add_node(nodeId); - iq['setResponse']['error']['var'] = "Output"; - iq['setResponse']['error']['text'] = "Timeout."; - iq.send(block=False); + iq = self.xmpp.Iq() + iq['from'] = self.sessions[session]['to'] + iq['to'] = self.sessions[session]['from'] + iq['type'] = "error" + iq['id'] = self.sessions[session]['seqnr'] + iq['setResponse']['responseCode'] = "OtherError" + iq['setResponse'].add_node(nodeId) + iq['setResponse']['error']['var'] = "Output" + iq['setResponse']['error']['text'] = "Timeout." + iq.send(block=False) ## TODO - should we send one timeout per node?? # Drop communication with this device and check if we are done - self.sessions[session]["nodeDone"][nodeId] = True; + self.sessions[session]["nodeDone"][nodeId] = True if (self._all_nodes_done(session)): # The session is complete, delete it - del self.sessions[session]; + del self.sessions[session] def _all_nodes_done(self, session): - """ + """ Checks wheter all devices are done replying to the control command. - + Arguments: session -- The request session id """ for n in self.sessions[session]["nodeDone"]: if not self.sessions[session]["nodeDone"][n]: - return False; - return True; + return False + return True def _device_set_command_callback(self, session, nodeId, result, error_field=None, error_msg=None): - """ - Callback function called by the devices when the control command is + """ + Callback function called by the devices when the control command is complete or failed. - If needed, composes a message with the result and sends it back to the + If needed, composes a message with the result and sends it back to the client. - + Arguments: session -- The request session id nodeId -- The device id which initiated the callback result -- The current result status of the control command. Valid values are: "error" - Set fields failed. "ok" - All fields were set. - error_field -- [optional] Only applies when result == "error" + error_field -- [optional] Only applies when result == "error" The field name that failed (usually means it is missing) error_msg -- [optional] Only applies when result == "error". Error details when a request failed. @@ -428,62 +425,62 @@ class XEP_0325(BasePlugin): return if result == "error": - self.sessions[session]["commTimers"][nodeId].cancel(); + self.sessions[session]["commTimers"][nodeId].cancel() if self.sessions[session]["reply"]: # Reply is exected when we are done - iq = self.xmpp.Iq(); - iq['from'] = self.sessions[session]['to']; - iq['to'] = self.sessions[session]['from']; - iq['type'] = "error"; - iq['id'] = self.sessions[session]['seqnr']; - iq['setResponse']['responseCode'] = "OtherError"; - iq['setResponse'].add_node(nodeId); + iq = self.xmpp.Iq() + iq['from'] = self.sessions[session]['to'] + iq['to'] = self.sessions[session]['from'] + iq['type'] = "error" + iq['id'] = self.sessions[session]['seqnr'] + iq['setResponse']['responseCode'] = "OtherError" + iq['setResponse'].add_node(nodeId) if error_field is not None: - iq['setResponse'].add_data(error_field); - iq['setResponse']['error']['var'] = error_field; - iq['setResponse']['error']['text'] = error_msg; - iq.send(block=False); + iq['setResponse'].add_data(error_field) + iq['setResponse']['error']['var'] = error_field + iq['setResponse']['error']['text'] = error_msg + iq.send(block=False) # Drop communication with this device and check if we are done - self.sessions[session]["nodeDone"][nodeId] = True; + self.sessions[session]["nodeDone"][nodeId] = True if (self._all_nodes_done(session)): # The session is complete, delete it - del self.sessions[session]; + del self.sessions[session] else: - self.sessions[session]["commTimers"][nodeId].cancel(); + self.sessions[session]["commTimers"][nodeId].cancel() - self.sessions[session]["nodeDone"][nodeId] = True; + self.sessions[session]["nodeDone"][nodeId] = True if (self._all_nodes_done(session)): if self.sessions[session]["reply"]: # Reply is exected when we are done - iq = self.xmpp.Iq(); - iq['from'] = self.sessions[session]['to']; - iq['to'] = self.sessions[session]['from']; - iq['type'] = "result"; - iq['id'] = self.sessions[session]['seqnr']; - iq['setResponse']['responseCode'] = "OK"; - iq.send(block=False); + iq = self.xmpp.Iq() + iq['from'] = self.sessions[session]['to'] + iq['to'] = self.sessions[session]['from'] + iq['type'] = "result" + iq['id'] = self.sessions[session]['seqnr'] + iq['setResponse']['responseCode'] = "OK" + iq.send(block=False) # The session is complete, delete it - del self.sessions[session]; + del self.sessions[session] # ================================================================= # Client side (data controller) API def set_request(self, from_jid, to_jid, callback, fields, nodeIds=None): - """ + """ Called on the client side to initiade a control request. Composes a message with the request and sends it to the device(s). - Does not block, the callback will be called when the device(s) + Does not block, the callback will be called when the device(s) has responded. - + 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 availble. + The callback function must support the following arguments: from_jid -- The jid of the responding device(s) @@ -494,46 +491,46 @@ class XEP_0325(BasePlugin): "Locked" - Field(s) is locked and cannot be changed at the moment. "NotImplemented" - Request feature not implemented. - "FormError" - Error while setting with + "FormError" - Error while setting with a form (not implemented). - "OtherError" - Indicates other types of - errors, such as timeout. + "OtherError" - Indicates other types of + errors, such as timeout. Details in the error_msg. - - nodeId -- [optional] Only applicable when result == "error" - List of node Ids of failing device(s). - fields -- [optional] Only applicable when result == "error" + nodeId -- [optional] Only applicable when result == "error" + List of node Ids of failing device(s). + + fields -- [optional] Only applicable when result == "error" List of fields that failed.[optional] Mandatory when result == "rejected" or "failure". - - error_msg -- Details about why the request failed. + + error_msg -- Details about why the request failed. fields -- Fields to set. List of tuple format: (name, typename, value). nodeIds -- [optional] Limits the request to the node Ids in this list. """ - iq = self.xmpp.Iq(); - iq['from'] = from_jid; - iq['to'] = to_jid; - seqnr = self._get_new_seqnr(); - iq['id'] = seqnr; - iq['type'] = "set"; + iq = self.xmpp.Iq() + iq['from'] = from_jid + iq['to'] = to_jid + seqnr = self._get_new_seqnr() + iq['id'] = seqnr + iq['type'] = "set" if nodeIds is not None: for nodeId in nodeIds: - iq['set'].add_node(nodeId); + iq['set'].add_node(nodeId) if fields is not None: for name, typename, value in fields: - iq['set'].add_data(name=name, typename=typename, value=value); + iq['set'].add_data(name=name, typename=typename, value=value) - self.sessions[seqnr] = {"from": iq['from'], "to": iq['to'], "callback": callback}; - iq.send(block=False); + self.sessions[seqnr] = {"from": iq['from'], "to": iq['to'], "callback": callback} + iq.send(block=False) def set_command(self, from_jid, to_jid, fields, nodeIds=None): - """ + """ Called on the client side to initiade a control command. Composes a message with the set commandand sends it to the device(s). Does not block. Device(s) will not respond, regardless of result. - + Arguments: from_jid -- The jid of the requester to_jid -- The jid of the device(s) @@ -541,34 +538,32 @@ class XEP_0325(BasePlugin): fields -- Fields to set. List of tuple format: (name, typename, value). nodeIds -- [optional] Limits the request to the node Ids in this list. """ - msg = self.xmpp.Message(); - msg['from'] = from_jid; - msg['to'] = to_jid; - msg['type'] = "set"; + msg = self.xmpp.Message() + msg['from'] = from_jid + msg['to'] = to_jid + msg['type'] = "set" if nodeIds is not None: for nodeId in nodeIds: - msg['set'].add_node(nodeId); + msg['set'].add_node(nodeId) if fields is not None: for name, typename, value in fields: - msg['set'].add_data(name, typename, value); + msg['set'].add_data(name, typename, value) # We won't get any reply, so don't create a session - msg.send(); + msg.send() def _handle_set_response(self, iq): """ Received response from device(s) """ #print("ooh") - seqnr = iq['id']; - from_jid = str(iq['from']); - result = iq['setResponse']['responseCode']; - nodeIds = [n['name'] for n in iq['setResponse']['nodes']]; - fields = [f['name'] for f in iq['setResponse']['datas']]; - error_msg = None; + seqnr = iq['id'] + from_jid = str(iq['from']) + result = iq['setResponse']['responseCode'] + nodeIds = [n['name'] for n in iq['setResponse']['nodes']] + fields = [f['name'] for f in iq['setResponse']['datas']] + error_msg = None if not iq['setResponse'].find('error') is None and not iq['setResponse']['error']['text'] == "": - error_msg = iq['setResponse']['error']['text']; - - callback = self.sessions[seqnr]["callback"]; - callback(from_jid=from_jid, result=result, nodeIds=nodeIds, fields=fields, error_msg=error_msg); + error_msg = iq['setResponse']['error']['text'] - + callback = self.sessions[seqnr]["callback"] + callback(from_jid=from_jid, result=result, nodeIds=nodeIds, fields=fields, error_msg=error_msg) diff --git a/sleekxmpp/plugins/xep_0325/device.py b/sleekxmpp/plugins/xep_0325/device.py index a60d5f9a..f1ed0733 100644 --- a/sleekxmpp/plugins/xep_0325/device.py +++ b/sleekxmpp/plugins/xep_0325/device.py @@ -13,16 +13,16 @@ import datetime class Device(object): """ Example implementation of a device control object. - - The device object may by any custom implementation to support + + The device object may by any custom implementation to support specific devices, but it must implement the functions: has_control_field set_control_fields """ def __init__(self, nodeId): - self.nodeId = nodeId; - self.control_fields = {}; + self.nodeId = nodeId + self.control_fields = {} def has_control_field(self, field, typename): """ @@ -30,12 +30,12 @@ class Device(object): and the type matches for control in this device. Arguments: - field -- The field name + field -- The field name typename -- The expected type """ if field in self.control_fields and self.control_fields[field]["type"] == typename: - return True; - return False; + return True + return False def set_control_fields(self, fields, session, callback): """ @@ -43,22 +43,22 @@ class Device(object): sets the data and (if needed) and calls the callback. Arguments: - fields -- List of control fields in tuple format: + fields -- List of control fields in tuple format: (name, typename, value) session -- Session id, only used in the callback as identifier callback -- Callback function to call when control set is complete. The callback function must support the following arguments: - session -- Session id, as supplied in the + session -- Session id, as supplied in the request_fields call nodeId -- Identifier for this device - result -- The current result status of the readout. + result -- The current result status of the readout. Valid values are: "error" - Set fields failed. "ok" - All fields were set. - error_field -- [optional] Only applies when result == "error" - The field name that failed + error_field -- [optional] Only applies when result == "error" + The field name that failed (usually means it is missing) error_msg -- [optional] Only applies when result == "error". Error details when a request failed. @@ -69,12 +69,12 @@ class Device(object): for name, typename, value in fields: if not self.has_control_field(name, typename): self._send_control_reject(session, name, "NotFound", callback) - return False; + return False for name, typename, value in fields: self._set_field_value(name, value) - callback(session, result="ok", nodeId=self.nodeId); + callback(session, result="ok", nodeId=self.nodeId) return True def _send_control_reject(self, session, field, message, callback): @@ -82,12 +82,12 @@ class Device(object): Sends a reject to the caller Arguments: - session -- Session id, see definition in + session -- Session id, see definition in set_control_fields function - callback -- Callback function, see definition in + callback -- Callback function, see definition in set_control_fields function """ - callback(session, result="error", nodeId=self.nodeId, error_field=field, error_msg=message); + callback(session, result="error", nodeId=self.nodeId, error_field=field, error_msg=message) def _add_control_field(self, name, typename, value): """ @@ -95,12 +95,12 @@ class Device(object): Arguments: name -- Name of the field - typename -- Type of the field, one of: - (boolean, color, string, date, dateTime, + typename -- Type of the field, one of: + (boolean, color, string, date, dateTime, double, duration, int, long, time) value -- Field value """ - self.control_fields[name] = {"type": typename, "value": value}; + self.control_fields[name] = {"type": typename, "value": value} def _set_field_value(self, name, value): """ @@ -111,7 +111,7 @@ class Device(object): value -- New value for the field """ if name in self.control_fields: - self.control_fields[name]["value"] = value; + self.control_fields[name]["value"] = value def _get_field_value(self, name): """ @@ -121,5 +121,5 @@ class Device(object): name -- Name of the field """ if name in self.control_fields: - return self.control_fields[name]["value"]; - return None; + return self.control_fields[name]["value"] + return None diff --git a/sleekxmpp/plugins/xep_0325/stanza/control.py b/sleekxmpp/plugins/xep_0325/stanza/control.py index 67107ecb..1fd5c35d 100644 --- a/sleekxmpp/plugins/xep_0325/stanza/control.py +++ b/sleekxmpp/plugins/xep_0325/stanza/control.py @@ -26,7 +26,7 @@ class ControlSet(ElementBase): interfaces = set(['nodes','datas']) def __init__(self, xml=None, parent=None): - ElementBase.__init__(self, xml, parent); + ElementBase.__init__(self, xml, parent) self._nodes = set() self._datas = set() @@ -53,7 +53,7 @@ class ControlSet(ElementBase): Arguments: nodeId -- The ID for the node. sourceId -- [optional] identifying the data source controlling the device - cacheType -- [optional] narrowing down the search to a specific kind of node + cacheType -- [optional] narrowing down the search to a specific kind of node """ if nodeId not in self._nodes: self._nodes.add((nodeId)) @@ -117,40 +117,40 @@ class ControlSet(ElementBase): def add_data(self, name, typename, value): """ - Add a new data element. + Add a new data element. Arguments: name -- The name of the data element - typename -- The type of data element - (boolean, color, string, date, dateTime, + typename -- The type of data element + (boolean, color, string, date, dateTime, double, duration, int, long, time) value -- The value of the data element """ if name not in self._datas: - dataObj = None; + dataObj = None if typename == "boolean": - dataObj = BooleanParameter(parent=self); + dataObj = BooleanParameter(parent=self) elif typename == "color": - dataObj = ColorParameter(parent=self); + dataObj = ColorParameter(parent=self) elif typename == "string": - dataObj = StringParameter(parent=self); + dataObj = StringParameter(parent=self) elif typename == "date": - dataObj = DateParameter(parent=self); + dataObj = DateParameter(parent=self) elif typename == "dateTime": - dataObj = DateTimeParameter(parent=self); + dataObj = DateTimeParameter(parent=self) elif typename == "double": - dataObj = DoubleParameter(parent=self); + dataObj = DoubleParameter(parent=self) elif typename == "duration": - dataObj = DurationParameter(parent=self); + dataObj = DurationParameter(parent=self) elif typename == "int": - dataObj = IntParameter(parent=self); + dataObj = IntParameter(parent=self) elif typename == "long": - dataObj = LongParameter(parent=self); + dataObj = LongParameter(parent=self) elif typename == "time": - dataObj = TimeParameter(parent=self); + dataObj = TimeParameter(parent=self) - dataObj['name'] = name; - dataObj['value'] = value; + dataObj['name'] = name + dataObj['value'] = value self._datas.add(name) self.iterables.append(dataObj) @@ -217,7 +217,7 @@ class ControlSetResponse(ElementBase): interfaces = set(['responseCode']) def __init__(self, xml=None, parent=None): - ElementBase.__init__(self, xml, parent); + ElementBase.__init__(self, xml, parent) self._nodes = set() self._datas = set() @@ -244,7 +244,7 @@ class ControlSetResponse(ElementBase): Arguments: nodeId -- The ID for the node. sourceId -- [optional] identifying the data source controlling the device - cacheType -- [optional] narrowing down the search to a specific kind of node + cacheType -- [optional] narrowing down the search to a specific kind of node """ if nodeId not in self._nodes: self._nodes.add(nodeId) @@ -308,7 +308,7 @@ class ControlSetResponse(ElementBase): def add_data(self, name): """ - Add a new ResponseParameter element. + Add a new ResponseParameter element. Arguments: name -- Name of the parameter @@ -316,7 +316,7 @@ class ControlSetResponse(ElementBase): if name not in self._datas: self._datas.add(name) data = ResponseParameter(parent=self) - data['name'] = name; + data['name'] = name self.iterables.append(data) return data return None @@ -383,26 +383,26 @@ class Error(ElementBase): value -- string """ - self.xml.text = value; + self.xml.text = value return self def del_text(self): """Remove the contents inside the XML tag.""" self.xml.text = "" - return self + return self class ResponseParameter(ElementBase): - """ - Parameter element in ControlSetResponse. - """ + """ + Parameter element in ControlSetResponse. + """ namespace = 'urn:xmpp:iot:control' name = 'parameter' plugin_attrib = name - interfaces = set(['name']); + interfaces = set(['name']) class BaseParameter(ElementBase): - """ + """ Parameter element in SetCommand. This is a base class, all instances of parameters added to SetCommand must be of types: BooleanParameter @@ -415,90 +415,91 @@ class BaseParameter(ElementBase): IntParameter LongParameter TimeParameter - """ + """ namespace = 'urn:xmpp:iot:control' name = 'baseParameter' plugin_attrib = name - interfaces = set(['name','value']); + interfaces = set(['name','value']) def _get_typename(self): - return self.name; + return self.name + class BooleanParameter(BaseParameter): - """ - Field data of type boolean. - Note that the value is expressed as a string. + """ + Field data of type boolean. + Note that the value is expressed as a string. """ name = 'boolean' plugin_attrib = name class ColorParameter(BaseParameter): - """ - Field data of type color. - Note that the value is expressed as a string. + """ + Field data of type color. + Note that the value is expressed as a string. """ name = 'color' plugin_attrib = name class StringParameter(BaseParameter): - """ - Field data of type string. + """ + Field data of type string. """ name = 'string' plugin_attrib = name class DateParameter(BaseParameter): - """ - Field data of type date. - Note that the value is expressed as a string. + """ + Field data of type date. + Note that the value is expressed as a string. """ name = 'date' plugin_attrib = name class DateTimeParameter(BaseParameter): - """ - Field data of type dateTime. - Note that the value is expressed as a string. + """ + Field data of type dateTime. + Note that the value is expressed as a string. """ name = 'dateTime' plugin_attrib = name class DoubleParameter(BaseParameter): - """ - Field data of type double. - Note that the value is expressed as a string. + """ + Field data of type double. + Note that the value is expressed as a string. """ name = 'double' plugin_attrib = name class DurationParameter(BaseParameter): - """ - Field data of type duration. - Note that the value is expressed as a string. + """ + Field data of type duration. + Note that the value is expressed as a string. """ name = 'duration' plugin_attrib = name class IntParameter(BaseParameter): - """ - Field data of type int. - Note that the value is expressed as a string. + """ + Field data of type int. + Note that the value is expressed as a string. """ name = 'int' plugin_attrib = name class LongParameter(BaseParameter): - """ - Field data of type long (64-bit int). - Note that the value is expressed as a string. + """ + Field data of type long (64-bit int). + Note that the value is expressed as a string. """ name = 'long' plugin_attrib = name class TimeParameter(BaseParameter): - """ - Field data of type time. - Note that the value is expressed as a string. + """ + Field data of type time. + Note that the value is expressed as a string. """ name = 'time' plugin_attrib = name diff --git a/sleekxmpp/plugins/xep_0332/__init__.py b/sleekxmpp/plugins/xep_0332/__init__.py new file mode 100644 index 00000000..27755faa --- /dev/null +++ b/sleekxmpp/plugins/xep_0332/__init__.py @@ -0,0 +1,17 @@ +""" + SleekXMPP: The Sleek 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 SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.plugins.base import register_plugin + +from sleekxmpp.plugins.xep_0332 import stanza +from sleekxmpp.plugins.xep_0332.http import XEP_0332 + + +register_plugin(XEP_0332) diff --git a/sleekxmpp/plugins/xep_0332/http.py b/sleekxmpp/plugins/xep_0332/http.py new file mode 100644 index 00000000..70bcafa6 --- /dev/null +++ b/sleekxmpp/plugins/xep_0332/http.py @@ -0,0 +1,159 @@ +""" + SleekXMPP: The Sleek 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 SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import logging + +from sleekxmpp import Iq + +from sleekxmpp.xmlstream import register_stanza_plugin +from sleekxmpp.xmlstream.handler import Callback +from sleekxmpp.xmlstream.matcher import StanzaPath + +from sleekxmpp.plugins.base import BasePlugin +from sleekxmpp.plugins.xep_0332.stanza import ( + HTTPRequest, HTTPResponse, HTTPData +) +from sleekxmpp.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/sleekxmpp/plugins/xep_0332/stanza/__init__.py b/sleekxmpp/plugins/xep_0332/stanza/__init__.py new file mode 100644 index 00000000..201824b7 --- /dev/null +++ b/sleekxmpp/plugins/xep_0332/stanza/__init__.py @@ -0,0 +1,13 @@ +""" + SleekXMPP: The Sleek 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 SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.plugins.xep_0332.stanza.request import HTTPRequest +from sleekxmpp.plugins.xep_0332.stanza.response import HTTPResponse +from sleekxmpp.plugins.xep_0332.stanza.data import HTTPData diff --git a/sleekxmpp/plugins/xep_0332/stanza/data.py b/sleekxmpp/plugins/xep_0332/stanza/data.py new file mode 100644 index 00000000..a3678038 --- /dev/null +++ b/sleekxmpp/plugins/xep_0332/stanza/data.py @@ -0,0 +1,30 @@ +""" + SleekXMPP: The Sleek 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 SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.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/sleekxmpp/plugins/xep_0332/stanza/request.py b/sleekxmpp/plugins/xep_0332/stanza/request.py new file mode 100644 index 00000000..9a298e57 --- /dev/null +++ b/sleekxmpp/plugins/xep_0332/stanza/request.py @@ -0,0 +1,71 @@ +""" + SleekXMPP: The Sleek 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 SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.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/sleekxmpp/plugins/xep_0332/stanza/response.py b/sleekxmpp/plugins/xep_0332/stanza/response.py new file mode 100644 index 00000000..6804ade9 --- /dev/null +++ b/sleekxmpp/plugins/xep_0332/stanza/response.py @@ -0,0 +1,66 @@ +""" + SleekXMPP: The Sleek 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 SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.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/sleekxmpp/roster/single.py b/sleekxmpp/roster/single.py index f080ae8a..e9ce4f21 100644 --- a/sleekxmpp/roster/single.py +++ b/sleekxmpp/roster/single.py @@ -237,8 +237,7 @@ class RosterNode(object): if not self.xmpp.is_component: return self.update(jid, subscription='remove') - def update(self, jid, name=None, subscription=None, groups=[], - block=True, timeout=None, callback=None): + def update(self, jid, name=None, subscription=None, groups=None, block=True, timeout=None, callback=None): """ Update a JID's subscription information. @@ -258,6 +257,9 @@ class RosterNode(object): Will be executed when the roster is received. Implies block=False. """ + if not groups: + groups = [] + self[jid]['name'] = name self[jid]['groups'] = groups self[jid].save() diff --git a/sleekxmpp/stanza/atom.py b/sleekxmpp/stanza/atom.py index 244ef315..4e9591a5 100644 --- a/sleekxmpp/stanza/atom.py +++ b/sleekxmpp/stanza/atom.py @@ -6,8 +6,7 @@ See the file LICENSE for copying permission. """ -from sleekxmpp.xmlstream import ElementBase - +from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase 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/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py index a7c2b218..52b807e5 100644 --- a/sleekxmpp/stanza/rootstanza.py +++ b/sleekxmpp/stanza/rootstanza.py @@ -60,7 +60,9 @@ class RootStanza(StanzaBase): self.send() elif isinstance(e, XMPPError): # We raised this deliberately + keep_id = self['id'] self.reply(clear=e.clear) + self['id'] = keep_id self['error']['condition'] = e.condition self['error']['text'] = e.text self['error']['type'] = e.etype @@ -72,7 +74,9 @@ class RootStanza(StanzaBase): self.send() else: # We probably didn't raise this on purpose, so send an error stanza + keep_id = self['id'] self.reply() + self['id'] = keep_id self['error']['condition'] = 'undefined-condition' self['error']['text'] = "SleekXMPP got into trouble." self['error']['type'] = 'cancel' diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py index d28f77e2..e26f99ce 100644 --- a/sleekxmpp/test/sleektest.py +++ b/sleekxmpp/test/sleektest.py @@ -288,11 +288,8 @@ class SleekTest(unittest.TestCase): if self.xmpp: self.xmpp.socket.disconnect_error() - def stream_start(self, mode='client', skip=True, header=None, - socket='mock', jid='tester@localhost', - password='test', server='localhost', - port=5222, sasl_mech=None, - plugins=None, plugin_config={}): + def stream_start(self, mode='client', skip=True, header=None, socket='mock', jid='tester@localhost', + password='test', server='localhost', port=5222, sasl_mech=None, plugins=None, plugin_config=None): """ Initialize an XMPP client or component using a dummy XML stream. @@ -315,6 +312,9 @@ class SleekTest(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, @@ -425,8 +425,7 @@ class SleekTest(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. @@ -447,6 +446,9 @@ class SleekTest(unittest.TestCase): timeout -- Time to wait in seconds for data to be received by a live connection. """ + if not defaults: + defaults = [] + if self.xmpp.socket.is_live: # we are working with a live connection, so we should # verify what has been received instead of simulating diff --git a/sleekxmpp/thirdparty/__init__.py b/sleekxmpp/thirdparty/__init__.py index 2a1db717..337598ac 100644 --- a/sleekxmpp/thirdparty/__init__.py +++ b/sleekxmpp/thirdparty/__init__.py @@ -10,3 +10,4 @@ except: from sleekxmpp.thirdparty import socks from sleekxmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso +from sleekxmpp.thirdparty.orderedset import OrderedSet diff --git a/sleekxmpp/thirdparty/orderedset.py b/sleekxmpp/thirdparty/orderedset.py new file mode 100644 index 00000000..f6642db3 --- /dev/null +++ b/sleekxmpp/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/sleekxmpp/thirdparty/socks.py b/sleekxmpp/thirdparty/socks.py index 9239a7b9..34090d51 100644 --- a/sleekxmpp/thirdparty/socks.py +++ b/sleekxmpp/thirdparty/socks.py @@ -28,6 +28,9 @@ OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. This module provides a standard socket-like interface for Python for tunneling connections through SOCKS proxies. +""" + +""" Minor modifications made by Christopher Gilbert (http://motomastyle.com/) for use in PyLoris (http://pyloris.sourceforge.net/) @@ -35,10 +38,13 @@ for use in PyLoris (http://pyloris.sourceforge.net/) Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) mainly to merge bug fixes found in Sourceforge +Minor modifications made by Eugene Dementiev (http://www.dementiev.eu/) + """ import socket import struct +import sys PROXY_TYPE_SOCKS4 = 1 PROXY_TYPE_SOCKS5 = 2 @@ -208,12 +214,12 @@ class socksocket(socket.socket): if self.__proxy[3]: # Resolve remotely ipaddr = None - req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr + req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr.encode() else: # Resolve locally ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) req = req + chr(0x01).encode() + ipaddr - req = req + struct.pack(">H", destport) + req += struct.pack(">H", destport) self.sendall(req) # Get the response resp = self.__recvall(4) @@ -282,7 +288,7 @@ class socksocket(socket.socket): # The username parameter is considered userid for SOCKS4 if self.__proxy[4] != None: req = req + self.__proxy[4] - req = req + chr(0x00).encode() + req += chr(0x00).encode() # DNS name if remote resolving is required # NOTE: This is actually an extension to the SOCKS4 protocol # called SOCKS4A and may not be supported in all cases. @@ -323,7 +329,10 @@ class socksocket(socket.socket): # We read the response until we get the string "\r\n\r\n" resp = self.recv(1) while resp.find("\r\n\r\n".encode()) == -1: - resp = resp + self.recv(1) + recv = self.recv(1) + if not recv: + raise GeneralProxyError((1, _generalerrors[1])) + resp = resp + recv # We just need the first line to check if the connection # was successful statusline = resp.splitlines()[0].split(" ".encode(), 2) diff --git a/sleekxmpp/thirdparty/statemachine.py b/sleekxmpp/thirdparty/statemachine.py index 113320fa..6c504dce 100644 --- a/sleekxmpp/thirdparty/statemachine.py +++ b/sleekxmpp/thirdparty/statemachine.py @@ -34,7 +34,7 @@ class StateMachine(object): self.lock.release() - def transition(self, from_state, to_state, wait=0.0, func=None, args=[], kwargs={}): + def transition(self, from_state, to_state, wait=0.0, func=None, args=None, kwargs=None): ''' Transition from the given `from_state` to the given `to_state`. This method will return `True` if the state machine is now in `to_state`. It @@ -65,15 +65,23 @@ class StateMachine(object): values for `args` and `kwargs` are provided, they are expanded and passed like so: `func( *args, **kwargs )`. ''' + if not args: + args = [] + if not kwargs: + kwargs = {} return self.transition_any((from_state,), to_state, wait=wait, func=func, args=args, kwargs=kwargs) - def transition_any(self, from_states, to_state, wait=0.0, func=None, args=[], kwargs={}): + def transition_any(self, from_states, to_state, wait=0.0, func=None, args=None, kwargs=None): ''' Transition from any of the given `from_states` to the given `to_state`. ''' + if not args: + args = [] + if not kwargs: + kwargs = {} if not isinstance(from_states, (tuple, list, set)): raise ValueError("from_states should be a list, tuple, or set") diff --git a/sleekxmpp/util/__init__.py b/sleekxmpp/util/__init__.py index 05286d33..47a935af 100644 --- a/sleekxmpp/util/__init__.py +++ b/sleekxmpp/util/__init__.py @@ -32,12 +32,17 @@ def _gevent_threads_enabled(): if _gevent_threads_enabled(): import gevent.queue as queue - Queue = queue.JoinableQueue + _queue = queue.JoinableQueue else: try: import queue except ImportError: import Queue as queue - Queue = queue.Queue + _queue = queue.Queue +class Queue(_queue): + def put(self, item, block=True, timeout=None): + if _queue.full(self): + _queue.get(self) + return _queue.put(self, item, block, timeout) QueueEmpty = queue.Empty diff --git a/sleekxmpp/util/sasl/mechanisms.py b/sleekxmpp/util/sasl/mechanisms.py index d341ed3e..7a7ebf7b 100644 --- a/sleekxmpp/util/sasl/mechanisms.py +++ b/sleekxmpp/util/sasl/mechanisms.py @@ -223,17 +223,16 @@ class SCRAM(Mech): return self.hash(text).digest() def saslname(self, value): - escaped = b'' - for char in bytes(value): - if char == b',': - escaped += b'=2C' - elif char == b'=': - escaped += b'=3D' + value = value.decode("utf-8") + escaped = [] + for char in value: + if char == ',': + escaped += '=2C' + elif char == '=': + escaped += '=3D' else: - if isinstance(char, int): - char = chr(char) - escaped += bytes(char) - return escaped + escaped += char + return "".join(escaped).encode("utf-8") def parse(self, challenge): items = {} diff --git a/sleekxmpp/version.py b/sleekxmpp/version.py index ecf62550..acea9334 100644 --- a/sleekxmpp/version.py +++ b/sleekxmpp/version.py @@ -9,5 +9,5 @@ # We don't want to have to import the entire library # just to get the version info for setup.py -__version__ = '1.3.1' -__version_info__ = (1, 3, 1, '', 0) +__version__ = '1.4.0' +__version_info__ = (1, 4, 0, '', 0) diff --git a/sleekxmpp/xmlstream/cert.py b/sleekxmpp/xmlstream/cert.py index 71146f36..d357b326 100644 --- a/sleekxmpp/xmlstream/cert.py +++ b/sleekxmpp/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/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 97107098..c2e0f718 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -19,6 +19,7 @@ import logging import weakref from xml.etree import cElementTree as ET +from sleekxmpp.util import safedict from sleekxmpp.xmlstream import JID from sleekxmpp.xmlstream.tostring import tostring from sleekxmpp.thirdparty import OrderedDict @@ -562,10 +563,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(): @@ -676,6 +680,8 @@ class ElementBase(object): if lang and attrib in self.lang_interfaces: kwargs['lang'] = lang + kwargs = safedict(kwargs) + if attrib == 'substanzas': return self.iterables elif attrib in self.interfaces or attrib == 'lang': @@ -752,6 +758,8 @@ class ElementBase(object): if lang and attrib in self.lang_interfaces: kwargs['lang'] = lang + kwargs = safedict(kwargs) + if attrib in self.interfaces or attrib == 'lang': if value is not None: set_method = "set_%s" % attrib.lower() @@ -838,6 +846,8 @@ class ElementBase(object): if lang and attrib in self.lang_interfaces: kwargs['lang'] = lang + kwargs = safedict(kwargs) + if attrib in self.interfaces or attrib == 'lang': del_method = "del_%s" % attrib.lower() del_method2 = "del%s" % attrib.title() diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 66985f3d..62d46100 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -114,7 +114,8 @@ class XMLStream(object): :param int port: The port to use for the connection. Defaults to 0. """ - def __init__(self, socket=None, host='', port=0): + def __init__(self, socket=None, host='', port=0, certfile=None, + keyfile=None, ca_certs=None, **kwargs): #: Most XMPP servers support TLSv1, but OpenFire in particular #: does not work well with it. For OpenFire, set #: :attr:`ssl_version` to use ``SSLv23``:: @@ -136,16 +137,16 @@ class XMLStream(object): #: #: On Mac OS X, certificates in the system keyring will #: be consulted, even if they are not in the provided file. - self.ca_certs = None + self.ca_certs = ca_certs #: Path to a file containing a client certificate to use for #: authenticating via SASL EXTERNAL. If set, there must also #: be a corresponding `:attr:keyfile` value. - self.certfile = None + self.certfile = certfile #: Path to a file containing the private key for the selected #: client certificate to use for authenticating via SASL EXTERNAL. - self.keyfile = None + self.keyfile = keyfile self._der_cert = None @@ -291,7 +292,7 @@ class XMLStream(object): self.event_queue = Queue() #: A queue of string data to be sent over the stream. - self.send_queue = Queue() + self.send_queue = Queue(maxsize=256) self.send_queue_lock = threading.Lock() self.send_lock = threading.RLock() @@ -460,9 +461,11 @@ class XMLStream(object): def _connect(self, reattempt=True): self.scheduler.remove('Session timeout check') - if self.reconnect_delay is None or not reattempt: + if self.reconnect_delay is None: delay = 1.0 - else: + self.reconnect_delay = delay + + if reattempt: delay = min(self.reconnect_delay * 2, self.reconnect_max_delay) delay = random.normalvariate(delay, delay * 0.1) log.debug('Waiting %s seconds before connecting.', delay) @@ -523,7 +526,8 @@ class XMLStream(object): 'keyfile': self.keyfile, 'ca_certs': self.ca_certs, 'cert_reqs': cert_policy, - 'do_handshake_on_connect': False + 'do_handshake_on_connect': False, + "ssl_version": self.ssl_version }) if sys.version_info >= (2, 7): @@ -847,13 +851,14 @@ class XMLStream(object): 'keyfile': self.keyfile, 'ca_certs': self.ca_certs, 'cert_reqs': cert_policy, - 'do_handshake_on_connect': False + 'do_handshake_on_connect': False, + "ssl_version": self.ssl_version }) if sys.version_info >= (2, 7): ssl_args['ciphers'] = self.ciphers - ssl_socket = ssl.wrap_socket(self.socket, **ssl_args); + ssl_socket = ssl.wrap_socket(self.socket, **ssl_args) if hasattr(self.socket, 'socket'): # We are using a testing socket, so preserve the top @@ -938,12 +943,13 @@ class XMLStream(object): self.whitespace_keepalive_interval = 300 """ - self.schedule('Whitespace Keepalive', - self.whitespace_keepalive_interval, - self.send_raw, - args=(' ',), - kwargs={'now': True}, - repeat=True) + if self.whitespace_keepalive: + self.schedule('Whitespace Keepalive', + self.whitespace_keepalive_interval, + self.send_raw, + args=(' ',), + kwargs={'now': True}, + repeat=True) def _remove_schedules(self, event): """Remove whitespace keepalive and certificate expiration schedules.""" @@ -1148,7 +1154,7 @@ class XMLStream(object): """ return len(self.__event_handlers.get(name, [])) - def event(self, name, data={}, direct=False): + def event(self, name, data=None, direct=False): """Manually trigger a custom event. :param name: The name of the event to trigger. @@ -1159,6 +1165,9 @@ class XMLStream(object): event queue. All event handlers will run in the same thread. """ + if not data: + data = {} + log.debug("Event triggered: " + name) handlers = self.__event_handlers.get(name, []) @@ -1318,9 +1327,6 @@ class XMLStream(object): try: sent += self.socket.send(data[sent:]) count += 1 - except Socket.error as serr: - if serr.errno != errno.EINTR: - raise except ssl.SSLError as serr: if tries >= self.ssl_retry_max: log.debug('SSL error: max retries reached') @@ -1335,6 +1341,9 @@ class XMLStream(object): if not self.stop.is_set(): time.sleep(self.ssl_retry_delay) tries += 1 + except Socket.error as serr: + if serr.errno != errno.EINTR: + raise if count > 1: log.debug('SENT: %d chunks', count) except (Socket.error, ssl.SSLError) as serr: @@ -1744,9 +1753,6 @@ class XMLStream(object): try: sent += self.socket.send(enc_data[sent:]) count += 1 - except Socket.error as serr: - if serr.errno != errno.EINTR: - raise except ssl.SSLError as serr: if tries >= self.ssl_retry_max: log.debug('SSL error: max retries reached') @@ -1759,6 +1765,9 @@ class XMLStream(object): if not self.stop.is_set(): time.sleep(self.ssl_retry_delay) tries += 1 + except Socket.error as serr: + if serr.errno != errno.EINTR: + raise if count > 1: log.debug('SENT: %d chunks', count) self.send_queue.task_done() diff --git a/tests/test_stanza_element.py b/tests/test_stanza_element.py index 2b9676cf..e678b56e 100644 --- a/tests/test_stanza_element.py +++ b/tests/test_stanza_element.py @@ -385,7 +385,7 @@ class TestElementBase(SleekTest): 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(SleekTest): 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 9056c663..b87afb24 100644 --- a/tests/test_stanza_xep_0004.py +++ b/tests/test_stanza_xep_0004.py @@ -11,8 +11,8 @@ class TestDataForms(SleekTest): 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(SleekTest): 'value': 'cool'}, {'label': 'Urgh!', 'value': 'urgh'}]} - form['fields'] = fields + form.set_fields(fields) self.check(msg, """ @@ -141,13 +141,13 @@ class TestDataForms(SleekTest): '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(SleekTest): 'value': 'cool'}, {'label': 'Urgh!', 'value': 'urgh'}]} - form['fields'] = fields + form.set_fields(fields) form['type'] = 'cancel' @@ -197,5 +197,52 @@ class TestDataForms(SleekTest): <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..fca49bbb --- /dev/null +++ b/tests/test_stanza_xep_0122.py @@ -0,0 +1,189 @@ +import unittest + +from sleekxmpp import Message +from sleekxmpp.test import SleekTest +import sleekxmpp.plugins.xep_0004 as xep_0004 +import sleekxmpp.plugins.xep_0122 as xep_0122 +from sleekxmpp.xmlstream import register_stanza_plugin + + +class TestDataForms(SleekTest): + + 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.addField(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.addField(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.addField(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.addField(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.addReported(var='f1', ftype='text-single', label='Text') + validation = field['validate'] + validation.set_basic(True) + + form.addItem({'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 67e0daf0..7b1dfe42 100644 --- a/tests/test_stanza_xep_0323.py +++ b/tests/test_stanza_xep_0323.py @@ -6,7 +6,7 @@ import sleekxmpp.plugins.xep_0323 as xep_0323 namespace='sn' class TestSensorDataStanzas(SleekTest): - + def setUp(self): pass @@ -59,8 +59,8 @@ class TestSensorDataStanzas(SleekTest): 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 +75,7 @@ class TestSensorDataStanzas(SleekTest): """ ) - iq['req'].del_node("Device02"); + iq['req'].del_node("Device02") self.check(iq,""" <iq type='get' @@ -89,7 +89,7 @@ class TestSensorDataStanzas(SleekTest): """ ) - iq['req'].del_nodes(); + iq['req'].del_nodes() self.check(iq,""" <iq type='get' @@ -115,8 +115,8 @@ class TestSensorDataStanzas(SleekTest): 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' @@ -171,7 +171,7 @@ class TestSensorDataStanzas(SleekTest): iq['accepted']['seqnr'] = '2' self.check(iq,""" - <iq type='result' + <iq type='result' from='device@clayster.com' to='master@clayster.com/amr' id='2'> @@ -193,7 +193,7 @@ class TestSensorDataStanzas(SleekTest): iq['rejected']['error'] = 'Access denied.' self.check(iq,""" - <iq type='error' + <iq type='error' from='device@clayster.com' to='master@clayster.com/amr' id='4'> @@ -237,12 +237,12 @@ class TestSensorDataStanzas(SleekTest): 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' @@ -250,7 +250,7 @@ class TestSensorDataStanzas(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='1'> <node nodeId='Device02'> <timestamp value='2013-03-07T16:24:30'> - <numeric name='Temperature' momentary='true' automaticReadout='true' value='-12.42' unit='K'/> + <numeric name='Temperature' momentary='true' automaticReadout='true' value='-12.42' unit='K'/> </timestamp> </node> </fields> @@ -258,10 +258,9 @@ class TestSensorDataStanzas(SleekTest): """ ) - 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' @@ -269,7 +268,7 @@ class TestSensorDataStanzas(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='1'> <node nodeId='Device02'> <timestamp value='2013-03-07T16:24:30'> - <numeric name='Temperature' momentary='true' automaticReadout='true' value='-12.42' unit='K'/> + <numeric name='Temperature' momentary='true' automaticReadout='true' value='-12.42' unit='K'/> </timestamp> </node> <node nodeId='EmptyDevice'/> @@ -281,32 +280,32 @@ class TestSensorDataStanzas(SleekTest): """ ) - 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' @@ -314,7 +313,7 @@ class TestSensorDataStanzas(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='1'> <node nodeId='Device02'> <timestamp value='2013-03-07T16:24:30'> - <numeric name='Temperature' momentary='true' automaticReadout='true' value='-12.42' unit='K'/> + <numeric name='Temperature' momentary='true' automaticReadout='true' value='-12.42' unit='K'/> </timestamp> </node> <node nodeId='EmptyDevice'/> @@ -323,18 +322,18 @@ class TestSensorDataStanzas(SleekTest): </node> <node nodeId='Device77'> <timestamp value='2013-05-03T12:00:01'> - <numeric name='Temperature' historicalDay='true' value='-12.42' unit='K'/> - <numeric name='Speed' historicalWeek='false' value='312.42' unit='km/h'/> - <string name='Temperature name' historicalMonth='true' value='Bottom oil'/> - <string name='Speed name' historicalQuarter='false' value='Top speed'/> - <dateTime name='T1' historicalYear='true' value='1979-01-01T00:00:00'/> - <dateTime name='T2' historicalOther='false' value='2000-01-01T01:02:03'/> - <timeSpan name='TS1' missing='true' value='P5Y'/> - <timeSpan name='TS2' manualEstimate='false' value='PT2M1S'/> - <enum name='top color' invoiced='true' value='red' dataType='string'/> - <enum name='bottom color' powerFailure='false' value='black' dataType='string'/> - <boolean name='Temperature real' historicalDay='true' value='false'/> - <boolean name='Speed real' historicalWeek='false' value='true'/> + <numeric name='Temperature' historicalDay='true' value='-12.42' unit='K'/> + <numeric name='Speed' historicalWeek='false' value='312.42' unit='km/h'/> + <string name='Temperature name' historicalMonth='true' value='Bottom oil'/> + <string name='Speed name' historicalQuarter='false' value='Top speed'/> + <dateTime name='T1' historicalYear='true' value='1979-01-01T00:00:00'/> + <dateTime name='T2' historicalOther='false' value='2000-01-01T01:02:03'/> + <timeSpan name='TS1' missing='true' value='P5Y'/> + <timeSpan name='TS2' manualEstimate='false' value='PT2M1S'/> + <enum name='top color' invoiced='true' value='red' dataType='string'/> + <enum name='bottom color' powerFailure='false' value='black' dataType='string'/> + <boolean name='Temperature real' historicalDay='true' value='false'/> + <boolean name='Speed real' historicalWeek='false' value='true'/> </timestamp> </node> </fields> @@ -342,21 +341,19 @@ class TestSensorDataStanzas(SleekTest): """ ) - + 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' @@ -386,8 +383,8 @@ class TestSensorDataStanzas(SleekTest): self.check(msg,emptyStringIdXML) msg['fields']['stringIds'] = "1" self.check(msg,emptyStringIdXML) - - + + suite = unittest.TestLoader().loadTestsFromTestCase(TestSensorDataStanzas) diff --git a/tests/test_stanza_xep_0325.py b/tests/test_stanza_xep_0325.py index b15b764c..dc2e8efe 100644 --- a/tests/test_stanza_xep_0325.py +++ b/tests/test_stanza_xep_0325.py @@ -15,7 +15,7 @@ import sleekxmpp.plugins.xep_0325 as xep_0325 namespace='sn' class TestControlStanzas(SleekTest): - + def setUp(self): pass @@ -29,8 +29,8 @@ class TestControlStanzas(SleekTest): 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 +47,7 @@ class TestControlStanzas(SleekTest): """ ) - iq['set'].del_node("Device02"); + iq['set'].del_node("Device02") self.check(iq,""" <iq type='set' @@ -62,7 +62,7 @@ class TestControlStanzas(SleekTest): """ ) - iq['set'].del_nodes(); + iq['set'].del_nodes() self.check(iq,""" <iq type='set' @@ -84,12 +84,12 @@ class TestControlStanzas(SleekTest): 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,""" - <message + <message from='master@clayster.com/amr' to='device@clayster.com'> <set xmlns='urn:xmpp:iot:control'> @@ -111,7 +111,7 @@ class TestControlStanzas(SleekTest): 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 +128,9 @@ class TestControlStanzas(SleekTest): 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 +149,10 @@ class TestControlStanzas(SleekTest): 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 +177,38 @@ class TestControlStanzas(SleekTest): 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' @@ -244,5 +242,5 @@ class TestControlStanzas(SleekTest): </iq> """ ) - + suite = unittest.TestLoader().loadTestsFromTestCase(TestControlStanzas) diff --git a/tests/test_stream_xep_0050.py b/tests/test_stream_xep_0050.py index 261a0057..0a3eb718 100644 --- a/tests/test_stream_xep_0050.py +++ b/tests/test_stream_xep_0050.py @@ -119,7 +119,7 @@ class TestAdHocCommands(SleekTest): def handle_command(iq, session): def handle_form(form, session): - results.append(form['values']['foo']) + results.append(form.get_values()['foo']) form = self.xmpp['xep_0004'].makeForm('form') form.addField(var='foo', ftype='text-single', label='Foo') @@ -191,10 +191,10 @@ class TestAdHocCommands(SleekTest): def handle_command(iq, session): def handle_step2(form, session): - results.append(form['values']['bar']) + results.append(form.get_values()['bar']) 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 +426,7 @@ class TestAdHocCommands(SleekTest): def handle_form(forms, session): for form in forms: - results.append(form['values']['FORM_TYPE']) + results.append(form.get_values()['FORM_TYPE']) 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 fd2ad225..94f1d638 100644 --- a/tests/test_stream_xep_0323.py +++ b/tests/test_stream_xep_0323.py @@ -19,7 +19,7 @@ class TestStreamSensorData(SleekTest): 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(SleekTest): 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' @@ -46,7 +46,7 @@ class TestStreamSensorData(SleekTest): """) self.send(""" - <iq type='result' + <iq type='result' from='device@clayster.com' to='master@clayster.com/amr' id='1'> @@ -60,11 +60,11 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='1' done='true'> <node nodeId='Device22'> <timestamp value='2013-03-07T16:24:30'> - <numeric name='Temperature' momentary='true' automaticReadout='true' value='23.4' unit='°C'/> + <numeric name='Temperature' momentary='true' automaticReadout='true' value='23.4' unit='°C'/> </timestamp> </node> </fields> - </message> + </message> """) def testRequestRejectAuth(self): @@ -73,7 +73,7 @@ class TestStreamSensorData(SleekTest): 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' @@ -85,7 +85,7 @@ class TestStreamSensorData(SleekTest): """) self.send(""" - <iq type='error' + <iq type='error' from='device@clayster.com' to='master@clayster.com/amr' id='4'> @@ -101,8 +101,8 @@ class TestStreamSensorData(SleekTest): 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("."), @@ -118,7 +118,7 @@ class TestStreamSensorData(SleekTest): """) self.send(""" - <iq type='error' + <iq type='error' from='device@clayster.com' to='master@clayster.com/amr' id='77'> @@ -142,7 +142,7 @@ class TestStreamSensorData(SleekTest): """) self.send(""" - <iq type='result' + <iq type='result' from='device@clayster.com' to='master@clayster.com/amr' id='8'> @@ -157,11 +157,11 @@ class TestStreamSensorData(SleekTest): 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("."), @@ -177,7 +177,7 @@ class TestStreamSensorData(SleekTest): """) self.send(""" - <iq type='error' + <iq type='error' from='device@clayster.com' to='master@clayster.com/amr' id='7'> @@ -201,7 +201,7 @@ class TestStreamSensorData(SleekTest): """) self.send(""" - <iq type='result' + <iq type='result' from='device@clayster.com' to='master@clayster.com/amr' id='8'> @@ -215,11 +215,11 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='7'> <node nodeId='Device44'> <timestamp value='2000-01-01T00:01:02'> - <numeric name='Voltage' invoiced='true' value='230.4' unit='V'/> + <numeric name='Voltage' invoiced='true' value='230.4' unit='V'/> </timestamp> </node> </fields> - </message> + </message> """) self.send(""" @@ -227,7 +227,7 @@ class TestStreamSensorData(SleekTest): to='master@clayster.com/amr'> <fields xmlns='urn:xmpp:iot:sensordata' seqnr='7' done='true'> </fields> - </message> + </message> """) def testRequestMultiTimestampSingleField(self): @@ -236,15 +236,15 @@ class TestStreamSensorData(SleekTest): 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("."), @@ -260,7 +260,7 @@ class TestStreamSensorData(SleekTest): """) self.send(""" - <iq type='result' + <iq type='result' from='device@clayster.com' to='master@clayster.com/amr' id='8'> @@ -274,11 +274,11 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='7'> <node nodeId='Device44'> <timestamp value='2000-01-01T00:01:02'> - <numeric name='Voltage' invoiced='true' value='230.4' unit='V'/> + <numeric name='Voltage' invoiced='true' value='230.4' unit='V'/> </timestamp> </node> </fields> - </message> + </message> """) self.send(""" @@ -287,11 +287,11 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='7'> <node nodeId='Device44'> <timestamp value='2000-01-01T01:01:02'> - <numeric name='Voltage' value='230.6' unit='V'/> + <numeric name='Voltage' value='230.6' unit='V'/> </timestamp> </node> </fields> - </message> + </message> """) self.send(""" @@ -299,7 +299,7 @@ class TestStreamSensorData(SleekTest): to='master@clayster.com/amr'> <fields xmlns='urn:xmpp:iot:sensordata' seqnr='7' done='true'> </fields> - </message> + </message> """) def testRequestMultiTimestampAllFields(self): @@ -308,15 +308,15 @@ class TestStreamSensorData(SleekTest): 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("."), @@ -330,7 +330,7 @@ class TestStreamSensorData(SleekTest): """) self.send(""" - <iq type='result' + <iq type='result' from='device@clayster.com' to='master@clayster.com/amr' id='8'> @@ -344,11 +344,11 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='7'> <node nodeId='Device44'> <timestamp value='2000-01-01T00:01:02'> - <numeric name='Voltage' invoiced='true' value='230.4' unit='V'/> + <numeric name='Voltage' invoiced='true' value='230.4' unit='V'/> </timestamp> </node> </fields> - </message> + </message> """) self.send(""" @@ -357,12 +357,12 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='7'> <node nodeId='Device44'> <timestamp value='2000-01-01T01:01:02'> - <numeric name='Voltage' value='230.6' unit='V'/> - <string name='Height' invoiced='true' value='115 m'/> + <numeric name='Voltage' value='230.6' unit='V'/> + <string name='Height' invoiced='true' value='115 m'/> </timestamp> </node> </fields> - </message> + </message> """) self.send(""" @@ -370,7 +370,7 @@ class TestStreamSensorData(SleekTest): to='master@clayster.com/amr'> <fields xmlns='urn:xmpp:iot:sensordata' seqnr='7' done='true'> </fields> - </message> + </message> """) def testRequestAPI(self): @@ -379,7 +379,7 @@ class TestStreamSensorData(SleekTest): 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(SleekTest): </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(SleekTest): </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(SleekTest): 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' @@ -445,20 +445,20 @@ class TestStreamSensorData(SleekTest): """) self.recv(""" - <iq type='error' + <iq type='error' from='you@google.com' to='tester@localhost' id='1'> <rejected xmlns='urn:xmpp:iot:sensordata' seqnr='1'> <error>Invalid device Device22</error> </rejected> - </iq> + </iq> """) time.sleep(.1) - self.failUnless(results == ["rejected"], - "Rejected callback was not properly executed"); + self.failUnless(results == ["rejected"], + "Rejected callback was not properly executed") def testRequestAcceptedAPI(self): @@ -466,12 +466,12 @@ class TestStreamSensorData(SleekTest): 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' @@ -486,18 +486,18 @@ class TestStreamSensorData(SleekTest): """) self.recv(""" - <iq type='result' + <iq type='result' from='you@google.com' to='tester@localhost' id='1'> <accepted xmlns='urn:xmpp:iot:sensordata' seqnr='1'/> - </iq> + </iq> """) time.sleep(.1) - self.failUnless(results == ["accepted"], - "Accepted callback was not properly executed"); + self.failUnless(results == ["accepted"], + "Accepted callback was not properly executed") def testRequestFieldsAPI(self): @@ -505,25 +505,25 @@ class TestStreamSensorData(SleekTest): 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 t1= threading.Thread(name="request_data", target=self.xmpp['xep_0323'].request_data, - kwargs={"from_jid": "tester@localhost", - "to_jid": "you@google.com", - "nodeIds": ['Device33'], - "callback": my_callback}); - t1.start(); + kwargs={"from_jid": "tester@localhost", + "to_jid": "you@google.com", + "nodeIds": ['Device33'], + "callback": my_callback}) + t1.start() #self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33'], callback=my_callback); self.send(""" @@ -538,12 +538,12 @@ class TestStreamSensorData(SleekTest): """) self.recv(""" - <iq type='result' + <iq type='result' from='you@google.com' to='tester@localhost' id='1'> <accepted xmlns='urn:xmpp:iot:sensordata' seqnr='1'/> - </iq> + </iq> """) self.recv(""" @@ -552,42 +552,42 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='1'> <node nodeId='Device33'> <timestamp value='2000-01-01T00:01:02'> - <numeric name='Voltage' invoiced='true' value='230.4' unit='V'/> - <boolean name='TestBool' value='true'/> + <numeric name='Voltage' invoiced='true' value='230.4' unit='V'/> + <boolean name='TestBool' value='true'/> </timestamp> </node> </fields> - </message> + </message> """) self.recv(""" <message from='you@google.com' to='tester@localhost'> <fields xmlns='urn:xmpp:iot:sensordata' seqnr='1' done='true'/> - </message> + </message> """) - t1.join(); + t1.join() time.sleep(.5) - 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' @@ -605,14 +605,14 @@ class TestStreamSensorData(SleekTest): <query xmlns='http://jabber.org/protocol/disco#info'> <identity category='client' type='bot'/> <feature var='urn:xmpp:iot:sensordata'/> - </query> - </iq> + </query> + </iq> """) def testServiceDiscoveryComponent(self): self.stream_start(mode='component', plugins=['xep_0030', - 'xep_0323']); + 'xep_0323']) self.recv(""" <iq type='get' @@ -631,8 +631,8 @@ class TestStreamSensorData(SleekTest): <query xmlns='http://jabber.org/protocol/disco#info'> <identity category='component' type='generic'/> <feature var='urn:xmpp:iot:sensordata'/> - </query> - </iq> + </query> + </iq> """) def testRequestTimeout(self): @@ -641,23 +641,23 @@ class TestStreamSensorData(SleekTest): 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 t1= threading.Thread(name="request_data", target=self.xmpp['xep_0323'].request_data, - kwargs={"from_jid": "tester@localhost", - "to_jid": "you@google.com", - "nodeIds": ['Device33'], - "callback": my_callback}); - t1.start(); + kwargs={"from_jid": "tester@localhost", + "to_jid": "you@google.com", + "nodeIds": ['Device33'], + "callback": my_callback}) + t1.start() self.send(""" <iq type='get' @@ -671,12 +671,12 @@ class TestStreamSensorData(SleekTest): """) self.recv(""" - <iq type='result' + <iq type='result' from='you@google.com' to='tester@localhost' id='1'> <accepted xmlns='urn:xmpp:iot:sensordata' seqnr='1'/> - </iq> + </iq> """) self.recv(""" @@ -688,31 +688,31 @@ class TestStreamSensorData(SleekTest): </message> """) - t1.join(); + t1.join() time.sleep(.5) - self.failUnlessEqual(results, ["accepted","failure"]); + 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) @@ -729,7 +729,7 @@ class TestStreamSensorData(SleekTest): """) self.send(""" - <iq type='result' + <iq type='result' from='device@clayster.com' to='master@clayster.com/amr' id='1'> @@ -743,7 +743,7 @@ class TestStreamSensorData(SleekTest): <message from='device@clayster.com' to='master@clayster.com/amr'> <started xmlns='urn:xmpp:iot:sensordata' seqnr='1' /> - </message> + </message> """) self.send(""" @@ -752,11 +752,11 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='1' done='true'> <node nodeId='Device22'> <timestamp value='2013-03-07T16:24:30'> - <numeric name='Temperature' momentary='true' automaticReadout='true' value='23.4' unit='°C'/> + <numeric name='Temperature' momentary='true' automaticReadout='true' value='23.4' unit='°C'/> </timestamp> </node> </fields> - </message> + </message> """) def testDelayedRequestFail(self): @@ -764,12 +764,12 @@ class TestStreamSensorData(SleekTest): 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) @@ -792,7 +792,7 @@ class TestStreamSensorData(SleekTest): xml_stanza['rejected']['error'] = error_text self._filtered_stanza_check(""" - <iq type='error' + <iq type='error' from='device@clayster.com' to='master@clayster.com/amr' id='1'> @@ -825,13 +825,13 @@ class TestStreamSensorData(SleekTest): 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("."), @@ -847,7 +847,7 @@ class TestStreamSensorData(SleekTest): """) self.send(""" - <iq type='result' + <iq type='result' from='device@clayster.com' to='master@clayster.com/amr' id='6'> @@ -861,11 +861,11 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='6'> <node nodeId='Device44'> <timestamp value='2000-02-01T00:01:02'> - <numeric name='Voltage' invoiced='true' value='230.2' unit='V'/> + <numeric name='Voltage' invoiced='true' value='230.2' unit='V'/> </timestamp> </node> </fields> - </message> + </message> """) self.send(""" @@ -874,11 +874,11 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='6'> <node nodeId='Device44'> <timestamp value='2000-03-01T00:01:02'> - <numeric name='Voltage' invoiced='true' value='230.3' unit='V'/> + <numeric name='Voltage' invoiced='true' value='230.3' unit='V'/> </timestamp> </node> </fields> - </message> + </message> """) self.send(""" @@ -886,7 +886,7 @@ class TestStreamSensorData(SleekTest): to='master@clayster.com/amr'> <fields xmlns='urn:xmpp:iot:sensordata' seqnr='6' done='true'> </fields> - </message> + </message> """) def testRequestFieldTo(self): @@ -895,13 +895,13 @@ class TestStreamSensorData(SleekTest): 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("."), @@ -917,7 +917,7 @@ class TestStreamSensorData(SleekTest): """) self.send(""" - <iq type='result' + <iq type='result' from='device@clayster.com' to='master@clayster.com/amr' id='6'> @@ -931,11 +931,11 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='6'> <node nodeId='Device44'> <timestamp value='2000-01-01T00:01:02'> - <numeric name='Voltage' invoiced='true' value='230.1' unit='V'/> + <numeric name='Voltage' invoiced='true' value='230.1' unit='V'/> </timestamp> </node> </fields> - </message> + </message> """) self.send(""" @@ -944,11 +944,11 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='6'> <node nodeId='Device44'> <timestamp value='2000-02-01T00:01:02'> - <numeric name='Voltage' invoiced='true' value='230.2' unit='V'/> + <numeric name='Voltage' invoiced='true' value='230.2' unit='V'/> </timestamp> </node> </fields> - </message> + </message> """) self.send(""" @@ -956,7 +956,7 @@ class TestStreamSensorData(SleekTest): to='master@clayster.com/amr'> <fields xmlns='urn:xmpp:iot:sensordata' seqnr='6' done='true'> </fields> - </message> + </message> """) def testRequestFieldFromTo(self): @@ -965,13 +965,13 @@ class TestStreamSensorData(SleekTest): 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("."), @@ -987,7 +987,7 @@ class TestStreamSensorData(SleekTest): """) self.send(""" - <iq type='result' + <iq type='result' from='device@clayster.com' to='master@clayster.com/amr' id='6'> @@ -1001,11 +1001,11 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='6'> <node nodeId='Device44'> <timestamp value='2000-02-01T00:01:02'> - <numeric name='Voltage' invoiced='true' value='230.2' unit='V'/> + <numeric name='Voltage' invoiced='true' value='230.2' unit='V'/> </timestamp> </node> </fields> - </message> + </message> """) self.send(""" @@ -1013,7 +1013,7 @@ class TestStreamSensorData(SleekTest): to='master@clayster.com/amr'> <fields xmlns='urn:xmpp:iot:sensordata' seqnr='6' done='true'> </fields> - </message> + </message> """) def testDelayedRequestClient(self): @@ -1021,25 +1021,25 @@ class TestStreamSensorData(SleekTest): 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 t1= threading.Thread(name="request_data", target=self.xmpp['xep_0323'].request_data, - kwargs={"from_jid": "tester@localhost", - "to_jid": "you@google.com", - "nodeIds": ['Device33'], - "callback": my_callback}); - t1.start(); + kwargs={"from_jid": "tester@localhost", + "to_jid": "you@google.com", + "nodeIds": ['Device33'], + "callback": my_callback}) + t1.start() #self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33'], callback=my_callback); self.send(""" @@ -1054,20 +1054,20 @@ class TestStreamSensorData(SleekTest): """) self.recv(""" - <iq type='result' + <iq type='result' from='you@google.com' to='tester@localhost' id='1'> <accepted xmlns='urn:xmpp:iot:sensordata' seqnr='1' queued='true'/> - </iq> + </iq> """) self.recv(""" <message from='device@clayster.com' to='master@clayster.com/amr'> <started xmlns='urn:xmpp:iot:sensordata' seqnr='1' /> - </message> - """) + </message> + """) self.recv(""" <message from='you@google.com' @@ -1075,37 +1075,37 @@ class TestStreamSensorData(SleekTest): <fields xmlns='urn:xmpp:iot:sensordata' seqnr='1'> <node nodeId='Device33'> <timestamp value='2000-01-01T00:01:02'> - <numeric name='Voltage' invoiced='true' value='230.4' unit='V'/> - <boolean name='TestBool' value='true'/> + <numeric name='Voltage' invoiced='true' value='230.4' unit='V'/> + <boolean name='TestBool' value='true'/> </timestamp> </node> </fields> - </message> + </message> """) self.recv(""" <message from='you@google.com' to='tester@localhost'> <fields xmlns='urn:xmpp:iot:sensordata' seqnr='1' done='true'/> - </message> + </message> """) - t1.join(); + t1.join() time.sleep(.5) - self.failUnlessEqual(results, ["queued","started","fields","done"]); + 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): @@ -1114,12 +1114,12 @@ class TestStreamSensorData(SleekTest): 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' @@ -1133,15 +1133,15 @@ class TestStreamSensorData(SleekTest): """) self.recv(""" - <iq type='result' + <iq type='result' from='you@google.com' to='tester@localhost' id='1'> <accepted xmlns='urn:xmpp:iot:sensordata' seqnr='1'/> - </iq> + </iq> """) - self.xmpp['xep_0323'].cancel_request(session=session); + self.xmpp['xep_0323'].cancel_request(session=session) self.send(""" <iq type='get' @@ -1163,19 +1163,19 @@ class TestStreamSensorData(SleekTest): time.sleep(.5) - 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) @@ -1192,7 +1192,7 @@ class TestStreamSensorData(SleekTest): """) self.send(""" - <iq type='result' + <iq type='result' from='device@clayster.com' to='master@clayster.com/amr' id='1'> diff --git a/tests/test_stream_xep_0325.py b/tests/test_stream_xep_0325.py index 4b3250fc..2ebdd121 100644 --- a/tests/test_stream_xep_0325.py +++ b/tests/test_stream_xep_0325.py @@ -28,7 +28,7 @@ class TestStreamControl(SleekTest): 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(SleekTest): 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' @@ -60,26 +60,26 @@ class TestStreamControl(SleekTest): to='master@clayster.com/amr' id='1'> <setResponse xmlns='urn:xmpp:iot:control' responseCode="OK" /> - </iq> + </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' @@ -99,11 +99,11 @@ class TestStreamControl(SleekTest): to='master@clayster.com/amr' id='1'> <setResponse xmlns='urn:xmpp:iot:control' responseCode="OK" /> - </iq> + </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' @@ -125,23 +125,23 @@ class TestStreamControl(SleekTest): to='master@clayster.com/amr' id='2'> <setResponse xmlns='urn:xmpp:iot:control' responseCode="OK" /> - </iq> + </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' @@ -163,24 +163,24 @@ class TestStreamControl(SleekTest): <parameter name='Voltage' /> <error var='Output'>Invalid field Voltage</error> </setResponse> - </iq> + </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 + <message from='master@clayster.com/amr' to='device@clayster.com'> <set xmlns='urn:xmpp:iot:control'> @@ -191,20 +191,20 @@ class TestStreamControl(SleekTest): time.sleep(.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 + <message from='master@clayster.com/amr' to='device@clayster.com'> <set xmlns='urn:xmpp:iot:control'> @@ -215,8 +215,8 @@ class TestStreamControl(SleekTest): time.sleep(.5) - 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 testRequestSetOkAPI(self): @@ -225,16 +225,16 @@ class TestStreamControl(SleekTest): 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' @@ -256,12 +256,12 @@ class TestStreamControl(SleekTest): to='tester@localhost' id='1'> <setResponse xmlns='urn:xmpp:iot:control' responseCode="OK" /> - </iq> + </iq> """) time.sleep(.5) - self.assertEqual(results, ["OK"]); + self.assertEqual(results, ["OK"]) def testRequestSetErrorAPI(self): @@ -269,16 +269,16 @@ class TestStreamControl(SleekTest): 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' @@ -302,17 +302,17 @@ class TestStreamControl(SleekTest): <setResponse xmlns='urn:xmpp:iot:control' responseCode="OtherError" > <error var='Temperature'>Sensor error</error> </setResponse> - </iq> + </iq> """) time.sleep(.5) - 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' @@ -330,14 +330,14 @@ class TestStreamControl(SleekTest): <query xmlns='http://jabber.org/protocol/disco#info'> <identity category='client' type='bot'/> <feature var='urn:xmpp:iot:control'/> - </query> - </iq> + </query> + </iq> """) def testServiceDiscoveryComponent(self): self.stream_start(mode='component', plugins=['xep_0030', - 'xep_0325']); + 'xep_0325']) self.recv(""" <iq type='get' @@ -356,8 +356,8 @@ class TestStreamControl(SleekTest): <query xmlns='http://jabber.org/protocol/disco#info'> <identity category='component' type='generic'/> <feature var='urn:xmpp:iot:control'/> - </query> - </iq> + </query> + </iq> """) @@ -1,5 +1,5 @@ [tox] -envlist = py26,py27,py31,py32,py33 +envlist = py26,py27,py34 [testenv] deps = nose commands = nosetests --where=tests --exclude=live -i sleektest.py |