# HG changeset patch # User Goffi # Date 1565952813 -7200 # Node ID ccb2a22ea0fc04d0645e053faad77ca189f709a5 # Parent a5edf5e1dd744ce86df90a2e559d1f5c4a976e84 Python 3 port: /!\ Python 3.6+ is now needed to use SàT Pubsub /!\ instability may occur and features may not be working anymore, this will improve with time The same procedure as in backend has been applied (check backend commit ab2696e34d29 logs for details). Python minimal version has been updated in setup.py diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/__init__.py --- a/sat_pubsub/__init__.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/__init__.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # Copyright (c) 2012-2019 Jérôme Poisson diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/backend.py --- a/sat_pubsub/backend.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/backend.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # # Copyright (c) 2012-2019 Jérôme Poisson @@ -64,7 +64,7 @@ import copy import uuid -from zope.interface import implements +from zope.interface import implementer from twisted.application import service from twisted.python import components, log @@ -123,6 +123,7 @@ + [copy.deepcopy(d) for d in item_data[1:]]) +@implementer(iidavoll.IBackendService) class BackendService(service.Service, utility.EventDispatcher): """ Generic publish-subscribe backend service. @@ -134,7 +135,6 @@ @cvar defaultConfig: The default node configuration. """ - implements(iidavoll.IBackendService) nodeOptions = { const.OPT_PERSIST_ITEMS: @@ -201,7 +201,7 @@ self.storage = storage self._callbackList = [] self.config = config - self.admins = config[u'admins_jids_list'] + self.admins = config['admins_jids_list'] def isAdmin(self, entity_jid): """Return True if an entity is an administrator""" @@ -255,7 +255,7 @@ def _makeMetaData(self, metaData): options = [] - for key, value in metaData.iteritems(): + for key, value in metaData.items(): if key in self.nodeOptions: option = {"var": key} option.update(self.nodeOptions[key]) @@ -324,7 +324,7 @@ """ categories = [] try: - entry_elt = item_elt.elements(const.NS_ATOM, "entry").next() + entry_elt = next(item_elt.elements(const.NS_ATOM, "entry")) except StopIteration: return categories @@ -373,7 +373,7 @@ # we now remove every field which is not in data schema to_remove = set() - for item_var, item_field in item_form.fields.iteritems(): + for item_var, item_field in item_form.fields.items(): if item_var not in schema_form.fields: to_remove.add(item_field) @@ -387,7 +387,7 @@ current publisher must correspond to each item publisher """ def doCheck(item_pub_map): - for item_publisher in item_pub_map.itervalues(): + for item_publisher in item_pub_map.values(): if item_publisher.userhost() != publisher.userhost(): raise error.ItemForbidden() @@ -431,13 +431,13 @@ item["id"] = yield node.getNextId() new_item = True if ret_payload is None: - ret_pubsub_elt = domish.Element((pubsub.NS_PUBSUB, u'pubsub')) - ret_publish_elt = ret_pubsub_elt.addElement(u'publish') - ret_publish_elt[u'node'] = node.nodeIdentifier + ret_pubsub_elt = domish.Element((pubsub.NS_PUBSUB, 'pubsub')) + ret_publish_elt = ret_pubsub_elt.addElement('publish') + ret_publish_elt['node'] = node.nodeIdentifier ret_payload = ret_pubsub_elt ret_publish_elt = ret_payload.publish - ret_item_elt = ret_publish_elt.addElement(u'item') - ret_item_elt["id"] = item[u"id"] + ret_item_elt = ret_publish_elt.addElement('item') + ret_item_elt["id"] = item["id"] else: check_overwrite = True new_item = False @@ -462,9 +462,9 @@ # TODO: handle multiple items publishing (from several # publishers) raise error.NoPublishing( - u"consistent_publisher is currently only possible when " - u"publishing items from a single publisher. Try to " - u"publish one item at a time") + "consistent_publisher is currently only possible when " + "publishing items from a single publisher. Try to " + "publish one item at a time") # we replace requestor and new payload's publisher by original # item publisher to keep publisher consistent requestor = publishers.pop() @@ -506,7 +506,7 @@ notifications = [(subscriber, subscriptions_, items_data) for subscriber, subscriptions_ - in subsBySubscriber.iteritems()] + in subsBySubscriber.items()] return notifications @@ -762,7 +762,7 @@ # if at least one other entity is owner for this node raise error.Forbidden("You can't change your own affiliation") - to_delete = [jid_ for jid_, affiliation in affiliations.iteritems() if affiliation == 'none'] + to_delete = [jid_ for jid_, affiliation in affiliations.items() if affiliation == 'none'] for jid_ in to_delete: del affiliations[jid_] @@ -960,7 +960,7 @@ elif access_model == const.VAL_AMODEL_WHITELIST: yield self.checkNodeAffiliations(node, requestor) else: - raise Exception(u"Unknown access_model") + raise Exception("Unknown access_model") defer.returnValue((affiliation, owner, roster, access_model)) @@ -1154,7 +1154,7 @@ """ # TODO: the behaviour should be configurable (per node ?) if (any((requestor.userhostJID() != publisher.userhostJID() - for publisher in publishers_map.itervalues())) + for publisher in publishers_map.values())) and not self.isAdmin(requestor) ): raise error.Forbidden() @@ -1278,7 +1278,7 @@ discoIdentity = disco.DiscoIdentity('pubsub', 'service', - u'Salut à Toi pubsub service') + 'Salut à Toi pubsub service') pubsubService = None @@ -1523,7 +1523,7 @@ return d def _mapErrors(self, failure): - e = failure.trap(*self._errorMap.keys()) + e = failure.trap(*list(self._errorMap.keys())) condition, pubsubCondition, feature = self._errorMap[e] msg = failure.value.msg @@ -1601,7 +1601,7 @@ def _publish_errb(self, failure, request): if failure.type == error.NodeNotFound and self.backend.supportsAutoCreate(): - print "Auto-creating node %s" % (request.nodeIdentifier,) + print("Auto-creating node %s" % (request.nodeIdentifier,)) d = self.backend.createNode(request.nodeIdentifier, request.sender, pep=self._isPep(request), @@ -1780,8 +1780,8 @@ +@implementer(iwokkel.IDisco) class ExtraDiscoHandler(XMPPHandler): - implements(iwokkel.IDisco) # see comment in twisted/plugins/pubsub.py # FIXME: upstream must be fixed so we can use custom (non pubsub#) disco features diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/const.py --- a/sat_pubsub/const.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/const.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # Copyright (c) 2012-2019 Jérôme Poisson diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/container.py --- a/sat_pubsub/container.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/container.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # Copyright (C) 2016 Jérôme Poisson (goffi@goffi.org) diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/delegation.py --- a/sat_pubsub/delegation.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/delegation.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # # Copyright (c) 2015 Jérôme Poisson @@ -31,7 +31,7 @@ from twisted.words.protocols.jabber import jid, error from twisted.words.protocols.jabber.xmlstream import toResponse from twisted.words.xish import domish -from zope.interface import implements +from zope.interface import implementer DELEGATION_NS = 'urn:xmpp:delegation:1' FORWARDED_NS = 'urn:xmpp:forward:0' @@ -49,8 +49,8 @@ pass +@implementer(iwokkel.IDisco) class DelegationsHandler(XMPPHandler): - implements(iwokkel.IDisco) _service_hacked = False def __init__(self): @@ -144,32 +144,32 @@ def onAdvertise(self, message): """Manage the advertising delegations""" - delegation_elt = message.elements(DELEGATION_NS, 'delegation').next() + delegation_elt = next(message.elements(DELEGATION_NS, 'delegation')) delegated = {} for delegated_elt in delegation_elt.elements(DELEGATION_NS): try: if delegated_elt.name != 'delegated': - raise InvalidStanza(u'unexpected element {}'.format(delegated_elt.name)) + raise InvalidStanza('unexpected element {}'.format(delegated_elt.name)) try: namespace = delegated_elt['namespace'] except KeyError: - raise InvalidStanza(u'was expecting a "namespace" attribute in delegated element') + raise InvalidStanza('was expecting a "namespace" attribute in delegated element') delegated[namespace] = [] for attribute_elt in delegated_elt.elements(DELEGATION_NS, 'attribute'): try: delegated[namespace].append(attribute_elt["name"]) except KeyError: - raise InvalidStanza(u'was expecting a "name" attribute in attribute element') + raise InvalidStanza('was expecting a "name" attribute in attribute element') except InvalidStanza as e: log.msg("Invalid stanza received ({})".format(e)) - log.msg(u'delegations updated:\n{}'.format( - u'\n'.join([u" - namespace {}{}".format(ns, - u"" if not attributes else u" with filtering on {} attribute(s)".format( - u", ".join(attributes))) for ns, attributes in delegated.items()]))) + log.msg('delegations updated:\n{}'.format( + '\n'.join([" - namespace {}{}".format(ns, + "" if not attributes else " with filtering on {} attribute(s)".format( + ", ".join(attributes))) for ns, attributes in list(delegated.items())]))) if not pubsub.NS_PUBSUB in delegated: - log.msg(u"Didn't got pubsub delegation from server, can't act as a PEP service") + log.msg("Didn't got pubsub delegation from server, can't act as a PEP service") def onForward(self, iq): """Manage forwarded iq @@ -183,14 +183,14 @@ # TODO: do proper origin security check _, allowed = iq['to'].split('.', 1) if jid.JID(iq['from']) != jid.JID(allowed): - log.msg((u"SECURITY WARNING: forwarded stanza doesn't come from our server: {}" + log.msg(("SECURITY WARNING: forwarded stanza doesn't come from our server: {}" .format(iq.toXml())).encode('utf-8')) raise error.StanzaError('not-allowed') try: - fwd_iq = (iq.elements(DELEGATION_NS, 'delegation').next() - .elements(FORWARDED_NS, 'forwarded').next() - .elements('jabber:client', 'iq').next()) + delegation_elt = next(iq.elements(DELEGATION_NS, 'delegation')) + forwarded_elt = next(delegation_elt.elements(FORWARDED_NS, 'forwarded')) + fwd_iq = next(forwarded_elt.elements('jabber:client', 'iq')) except StopIteration: raise error.StanzaError('not-acceptable') diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/error.py --- a/sat_pubsub/error.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/error.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # Copyright (c) 2012-2019 Jérôme Poisson diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/exceptions.py --- a/sat_pubsub/exceptions.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/exceptions.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # Copyright (c) 2012-2019 Jérôme Poisson diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/iidavoll.py --- a/sat_pubsub/iidavoll.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/iidavoll.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # Copyright (c) 2003-2011 Ralph Meijer diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/mam.py --- a/sat_pubsub/mam.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/mam.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # Copyright (c) 2016 Jérôme Poisson @@ -25,7 +25,7 @@ """ -from zope.interface import implements +from zope.interface import implementer from twisted.words.xish import domish from twisted.python import log @@ -40,8 +40,8 @@ from wokkel import delay +@implementer(mam.IMAMResource) class MAMResource(object): - implements(mam.IMAMResource) _errorMap = backend.PubSubResourceFromBackend._errorMap def __init__(self, backend_): @@ -49,7 +49,7 @@ def _mapErrors(self, failure): # XXX: come from backend.PubsubResourceFromBackend - e = failure.trap(*self._errorMap.keys()) + e = failure.trap(*list(self._errorMap.keys())) condition, pubsubCondition, feature = self._errorMap[e] msg = failure.value.msg @@ -77,7 +77,7 @@ pep = False ext_data = {'pep': pep} if mam_request.form: - ext_data['filters'] = mam_request.form.fields.values() + ext_data['filters'] = list(mam_request.form.fields.values()) if mam_request.rsm is None: if const.VAL_RSM_MAX_DEFAULT != None: log.msg("MAM request without RSM limited to {}".format(const.VAL_RSM_MAX_DEFAULT)) @@ -114,7 +114,7 @@ # "complete" # attribute set to "true". page_max = (int(rsm_elt.first['index']) + 1) * mam_request.rsm.max - count = int(unicode(rsm_elt.count)) + count = int(str(rsm_elt.count)) if page_max >= count: # the maximum items which can be displayed is equal to or # above the total number of items, which means we are complete diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/pgsql_storage.py --- a/sat_pubsub/pgsql_storage.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/pgsql_storage.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # Copyright (c) 2012-2019 Jérôme Poisson @@ -53,7 +53,7 @@ import copy, logging -from zope.interface import implements +from zope.interface import implementer from twisted.internet import reactor from twisted.internet import defer @@ -77,7 +77,7 @@ # parseXml manage str, but we get unicode parseXml = lambda unicode_data: generic.parseXml(unicode_data.encode('utf-8')) -ITEMS_SEQ_NAME = u'node_{node_id}_seq' +ITEMS_SEQ_NAME = 'node_{node_id}_seq' PEP_COL_NAME = 'pep' CURRENT_VERSION = '5' # retrieve the maximum integer item id + 1 @@ -102,9 +102,9 @@ return "{} {}".format(query, pep_check), values +@implementer(iidavoll.IStorage) class Storage: - implements(iidavoll.IStorage) defaultConfig = { 'leaf': { @@ -435,7 +435,7 @@ if node_accesses != ('open',) or item_accesses != ('open',): raise NotImplementedError('only "open" access model is handled for now') if not pep: - raise NotImplementedError(u"getLastItems is only implemented for PEP at the moment") + raise NotImplementedError("getLastItems is only implemented for PEP at the moment") d = self.dbpool.runQuery("""SELECT DISTINCT ON (node_id) pep, node, data::text, items.access_model FROM items NATURAL JOIN nodes @@ -452,9 +452,9 @@ return d +@implementer(iidavoll.INode) class Node: - implements(iidavoll.INode) def __init__(self, nodeDbId, nodeIdentifier, config, schema): self.nodeDbId = nodeDbId @@ -488,10 +488,10 @@ if self._config[const.OPT_SERIAL_IDS]: d = self.dbpool.runQuery("SELECT nextval('{seq_name}')".format( seq_name = ITEMS_SEQ_NAME.format(node_id=self.nodeDbId))) - d.addCallback(lambda rows: unicode(rows[0][0])) + d.addCallback(lambda rows: str(rows[0][0])) return d else: - return defer.succeed(unicode(uuid.uuid4())) + return defer.succeed(str(uuid.uuid4())) @staticmethod def _configurationTriggers(cursor, node_id, old_config, new_config): @@ -645,7 +645,7 @@ subscriptions = [] for row in rows: - subscriber = jid.JID(u'%s/%s' % (row.jid, row.resource)) + subscriber = jid.JID('%s/%s' % (row.jid, row.resource)) options = {} if row.subscription_type: @@ -730,7 +730,7 @@ values = [] for subscription in subscriptions: entity_id = entities_map[subscription.subscriber].entity_id - resource = subscription.subscriber.resource or u'' + resource = subscription.subscriber.resource or '' values.append((self.nodeDbId, entity_id, resource, subscription.state, None, None)) # we use upsert so new values are inserted and existing one updated. This feature is only available for PostgreSQL >= 9.5 cursor.execute("INSERT INTO subscriptions(node_id, entity_id, resource, state, subscription_type, subscription_depth) VALUES " + placeholders + " ON CONFLICT (entity_id, resource, node_id) DO UPDATE SET state=EXCLUDED.state", [v for v in values]) @@ -808,7 +808,8 @@ # then we construct values for affiliations update according to entity_id we just got placeholders = ','.join(len(affiliations) * ["(%s,%s,%s)"]) values = [] - map(values.extend, ((e.entity_id, affiliations[jid.JID(e.jid)], self.nodeDbId) for e in entities)) + for e in entities: + values.extend((e.entity_id, affiliations[jid.JID(e.jid)], self.nodeDbId)) # we use upsert so new values are inserted and existing one updated. This feature is only available for PostgreSQL >= 9.5 cursor.execute("INSERT INTO affiliations(entity_id,affiliation,node_id) VALUES " + placeholders + " ON CONFLICT (entity_id,node_id) DO UPDATE SET affiliation=EXCLUDED.affiliation", values) @@ -836,9 +837,9 @@ return [row[0] for row in rows] +@implementer(iidavoll.ILeafNode) class LeafNode(Node): - implements(iidavoll.ILeafNode) nodeType = 'leaf' @@ -851,7 +852,7 @@ """ keys = ext_data.get('order_by') if not keys: - return u'ORDER BY updated ' + direction + return 'ORDER BY updated ' + direction cols_statmnt = [] for key in keys: if key == 'creation': @@ -859,11 +860,11 @@ elif key == 'modification': column = 'updated' else: - log.msg(u"WARNING: Unknown order by key: {key}".format(key=key)) + log.msg("WARNING: Unknown order by key: {key}".format(key=key)) column = 'updated' - cols_statmnt.append(column + u' ' + direction) + cols_statmnt.append(column + ' ' + direction) - return u"ORDER BY " + u",".join([col for col in cols_statmnt]) + return "ORDER BY " + ",".join([col for col in cols_statmnt]) @defer.inlineCallbacks def storeItems(self, items_data, publisher): @@ -918,10 +919,10 @@ cursor.execute(NEXT_ITEM_ID_QUERY.format(node_id=self.nodeDbId)) next_id = cursor.fetchone()[0] # we update the sequence, so we can skip conflicting ids - cursor.execute(u"SELECT setval('{seq_name}', %s)".format( + cursor.execute("SELECT setval('{seq_name}', %s)".format( seq_name = ITEMS_SEQ_NAME.format(node_id=self.nodeDbId)), [next_id]) # and now we can retry the query with the new id - item['id'] = insert_data[1] = unicode(next_id) + item['id'] = insert_data[1] = str(next_id) # item saved in DB must also be updated with the new id insert_data[3] = item.toXml() cursor.execute(insert_query, insert_data) @@ -1059,7 +1060,7 @@ args.append(filter_.value) else: query_filters.append("AND publisher LIKE %s") - args.append(u"{}%".format(filter_.value)) + args.append("{}%".format(filter_.value)) elif filter_.var == const.MAM_FILTER_CATEGORY: query.append("LEFT JOIN item_categories USING (item_id)") query_filters.append("AND category=%s") diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/privilege.py --- a/sat_pubsub/privilege.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/privilege.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # # Copyright (c) 2015 Jérôme Poisson @@ -92,18 +92,18 @@ self._permissions will be updated according to advertised privileged """ - privilege_elt = message.elements(PRIV_ENT_NS, 'privilege').next() + privilege_elt = next(message.elements(PRIV_ENT_NS, 'privilege')) for perm_elt in privilege_elt.elements(PRIV_ENT_NS): try: if perm_elt.name != 'perm': - raise InvalidStanza(u'unexpected element {}'.format(perm_elt.name)) + raise InvalidStanza('unexpected element {}'.format(perm_elt.name)) perm_access = perm_elt['access'] perm_type = perm_elt['type'] try: if perm_type not in TO_CHECK[perm_access]: - raise InvalidStanza(u'bad type [{}] for permission {}'.format(perm_type, perm_access)) + raise InvalidStanza('bad type [{}] for permission {}'.format(perm_type, perm_access)) except KeyError: - raise InvalidStanza(u'bad permission [{}]'.format(perm_access)) + raise InvalidStanza('bad permission [{}]'.format(perm_access)) except InvalidStanza as e: log.msg("Invalid stanza received ({}), setting permission to none".format(e)) for perm in self._permissions: @@ -238,7 +238,7 @@ self.roster_cache[from_jid_bare] = {'timestamp': timestamp, 'roster': roster, } - for roster_jid, roster_item in roster.iteritems(): + for roster_jid, roster_item in roster.items(): if roster_item.subscriptionFrom: self.presence_map.setdefault(roster_jid, set()).add(from_jid_bare) @@ -296,7 +296,7 @@ """ auto_subscribers = [] roster = yield self.getRoster(recipient) - for roster_jid, roster_item in roster.iteritems(): + for roster_jid, roster_item in roster.items(): if roster_jid in explicit_subscribers: continue if roster_item.subscriptionFrom: @@ -304,7 +304,7 @@ online_resources = self.caps_map[roster_jid] except KeyError: continue - for res, disco_tuple in online_resources.iteritems(): + for res, disco_tuple in online_resources.items(): notify = self.hash_map[disco_tuple]['notify'] if nodeIdentifier in notify: full_jid = jid.JID(tuple=(roster_jid.user, roster_jid.host, res)) diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/pubsub_admin.py --- a/sat_pubsub/pubsub_admin.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/pubsub_admin.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # Copyright (c) 2019 Jérôme Poisson @@ -21,7 +21,7 @@ """ -from zope.interface import implements +from zope.interface import implementer from twisted.python import log from twisted.internet import defer from twisted.words.protocols.jabber import jid, error as jabber_error, xmlstream @@ -29,12 +29,12 @@ from wokkel.subprotocols import XMPPHandler from wokkel import disco, iwokkel, pubsub -NS_PUBSUB_ADMIN = u"https://salut-a-toi.org/spec/pubsub_admin:0" +NS_PUBSUB_ADMIN = "https://salut-a-toi.org/spec/pubsub_admin:0" ADMIN_REQUEST = '/iq[@type="set"]/admin[@xmlns="{}"]'.format(NS_PUBSUB_ADMIN) +@implementer(iwokkel.IDisco) class PubsubAdminHandler(XMPPHandler): - implements(iwokkel.IDisco) def __init__(self, backend): super(PubsubAdminHandler, self).__init__() @@ -43,7 +43,7 @@ def connectionInitialized(self): self.xmlstream.addObserver(ADMIN_REQUEST, self.onAdminRequest) - def sendError(self, iq_elt, condition=u'bad-request'): + def sendError(self, iq_elt, condition='bad-request'): stanza_error = jabber_error.StanzaError(condition) iq_error = stanza_error.toResponse(iq_elt) self.parent.xmlstream.send(iq_error) @@ -58,42 +58,42 @@ pep = False # is the sender really an admin? - admins = self.backend.config[u'admins_jids_list'] - from_jid = jid.JID(iq_elt[u'from']) + admins = self.backend.config['admins_jids_list'] + from_jid = jid.JID(iq_elt['from']) if from_jid.userhostJID() not in admins: log.msg("WARNING: admin request done by non admin entity {from_jid}" .format(from_jid=from_jid.full())) - self.sendError(iq_elt, u'forbidden') + self.sendError(iq_elt, 'forbidden') return # alright, we can proceed - recipient = jid.JID(iq_elt[u'to']) + recipient = jid.JID(iq_elt['to']) admin_elt = iq_elt.admin try: - pubsub_elt = next(admin_elt.elements(pubsub.NS_PUBSUB, u'pubsub')) - publish_elt = next(pubsub_elt.elements(pubsub.NS_PUBSUB, u'publish')) + pubsub_elt = next(admin_elt.elements(pubsub.NS_PUBSUB, 'pubsub')) + publish_elt = next(pubsub_elt.elements(pubsub.NS_PUBSUB, 'publish')) except StopIteration: self.sendError(iq_elt) return try: - node = publish_elt[u'node'] + node = publish_elt['node'] except KeyError: self.sendError(iq_elt) return # we prepare the result IQ request, we will fill it with item ids - iq_result_elt = xmlstream.toResponse(iq_elt, u'result') - result_admin_elt = iq_result_elt.addElement((NS_PUBSUB_ADMIN, u'admin')) - result_pubsub_elt = result_admin_elt.addElement((pubsub.NS_PUBSUB, u'pubsub')) - result_publish_elt = result_pubsub_elt.addElement(u'publish') - result_publish_elt[u'node'] = node + iq_result_elt = xmlstream.toResponse(iq_elt, 'result') + result_admin_elt = iq_result_elt.addElement((NS_PUBSUB_ADMIN, 'admin')) + result_pubsub_elt = result_admin_elt.addElement((pubsub.NS_PUBSUB, 'pubsub')) + result_publish_elt = result_pubsub_elt.addElement('publish') + result_publish_elt['node'] = node # now we can send the items - for item in publish_elt.elements(pubsub.NS_PUBSUB, u'item'): + for item in publish_elt.elements(pubsub.NS_PUBSUB, 'item'): try: - requestor = jid.JID(item.attributes.pop(u'publisher')) + requestor = jid.JID(item.attributes.pop('publisher')) except Exception as e: - log.msg(u"WARNING: invalid jid in publisher ({requestor}): {msg}" + log.msg("WARNING: invalid jid in publisher ({requestor}): {msg}" .format(requestor=requestor, msg=e)) self.sendError(iq_elt) return @@ -111,20 +111,20 @@ recipient=recipient) except (error.Forbidden, error.ItemForbidden): __import__('pudb').set_trace() - self.sendError(iq_elt, u"forbidden") + self.sendError(iq_elt, "forbidden") return except Exception as e: - self.sendError(iq_elt, u"internal-server-error") - log.msg(u"INTERNAL ERROR: {msg}".format(msg=e)) + self.sendError(iq_elt, "internal-server-error") + log.msg("INTERNAL ERROR: {msg}".format(msg=e)) return - result_item_elt = result_publish_elt.addElement(u'item') + result_item_elt = result_publish_elt.addElement('item') # either the id was given and it is available in item # either it's a new item, and we can retrieve it from return payload try: - result_item_elt[u'id'] = item[u'id'] + result_item_elt['id'] = item['id'] except KeyError: - result_item_elt = payload.publish.item[u'id'] + result_item_elt = payload.publish.item['id'] self.xmlstream.send(iq_result_elt) diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/schema.py --- a/sat_pubsub/schema.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/schema.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # # Copyright (c) 2015 Jérôme Poisson @@ -27,14 +27,14 @@ from wokkel.iwokkel import IPubSubService from wokkel.subprotocols import XMPPHandler, IQHandlerMixin from wokkel import data_form, pubsub -from zope.interface import implements +from zope.interface import implementer from sat_pubsub import const QUERY_SCHEMA = "/pubsub[@xmlns='" + const.NS_SCHEMA + "']" +@implementer(iwokkel.IDisco) class SchemaHandler(XMPPHandler, IQHandlerMixin): - implements(iwokkel.IDisco) iqHandlers = {"/iq[@type='get']" + QUERY_SCHEMA: 'onSchemaGet', "/iq[@type='set']" + QUERY_SCHEMA: 'onSchemaSet'} @@ -54,7 +54,7 @@ schema_elt = domish.Element((const.NS_SCHEMA, 'schema')) schema_elt['node'] = nodeIdentifier if x_elt is not None: - assert x_elt.uri == u'jabber:x:data' + assert x_elt.uri == 'jabber:x:data' schema_elt.addChild(x_elt) return schema_elt @@ -86,7 +86,7 @@ pep = iq_elt.delegated recipient = jid.JID(iq_elt['to']) try: - x_elt = next(schema_elt.elements(data_form.NS_X_DATA, u'x')) + x_elt = next(schema_elt.elements(data_form.NS_X_DATA, 'x')) except StopIteration: # no schema form has been found x_elt = None diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/test/test_backend.py --- a/sat_pubsub/test/test_backend.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/test/test_backend.py Fri Aug 16 12:53:33 2019 +0200 @@ -105,9 +105,9 @@ return defer.succeed(None) def cb(result): - self.assertEquals(1, len(preDeleteCalled)) + self.assertEqual(1, len(preDeleteCalled)) data = preDeleteCalled[-1] - self.assertEquals('to-be-deleted', data['node'].nodeIdentifier) + self.assertEqual('to-be-deleted', data['node'].nodeIdentifier) self.assertTrue(self.storage.deleteCalled) self.storage = TestStorage() @@ -150,10 +150,10 @@ return defer.succeed(None) def cb(result): - self.assertEquals(1, len(preDeleteCalled)) + self.assertEqual(1, len(preDeleteCalled)) data = preDeleteCalled[-1] - self.assertEquals('to-be-deleted', data['node'].nodeIdentifier) - self.assertEquals(uri, data['redirectURI']) + self.assertEqual('to-be-deleted', data['node'].nodeIdentifier) + self.assertEqual(uri, data['redirectURI']) self.assertTrue(self.storage.deleteCalled) self.storage = TestStorage() @@ -217,12 +217,12 @@ return [sub] def cb(result): - self.assertEquals(1, len(result)) + self.assertEqual(1, len(result)) subscriber, subscriptions, items = result[-1] - self.assertEquals(OWNER, subscriber) - self.assertEquals({sub}, subscriptions) - self.assertEquals([item], items) + self.assertEqual(OWNER, subscriber) + self.assertEqual({sub}, subscriptions) + self.assertEqual([item], items) self.storage = self.NodeStore({'test': TestNode()}) self.backend = backend.BackendService(self.storage) @@ -251,11 +251,11 @@ return [subRoot] def cb(result): - self.assertEquals(1, len(result)) + self.assertEqual(1, len(result)) subscriber, subscriptions, items = result[-1] - self.assertEquals(OWNER, subscriber) - self.assertEquals({subRoot}, subscriptions) - self.assertEquals([item], items) + self.assertEqual(OWNER, subscriber) + self.assertEqual({subRoot}, subscriptions) + self.assertEqual([item], items) self.storage = self.NodeStore({'test': TestNode(), '': TestRootNode()}) @@ -283,12 +283,12 @@ return [subRoot] def cb(result): - self.assertEquals(1, len(result)) + self.assertEqual(1, len(result)) subscriber, subscriptions, items = result[-1] - self.assertEquals(OWNER, subscriber) - self.assertEquals({sub, subRoot}, subscriptions) - self.assertEquals([item], items) + self.assertEqual(OWNER, subscriber) + self.assertEqual({sub, subRoot}, subscriptions) + self.assertEqual([item], items) self.storage = self.NodeStore({'test': TestNode(), '': TestRootNode()}) @@ -452,9 +452,9 @@ return defer.succeed(TestNode()) def cb(data): - self.assertEquals('node', data['node'].nodeIdentifier) - self.assertEquals([ITEM], data['items']) - self.assertEquals(OWNER, data['subscription'].subscriber) + self.assertEqual('node', data['node'].nodeIdentifier) + self.assertEqual([ITEM], data['items']) + self.assertEqual(OWNER, data['subscription'].subscriber) self.storage = TestStorage() self.backend = backend.BackendService(self.storage) @@ -616,7 +616,7 @@ return defer.fail(error.NotSubscribed()) def cb(e): - self.assertEquals('unexpected-request', e.condition) + self.assertEqual('unexpected-request', e.condition) resource = backend.PubSubResourceFromBackend(TestBackend()) request = pubsub.PubSubRequest() @@ -644,9 +644,9 @@ def cb(info): self.assertIn('type', info) - self.assertEquals('leaf', info['type']) + self.assertEqual('leaf', info['type']) self.assertIn('meta-data', info) - self.assertEquals({'pubsub#persist_items': True}, info['meta-data']) + self.assertEqual({'pubsub#persist_items': True}, info['meta-data']) resource = backend.PubSubResourceFromBackend(TestBackend()) d = resource.getInfo(OWNER, SERVICE, 'test') @@ -680,7 +680,7 @@ return defer.succeed(options) def cb(options): - self.assertEquals(True, options["pubsub#persist_items"]) + self.assertEqual(True, options["pubsub#persist_items"]) resource = backend.PubSubResourceFromBackend(TestBackend()) request = pubsub.PubSubRequest() diff -r a5edf5e1dd74 -r ccb2a22ea0fc sat_pubsub/test/test_storage.py --- a/sat_pubsub/test/test_storage.py Fri Aug 16 12:48:34 2019 +0200 +++ b/sat_pubsub/test/test_storage.py Fri Aug 16 12:53:33 2019 +0200 @@ -70,16 +70,16 @@ PUBLISHER = jid.JID('publisher@example.com') ITEM = domish.Element((None, 'item')) ITEM['id'] = 'current' -ITEM.addElement(('testns', 'test'), content=u'Test \u2083 item') +ITEM.addElement(('testns', 'test'), content='Test \u2083 item') ITEM_NEW = domish.Element((None, 'item')) ITEM_NEW['id'] = 'new' -ITEM_NEW.addElement(('testns', 'test'), content=u'Test \u2083 item') +ITEM_NEW.addElement(('testns', 'test'), content='Test \u2083 item') ITEM_UPDATED = domish.Element((None, 'item')) ITEM_UPDATED['id'] = 'current' -ITEM_UPDATED.addElement(('testns', 'test'), content=u'Test \u2084 item') +ITEM_UPDATED.addElement(('testns', 'test'), content='Test \u2084 item') ITEM_TO_BE_DELETED = domish.Element((None, 'item')) ITEM_TO_BE_DELETED['id'] = 'to-be-deleted' -ITEM_TO_BE_DELETED.addElement(('testns', 'test'), content=u'Test \u2083 item') +ITEM_TO_BE_DELETED.addElement(('testns', 'test'), content='Test \u2083 item') def decode(object): if isinstance(object, str): @@ -230,8 +230,8 @@ def test_getConfiguration(self): config = self.node.getConfiguration() - self.assertIn('pubsub#persist_items', config.iterkeys()) - self.assertIn('pubsub#deliver_payloads', config.iterkeys()) + self.assertIn('pubsub#persist_items', iter(config.keys())) + self.assertIn('pubsub#deliver_payloads', iter(config.keys())) self.assertEqual(config['pubsub#persist_items'], True) self.assertEqual(config['pubsub#deliver_payloads'], True) @@ -263,10 +263,10 @@ def test_getMetaData(self): metaData = self.node.getMetaData() - for key, value in self.node.getConfiguration().iteritems(): - self.assertIn(key, metaData.iterkeys()) + for key, value in self.node.getConfiguration().items(): + self.assertIn(key, iter(metaData.keys())) self.assertEqual(value, metaData[key]) - self.assertIn('pubsub#node_type', metaData.iterkeys()) + self.assertIn('pubsub#node_type', iter(metaData.keys())) self.assertEqual(metaData['pubsub#node_type'], 'leaf') @@ -309,9 +309,9 @@ def test_getSubscription(self): def cb(subscriptions): - self.assertEquals(subscriptions[0].state, 'subscribed') - self.assertEquals(subscriptions[1].state, 'pending') - self.assertEquals(subscriptions[2], None) + self.assertEqual(subscriptions[0].state, 'subscribed') + self.assertEqual(subscriptions[1].state, 'pending') + self.assertEqual(subscriptions[2], None) d = defer.gatherResults([self.node.getSubscription(SUBSCRIBER), self.node.getSubscription(SUBSCRIBER_PENDING), @@ -347,10 +347,10 @@ def test_isSubscriber(self): def cb(subscribed): - self.assertEquals(subscribed[0][1], True) - self.assertEquals(subscribed[1][1], True) - self.assertEquals(subscribed[2][1], False) - self.assertEquals(subscribed[3][1], False) + self.assertEqual(subscribed[0][1], True) + self.assertEqual(subscribed[1][1], True) + self.assertEqual(subscribed[2][1], False) + self.assertEqual(subscribed[3][1], False) d = defer.DeferredList([self.node.isSubscribed(SUBSCRIBER), self.node.isSubscribed(SUBSCRIBER.userhostJID()), @@ -471,7 +471,7 @@ def cb2(affiliations): affiliations = dict(((a[0].full(), a[1]) for a in affiliations)) - self.assertEquals(affiliations[OWNER.userhost()], 'owner') + self.assertEqual(affiliations[OWNER.userhost()], 'owner') d = self.s.getNode('pre-existing') d.addCallback(cb1) diff -r a5edf5e1dd74 -r ccb2a22ea0fc setup.py --- a/setup.py Fri Aug 16 12:48:34 2019 +0200 +++ b/setup.py Fri Aug 16 12:53:33 2019 +0200 @@ -52,8 +52,8 @@ setup(name=NAME, version=VERSION, - description=u'XMPP Publish-Subscribe Service Component, build for the need of ' - u'the « Salut à Toi » project', + description='XMPP Publish-Subscribe Service Component, build for the need of ' + 'the « Salut à Toi » project', author='Association « Salut à Toi »', author_email='goffi@goffi.org', url='https://salut-a-toi.org', @@ -72,5 +72,5 @@ use_scm_version=sat_dev_version if is_dev_version else False, install_requires=install_requires, package_data={'sat_pubsub': ['VERSION']}, - python_requires='~=2.7', + python_requires='>=3.6', ) diff -r a5edf5e1dd74 -r ccb2a22ea0fc twisted/plugins/pubsub.py --- a/twisted/plugins/pubsub.py Fri Aug 16 12:48:34 2019 +0200 +++ b/twisted/plugins/pubsub.py Fri Aug 16 12:53:33 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 #-*- coding: utf-8 -*- # Copyright (c) 2012-2019 Jérôme Poisson @@ -67,7 +67,7 @@ from wokkel import pubsub from wokkel import rsm from wokkel import mam -from zope.interface import implements +from zope.interface import implementer from sat_pubsub import const from sat_pubsub import mam as pubsub_mam @@ -77,19 +77,19 @@ from sat_pubsub.privilege import PrivilegesHandler from sat_pubsub.delegation import DelegationsHandler from os.path import expanduser, realpath -import ConfigParser +import configparser def coerceListType(value): - return csv.reader( + return next(csv.reader( [value], delimiter=",", quotechar='"', skipinitialspace=True - ).next() + )) def coerceJidListType(value): values = [JID(v) for v in coerceListType(value)] if any((j.resource for j in values)): - raise ValueError(u"you must use bare jids") + raise ValueError("you must use bare jids") return values @@ -112,7 +112,7 @@ coerceJidListType] ] -CONFIG_FILENAME = u'sat' +CONFIG_FILENAME = 'sat' # List of the configuration filenames sorted by ascending priority CONFIG_FILES = [realpath(expanduser(path) + CONFIG_FILENAME + '.conf') for path in ( '/etc/', '/etc/{}/'.format(CONFIG_FILENAME), @@ -142,23 +142,21 @@ # if the options values are the hard-coded ones or if they have been passed on the command line. # FIXME: must be refactored + code can be factorised with backend - config_parser = ConfigParser.SafeConfigParser() + config_parser = configparser.ConfigParser() config_parser.read(CONFIG_FILES) for param in self.optParameters + OPT_PARAMETERS_CFG: name = param[0] try: value = config_parser.get(CONFIG_SECTION, name) - if isinstance(value, unicode): - value = value.encode('utf-8') try: param[2] = param[4](value) except IndexError: # the coerce method is optional param[2] = value except Exception as e: - log.err(u'Invalid value for setting "{name}": {msg}'.format( + log.err('Invalid value for setting "{name}": {msg}'.format( name=name, msg=e)) sys.exit(1) - except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): + except (configparser.NoSectionError, configparser.NoOptionError): pass usage.Options.__init__(self) for opt_data in OPT_PARAMETERS_CFG: @@ -173,10 +171,10 @@ self['jid'] = JID(self['jid']) if self['jid'] else None +@implementer(IServiceMaker, IPlugin) class SatPubsubMaker(object): - implements(IServiceMaker, IPlugin) tapname = "sat-pubsub" - description = u"Salut à Toi Publish-Subscribe Service Component".encode('utf-8') + description = "Salut à Toi Publish-Subscribe Service Component" options = Options def makeService(self, config): @@ -198,7 +196,7 @@ 'db_port': 'port', } kwargs = {} - for config_k, k in keys_map.iteritems(): + for config_k, k in keys_map.items(): v = config.get(config_k) if v is None: continue @@ -230,7 +228,7 @@ cs.logTraffic = True FallbackHandler().setHandlerParent(cs) - VersionHandler(u'SàT Pubsub', sat_pubsub.__version__).setHandlerParent(cs) + VersionHandler('SàT Pubsub', sat_pubsub.__version__).setHandlerParent(cs) DiscoHandler().setHandlerParent(cs) ph = PrivilegesHandler(config['jid'])